Advertisement
ZeekoSec

.NET Built-in Encrypt(AES)-Then-MAC(HMAC)

Mar 27th, 2015
614
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 13.69 KB | None | 0 0
  1. using System;
  2. using System.IO;
  3. using System.Security.Cryptography;
  4. using System.Text;
  5.  
  6. namespace Encryption
  7. {
  8.   public static class AESThenHMAC
  9.   {
  10.     private static readonly RandomNumberGenerator Random = RandomNumberGenerator.Create();
  11.  
  12.     //Preconfigured Encryption Parameters
  13.     public static readonly int BlockBitSize = 128;
  14.     public static readonly int KeyBitSize = 256;
  15.  
  16.     //Preconfigured Password Key Derivation Parameters
  17.     public static readonly int SaltBitSize = 64;
  18.     public static readonly int Iterations = 10000;
  19.     public static readonly int MinPasswordLength = 12;
  20.  
  21.     /// <summary>
  22.     /// Helper that generates a random key on each call.
  23.     /// </summary>
  24.     /// <returns></returns>
  25.     public static byte[] NewKey()
  26.     {
  27.       var key = new byte[KeyBitSize / 8];
  28.       Random.GetBytes(key);
  29.       return key;
  30.     }
  31.  
  32.     /// <summary>
  33.     /// Simple Encryption (AES) then Authentication (HMAC) for a UTF8 Message.
  34.     /// </summary>
  35.     /// <param name="secretMessage">The secret message.</param>
  36.     /// <param name="cryptKey">The crypt key.</param>
  37.     /// <param name="authKey">The auth key.</param>
  38.     /// <param name="nonSecretPayload">(Optional) Non-Secret Payload.</param>
  39.     /// <returns>
  40.     /// Encrypted Message
  41.     /// </returns>
  42.     /// <exception cref="System.ArgumentException">Secret Message Required!;secretMessage</exception>
  43.     /// <remarks>
  44.     /// Adds overhead of (Optional-Payload + BlockSize(16) + Message-Padded-To-Blocksize +  HMac-Tag(32)) * 1.33 Base64
  45.     /// </remarks>
  46.     public static string SimpleEncrypt(string secretMessage, byte[] cryptKey, byte[] authKey,
  47.                        byte[] nonSecretPayload = null)
  48.     {
  49.       if (string.IsNullOrEmpty(secretMessage))
  50.         throw new ArgumentException("Secret Message Required!", "secretMessage");
  51.  
  52.       var plainText = Encoding.UTF8.GetBytes(secretMessage);
  53.       var cipherText = SimpleEncrypt(plainText, cryptKey, authKey, nonSecretPayload);
  54.       return Convert.ToBase64String(cipherText);
  55.     }
  56.  
  57.     /// <summary>
  58.     /// Simple Authentication (HMAC) then Decryption (AES) for a secrets UTF8 Message.
  59.     /// </summary>
  60.     /// <param name="encryptedMessage">The encrypted message.</param>
  61.     /// <param name="cryptKey">The crypt key.</param>
  62.     /// <param name="authKey">The auth key.</param>
  63.     /// <param name="nonSecretPayloadLength">Length of the non secret payload.</param>
  64.     /// <returns>
  65.     /// Decrypted Message
  66.     /// </returns>
  67.     /// <exception cref="System.ArgumentException">Encrypted Message Required!;encryptedMessage</exception>
  68.     public static string SimpleDecrypt(string encryptedMessage, byte[] cryptKey, byte[] authKey,
  69.                        int nonSecretPayloadLength = 0)
  70.     {
  71.       if (string.IsNullOrWhiteSpace(encryptedMessage))
  72.         throw new ArgumentException("Encrypted Message Required!", "encryptedMessage");
  73.  
  74.       var cipherText = Convert.FromBase64String(encryptedMessage);
  75.       var plainText = SimpleDecrypt(cipherText, cryptKey, authKey, nonSecretPayloadLength);
  76.       return plaintext == null ? null : Encoding.UTF8.GetString(plainText);
  77.     }
  78.  
  79.     /// <summary>
  80.     /// Simple Encryption (AES) then Authentication (HMAC) of a UTF8 message
  81.     /// using Keys derived from a Password (PBKDF2).
  82.     /// </summary>
  83.     /// <param name="secretMessage">The secret message.</param>
  84.     /// <param name="password">The password.</param>
  85.     /// <param name="nonSecretPayload">The non secret payload.</param>
  86.     /// <returns>
  87.     /// Encrypted Message
  88.     /// </returns>
  89.     /// <exception cref="System.ArgumentException">password</exception>
  90.     /// <remarks>
  91.     /// Significantly less secure than using random binary keys.
  92.     /// Adds additional non secret payload for key generation parameters.
  93.     /// </remarks>
  94.     public static string SimpleEncryptWithPassword(string secretMessage, string password,
  95.                              byte[] nonSecretPayload = null)
  96.     {
  97.       if (string.IsNullOrEmpty(secretMessage))
  98.         throw new ArgumentException("Secret Message Required!", "secretMessage");
  99.  
  100.       var plainText = Encoding.UTF8.GetBytes(secretMessage);
  101.       var cipherText = SimpleEncryptWithPassword(plainText, password, nonSecretPayload);
  102.       return Convert.ToBase64String(cipherText);
  103.     }
  104.  
  105.     /// <summary>
  106.     /// Simple Authentication (HMAC) and then Descryption (AES) of a UTF8 Message
  107.     /// using keys derived from a password (PBKDF2).
  108.     /// </summary>
  109.     /// <param name="encryptedMessage">The encrypted message.</param>
  110.     /// <param name="password">The password.</param>
  111.     /// <param name="nonSecretPayloadLength">Length of the non secret payload.</param>
  112.     /// <returns>
  113.     /// Decrypted Message
  114.     /// </returns>
  115.     /// <exception cref="System.ArgumentException">Encrypted Message Required!;encryptedMessage</exception>
  116.     /// <remarks>
  117.     /// Significantly less secure than using random binary keys.
  118.     /// </remarks>
  119.     public static string SimpleDecryptWithPassword(string encryptedMessage, string password,
  120.                              int nonSecretPayloadLength = 0)
  121.     {
  122.       if (string.IsNullOrWhiteSpace(encryptedMessage))
  123.         throw new ArgumentException("Encrypted Message Required!", "encryptedMessage");
  124.  
  125.       var cipherText = Convert.FromBase64String(encryptedMessage);
  126.       var plainText = SimpleDecryptWithPassword(cipherText, password, nonSecretPayloadLength);
  127.       return plaintext == null ? null : Encoding.UTF8.GetString(plainText);
  128.     }
  129.  
  130.     public static byte[] SimpleEncrypt(byte[] secretMessage, byte[] cryptKey, byte[] authKey, byte[] nonSecretPayload = null)
  131.     {
  132.       //User Error Checks
  133.       if (cryptKey == null || cryptKey.Length != KeyBitSize / 8)
  134.         throw new ArgumentException(String.Format("Key needs to be {0} bit!", KeyBitSize), "cryptKey");
  135.  
  136.       if (authKey == null || authKey.Length != KeyBitSize / 8)
  137.         throw new ArgumentException(String.Format("Key needs to be {0} bit!", KeyBitSize), "authKey");
  138.  
  139.       if (secretMessage == null || secretMessage.Length < 1)
  140.         throw new ArgumentException("Secret Message Required!", "secretMessage");
  141.  
  142.       //non-secret payload optional
  143.       nonSecretPayload = nonSecretPayload ?? new byte[] { };
  144.  
  145.       byte[] cipherText;
  146.       byte[] iv;
  147.  
  148.       using (var aes = new AesManaged
  149.       {
  150.         KeySize = KeyBitSize,
  151.         BlockSize = BlockBitSize,
  152.         Mode = CipherMode.CBC,
  153.         Padding = PaddingMode.PKCS7
  154.       })
  155.       {
  156.  
  157.         //Use random IV
  158.         aes.GenerateIV();
  159.         iv = aes.IV;
  160.  
  161.         using (var encrypter = aes.CreateEncryptor(cryptKey, iv))
  162.         using (var cipherStream = new MemoryStream())
  163.         {
  164.           using (var cryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
  165.           using (var binaryWriter = new BinaryWriter(cryptoStream))
  166.           {
  167.             //Encrypt Data
  168.             binaryWriter.Write(secretMessage);
  169.           }
  170.  
  171.           cipherText = cipherStream.ToArray();
  172.         }
  173.  
  174.       }
  175.  
  176.       //Assemble encrypted message and add authentication
  177.       using (var hmac = new HMACSHA256(authKey))
  178.       using (var encryptedStream = new MemoryStream())
  179.       {
  180.         using (var binaryWriter = new BinaryWriter(encryptedStream))
  181.         {
  182.           //Prepend non-secret payload if any
  183.           binaryWriter.Write(nonSecretPayload);
  184.           //Prepend IV
  185.           binaryWriter.Write(iv);
  186.           //Write Ciphertext
  187.           binaryWriter.Write(cipherText);
  188.           binaryWriter.Flush();
  189.  
  190.           //Authenticate all data
  191.           var tag = hmac.ComputeHash(encryptedStream.ToArray());
  192.           //Postpend tag
  193.           binaryWriter.Write(tag);
  194.         }
  195.         return encryptedStream.ToArray();
  196.       }
  197.  
  198.     }
  199.  
  200.     public static byte[] SimpleDecrypt(byte[] encryptedMessage, byte[] cryptKey, byte[] authKey, int nonSecretPayloadLength = 0)
  201.     {
  202.  
  203.       //Basic Usage Error Checks
  204.       if (cryptKey == null || cryptKey.Length != KeyBitSize / 8)
  205.         throw new ArgumentException(String.Format("CryptKey needs to be {0} bit!", KeyBitSize), "cryptKey");
  206.  
  207.       if (authKey == null || authKey.Length != KeyBitSize / 8)
  208.         throw new ArgumentException(String.Format("AuthKey needs to be {0} bit!", KeyBitSize), "authKey");
  209.  
  210.       if (encryptedMessage == null || encryptedMessage.Length == 0)
  211.         throw new ArgumentException("Encrypted Message Required!", "encryptedMessage");
  212.  
  213.       using (var hmac = new HMACSHA256(authKey))
  214.       {
  215.         var sentTag = new byte[hmac.HashSize / 8];
  216.         //Calculate Tag
  217.         var calcTag = hmac.ComputeHash(encryptedMessage, 0, encryptedMessage.Length - sentTag.Length);
  218.         var ivLength = (BlockBitSize / 8);
  219.  
  220.         //if message length is to small just return null
  221.         if (encryptedMessage.Length < sentTag.Length + nonSecretPayloadLength + ivLength)
  222.           return null;
  223.  
  224.         //Grab Sent Tag
  225.         Array.Copy(encryptedMessage, encryptedMessage.Length - sentTag.Length, sentTag, 0, sentTag.Length);
  226.  
  227.         //Compare Tag with constant time comparison
  228.         var compare = 0;
  229.         for (var i = 0; i < sentTag.Length; i++)
  230.           compare |= sentTag[i] ^ calcTag[i];
  231.  
  232.         //if message doesn't authenticate return null
  233.         if (compare != 0)
  234.           return null;
  235.  
  236.         using (var aes = new AesManaged
  237.         {
  238.           KeySize = KeyBitSize,
  239.           BlockSize = BlockBitSize,
  240.           Mode = CipherMode.CBC,
  241.           Padding = PaddingMode.PKCS7
  242.         })
  243.         {
  244.  
  245.           //Grab IV from message
  246.           var iv = new byte[ivLength];
  247.           Array.Copy(encryptedMessage, nonSecretPayloadLength, iv, 0, iv.Length);
  248.  
  249.           using (var decrypter = aes.CreateDecryptor(cryptKey, iv))
  250.           using (var plainTextStream = new MemoryStream())
  251.           {
  252.             using (var decrypterStream = new CryptoStream(plainTextStream, decrypter, CryptoStreamMode.Write))
  253.             using (var binaryWriter = new BinaryWriter(decrypterStream))
  254.             {
  255.               //Decrypt Cipher Text from Message
  256.               binaryWriter.Write(
  257.                 encryptedMessage,
  258.                 nonSecretPayloadLength + iv.Length,
  259.                 encryptedMessage.Length - nonSecretPayloadLength - iv.Length - sentTag.Length
  260.               );
  261.             }
  262.             //Return Plain Text
  263.             return plainTextStream.ToArray();
  264.           }
  265.         }
  266.       }
  267.     }
  268.  
  269.     public static byte[] SimpleEncryptWithPassword(byte[] secretMessage, string password, byte[] nonSecretPayload = null)
  270.     {
  271.       nonSecretPayload = nonSecretPayload ?? new byte[] {};
  272.  
  273.       //User Error Checks
  274.       if (string.IsNullOrWhiteSpace(password) || password.Length < MinPasswordLength)
  275.         throw new ArgumentException(String.Format("Must have a password of at least {0} characters!", MinPasswordLength), "password");
  276.  
  277.       if (secretMessage == null || secretMessage.Length ==0)
  278.         throw new ArgumentException("Secret Message Required!", "secretMessage");
  279.  
  280.       var payload = new byte[((SaltBitSize / 8) * 2) + nonSecretPayload.Length];
  281.  
  282.       Array.Copy(nonSecretPayload, payload, nonSecretPayload.Length);
  283.       int payloadIndex = nonSecretPayload.Length;
  284.  
  285.       byte[] cryptKey;
  286.       byte[] authKey;
  287.       //Use Random Salt to prevent pre-generated weak password attacks.
  288.       using (var generator = new Rfc2898DeriveBytes(password, SaltBitSize / 8, Iterations))
  289.       {
  290.         var salt = generator.Salt;
  291.  
  292.         //Generate Keys
  293.         cryptKey = generator.GetBytes(KeyBitSize / 8);
  294.  
  295.         //Create Non Secret Payload
  296.         Array.Copy(salt, 0, payload, payloadIndex, salt.Length);
  297.         payloadIndex += salt.Length;
  298.       }
  299.  
  300.       //Deriving separate key, might be less efficient than using HKDF,
  301.       //but now compatible with RNEncryptor which had a very similar wireformat and requires less code than HKDF.
  302.       using (var generator = new Rfc2898DeriveBytes(password, SaltBitSize / 8, Iterations))
  303.       {
  304.         var salt = generator.Salt;
  305.  
  306.         //Generate Keys
  307.         authKey = generator.GetBytes(KeyBitSize / 8);
  308.  
  309.         //Create Rest of Non Secret Payload
  310.         Array.Copy(salt, 0, payload, payloadIndex, salt.Length);
  311.       }
  312.  
  313.       return SimpleEncrypt(secretMessage, cryptKey, authKey, payload);
  314.     }
  315.  
  316.     public static byte[] SimpleDecryptWithPassword(byte[] encryptedMessage, string password, int nonSecretPayloadLength = 0)
  317.     {
  318.       //User Error Checks
  319.       if (string.IsNullOrWhiteSpace(password) || password.Length < MinPasswordLength)
  320.         throw new ArgumentException(String.Format("Must have a password of at least {0} characters!", MinPasswordLength), "password");
  321.  
  322.       if (encryptedMessage == null || encryptedMessage.Length == 0)
  323.         throw new ArgumentException("Encrypted Message Required!", "encryptedMessage");
  324.  
  325.       var cryptSalt = new byte[SaltBitSize / 8];
  326.       var authSalt = new byte[SaltBitSize / 8];
  327.  
  328.       //Grab Salt from Non-Secret Payload
  329.       Array.Copy(encryptedMessage, nonSecretPayloadLength, cryptSalt, 0, cryptSalt.Length);
  330.       Array.Copy(encryptedMessage, nonSecretPayloadLength + cryptSalt.Length, authSalt, 0, authSalt.Length);
  331.  
  332.       byte[] cryptKey;
  333.       byte[] authKey;
  334.  
  335.       //Generate crypt key
  336.       using (var generator = new Rfc2898DeriveBytes(password, cryptSalt, Iterations))
  337.       {
  338.         cryptKey = generator.GetBytes(KeyBitSize / 8);
  339.       }
  340.       //Generate auth key
  341.       using (var generator = new Rfc2898DeriveBytes(password, authSalt, Iterations))
  342.       {
  343.         authKey = generator.GetBytes(KeyBitSize / 8);
  344.       }
  345.  
  346.       return SimpleDecrypt(encryptedMessage, cryptKey, authKey, cryptSalt.Length + authSalt.Length + nonSecretPayloadLength);
  347.     }
  348.   }
  349. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement