using System; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; namespace BinaryDad.Extensions { /// /// Set of cryptography helpers using AES and Rijndael cipher /// public static class CryptoHelper { // 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 DefaultKeySize = 256; // This constant determines the number of iterations for the password bytes generation function. private const int DerivationIterations = 1000; #region Encrypt /// /// Encrypts a string with pass key using AES (Rijndael cipher) /// /// /// /// public static string Encrypt(string plainText, string passPhrase) { return Encrypt(plainText, passPhrase, DefaultKeySize); } /// /// Encrypts a string with pass key using AES (Rijndael cipher) /// /// /// /// /// public static string Encrypt(string plainText, string passPhrase, int keySize) { ValidateKeySize(keySize); // 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 = GenerateBitsOfRandomEntropy(keySize); var ivStringBytes = GenerateBitsOfRandomEntropy(keySize); 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 = keySize; 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); } } } } } } #endregion #region Decrypt /// /// Decrypts a string with pass key using AES (Rijndael cipher) /// /// /// /// public static string Decrypt(string cipherText, string passPhrase) { return Decrypt(cipherText, passPhrase, DefaultKeySize); } /// /// Decrypts a string with pass key using AES (Rijndael cipher) /// /// /// /// /// public static string Decrypt(string cipherText, string passPhrase, int keySize) { ValidateKeySize(keySize); // 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 = keySize; 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); } } } } } } #endregion #region Private Methods private static byte[] GenerateBitsOfRandomEntropy(int keySize) { var randomBytes = new byte[keySize / 8]; using (var rngCsp = new RNGCryptoServiceProvider()) { // Fill the array with cryptographically secure random bytes. rngCsp.GetBytes(randomBytes); } return randomBytes; } private static void ValidateKeySize(int keySize) { if (keySize % 8 != 0) { throw new ArgumentException("Key size must be a multiple of 8", nameof(keySize)); } } #endregion } }