-
-
Save jbtule/4336842 to your computer and use it in GitHub Desktop.
/* | |
* This work (Modern Encryption of a String C#, by James Tuley), | |
* identified by James Tuley, is free of known copyright restrictions. | |
* https://gist.github.com/4336842 | |
* http://creativecommons.org/publicdomain/mark/1.0/ | |
*/ | |
using System; | |
using System.IO; | |
using System.Text; | |
using Org.BouncyCastle.Crypto; | |
using Org.BouncyCastle.Crypto.Engines; | |
using Org.BouncyCastle.Crypto.Generators; | |
using Org.BouncyCastle.Crypto.Modes; | |
using Org.BouncyCastle.Crypto.Parameters; | |
using Org.BouncyCastle.Security; | |
namespace Encryption | |
{ | |
public static class AESGCM | |
{ | |
private static readonly SecureRandom Random = new SecureRandom(); | |
//Preconfigured Encryption Parameters | |
public static readonly int NonceBitSize = 128; | |
public static readonly int MacBitSize = 128; | |
public static readonly int KeyBitSize = 256; | |
//Preconfigured Password Key Derivation Parameters | |
public static readonly int SaltBitSize = 128; | |
public static readonly int Iterations = 10000; | |
public static readonly int MinPasswordLength = 12; | |
/// <summary> | |
/// Helper that generates a random new key on each call. | |
/// </summary> | |
/// <returns></returns> | |
public static byte[] NewKey() | |
{ | |
var key = new byte[KeyBitSize / 8]; | |
Random.NextBytes(key); | |
return key; | |
} | |
/// <summary> | |
/// Simple Encryption And Authentication (AES-GCM) of a UTF8 string. | |
/// </summary> | |
/// <param name="secretMessage">The secret message.</param> | |
/// <param name="key">The key.</param> | |
/// <param name="nonSecretPayload">Optional non-secret payload.</param> | |
/// <returns> | |
/// Encrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">Secret Message Required!;secretMessage</exception> | |
/// <remarks> | |
/// Adds overhead of (Optional-Payload + BlockSize(16) + Message + HMac-Tag(16)) * 1.33 Base64 | |
/// </remarks> | |
public static string SimpleEncrypt(string secretMessage, byte[] key, byte[] nonSecretPayload = null) | |
{ | |
if (string.IsNullOrEmpty(secretMessage)) | |
throw new ArgumentException("Secret Message Required!", "secretMessage"); | |
var plainText = Encoding.UTF8.GetBytes(secretMessage); | |
var cipherText = SimpleEncrypt(plainText, key, nonSecretPayload); | |
return Convert.ToBase64String(cipherText); | |
} | |
/// <summary> | |
/// Simple Decryption & Authentication (AES-GCM) of a UTF8 Message | |
/// </summary> | |
/// <param name="encryptedMessage">The encrypted message.</param> | |
/// <param name="key">The key.</param> | |
/// <param name="nonSecretPayloadLength">Length of the optional non-secret payload.</param> | |
/// <returns>Decrypted Message</returns> | |
public static string SimpleDecrypt(string encryptedMessage, byte[] key, int nonSecretPayloadLength = 0) | |
{ | |
if (string.IsNullOrEmpty(encryptedMessage)) | |
throw new ArgumentException("Encrypted Message Required!", "encryptedMessage"); | |
var cipherText = Convert.FromBase64String(encryptedMessage); | |
var plainText = SimpleDecrypt(cipherText, key, nonSecretPayloadLength); | |
return plainText == null ? null : Encoding.UTF8.GetString(plainText); | |
} | |
/// <summary> | |
/// Simple Encryption And Authentication (AES-GCM) of a UTF8 String | |
/// using key derived from a password (PBKDF2). | |
/// </summary> | |
/// <param name="secretMessage">The secret message.</param> | |
/// <param name="password">The password.</param> | |
/// <param name="nonSecretPayload">The non secret payload.</param> | |
/// <returns> | |
/// Encrypted Message | |
/// </returns> | |
/// <remarks> | |
/// Significantly less secure than using random binary keys. | |
/// Adds additional non secret payload for key generation parameters. | |
/// </remarks> | |
public static string SimpleEncryptWithPassword(string secretMessage, string password, | |
byte[] nonSecretPayload = null) | |
{ | |
if (string.IsNullOrEmpty(secretMessage)) | |
throw new ArgumentException("Secret Message Required!", "secretMessage"); | |
var plainText = Encoding.UTF8.GetBytes(secretMessage); | |
var cipherText = SimpleEncryptWithPassword(plainText, password, nonSecretPayload); | |
return Convert.ToBase64String(cipherText); | |
} | |
/// <summary> | |
/// Simple Decryption and Authentication (AES-GCM) of a UTF8 message | |
/// using a key derived from a password (PBKDF2) | |
/// </summary> | |
/// <param name="encryptedMessage">The encrypted message.</param> | |
/// <param name="password">The password.</param> | |
/// <param name="nonSecretPayloadLength">Length of the non secret payload.</param> | |
/// <returns> | |
/// Decrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">Encrypted Message Required!;encryptedMessage</exception> | |
/// <remarks> | |
/// Significantly less secure than using random binary keys. | |
/// </remarks> | |
public static string SimpleDecryptWithPassword(string encryptedMessage, string password, | |
int nonSecretPayloadLength = 0) | |
{ | |
if (string.IsNullOrWhiteSpace(encryptedMessage)) | |
throw new ArgumentException("Encrypted Message Required!", "encryptedMessage"); | |
var cipherText = Convert.FromBase64String(encryptedMessage); | |
var plainText = SimpleDecryptWithPassword(cipherText, password, nonSecretPayloadLength); | |
return plainText == null ? null : Encoding.UTF8.GetString(plainText); | |
} | |
/// <summary> | |
/// Simple Encryption And Authentication (AES-GCM) of a UTF8 string. | |
/// </summary> | |
/// <param name="secretMessage">The secret message.</param> | |
/// <param name="key">The key.</param> | |
/// <param name="nonSecretPayload">Optional non-secret payload.</param> | |
/// <returns>Encrypted Message</returns> | |
/// <remarks> | |
/// Adds overhead of (Optional-Payload + BlockSize(16) + Message + HMac-Tag(16)) * 1.33 Base64 | |
/// </remarks> | |
public static byte[] SimpleEncrypt(byte[] secretMessage, byte[] key, byte[] nonSecretPayload = null) | |
{ | |
//User Error Checks | |
if (key == null || key.Length != KeyBitSize / 8) | |
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KeyBitSize), "key"); | |
if (secretMessage == null || secretMessage.Length == 0) | |
throw new ArgumentException("Secret Message Required!", "secretMessage"); | |
//Non-secret Payload Optional | |
nonSecretPayload = nonSecretPayload ?? new byte[] { }; | |
//Using random nonce large enough not to repeat | |
var nonce = new byte[NonceBitSize / 8]; | |
Random.NextBytes(nonce, 0, nonce.Length); | |
var cipher = new GcmBlockCipher(new AesFastEngine()); | |
var parameters = new AeadParameters(new KeyParameter(key), MacBitSize, nonce, nonSecretPayload); | |
cipher.Init(true, parameters); | |
//Generate Cipher Text With Auth Tag | |
var cipherText = new byte[cipher.GetOutputSize(secretMessage.Length)]; | |
var len = cipher.ProcessBytes(secretMessage, 0, secretMessage.Length, cipherText, 0); | |
cipher.DoFinal(cipherText, len); | |
//Assemble Message | |
using (var combinedStream = new MemoryStream()) | |
{ | |
using (var binaryWriter = new BinaryWriter(combinedStream)) | |
{ | |
//Prepend Authenticated Payload | |
binaryWriter.Write(nonSecretPayload); | |
//Prepend Nonce | |
binaryWriter.Write(nonce); | |
//Write Cipher Text | |
binaryWriter.Write(cipherText); | |
} | |
return combinedStream.ToArray(); | |
} | |
} | |
/// <summary> | |
/// Simple Decryption & Authentication (AES-GCM) of a UTF8 Message | |
/// </summary> | |
/// <param name="encryptedMessage">The encrypted message.</param> | |
/// <param name="key">The key.</param> | |
/// <param name="nonSecretPayloadLength">Length of the optional non-secret payload.</param> | |
/// <returns>Decrypted Message</returns> | |
public static byte[] SimpleDecrypt(byte[] encryptedMessage, byte[] key, int nonSecretPayloadLength = 0) | |
{ | |
//User Error Checks | |
if (key == null || key.Length != KeyBitSize / 8) | |
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KeyBitSize), "key"); | |
if (encryptedMessage == null || encryptedMessage.Length == 0) | |
throw new ArgumentException("Encrypted Message Required!", "encryptedMessage"); | |
using (var cipherStream = new MemoryStream(encryptedMessage)) | |
using (var cipherReader = new BinaryReader(cipherStream)) | |
{ | |
//Grab Payload | |
var nonSecretPayload = cipherReader.ReadBytes(nonSecretPayloadLength); | |
//Grab Nonce | |
var nonce = cipherReader.ReadBytes(NonceBitSize / 8); | |
var cipher = new GcmBlockCipher(new AesFastEngine()); | |
var parameters = new AeadParameters(new KeyParameter(key), MacBitSize, nonce, nonSecretPayload); | |
cipher.Init(false, parameters); | |
//Decrypt Cipher Text | |
var cipherText = cipherReader.ReadBytes(encryptedMessage.Length - nonSecretPayloadLength - nonce.Length); | |
var plainText = new byte[cipher.GetOutputSize(cipherText.Length)]; | |
try | |
{ | |
var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0); | |
cipher.DoFinal(plainText, len); | |
} | |
catch (InvalidCipherTextException) | |
{ | |
//Return null if it doesn't authenticate | |
return null; | |
} | |
return plainText; | |
} | |
} | |
/// <summary> | |
/// Simple Encryption And Authentication (AES-GCM) of a UTF8 String | |
/// using key derived from a password. | |
/// </summary> | |
/// <param name="secretMessage">The secret message.</param> | |
/// <param name="password">The password.</param> | |
/// <param name="nonSecretPayload">The non secret payload.</param> | |
/// <returns> | |
/// Encrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">Must have a password of minimum length;password</exception> | |
/// <remarks> | |
/// Significantly less secure than using random binary keys. | |
/// Adds additional non secret payload for key generation parameters. | |
/// </remarks> | |
public static byte[] SimpleEncryptWithPassword(byte[] secretMessage, string password, byte[] nonSecretPayload = null) | |
{ | |
nonSecretPayload = nonSecretPayload ?? new byte[] {}; | |
//User Error Checks | |
if (string.IsNullOrWhiteSpace(password) || password.Length < MinPasswordLength) | |
throw new ArgumentException(String.Format("Must have a password of at least {0} characters!", MinPasswordLength), "password"); | |
if (secretMessage == null || secretMessage.Length == 0) | |
throw new ArgumentException("Secret Message Required!", "secretMessage"); | |
var generator = new Pkcs5S2ParametersGenerator(); | |
//Use Random Salt to minimize pre-generated weak password attacks. | |
var salt = new byte[SaltBitSize / 8]; | |
Random.NextBytes(salt); | |
generator.Init( | |
PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), | |
salt, | |
Iterations); | |
//Generate Key | |
var key = (KeyParameter)generator.GenerateDerivedMacParameters(KeyBitSize); | |
//Create Full Non Secret Payload | |
var payload = new byte[salt.Length + nonSecretPayload.Length]; | |
Array.Copy(nonSecretPayload, payload, nonSecretPayload.Length); | |
Array.Copy(salt,0, payload,nonSecretPayload.Length, salt.Length); | |
return SimpleEncrypt(secretMessage, key.GetKey(), payload); | |
} | |
/// <summary> | |
/// Simple Decryption and Authentication of a UTF8 message | |
/// using a key derived from a password | |
/// </summary> | |
/// <param name="encryptedMessage">The encrypted message.</param> | |
/// <param name="password">The password.</param> | |
/// <param name="nonSecretPayloadLength">Length of the non secret payload.</param> | |
/// <returns> | |
/// Decrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">Must have a password of minimum length;password</exception> | |
/// <remarks> | |
/// Significantly less secure than using random binary keys. | |
/// </remarks> | |
public static byte[] SimpleDecryptWithPassword(byte[] encryptedMessage, string password, int nonSecretPayloadLength = 0) | |
{ | |
//User Error Checks | |
if (string.IsNullOrWhiteSpace(password) || password.Length < MinPasswordLength) | |
throw new ArgumentException(String.Format("Must have a password of at least {0} characters!", MinPasswordLength), "password"); | |
if (encryptedMessage == null || encryptedMessage.Length == 0) | |
throw new ArgumentException("Encrypted Message Required!", "encryptedMessage"); | |
var generator = new Pkcs5S2ParametersGenerator(); | |
//Grab Salt from Payload | |
var salt = new byte[SaltBitSize / 8]; | |
Array.Copy(encryptedMessage, nonSecretPayloadLength, salt, 0, salt.Length); | |
generator.Init( | |
PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), | |
salt, | |
Iterations); | |
//Generate Key | |
var key = (KeyParameter)generator.GenerateDerivedMacParameters(KeyBitSize); | |
return SimpleDecrypt(encryptedMessage, key.GetKey(), salt.Length + nonSecretPayloadLength); | |
} | |
} | |
} |
/* | |
* This work (Modern Encryption of a String C#, by James Tuley), | |
* identified by James Tuley, is free of known copyright restrictions. | |
* https://gist.github.com/4336842 | |
* http://creativecommons.org/publicdomain/mark/1.0/ | |
*/ | |
using System; | |
using System.IO; | |
using System.Security.Cryptography; | |
using System.Text; | |
namespace Encryption | |
{ | |
public static class AESThenHMAC | |
{ | |
private static readonly RandomNumberGenerator Random = RandomNumberGenerator.Create(); | |
//Preconfigured Encryption Parameters | |
public static readonly int BlockBitSize = 128; | |
public static readonly int KeyBitSize = 256; | |
//Preconfigured Password Key Derivation Parameters | |
public static readonly int SaltBitSize = 64; | |
public static readonly int Iterations = 10000; | |
public static readonly int MinPasswordLength = 12; | |
/// <summary> | |
/// Helper that generates a random key on each call. | |
/// </summary> | |
/// <returns></returns> | |
public static byte[] NewKey() | |
{ | |
var key = new byte[KeyBitSize / 8]; | |
Random.GetBytes(key); | |
return key; | |
} | |
/// <summary> | |
/// Simple Encryption (AES) then Authentication (HMAC) for a UTF8 Message. | |
/// </summary> | |
/// <param name="secretMessage">The secret message.</param> | |
/// <param name="cryptKey">The crypt key.</param> | |
/// <param name="authKey">The auth key.</param> | |
/// <param name="nonSecretPayload">(Optional) Non-Secret Payload.</param> | |
/// <returns> | |
/// Encrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">Secret Message Required!;secretMessage</exception> | |
/// <remarks> | |
/// Adds overhead of (Optional-Payload + BlockSize(16) + Message-Padded-To-Blocksize + HMac-Tag(32)) * 1.33 Base64 | |
/// </remarks> | |
public static string SimpleEncrypt(string secretMessage, byte[] cryptKey, byte[] authKey, | |
byte[] nonSecretPayload = null) | |
{ | |
if (string.IsNullOrEmpty(secretMessage)) | |
throw new ArgumentException("Secret Message Required!", "secretMessage"); | |
var plainText = Encoding.UTF8.GetBytes(secretMessage); | |
var cipherText = SimpleEncrypt(plainText, cryptKey, authKey, nonSecretPayload); | |
return Convert.ToBase64String(cipherText); | |
} | |
/// <summary> | |
/// Simple Authentication (HMAC) then Decryption (AES) for a secrets UTF8 Message. | |
/// </summary> | |
/// <param name="encryptedMessage">The encrypted message.</param> | |
/// <param name="cryptKey">The crypt key.</param> | |
/// <param name="authKey">The auth key.</param> | |
/// <param name="nonSecretPayloadLength">Length of the non secret payload.</param> | |
/// <returns> | |
/// Decrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">Encrypted Message Required!;encryptedMessage</exception> | |
public static string SimpleDecrypt(string encryptedMessage, byte[] cryptKey, byte[] authKey, | |
int nonSecretPayloadLength = 0) | |
{ | |
if (string.IsNullOrWhiteSpace(encryptedMessage)) | |
throw new ArgumentException("Encrypted Message Required!", "encryptedMessage"); | |
var cipherText = Convert.FromBase64String(encryptedMessage); | |
var plainText = SimpleDecrypt(cipherText, cryptKey, authKey, nonSecretPayloadLength); | |
return plainText == null ? null : Encoding.UTF8.GetString(plainText); | |
} | |
/// <summary> | |
/// Simple Encryption (AES) then Authentication (HMAC) of a UTF8 message | |
/// using Keys derived from a Password (PBKDF2). | |
/// </summary> | |
/// <param name="secretMessage">The secret message.</param> | |
/// <param name="password">The password.</param> | |
/// <param name="nonSecretPayload">The non secret payload.</param> | |
/// <returns> | |
/// Encrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">password</exception> | |
/// <remarks> | |
/// Significantly less secure than using random binary keys. | |
/// Adds additional non secret payload for key generation parameters. | |
/// </remarks> | |
public static string SimpleEncryptWithPassword(string secretMessage, string password, | |
byte[] nonSecretPayload = null) | |
{ | |
if (string.IsNullOrEmpty(secretMessage)) | |
throw new ArgumentException("Secret Message Required!", "secretMessage"); | |
var plainText = Encoding.UTF8.GetBytes(secretMessage); | |
var cipherText = SimpleEncryptWithPassword(plainText, password, nonSecretPayload); | |
return Convert.ToBase64String(cipherText); | |
} | |
/// <summary> | |
/// Simple Authentication (HMAC) and then Descryption (AES) of a UTF8 Message | |
/// using keys derived from a password (PBKDF2). | |
/// </summary> | |
/// <param name="encryptedMessage">The encrypted message.</param> | |
/// <param name="password">The password.</param> | |
/// <param name="nonSecretPayloadLength">Length of the non secret payload.</param> | |
/// <returns> | |
/// Decrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">Encrypted Message Required!;encryptedMessage</exception> | |
/// <remarks> | |
/// Significantly less secure than using random binary keys. | |
/// </remarks> | |
public static string SimpleDecryptWithPassword(string encryptedMessage, string password, | |
int nonSecretPayloadLength = 0) | |
{ | |
if (string.IsNullOrWhiteSpace(encryptedMessage)) | |
throw new ArgumentException("Encrypted Message Required!", "encryptedMessage"); | |
var cipherText = Convert.FromBase64String(encryptedMessage); | |
var plainText = SimpleDecryptWithPassword(cipherText, password, nonSecretPayloadLength); | |
return plainText == null ? null : Encoding.UTF8.GetString(plainText); | |
} | |
/// <summary> | |
/// Simple Encryption(AES) then Authentication (HMAC) for a UTF8 Message. | |
/// </summary> | |
/// <param name="secretMessage">The secret message.</param> | |
/// <param name="cryptKey">The crypt key.</param> | |
/// <param name="authKey">The auth key.</param> | |
/// <param name="nonSecretPayload">(Optional) Non-Secret Payload.</param> | |
/// <returns> | |
/// Encrypted Message | |
/// </returns> | |
/// <remarks> | |
/// Adds overhead of (Optional-Payload + BlockSize(16) + Message-Padded-To-Blocksize + HMac-Tag(32)) * 1.33 Base64 | |
/// </remarks> | |
public static byte[] SimpleEncrypt(byte[] secretMessage, byte[] cryptKey, byte[] authKey, byte[] nonSecretPayload = null) | |
{ | |
//User Error Checks | |
if (cryptKey == null || cryptKey.Length != KeyBitSize / 8) | |
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KeyBitSize), "cryptKey"); | |
if (authKey == null || authKey.Length != KeyBitSize / 8) | |
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KeyBitSize), "authKey"); | |
if (secretMessage == null || secretMessage.Length < 1) | |
throw new ArgumentException("Secret Message Required!", "secretMessage"); | |
//non-secret payload optional | |
nonSecretPayload = nonSecretPayload ?? new byte[] { }; | |
byte[] cipherText; | |
byte[] iv; | |
using (var aes = new AesManaged | |
{ | |
KeySize = KeyBitSize, | |
BlockSize = BlockBitSize, | |
Mode = CipherMode.CBC, | |
Padding = PaddingMode.PKCS7 | |
}) | |
{ | |
//Use random IV | |
aes.GenerateIV(); | |
iv = aes.IV; | |
using (var encrypter = aes.CreateEncryptor(cryptKey, iv)) | |
using (var cipherStream = new MemoryStream()) | |
{ | |
using (var cryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write)) | |
using (var binaryWriter = new BinaryWriter(cryptoStream)) | |
{ | |
//Encrypt Data | |
binaryWriter.Write(secretMessage); | |
} | |
cipherText = cipherStream.ToArray(); | |
} | |
} | |
//Assemble encrypted message and add authentication | |
using (var hmac = new HMACSHA256(authKey)) | |
using (var encryptedStream = new MemoryStream()) | |
{ | |
using (var binaryWriter = new BinaryWriter(encryptedStream)) | |
{ | |
//Prepend non-secret payload if any | |
binaryWriter.Write(nonSecretPayload); | |
//Prepend IV | |
binaryWriter.Write(iv); | |
//Write Ciphertext | |
binaryWriter.Write(cipherText); | |
binaryWriter.Flush(); | |
//Authenticate all data | |
var tag = hmac.ComputeHash(encryptedStream.ToArray()); | |
//Postpend tag | |
binaryWriter.Write(tag); | |
} | |
return encryptedStream.ToArray(); | |
} | |
} | |
/// <summary> | |
/// Simple Authentication (HMAC) then Decryption (AES) for a secrets UTF8 Message. | |
/// </summary> | |
/// <param name="encryptedMessage">The encrypted message.</param> | |
/// <param name="cryptKey">The crypt key.</param> | |
/// <param name="authKey">The auth key.</param> | |
/// <param name="nonSecretPayloadLength">Length of the non secret payload.</param> | |
/// <returns>Decrypted Message</returns> | |
public static byte[] SimpleDecrypt(byte[] encryptedMessage, byte[] cryptKey, byte[] authKey, int nonSecretPayloadLength = 0) | |
{ | |
//Basic Usage Error Checks | |
if (cryptKey == null || cryptKey.Length != KeyBitSize / 8) | |
throw new ArgumentException(String.Format("CryptKey needs to be {0} bit!", KeyBitSize), "cryptKey"); | |
if (authKey == null || authKey.Length != KeyBitSize / 8) | |
throw new ArgumentException(String.Format("AuthKey needs to be {0} bit!", KeyBitSize), "authKey"); | |
if (encryptedMessage == null || encryptedMessage.Length == 0) | |
throw new ArgumentException("Encrypted Message Required!", "encryptedMessage"); | |
using (var hmac = new HMACSHA256(authKey)) | |
{ | |
var sentTag = new byte[hmac.HashSize / 8]; | |
//Calculate Tag | |
var calcTag = hmac.ComputeHash(encryptedMessage, 0, encryptedMessage.Length - sentTag.Length); | |
var ivLength = (BlockBitSize / 8); | |
//if message length is to small just return null | |
if (encryptedMessage.Length < sentTag.Length + nonSecretPayloadLength + ivLength) | |
return null; | |
//Grab Sent Tag | |
Array.Copy(encryptedMessage, encryptedMessage.Length - sentTag.Length, sentTag, 0, sentTag.Length); | |
//Compare Tag with constant time comparison | |
var compare = 0; | |
for (var i = 0; i < sentTag.Length; i++) | |
compare |= sentTag[i] ^ calcTag[i]; | |
//if message doesn't authenticate return null | |
if (compare != 0) | |
return null; | |
using (var aes = new AesManaged | |
{ | |
KeySize = KeyBitSize, | |
BlockSize = BlockBitSize, | |
Mode = CipherMode.CBC, | |
Padding = PaddingMode.PKCS7 | |
}) | |
{ | |
//Grab IV from message | |
var iv = new byte[ivLength]; | |
Array.Copy(encryptedMessage, nonSecretPayloadLength, iv, 0, iv.Length); | |
using (var decrypter = aes.CreateDecryptor(cryptKey, iv)) | |
using (var plainTextStream = new MemoryStream()) | |
{ | |
using (var decrypterStream = new CryptoStream(plainTextStream, decrypter, CryptoStreamMode.Write)) | |
using (var binaryWriter = new BinaryWriter(decrypterStream)) | |
{ | |
//Decrypt Cipher Text from Message | |
binaryWriter.Write( | |
encryptedMessage, | |
nonSecretPayloadLength + iv.Length, | |
encryptedMessage.Length - nonSecretPayloadLength - iv.Length - sentTag.Length | |
); | |
} | |
//Return Plain Text | |
return plainTextStream.ToArray(); | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// Simple Encryption (AES) then Authentication (HMAC) of a UTF8 message | |
/// using Keys derived from a Password (PBKDF2) | |
/// </summary> | |
/// <param name="secretMessage">The secret message.</param> | |
/// <param name="password">The password.</param> | |
/// <param name="nonSecretPayload">The non secret payload.</param> | |
/// <returns> | |
/// Encrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">Must have a password of minimum length;password</exception> | |
/// <remarks> | |
/// Significantly less secure than using random binary keys. | |
/// Adds additional non secret payload for key generation parameters. | |
/// </remarks> | |
public static byte[] SimpleEncryptWithPassword(byte[] secretMessage, string password, byte[] nonSecretPayload = null) | |
{ | |
nonSecretPayload = nonSecretPayload ?? new byte[] {}; | |
//User Error Checks | |
if (string.IsNullOrWhiteSpace(password) || password.Length < MinPasswordLength) | |
throw new ArgumentException(String.Format("Must have a password of at least {0} characters!", MinPasswordLength), "password"); | |
if (secretMessage == null || secretMessage.Length ==0) | |
throw new ArgumentException("Secret Message Required!", "secretMessage"); | |
var payload = new byte[((SaltBitSize / 8) * 2) + nonSecretPayload.Length]; | |
Array.Copy(nonSecretPayload, payload, nonSecretPayload.Length); | |
int payloadIndex = nonSecretPayload.Length; | |
byte[] cryptKey; | |
byte[] authKey; | |
//Use Random Salt to prevent pre-generated weak password attacks. | |
using (var generator = new Rfc2898DeriveBytes(password, SaltBitSize / 8, Iterations)) | |
{ | |
var salt = generator.Salt; | |
//Generate Keys | |
cryptKey = generator.GetBytes(KeyBitSize / 8); | |
//Create Non Secret Payload | |
Array.Copy(salt, 0, payload, payloadIndex, salt.Length); | |
payloadIndex += salt.Length; | |
} | |
//Deriving separate key, might be less efficient than using HKDF, | |
//but now compatible with RNEncryptor which had a very similar wireformat and requires less code than HKDF. | |
using (var generator = new Rfc2898DeriveBytes(password, SaltBitSize / 8, Iterations)) | |
{ | |
var salt = generator.Salt; | |
//Generate Keys | |
authKey = generator.GetBytes(KeyBitSize / 8); | |
//Create Rest of Non Secret Payload | |
Array.Copy(salt, 0, payload, payloadIndex, salt.Length); | |
} | |
return SimpleEncrypt(secretMessage, cryptKey, authKey, payload); | |
} | |
/// <summary> | |
/// Simple Authentication (HMAC) and then Descryption (AES) of a UTF8 Message | |
/// using keys derived from a password (PBKDF2). | |
/// </summary> | |
/// <param name="encryptedMessage">The encrypted message.</param> | |
/// <param name="password">The password.</param> | |
/// <param name="nonSecretPayloadLength">Length of the non secret payload.</param> | |
/// <returns> | |
/// Decrypted Message | |
/// </returns> | |
/// <exception cref="System.ArgumentException">Must have a password of minimum length;password</exception> | |
/// <remarks> | |
/// Significantly less secure than using random binary keys. | |
/// </remarks> | |
public static byte[] SimpleDecryptWithPassword(byte[] encryptedMessage, string password, int nonSecretPayloadLength = 0) | |
{ | |
//User Error Checks | |
if (string.IsNullOrWhiteSpace(password) || password.Length < MinPasswordLength) | |
throw new ArgumentException(String.Format("Must have a password of at least {0} characters!", MinPasswordLength), "password"); | |
if (encryptedMessage == null || encryptedMessage.Length == 0) | |
throw new ArgumentException("Encrypted Message Required!", "encryptedMessage"); | |
var cryptSalt = new byte[SaltBitSize / 8]; | |
var authSalt = new byte[SaltBitSize / 8]; | |
//Grab Salt from Non-Secret Payload | |
Array.Copy(encryptedMessage, nonSecretPayloadLength, cryptSalt, 0, cryptSalt.Length); | |
Array.Copy(encryptedMessage, nonSecretPayloadLength + cryptSalt.Length, authSalt, 0, authSalt.Length); | |
byte[] cryptKey; | |
byte[] authKey; | |
//Generate crypt key | |
using (var generator = new Rfc2898DeriveBytes(password, cryptSalt, Iterations)) | |
{ | |
cryptKey = generator.GetBytes(KeyBitSize / 8); | |
} | |
//Generate auth key | |
using (var generator = new Rfc2898DeriveBytes(password, authSalt, Iterations)) | |
{ | |
authKey = generator.GetBytes(KeyBitSize / 8); | |
} | |
return SimpleDecrypt(encryptedMessage, cryptKey, authKey, cryptSalt.Length + authSalt.Length + nonSecretPayloadLength); | |
} | |
} | |
} |
The AESGCM code doesn't perform any authentication of the associated data (AD part of AEAD). I would have expected the Decrypt to catch it but it seems only the length is used; not sure how decrypt would even be able to check. If you keep the AD length the same Decrypt doesn't complain ... the following piece shows what I'm saying:
public void Run()
{
byte[] key256 = new byte[32];
for (int i = 0; i < 32; i++)
key256[i] = Convert.ToByte(i % 256);
string message = "SuperSecretInfoHere!";
byte[] nonSecretOrg = Encoding.UTF8.GetBytes("Pay Bob Zero Dollars");
byte[] nonSecretMod = Encoding.UTF8.GetBytes("Pay Bob $ 1,000,000.");
// Encrypt with associated data
string encrypted = AESGCM.SimpleEncrypt(message, key256, nonSecretOrg);
// Decrypt with original associated data
string decrypted = AESGCM.SimpleDecrypt(encrypted, key256, nonSecretOrg.Length);
Console.WriteLine("Decrypted: {0}", decrypted);
Console.WriteLine("Auth cleartext: {0}", Encoding.UTF8.GetString(nonSecretOrg));
// Decrypt with modified associated data (only length checks out)
string decrypted2 = AESGCM.SimpleDecrypt(encrypted, key256, nonSecretMod.Length);
Console.WriteLine("Decrypted: {0}", decrypted2);
Console.WriteLine("Auth cleartext: {0}", Encoding.UTF8.GetString(nonSecretMod));
Console.ReadLine();
}
Also, it's best to make the SecureRandom Random = new SecureRandom();
into Random Random = new Random();
since the requirement for GCM isn't a cryptographic random IV like AES-CBC - it's that the IV should be different each time. You could very well use an incremental counter. The reason is speed - I'm seeing a substantial throughput boost (only for small byte sizes, less than 10 bytes) without impacting the security of the scheme. That simulates many but small writes to database cells.
@SidShetye, Sorry, gist, didn't notify me of these comments :-/
In relation to the comment about additional authenticated data, that is prepended to the ciphertext in the clear, ideally meant for headers of data, and it does get checked by the algorithm for modification, it's also used to pass the salt when the password method is used too.
In regards to performance based on the IV generation, since this is single file encryption, i didn't want to use a counter. And while you are right unpredictable isn't necessary, just nonrepeating, I feel better about using SecureRandom than Random, in this example specifically, as the ciphertext (without AD) is ideally indistinguishable from random data in this case.
I've actually made some performance changes to my keyczar-dotnet library though, based on your BouncyBench, but until the next bouncy castle is released on nuget, my library won't be seeing the boost yet.
Thanks a lot, for the comments!
I strongly disagree with @SidShetye's suggestion to use System.Random
instead of a CSPRNG. Using System.Random
will likely lead to catastrophic failures when you encrypt 100k messages under a key.
The way System.Random
is seeded is a 31 bit value returned by GetTickCount()
. If we'd assume that this value is totally random, we'd get a nonce collision after on average 46k message. A single reused nonce completely destroys the MAC part of GCM for all future and past message and destroys the confidentiality for that particular message.
But GetTickCount()
isn't even random, it's the number of milliseconds since the last reboot. For people who regularly restart their computer, this causes a bias towards small values.
I have posted your source here: http://www.volatileread.com/UtilityLibrary?id=1083, with minor modifications and an online test case. Please let me know if you have any objections to this. I'll pull it down.
@vayuta no objections, I created this sample code to help combat all the unsourced bad examples of encryption that get cut and pasted into things.
There is a small issue when compiling this gist:
In file AESThenHMAC.cs:
Line 83: return plaintext == null ? null : Encoding.UTF8.GetString(plainText);
should be: return plainText ... (with a capital "T")
@jbtule What are good sources for the crypt and auth keys? This answer suggests we can use the SHA256 hash of a master key to create 256bit of "key material" but since the crypt and auth keys are hard-coded to require 256bits each, should we re-use the SHA256 hash for both keys or do you recommend using a SHA512 hash we can split in 2 to create the auth/crypt keys? Should we use Rfc2898DeriveBytes on SHA hash to derive the keys?
Small typo (a couple of occurrences): plaintext
--> plainText
Hi,
Could anyone help me to do in C# for the below java code..
Java Code:
FileInputStream fileInputStream = null;
File file = new File("C:\temp\DI_Yudo_US_NP_1_ACCT.der");
byte[] bFile = new byte[(int) file.length()];
try {
/* Convert file into array of bytes */
fileInputStream = new FileInputStream(file);
fileInputStream.read(bFile);
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
/* Encrypt the iss /
X509EncodedKeySpec spec = new X509EncodedKeySpec(bFile);
KeyFactory kf = KeyFactory.getInstance("RSA");
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, kf.generatePublic(spec));
byte[] stringBytes = ("[email protected]").getBytes("UTF8");
byte[] raw = cipher.doFinal(stringBytes);
/ Encode the iss */
String clientIdbase64 = Base64.encodeBase64String(raw);
System.out.println(clientIdbase64);
Your help will be greatly appriciated..
typos fixed, added a null guard into the gist and didn't check to see that I wasn't consistent in my casing.
@mythz unfortunately gist comments don't send emails or provide any notification or anything. However, if you want to derive key material you can do like that that stackexchange post suggests, but use SHA512. Also the hard coded values at the top are adjustable. I hard coded them as this is example code, if you want to change them and drop down to 128, everything should be consistent, I think it's a better practice to use 256 in general, but use cases vary and it's still secure at this point in time.
Hi all,
i got it working. What is the difference between "SimdpleDecrypt" and "SimpleDecryptWithPassword"?
If I'm using key rotation would it be sensible to store the key ID (e.g. a GUID) in the non-secret portion,
then use this GUID to obtain the keys from the key repository?
I suggest to put the Aes class creation into the separate method in the AESThenHMAC
:
private static Aes CreateAes()
{
return new AesManaged
{
KeySize = KeyBitSize,
BlockSize = BlockBitSize,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
};
}
So as it easy to switch to the AesCryptoServiceProvider
(or change parameters, etc.)
@jbtule Has anyone made a NuGet package with this?
I am thinking of doing that, if nobody else has. I only need the AESThenHMAC one (and I don't need the SimplePassword methods). If someone already did that, it would save me the trouble :)
For anyone looking for a simple library for this, that you can install via NuGet, I packed it together here: https://github.com/trustpilot/nuget-authenticated-encryption
NuGet package: https://www.nuget.org/packages/AuthenticatedEncryption/
Install-Package AuthenticatedEncryption
Why do you return null instead of an exception when checking the message length or when autenticating the message in the SimpleDecrypt method?
it is for security reason?
In .NET Built-in Encrypt(AES)-Then-MAC(HMAC) , why all methods are RECURSIVE ???
And where is their recursive-Exit-Condition ?!??!
I Cant understand it...
Why not making this a NuGet package?
Very interesting! Thanks fro sharing.
What modifications would I need to make to get deterministic encryption?
Just a FYI. In VS 2019 and target /NET 4.6, using BouncyCastle Version 1.8.5, I get two lines (165, 215) in the version listed above with 'AesFastEngine' is obsolete: 'Use AesEngine instead'. It compiles OK after that change.
What is nonSecretPayload? What is it's purpose? I know it is optional. It appears to be an array of bytes (or chars) that are prepended along with the initialization vector (iv) to the encrypted message. Then the hash is computed and added to the end. So, is nonSecretPayload simply the optional salt for the hash?
It's just an authenticated payload that is not encrypted. You can use it if you have anything that you want to be visible but not manipulated by a third party.
Ah. OK. Now, that I think about it, I can think of a few scenarios in addition to it simply being salt for the hash. Thank you for your reply.
👍 for a Nuget package.
It would be nice to make it into a VS2012 solution which spits out the time and size characteristics. Will bring this "alive". I usually leave my stackoverflow answers in just SO and then forget it. Just wanted to say - "great stuff" !