Managing Passwords without storing them

This article explores an alternative to regular password managers that attempts to store as little information about passwords as possible.

Password managers are the generally recommended way of storing your passwords1. Currently available password managers typically have encrypted data files stored either locally or in the cloud, that can only be decrypted with your "master password" These data files contain all of the information necessary to log you in. Password managers also contain password generators and especially browser-based ones have autofill support.

This approach is good enough for most users and provides a signifficantly higher level of security than approaches that don't use password managers (mostly involving the memorization of passwords)

The Initial Idea

I initially had the idea for doing this around 2015 after byuu, a former retro gaming console (most notably SNES) emulator author, briefly explained their personal password manager, which they apparently use to this day2.

My initial implementation of the same basic idea was a lot more convoluted and certainly less secure (even though it shouldn't matter for the password it generated in the end), however I hope my new implementation is a lot closer to secure.

The Gist

In an ideal world, we would not need to store any information about the password at all. However due to conflicting password requirements and counterproductive password expiry rules, you will not be able to find a function that can generate the most secure passwords for every given available service.

We therefore have to store a small amount of metadata, but we can afford to store the sensitive parts of it in a hashed form, since it is only needed to pick the correct entry from the database.

In particular we need to store:

  1. A hash of some sort unique per login combination (service + username/email)
  2. The "iteration" number in case you need to rotate the password
  3. A description on how to generate the password (in machine-readable form, for example by naming a specific generator function)

Proposed Mechanism

Let me preface this section by saying that I'm not a cryptographer or a security export, so before you implement it and start using this mechanism in production, let someone a lot more qualified than me look over this article. I should also mention that I found less-than-ideal decisions with questionable impact on security in my personal implementation of my password manager. These issues have been rectified in this document.

Generic version

You need a CSPRNG, which is a random number generator which is cryptographically secure. In particular this also requires that no matter how much of the output you already know, you cannot predict the next n bits any more accurately than a 1/2^n chance, however as a PRNG it is fully deterministic, meaning that for the same exact input you receive the same exact output.

This exact property is exploited in stream-ciphers like chacha20 or AES-CTR, which XOR the data with enough random data from the namesake CSPRNG. It has the benefit that you can encrypt data without any padding being necessary, which are difficult to implement correctly and has the chance of being abused in certain cases3.

The key of the CSPRNG is generated by a "Key Derivation Function" which creates a cryptographic key from less secure imputs, such as Passwords. The input to this KDF should be based on the user password, the login (service + username/email) and the "iteration" number to generate truly unique "seeds".

The output of the CSPRNG should be used to choose individual characters inside of the password. This can include unicode characters for certain services.


key = kdf(password, salt=(site ++ username ++ iteration.to_bytes(8)))
rng = csprng(seed = key)
password = ""
for x in range(password_len):
  password += rng.choose(alphabet)

Potential Implementation

This is close to my current implementation, but questionable choices have been replaced. The design does not allow changing the implementation without invalidating the passwords.

  • As CSPRNG I chose chacha20 with a 128 bit counter (and no nonce). This is because of the large block size (512 bits) and large key size (256 bits). A nonce is not necessary as each key is only used for one purpose.
  • As PRF I chose argon2id, as that is considered a great choice for hashing passwords. It has configurable computation and memory hardness and therefore makes bruteforce difficult.
  • The salt is the sha256 hash of the byte string "{site}\0{username}\0{iteration}". The reason behind this is that this allows the password to be different even with the same master password
  • The database entry hashes are HMAC-SHA256("{site}\0{username}") with the key being generated by argon2id with salt "password". This is to prevent "mining" the public part of a credential from the password database

Randomness Generation

Unlike described above in the pseudocode section, it might make sense to generate all of the password characters at once. n bits of randomness can generate n log_d(2) with d being the alphabet size, or alternatively, n characters in an alphabet of d requires ceil(n log_2(d)) bits of information.

An implementation of this would require a bigint implementation however as a 64 character password requires 420 bits (ASCII) or 953 bits (Unicode 11) of random data, but it would fundamentally work like this:

bits_needed = int(math.ceil(password_len * math.log2(len(alphabet))))
bits = rng.bits(bits_needed)
password = ""
for x in range(password_len):
  password += alphabet[bits % len(alphabet)]
  bits //= len(alphabet)

A more simple method could involve just generating a 32 bit number mod the alphabet size, even if it requires 4x the amount of randomness and doesn't use bits efficiently. I don't know if one is to be preferred over the other in cryptography

Password Alphabets

Currently I can't recommend a unicode-based alphabet, even though Unicode password support is recommended by the NIST4, support is spotty at best and might even cause problems with the software you are using. For example I found that passwords containing unicode 12, 12.1 or 13 codepoints did not copy correctly from termux, as they replace every codepoint not assigned (according to your device's OS) with a replacement character. Other services may limit the amount of bytes a password can be in length as opposed to the amount of characters, which means that passwords with 64 random unicode characters are almost certainly longer than the limit of 72 bytes they impose.

On the other hand, many big tech companies do not struggle with unicode at all!

Cons of this Design

Due to design limitations there are a few things that it cannot do

  • You can't change the master password without changing all of your passwords
  • You can't change the username without the password
  • You can't add passwords that you don't get to choose yourself

The implementation I described also can not safely store notes together with the passwords, such as recovery codes for 2fa. This is definitely possible with an encrypted database. I believe an encrypted database wouldn't improve the security of the design, since it can be basically boiled down to 32 random bytes followed by a low number

Pros of this designs

The pros are quite theoretical but personally I think it's neater since the password is kind of virtual?

It's never stored anywhere and combine it with Zero-Knowledge Password Proofs it's also never sent anywhere.



See NCSC, Troy Hunt, etc


Both tweets have been deleted since then, but my response to the 2020 tweet can still be read on


Padding Oracle Attacks for example


EDIT 2 hours later: Shortly after releasing this blog article I have been told that an app similar in concept has already been publically released. It is similar in concept with a few key differences:

  • It uses scrypt instead of argon2id. I do not believe there is any meaningful difference in security between these two
  • It does not use a CSPRNG to generate randomness, but instead uses the hash directly. This limits the maximum password length to the hash output size.
  • Maximum password length is only 20, but could be increased to 63 31, not effectively unlimited5
  • The keyspace for the maximum security password is over 50x times smaller compared to password with the same length and same alphabet due to the added restrictions, it is however statistically unlikely to not have any alphabetic characters in the password6
  • Implementations with graphical UIs already exists for all major operating systems

Note: the above list might be slightly inaccurate as the whitepaper on the homepage errorneously claims that hmac-sha-256 outputs 512 bits. Maybe the actual implementation uses HMAC-SHA-512 or maybe the theoretical password size limit is 31 EDIT: The whitepaper does errorneously claim that hmac-sha-256 outputs 512 bits. The implementation however seems to use the correct 256 bits.


The actual limit is at 2^131 bits of randomness, maximum limit in characters varies per charset


There are 52 alphabetic characters out of 86 total characters, the chance of it not being chosen 20 times is (1-(52/86))^20 or a percentage with 7 zeroes after the decimal point.