browserforensics
Retour à tous les articles

Déchiffrer les mots de passe Firefox : NSS, key4.db et logins.json

2026-06-17 · 4 min

Chrome s'appuie sur le magasin de clés du système d'exploitation — DPAPI sous Windows, le Keychain sous macOS. Firefox fait sa propre cuisine : les mots de passe enregistrés sont protégés par NSS (Network Security Services), la même bibliothèque cryptographique que Firefox utilise pour TLS. Rien n'est lié à votre compte Windows, ce qui rend les mots de passe Firefox plus récupérables hors ligne que ceux de Chrome — il vous suffit de deux fichiers et (parfois) du mot de passe principal.

Les deux fichiers

FichierContenuFormat
logins.jsonles noms d'utilisateur et mots de passe chiffrésJSON
key4.dbla clé qui les déchiffreSQLite

Les deux se trouvent à la racine du profil (voir emplacements des fichiers Firefox). Les cookies, c'est une autre histoire : cookies.sqlite est en clair, il n'a donc besoin d'aucune clé (contrairement aux cookies Chrome).

logins.json est un tableau d'entrées :

{
  "logins": [
    {
      "hostname": "https://example.com",
      "encryptedUsername": "MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECC4...",
      "encryptedPassword": "MEIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECF...",
      "timeCreated": 1700000000000,
      "timeLastUsed": 1701234567890
    }
  ]
}

Chaque valeur encrypted* est le base64 d'une structure ASN.1 : un identifiant de clé, un chiffrement + IV, et le texte chiffré. Le chiffrement des entrées elles-mêmes est toujours 3DES-CBC — mais la clé provient de key4.db.

À l'intérieur de key4.db

key4.db (SQLite ; il a remplacé l'ancien key3.db au format Berkeley-DB aux alentours de 2018) comporte deux tables qui comptent :

  • metadata — la ligne id = 'password' porte item1 (le sel global) et item2 (un blob de vérification du mot de passe).
  • nssPrivate — la ligne dont le a102 vaut l'identifiant bien connu f8000000000000000000000000000001 porte a11, la clé chiffrée.

item2 et a11 sont tous deux encapsulés de la même manière, par une clé dérivée du sel global et de votre mot de passe principal (vide par défaut). L'encapsulage utilisé est annoncé par l'OID de l'algorithme dans l'ASN.1 :

OIDSchéma
1.2.840.113549.1.12.5.1.3pbeWithSha1And3KeyTripleDES-CBC (3DES historique)
1.2.840.113549.1.5.13PBES2 → PBKDF2-HMAC-SHA256 + AES-256-CBC (moderne)

Dérivation de la clé

PBE 3DES historique. Une cascade SHA-1/HMAC-SHA1 sur les sels :

hp  = SHA1(globalSalt || primaryPassword)
chp = SHA1(hp || entrySalt)
k1  = HMAC-SHA1(chp, pes || entrySalt)      # pes = entrySalt padded to 20 bytes
tk  = HMAC-SHA1(chp, pes)
k2  = HMAC-SHA1(chp, tk || entrySalt)
k   = k1 || k2 ;  key = k[:24] ;  iv = k[-8:]

PBES2 moderne. Plus propre, et le cas courant aujourd'hui :

k   = SHA1(globalSalt || primaryPassword)
key = PBKDF2-HMAC-SHA256(k, entrySalt, iterations, 32)
iv  = 0x04 0x0e || <14-byte stored IV>      # an NSS quirk: real IV is 16 bytes

Dans les deux cas, le mot de passe principal n'intervient que via ce premier SHA1(globalSalt || password). Aucun mot de passe défini → le mot de passe est la chaîne vide, et tout se déchiffre sans le moindre secret.

L'autovérification qui le rend fiable

Vous n'avez jamais à deviner si la dérivation a fonctionné. Déchiffrez item2 et le texte en clair doit être exactement :

password-check\x02\x02

(la chaîne littérale password-check plus ses deux octets de remplissage PKCS#7). En cas de correspondance, le mot de passe principal et toute la dérivation sont corrects — un mauvais mot de passe échoue donc bruyamment au lieu de produire silencieusement du charabia. C'est la même propriété « le déchiffrement se prouve lui-même » que le HMAC de la masterkey DPAPI.

Une fois la vérification faite, déchiffrez a11 pour récupérer la clé 3DES de 24 octets, puis déchiffrez chaque entrée de logins.json avec elle (3DES-CBC, retirez le remplissage PKCS#7) pour obtenir en clair le nom d'utilisateur et le mot de passe.

Pourquoi cela tourne dans un onglet de navigateur

Toute la chaîne est SHA-1, HMAC-SHA1, PBKDF2, 3DES et AES — aucun appel système, aucun magasin de clés. C'est donc un candidat idéal pour un module WebAssembly : notre déchiffreur est une petite crate Rust (RustCrypto + un minuscule lecteur ASN.1) compilée en WASM. La page lit key4.db et logins.json, transmet les blobs bruts au WASM, et affiche les identifiants déchiffrés — sans rien téléverser. Déposez les deux fichiers dans le parseur dans le navigateur, saisissez le mot de passe principal (ou laissez-le vide), et déchiffrez.

Pièges

  • Mot de passe principal. Si l'utilisateur en a défini un et que vous ne l'avez pas, la password-check échoue et il n'y a aucun raccourci — il verrouille tout.
  • key3.db. Les très anciens profils utilisent le key3.db au format Berkeley-DB au lieu de key4.db ; le schéma est similaire mais le conteneur diffère.
  • N'oubliez pas la source du sel. Le sel global est dans key4.db, pas dans logins.json — il vous faut les deux fichiers, à chaque fois.

Pour aller plus loin