BouncyCastle is a really powerful encryption library that most Java developers will know about. It has been a fundamental library for many years now. They also have an inter-operable library for C# that can be compiled in the full .net framework and Silverlight. Most .net developers use the CNG library wrapper class library which has advantages such as some FIPS certifications. However, that is only implemented as of Vista and above and is not implemented in platforms such as Silverlight or any of the Mono or MonoTouch frameworks so there are still compelling reasons to use BouncyCastle in C#. In an ideal world you should be able to encrypt/decrypt across all libraries and, as it turns out, you can. Ok, I’ll shut up and get to the code. The following is an example of using BouncyCastle in Java, C# and an equivalent example in .net CNG:
// C# BouncyCastle public static byte[] Encrypt(byte[] input, byte[] baseKey, byte[] HMACKey) { byte[] cryptKey = new byte[32]; // Generate initialization vector byte[] iv = GenerateCryptoRandomByteArray(16); Array.Copy(baseKey, 0, cryptKey, 0, 32); ParametersWithIV key = new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", cryptKey), iv); IBufferedCipher outCipher = CipherUtilities.GetCipher(NistObjectIdentifiers.IdAes256Cbc.Id); outCipher.Init(true, key); byte[] bytes = outCipher.DoFinal(input); byte[] hmac = HMACSignBytes(HMACKey, bytes); byte[] result = new byte[bytes.Length + iv.Length + hmac.Length]; Buffer.BlockCopy(bytes, 0, result, 0, bytes.Length); Buffer.BlockCopy(iv, 0, result, bytes.Length, iv.Length); Buffer.BlockCopy(hmac, 0, result, bytes.Length+iv.Length, hmac.Length); return result; } public static byte[] HMACSignBytes(byte[] Key, byte[] Data) { HMac hmac = new HMac(new Sha512Digest()); byte[] resBuf = new byte[hmac.GetMacSize()]; hmac.Init(new KeyParameter(Key)); hmac.BlockUpdate(Data, 0, Data.Length); hmac.DoFinal(resBuf, 0); return resBuf; } public static byte[] GenerateCryptoRandomByteArray(int length) { byte[] buffer = new byte[length]; SecureRandom sr = new SecureRandom(); sr.NextBytes(buffer, 0, length); return buffer; }
// Java BouncyCastle public static byte[] Encrypt(byte[] input, byte[] baseKey, byte[] HMACKey) throws EncryptionException { try { // Generate initialization vector byte[] iv = EncryptionEngine.GenerateCryptoRandomByteArray(16); Key key = new SecretKeySpec(baseKey, "AES"); Cipher outCipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); outCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); byte[] bytes = outCipher.doFinal(input); byte[] hmac = HMACSignBytes(HMACKey, bytes); byte[] result = new byte[bytes.length + iv.length + hmac.length]; System.arraycopy(bytes, 0, result, 0, bytes.length); System.arraycopy(iv, 0, result, bytes.length, iv.length); System.arraycopy(hmac, 0, result, bytes.length+iv.length, hmac.length); String hexhmac = new String(Hex.encode(hmac)); String hexiv = new String(Hex.encode(iv)); String hexcipher = new String(Hex.encode(bytes)); return result; } catch(Exception ex) { throw new EncryptionException(ex); } } public static byte[] GenerateCryptoRandomByteArray(int length) { byte[] buffer = new byte[length]; SecureRandom sr = new SecureRandom(); sr.nextBytes(buffer); return buffer; } public static byte[] HMACSignBytes(byte[] Key, byte[] Data) { HMac hmac = new HMac(new SHA512Digest()); byte[] resBuf = new byte[hmac.getMacSize()]; hmac.init(new KeyParameter(Key)); hmac.update(Data, 0, Data.length); hmac.doFinal(resBuf, 0); return resBuf; }
// C# CNG public static byte[] Encrypt(byte[] srcBytes, byte[] baseKey, byte[] HMACKey) { // Encryption will be performed using memory stream. MemoryStream memoryStream = new MemoryStream(); // Let's make cryptographic operations thread-safe. lock (lockObj) { byte[] IV = EncryptionEngine.GenerateIV(); ICryptoTransform encryptor = BuildEncryptor(baseKey, IV); // To perform encryption, we must use the Write mode. CryptoStream cryptoStream = new CryptoStream( memoryStream, encryptor, CryptoStreamMode.Write); // Start encrypting data. cryptoStream.Write(srcBytes, 0, srcBytes.Length); // Finish the encryption operation. cryptoStream.FlushFinalBlock(); // Move encrypted data from memory into a byte array. Byte[] cipherTextbyte = memoryStream.ToArray(); List<byte> cipher_IV_HMAC = new List<byte>(); cipher_IV_HMAC.AddRange(cipherTextbyte); cipher_IV_HMAC.AddRange(IV); cipher_IV_HMAC.AddRange(HMACSignBytes(HMACKey, cipherTextbyte)); // Close memory streams. cryptoStream.Close(); memoryStream.Close(); encryptor.Dispose(); return cipher_IV_HMAC.ToArray(); } } public static byte[] GenerateIV() { using (AesCryptoServiceProvider am = new AesCryptoServiceProvider()) { am.GenerateIV(); return am.IV; } } public static byte[] HMACSignBytes(byte[] Key, byte[] Data) { using (FIPSHmacSha512 hmac = new FIPSHmacSha512(Key)) { byte[] HashValue = hmac.ComputeHash(Data); hmac.Dispose(); return HashValue; } } internal class FIPSHmacSha512 : System.Security.Cryptography.HMAC { public FIPSHmacSha512(byte[] key) { if (key == null) throw new ArgumentNullException("key"); HashName = "System.Security.Cryptography.SHA512CryptoServiceProvider"; HashSizeValue = 512; BlockSizeValue = 128; Key = key; } }
“Ok,, what the heck am I looking at?!?!?”. Well here’s what happens, each one of these functions is ultimately doing the same 3 things:
- Generating a Nonce (Number Used Only Once) for the initialization vector that AES 256 requires
- Encrypt the data
- Sign the cipher text with an HMAC function
First, the initialization vector is required as we are using AES256 CBC block mode I won’t go into more detail on the specifics of why we use CBC but suffice it to say, CBC is much stronger than some of the other block modes such as ECB especially if you need to encrypt the same data more than one time with the same secret key. This block mode requires an initialization vector that uses a crypto pseudo random function. There are many people who have used linear congruential random generators by mistake instead of a strong random generator such as earlier versions of Kerberos. Don’t make that mistake! If an attacker can predict the next number in a random number used, it weakens if not completely compromises the cipher strength.
Next, we encrypt the data. Woo hoo! A couple things to note, we are using PKCS7Padding. All three functions must use this padding to be inter-operable. This helps to reduce the predictability of the plaintext.
Next up, we sign the cipher with an HMAC function. Why do we do this? Well as it turns out there even though a cipher may not be decrypted, it can be modified. There are several possible attacks against ciphers that can modify the original plain text so we use an HMAC function to sign the cipher. This means that during our decryption we must verify the HMAC signature with the original HMAC key to verify that the original cipher has not been tampered with in any way. Finally we just append all the data together in one big cipher package that we can pull apart and decrypt with later.
So to decrypt… well.. how about we just look at code…
// C# BouncyCastle public static byte[] Decrypt(byte[] input, byte[] baseKey, byte[] HMACKey) { byte[] hmac = new byte[64]; byte[] iv = new byte[16]; byte[] cipher = new byte[input.Length - 16 - 64]; byte[] cryptKey = new byte[32]; Array.Copy(input, input.Length - 64, hmac, 0, 64); Array.Copy(input, input.Length - 64 - 16, iv, 0, 16); Array.Copy(input, 0, cipher, 0, input.Length - 16 - 64); Array.Copy(baseKey, 0, cryptKey, 0, 32); string hexkey = Hex.ToHexString(baseKey); string hexhmackey = Hex.ToHexString(HMACKey); string hexhmac = Hex.ToHexString(hmac); string hexiv = Hex.ToHexString(iv); string hexcipher = Hex.ToHexString(cipher); if (!HMACVerifyBytes(HMACKey, hmac, cipher)) { throw new Exception("HMAC is invalid"); } ParametersWithIV key = new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", cryptKey), iv); IBufferedCipher inCipher = CipherUtilities.GetCipher(NistObjectIdentifiers.IdAes256Cbc.Id); inCipher.Init(false, key); return inCipher.DoFinal(cipher).ToArray(); } public static bool HMACVerifyBytes(byte[] Key, byte[] HMACSignature, byte[] Data) { byte[] signature = HMACSignBytes(Key, Data); if (signature.SequenceEqual(HMACSignature)) { return true; } else { return false; } }
public static byte[] Decrypt(byte[] input, byte[] baseKey, byte[] HMACKey) throws EncryptionException { try { byte[] hmac = new byte[64]; byte[] iv = new byte[16]; byte[] cipher = new byte[input.length - 16 - 64]; System.arraycopy(input, input.length - 64, hmac, 0, 64); System.arraycopy(input, input.length - 64 - 16, iv, 0, 16); System.arraycopy(input, 0, cipher, 0, input.length - 16 - 64); if (!HMACVerifyBytes(HMACKey, hmac, cipher)) { throw new Exception("HMAC is invalid"); } Key key = new SecretKeySpec(baseKey, "AES"); Cipher inCipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); inCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); byte[] results = inCipher.doFinal(cipher); return results; } catch(Exception ex) { throw new EncryptionException(ex); } } public static Boolean HMACVerifyBytes(byte[] Key, byte[] HMACSignature, byte[] Data) { byte[] signature = HMACSignBytes(Key, Data); if (Arrays.areEqual(signature, HMACSignature)) { return true; } else { return false; } }
// C# CNG public static byte[] Decrypt(byte[] srcBytes, byte[] baseKey, byte[] HMACKey) { List<byte> SourceBytes = new List<byte>(srcBytes); //Extract the cipher, initialization vector and the hmac byte[] cipher = SourceBytes.GetRange(0, srcBytes.Length - 80).ToArray(); byte[] IV = SourceBytes.GetRange(srcBytes.Length - 80, 16).ToArray(); byte[] HMAC = SourceBytes.GetRange(SourceBytes.Count - 64, 64).ToArray(); // Validate HMAC if (!HMACVerifyBytes(HMACKey, HMAC, cipher)) { throw new Exception("HMAC is invalid"); } // Encryption will be performed using memory stream. MemoryStream memoryStream = new MemoryStream(cipher); // Let's make cryptographic operations thread-safe. lock (lockObj) { ICryptoTransform decryptor = BuildDecryptor(baseKey, IV); // To perform encryption, we must use the Write mode. CryptoStream cryptoStream = new CryptoStream( memoryStream, decryptor, CryptoStreamMode.Read); byte[] source = new byte[memoryStream.Length]; // Start decrypting data. int byteCount = cryptoStream.Read(source, 0, source.Length); byte[] ReturnSource = new byte[byteCount]; Buffer.BlockCopy(source, 0, ReturnSource, 0, byteCount); // Close memory streams. cryptoStream.Close(); memoryStream.Close(); decryptor.Dispose(); return ReturnSource; } } public static bool HMACVerifyBytes(byte[] Key, byte[] HMACSignature, byte[] Data) { using (FIPSHmacSha512 hmac = new FIPSHmacSha512(Key)) { byte[] HashValue = hmac.ComputeHash(Data); hmac.Dispose(); return HMACSignature.SequenceEqual(HashValue); } }
The process in reverse. We simple take apart that which we have created and decrypt it. It starts by verifying that the signature is the same so we know that the cipher has not been tampered with. Then we extract the IV and use that as well as the secret key to decrypt our data and return our original plaintext, or in this case, a byte array.
