As a novice programmer you often face challenges that were solved in the past, but solving them again takes you from one site to another and another and another. I’d like to share what I have come up with and/or found and which I have not found combined on a single page yet:
Today, I want to share a simple solution for storing something like application settings in a json file with some properties being encrypted and decrypted in the process (e.g. passwords).
.NET offers some built in functionality to store application settings very easily, e.g. see here.
The problem with this is that there might be situations in which you would like to store the settings alongside your application. In my case I wanted to have a portable solution, including portable settings.
Part of these settings contain sensitive data like passwords, so encryption was needed. In order to decrypt, the user has to provide the decryption password within the GUI/workflow (not covered in this post).
But first things first:
Creating a simple settings class for your application is easy. Here is (part of) my class:
public class Settings : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private const string DEFAULT_FILENAME = "settings.json"; private string _user; public string User { get { return _user; } set { if(_user != value) { _user = value; OnPropertyChanged("User"); } } } private string _password; public string Password { get { return _password; } set { if (_password != value) { _password = value; OnPropertyChanged("Password"); } } } protected void OnPropertyChanged(string name) { PropertyChangedEventHandler handler = PropertyChanged; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } public void Save(string filename = DEFAULT_FILENAME) { File.WriteAllText(filename, JsonConvert.SerializeObject(this)); } public Settings Load(string filename = DEFAULT_FILENAME) { Settings s = new Settings(); if(File.Exists(filename)) { s = JsonConvert.DeserializeObject(File.ReadAllText(filename)); } return s; }
I’m using JSON.NET for serializing and deserializing which works like a charm.
Next was finding an easy way of encrypting and decrypting strings. While digging deeper into the depths of encryption in general is on my „learn something“-backlog, I wasn’t ready for that yet and found a neat working solution on StackOverflow by CraigTP. I’ve slightly altered it so that the cipher property is within the class rather than within each method call:
public static class StringCipher { // This constant is used to determine the keysize of the encryption algorithm in bits. // We divide this by 8 within the code below to get the equivalent number of bytes. private const int Keysize = 256; // This constant determines the number of iterations for the password bytes generation function. private const int DerivationIterations = 1000; public static string passPhrase; public static string Encrypt(string plainText) { // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text // so that the same Salt and IV values can be used when decrypting. var saltStringBytes = Generate256BitsOfRandomEntropy(); var ivStringBytes = Generate256BitsOfRandomEntropy(); var plainTextBytes = Encoding.UTF8.GetBytes(plainText); using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) { var keyBytes = password.GetBytes(Keysize / 8); using (var symmetricKey = new RijndaelManaged()) { symmetricKey.BlockSize = 256; symmetricKey.Mode = CipherMode.CBC; symmetricKey.Padding = PaddingMode.PKCS7; using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) { using (var memoryStream = new MemoryStream()) { using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) { cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); cryptoStream.FlushFinalBlock(); // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. var cipherTextBytes = saltStringBytes; cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); memoryStream.Close(); cryptoStream.Close(); return Convert.ToBase64String(cipherTextBytes); } } } } } } public static string Decrypt(string cipherText) { if (cipherText == null) { return null; } else { // Get the complete stream of bytes that represent: // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText] var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes. var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes. var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) { var keyBytes = password.GetBytes(Keysize / 8); using (var symmetricKey = new RijndaelManaged()) { symmetricKey.BlockSize = 256; symmetricKey.Mode = CipherMode.CBC; symmetricKey.Padding = PaddingMode.PKCS7; using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) { using (var memoryStream = new MemoryStream(cipherTextBytes)) { using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) { var plainTextBytes = new byte[cipherTextBytes.Length]; var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); memoryStream.Close(); cryptoStream.Close(); return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); } } } } } } } private static byte[] Generate256BitsOfRandomEntropy() { var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits. using (var rngCsp = new RNGCryptoServiceProvider()) { // Fill the array with cryptographically secure random bytes. rngCsp.GetBytes(randomBytes); } return randomBytes; } }
After a few tests I found this sufficient for my solution. Now, this needs to be combined with the settings class. Simply using the en/decrypting in the setter/getter of a property doesn’t do the trick:
private string _password; public string Password { get { return StringCipher.Decrypt(_password); } set { if (_password != StringCipher.Encrypt(value) { _password = StringCipher.Encrypt(value); OnPropertyChanged("Password"); } } }
The reason for that is, during serialization JSON.NET uses the standard getter/setter so a decrypted version of the password is stored (as the library gets it from the class) while the internal storage in _password during runtime would be encrypted.
A popular way would be having two versions of a property and ignoring the one containing the plain-text-version during serialization works, but I found it not very elegant as it would require adaption for many properties:
public string PasswordEncrypted { get; set; } [JsonIgnore] public string Password { get { return StringCipher.Decrypt(PasswordEncrypted); } set { PasswordEncrypted = StringCipher.Encrypt(value); }
While evaluating this option I stumbled across the option to add custom converters for properties in JSON.NET. So I built a very easy custom converter which support en/decryption via the above mentioned class:
public class EncryptionJsonConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(string); public override void WriteJson(JsonWriter writer, object value) { var stringValue = (string)value; if (string.IsNullOrEmpty(stringValue)) { writer.WriteNull(); return; } writer.WriteValue(StringCipher.Encrypt(stringValue)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue) { var value = reader.Value as string; if (string.IsNullOrEmpty(value)) { return reader.Value; } try { return StringCipher.Decrypt(value); } catch { return string.Empty; } } }
With that in place, all you have to do is add the converter for the settings properties that you want to encrypt/decrypt during serialization. No special behavior has to be built into the settings class:
private string _password; [JsonConverter(typeof(EncryptionJsonConverter))] public string Password { get { return _password; } set { if (_password != value) { _password = value; OnPropertyChanged("Password"); } } }
I liked this approach and it works well for my use-case. I hope this summary helps those looking for a simple solution in one place.