5

I have a scenario where user entered passwords need to be stored for later use. I cannot use hashing because I need to get the original password for later use.

For example, consider an email sender app where a user would initially enter a password for his email account and the application would store it in encrypted form and when the application needs to send an email it would decrypt the password and use it to send the mail.

Now, here is what I am doing:

  1. The application stores a master key (generated using RNGCryptoServiceProvider) in the database
  2. Both the Key and IV for AES are derived from the master key using Rfc2898DeriveBytes (password=masterkey and salt=user_id for Rfc2898DeriveBytes)

    // the initial, one time masterkey generation, which will be used for all passwords
    byte[] masterKeyBytes = new byte[32];
    new RNGCryptoServiceProvider().GetBytes(masterKeyBytes);
    // masterkey is saved in database
    string masterKey = Convert.ToBase64String(masterKeyBytes);
    
    // password encryption of a user's password
    var derivative = new Rfc2898DeriveBytes(masterKey, 128_bit_guid_of_user_id, numberOfIterations)
    var aes = new AesCryptoServiceProvider();
    aes.Key = derivative.GetBytes(32);
    aes.IV = derivative.GetBytes(16);
    var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
    // get the encrypted password from encryptor
    .....
    .....
    

Am I doing this the right way? Are there any corrections to be made? Also, are the sizes being used (for key size, masterkeysize, etc.) good enough?

Few more questions(just for the sake of knowing more):

  1. Is deriving keys(seperate AES key for each user) from a high entropy master key considered not worthy(or worse)? (i.e using just the master key as AES key is more than enough). If so, why?, my argument is - should someone deduce the AES key for a particular user, he/she will still not be able to deduce the master key, so other users are safe. I came across - 'Key Diversification' which is used in some smart card apps where they derive a seperate AES key from a master key for each instance(does the same not apply here?)

  2. Can we just use PBKDF to derive keys in this scenario, or should we use a KBKDF? if so, could you mention a library(preferably from .NET framework) that offers KBKDF? I know that RfcDerivativeBytes uses a PBKDF to derive a key, but i couldnt find out how to derive a key using KBKDF.

1
  • I have added few more questions(under the 'Few more questions..' heading), if someone knows something could you answer them? Oct 26, 2015 at 15:30

1 Answer 1

9

When you encrypt passwords, it is because you fear eavesdropping; you envision an attacker who could get a look at your database contents. Encryption is the right tool for that, but if you put the encryption key in the same database, then you just did the equivalent of hiding your door key under the doormat. An attacker who can get a glimpse at the database may usually also obtain the key that way. Usually, when database contents leak, it is through a method that impacts the complete database (most successful SQL injection attacks allow arbitrary data extraction; the same applies, naturally, to lost/purloined database backups). Thus, it would make more sense to store the master key in a place distinct from the database, to limit the risk of exposure of both the key and the encrypted password.

Since you are apparently using C# and .NET, I infer a Windows system, and thus you may want to use DPAPI, through the ProtectedData class.


Deriving the actual encryption key and the IV from the same master key, combined with an instance-specific value, is a good thing, provided that you use a proper Key Derivation Function. However, the extra "randomization" value you use is a user-specific identifier; this means that if you change the encrypted data for a given user (say, the user changes his password), then both the old and the new passwords will be encrypted with the same key and the same IV. For most encryption systems, IV reuse is a sin.

Instead, you should use a random value that you regenerates whenever you encrypt a new piece of data, and store along with the encrypted value. There can be several methods for that; the simplest is to use the "master key" as the actual encryption key for AES, and generate a random IV every time you want to encrypt something; the encrypted result will then be the concatenation of the IV and the encrypted data, in that order.

Remember that the IV is not meant to be a secret; otherwise, we would call it a key. Thus it is not a problem if it is made available to attackers. However, each encryption system has its own requirements about the IV; in particular, a block cipher (such as AES) in CBC mode needs the IV to be randomly generated with a strong random source (AesCryptoServiceProvider defaults to CBC). Therefore, generate the IV with a RNGCryptoServiceProvider instance.


Speaking of the KDF, you use Rfc2898DeriveBytes, which is overkill. That class implements PBKDF2, which is a password-based KDF. "Passwords" are special because they are compatible with human brains, and this implies an inherent weakness. Brute force attacks work against passwords (we then call them "dictionary attacks"). To improve the resistance, password-based KDF are made slow with many repeated iterations. Unfortunately, this also makes the KDF expensive to use.

Since your master secret is not a password, but an appropriately long and random key generated with a strong source of randomness, then it is not vulnerable to brute forces. The cost of PBKDF2 is thus a waste of CPU cycles. If you still want to use PBKDF2 to derive encryption keys and/or IV, you should set the number of iterations to 1. Alternatively, as explained above, don't use a KDF at all: encrypt the data with the master key "as is", but with an instance-specific random IV that you store along the encryption result.


AesCryptoServiceProvider relies on the underlying native code implementation. RijndaelManaged is written in "pure .NET". For encrypting short elements, RijndaelManaged may actually be more efficient; it will also lower the risk of handle leakage if you forget to call the Dispose() method where necessary. Usually, performance doesn't actually matter (the encryption and decryption time will be negligible with regards to the rest of what your server does), but if it does in your case, then remember that you have several options for the implementation.

A 32-byte (256 bits) key for AES is a bit overkill, but it won't harm much. A 256-bit key implies a +40% CPU cost for encryption and decryption, compared with a 128-bit key, but for encrypted small elements it should be negligible.

4
  • thank you very much for the pointers, especially the IV one few queries(asking them out of curiousity :)) in below comment Sep 24, 2015 at 16:06
  • 1. regarding PBKDF2, i didnt understand why iterations should be low in our case, will it not make easier for someone who is trying to do a bruteforce attack? (i did get the 'master key is not a password, but a long random data, so bruteforcing is fruitless' part, but what if someone tries to have a list of passwords, encrypt each and compare them with the encrypted data, surely slowness would help right) 2. is 'different derived keys' not worthy than 'same master key'? (my concern being here is - even if someone deduces a user's derived key, he would not be able to deduce others) Sep 24, 2015 at 16:08
  • If someone can encrypt passwords with the "master key" then that someone has the key, and you lost. A 256-bit master key, properly generated, cannot be brute-forced. A higher iteration count only turn an utterly impossible attack into another utterly impossible attack, so there is no actual gain.
    – Tom Leek
    Sep 24, 2015 at 16:47
  • You are right, I framed my question wrong, also my understanding of the brute forcing is wrong. Regarding the 2'nd question, do you think deriving different keys is not any good (or worse), is using a single master key for all passwords more than enough? Sep 27, 2015 at 18:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .