After smashing my head against a cryptographic wall and going through some code samples, blogs and articles, I think I have finally solved my symmetric cryptography problem. Those who don’t know what I am talking about should probably read my previous posts on the subject – Tag Clouds Series. The programming language used is C# 2.0.
Before putting forth the solution, let me briefly state what I wanted to do.
- The application allows the user to create multiple blog accounts and accepts his passwords for each of them. When he uses the app, he only has to enter one master password and the app will communicate with his accounts using the passwords it already has.
- Since the account details are stored in an xml file, I have two options.
- I can decrypt and encrypt the whole file every time it is loaded and saved. The problem with this method is that the contents of the whole file will be in memory in the clear, including the passwords!
- I can keep each password encrypted with a separate key. In this case too, I load the entire file into memory. But I only encrypt/ decrypt passwords when actually required.
Naturally, I prefer the second method. So does Keith Brown (refer to Tag clouds, part 6 for links to his MSDN articles and accompanying code).
Now, my unoriginal solution.
Setting things up
- User provides a master password.
- A random salt, known as the master-salt, is generated using the RNGCryptoServiceProvider.
- An iteration-count is determined. It can be any integer >= 1000. It can be hard coded into the application.
- The Rfc2898DeriveBytes class is used to generate a master-key. The class constructor takes the master password, salt and iteration-count as parameters. At this point the master password is no longer required. But since .net strings are immutable, I don’t know how and when the GC will collect the memory. Anyway, since that is out of my control, I simply forget about that. If anybody knows why I should not do that, please tell me.
- Another key, known as a verification-key is derived similar to steps 2-4. The only differences being – the master-key is used as the password and the salt, called the verification-key-salt, is a known string hard coded into the application.
- I now store the master-salt and verification-key in the xml file in a separate section.
- Every time the user starts the app, it prompts for the master password. Once it is acquired, the app loads the master-salt from the xml file and goes through steps 4 and 5. If the computed verification key matches the one stored in the file, the password is correct. Otherwise it is not.
Encrypting and decrypting records
- As far as a new blog account goes, I am only interested in keeping the password safe. Every time a new account is created, I generate a salt using RNGCryptoServiceProvider. This is stored in the file along with each record since it is a per record salt.
- When the account password is provided, I take that, the master-key, record-salt and the iteration-count and send it to an EncryptData function.
- The EncryptData function passes the master-key, record-salt and the iteration-count through the Rfc2898DeriveBytes class to generate the record-key.
- This record-key is used to derive a 256-bit key and a 128-bit initialization vector for the Rijndael algorithm. The record-key is discarded after this.
- The password string is converted to a byte array and is encrypted. The resultant byte array is converted to its base64 equivalent. This encrypted string is stored in the xml file with the record.
- When the clear password is required, the encrypted-password, master-key, record-salt and the iteration-count are sent to a DecryptData function.
- The record key is regenerated by the Rfc2898DeriveBytes class and the key and initialization vector for Rijndael are acquired.
- The encrypted-password is converted from base64 to a byte array and is decrypted. The resultant byte array is converted to its text equivalent. This clear password is returned.
What happens when the user changes the master password
Well, very simple really.
- I generate a new master-salt and master-key (don’t discard the old ones just yet).
- Then, for each record-password, a new salt is generated, keeping the old one safe. The encrypted password is first decrypted using the old master-key, old record-salt and the iteration-count. Then it is encrypted using the new master-key, new record-salt and iteration-count.
- When all this is done, I generate the new verification-key and save that and the new master-salt in the file. That is all.
The actual code that does all these things is spread over three different modules. If you want all that, you will have to wait for me to release the app and the source code. But I am providing the entire code of the CryptoHelper class in a text file – CryptoHelper Code. A few clarifications. One, I use two different iteration counts – 4096 for generating a master-key and 1024 for regular ones. And two, I understand that the Rfc2898DeriveBytes class generates the key using a PBKDF2 (Password Based Key Derivation Function 2) function which is specified in the RSA PKCS # 5 guidelines. Never read them. But you might find the information useful.
This and that
Okay, now that the technical part is done, I provide a few links to various write-ups I found helpful.
- Keith Brown’s two MSDN articles from 2004 and the source code provided by him are the basis for my code (Security Briefs: Mind Those Passwords! and Security Briefs: Password Minder Internals). But I have to say that I could not understand what was going on till I stepped through the code in the debugger.
- Cryptography 101 for the .NET Framework
- .NET Security Blog : Generating a Key from a Password
- I discovered Symmetric Key Encryption using Rijndael and C# when I was searching for information on whether it is safe to pluck the Initialization Vector from the PBKDF2-generated key. It is written for .net 1.1 and is good enough except for one serious flaw – the password is used as salt, which to me is very unsafe. It is basically a concatenation and defeats the very purpose of using a salt. A salt is preferably generated randomly (Salt (cryptography)).
Readers should note that the application is still under development and I might change the code a bit (or a lot) depending on what else I discover. If someone thinks that there is something seriously wrong with the code (as far as cryptography goes – don’t talk of coding style), I would appreciate a comment regarding the same.