Suppose I want to keep a client's password database secure (not in plaintext), while still having access to the plaintext password.

Now I generates a public/private keypair. Then I send the public key to all servers, while keeping the private key on a heavy-secured, airgapped computer. Now, instead of salting and hashing the passwords, I salt it and then encrypt it with the public key, and store it in the database.

The method to store the password is Encrypt(password+salt). The encryption algorithm is deterministic. (If the input is the same, the output is the same.

If the bad guys now hack my server, they won't be able to get the passwords, since they don't have the private key to decrypt them.

If I ever need the passwords, I can just copy the database to my airgapped machine and decrypt the passwords in the database.

What are the disadvantages of doing so?

  • 1
    $\begingroup$ If you hash then encrypt, you won't have access to the plaintext password. What is the point? What are you storing in the database? $\endgroup$
    – otus
    Nov 25 '15 at 17:55
  • $\begingroup$ public-key encryption is usually not deterministic, so you can't really use this to check if the password matches. $\endgroup$ Nov 25 '15 at 18:13
  • $\begingroup$ @PaŭloEbermann why not? If I use the same salt (all same inputs), the output should be the same. (Assuming you don't include the time of encryption/random value) $\endgroup$
    – redfast00
    Nov 25 '15 at 19:29
  • $\begingroup$ @otus The purpose is to be able to still get the password, but if the bad guys hack the server, they won't have the passwords. $\endgroup$
    – redfast00
    Nov 25 '15 at 19:30
  • $\begingroup$ @redfast00, if you store Encrypt(Hash(password)), how can you recover the plaintext password? Regarding what Paŭlo Ebermann wrote, most public key encryption algorithms are not deterministic – they use random padding or similar – but there are deterministic algorithms you could use (like raw RSA). $\endgroup$
    – otus
    Nov 25 '15 at 20:02

If you use a deterministic encryption algorithm (so that you can actually verify passwords without the private key) it basically works like a backdoored hash. An attacker will be able to use a brute force or dictionary attack normally.

One obvious problem with any reversible encryption is that it reveals (at least something about) the password length. (E.g. if you use raw RSA you have to decide how to handle passwords longer than the modulus.) With a real password hash you can allow practically unlimited length without leaking anything about it if your database is compromised. Among other things, this may also allow an attacker to spend their resources on cracking the shortest passwords without wasting them on those that are too strong to crack.

You also miss out on the opportunity to use a slow and/or memory hard password hash. While asymmetric encryption is usually slower than a simple cryptographic hash would be, it is not slower by many orders of magnitude like a password hash allows.

Finally, there is a single point of failure (your private key) that allows leaking all the passwords in the database. If your key is, despite you precautions, compromised an attack can decrypt all the hashes. This means the scheme is at least theoretically weaker than password-hashing that has no back door and so requires every password to be attacker individually.

I would also recommend using established password hashing functions, though rather than either of the ones SEJPM mentions, I would recommend using whatever your programming language or available libraries already support. Whether that is bcrypt or scrypt.

If you really need the plaintext password to be recoverable, you could store both a strong password hash and a normal, non-deterministic public-key encryption of the password: $H(s, p)||E_{pk}(p)$. Password verification would be done using the slow password hash and possible recovery of the plaintext password would be done by decrypting. The limitations with regard to password length and a single point of failure that I mentioned above would still apply.

  • $\begingroup$ Are there any established hashing functions that allow for a backdoor? Scrypt and bcrypt don't allow it, and I might not trust Makwa. $\endgroup$
    – redfast00
    Nov 26 '15 at 11:55
  • $\begingroup$ @redfast00, not that I know of, but I added another possibility as an edit. $\endgroup$
    – otus
    Nov 26 '15 at 14:23
  • $\begingroup$ @redfast00 You could also mitigate the weaker security by using hash(private_key + salt) as the actual private key for encrypting each password. $\endgroup$
    – mtraceur
    Jul 10 '19 at 0:38

This is a really bad (and somewhat pointless) idea (if you do it on your own), because it provides less security than standard hashing and should only be considered if password escrow is a necessary feature.
If you don't need the password escrow (= recover the password using the heavily secured airgapped private key) you can simply password-hash the password using a modern scheme such as Argon2 which won the Password Hashing Competition.
If you really need the escrow functionality you should use Thomas Pornin's (who's here around sometimes) Makwa password hashing scheme which has been recommended by the Password Hashing Competition (PHC) for special use cases (as yours).

Now for the obvious approach and why it is bad.

I'd assume you would implement this by applying straight, raw RSA to your passwords and compare the results.

Now, if you just do $E_S(PW)$ without any randomness, you're basically creating a one-way hash function (with a backdoor and without compression) which is unsalted and doesn't use stretching (like for example raw MD5 password hashes). As your update reveals you indeed planned on using salting so it's not completely bad, but still far from optimal.
The second issue is that you won't get a compression. Meaning any password will occupy 256 bytes in your database as opposed to the standard 16 or 32 because you need the whole RSA encryption for key escrow.
The next issue is that you could add a salt, but because it's RSA you need to use a big salt, because there is an attack with run-time $O(\sqrt{m})$ meaning you need at least another 32 bytes salt, which further increases storage penalty.
Now you can also quite fastly compute the RSA encryption, meaning brute-force attacks won't be slowed down. This would mean you'd have to apply RSA multiple times are do some intense pre- or postprocessing without loosing information which is quite not easy.

I presume you didn't think of all these issues (yet), so better go with Argon2 or Makwa.

  • $\begingroup$ @otus, this was indeed clarified while I was writing my answer (so I didn't notice it) or only minutes afterwards. I'll quickly update my answer to reflect the new state of things. $\endgroup$
    – SEJPM
    Nov 26 '15 at 21:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.