firefox-logins
Read and write Firefox saved passwords, by driving the browser's own NSS
Secret Decoder Ring through ctypes.
Plenty of tools decrypt logins.json. This one also seals new credentials into
it, using the same code path Firefox itself uses — so entries it writes are
indistinguishable from ones you saved by hand. That makes browser-to-browser
password migration possible without a plaintext CSV round trip.
No runtime dependencies: the crypto comes from the Firefox install already on the machine.
Install
pip install firefox-logins
Requires Python 3.11+ and a Firefox installation. Tested on macOS; Linux paths are supported but less exercised.
Usage
Read
from firefox_logins import LoginStore
with LoginStore.open() as store:
for login in store:
print(login.hostname, login.username, login.password)
LoginStore.open() uses the profile Firefox would launch. Pick another with
find_profiles(), and pass primary_password= if the profile has one.
Write
from firefox_logins import Login, LoginStore
with LoginStore.open() as store:
store.add(Login(
hostname="https://example.com",
username="ada",
password="hunter2",
form_submit_url="https://example.com",
))
written = store.save() # Firefox must be quit
save() decrypts every entry — existing and new — before replacing the file,
and abandons the write if any fails. It backs up to logins.json.bak, writes
to a staging file, then renames, so an interrupted run cannot leave a partial
file where your credentials were.
Set exactly one of form_submit_url (form logins) or http_realm (HTTP auth);
Firefox rejects entries with both or neither, so the constructor does too.
Profiles
from firefox_logins import find_profiles
for profile in find_profiles():
print(profile.name, profile.path, profile.is_default)
Low-level
from firefox_logins import SecretDecoderRing, keyid_from_blob
with SecretDecoderRing("/path/to/profile") as sdr:
sdr.selftest()
blob = sdr.encrypt("secret")
assert sdr.decrypt(blob) == "secret"
Notes on the write path
Four things are needed to seal a value the way Firefox does, none of them covered by the read-only references this was built against. They are recorded here because each one fails in a way that is easy to misdiagnose:
libnss3declares@rpath/libmozglue, which the loader cannot resolve from a host process. The dependency is loaded by absolute path first, and the working directory stays inside the Firefox bundle throughNSS_Init, because NSSdlopens softokn and freebl during initialisation.- Firefox does not export
PK11SDR_Encrypt— onlyPK11SDR_EncryptWithMechanism. - The mechanism must be
CKM_AES_CBC.CKM_AES_CBC_PADand both DES3 forms returnSEC_ERROR_INVALID_ARGS, because the Secret Decoder Ring applies PKCS#7 padding itself rather than delegating it to PKCS#11. - The key id must be non-empty. NSS's default is
f8000000000000000000000000000001;keyid_from_blob()recovers a profile's own from any entry it already holds, since the stored value is DERSEQUENCE { OCTET STRING keyid, AlgorithmIdentifier, OCTET STRING ciphertext }.
selftest() runs a seal/unseal round trip and is called automatically when a
LoginStore is opened, so a wrong mechanism or key id fails immediately rather
than producing a file Firefox silently cannot read.
Scope and safety
This reads and writes credentials belonging to the user running it, using keys already present in that user's own profile. It grants no access that running as that user does not already imply, and it cannot bypass a Primary Password — without the correct one, NSS refuses to unlock the key slot.
If a profile has a Primary Password, pass it:
LoginStore.open(primary_password="…")
Quit Firefox before writing. Firefox rewrites logins.json on exit, so changes
made while it runs are discarded; save() refuses by default and explains why.
Companion
chromium-reader does the
equivalent job for Chromium's on-disk storage formats.
License
MIT
Metadata
Release files for firefox-logins 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| firefox_logins-0.1.0.tar.gz | 17.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| firefox_logins-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.0 kB
Release files / firefox_logins-0.1.0.tar.gz
| Download URL | firefox_logins-0.1.0.tar.gz |
|---|---|
| Size | 17.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8230db7ace1ad0f465b5a9a1a4ce26c8518b2d8f19563cba21ee1d67fe7fdd3e
|
|
BLAKE2b-256 checksum How to use checksums |
35cf7171cb2653b62336d73f60a7c8c50682012c6eb5a7f1fd7f35c9482cdcb3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency logRelease files / firefox_logins-0.1.0-py3-none-any.whl
| Download URL | firefox_logins-0.1.0-py3-none-any.whl |
|---|---|
| Size | 14.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
df167f00a1784194764d3d537114b658c497c8e8f71823d0f440f4cdd585718b
|
|
BLAKE2b-256 checksum How to use checksums |
5b6b6aa0bc13da0ecce050c52eb10faede93614d1170a4a7cb8517306befa44b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency log