public static byte[] Encrypt(byte[] keyEnc, byte[] keyMac, byte[] message) { if (keyEnc == null) { throw new ArgumentNullException(nameof(keyEnc)); } if (keyMac == null) { throw new ArgumentNullException(nameof(keyMac)); } if (keyEnc.Length < 32) { throw new ArgumentOutOfRangeException( nameof(keyEnc), "Encryption Key must be at least 256 bits (32 bytes)"); } if (keyMac.Length < 32) { throw new ArgumentOutOfRangeException( nameof(keyMac), "Mac Key must be at least 256 bits (32 bytes)"); } if (message == null) { throw new ArgumentNullException(nameof(message)); } byte[] iv; byte[] cipherText; byte[] tag; using (HMAC tagGenerator = GetMac(aeMac, keyMac)) { using (SymmetricAlgorithm cipher = GetCipher(aeCipher, keyEnc)) using (ICryptoTransform encryptor = cipher.CreateEncryptor()) { // Since no IV was provided, a random one has been generated // during the call to CreateEncryptor. // // But note that it only does the auto-generation once. If the cipher // object were used again, a call to GenerateIV would have been // required. iv = cipher.IV; cipherText = Transform(encryptor, message, 0, message.Length); } // The IV and ciphertest both need to be included in the MAC to prevent // tampering. // // By including the algorithm identifiers, we have technically moved from // simple Authenticated Encryption (AE) to Authenticated Encryption with // Additional Data (AEAD). By including the algorithm identifiers in the // MAC, it becomes harder for an attacker to change them as an attempt to // perform a downgrade attack. byte[] tagData = new byte[algorithmChoices.Length + iv.Length + cipherText.Length]; int tagDataOffset = 0; Append(algorithmChoices, tagData, ref tagDataOffset); Append(iv, tagData, ref tagDataOffset); Append(cipherText, tagData, ref tagDataOffset); tagGenerator.AppendData(tagData); tag = tagGenerator.GetHashAndReset(); } // Build the final result as the concatenation of everything except the keys. int totalLength = algorithmChoices.Length + tag.Length + iv.Length + cipherText.Length; byte[] output = new byte[totalLength]; int outputOffset = 0; Append(algorithmChoices, output, ref outputOffset); Append(tag, output, ref outputOffset); Append(iv, output, ref outputOffset); Append(cipherText, output, ref outputOffset); return(output); }
public static byte[] Decrypt(byte[] keyEnc, byte[] keyMac, byte[] cipherText) { if (keyEnc == null) { throw new ArgumentNullException(nameof(keyEnc)); } if (keyMac == null) { throw new ArgumentNullException(nameof(keyMac)); } if (keyEnc.Length < 32) { throw new ArgumentOutOfRangeException( nameof(keyEnc), "Encryption Key must be at least 256 bits (32 bytes)"); } if (keyMac.Length < 32) { throw new ArgumentOutOfRangeException( nameof(keyMac), "Mac Key must be at least 256 bits (32 bytes)"); } if (cipherText == null) { throw new ArgumentNullException(nameof(cipherText)); } // The format of this message is assumed to be public, so there's no harm in // saying ahead of time that the message makes no sense. if (cipherText.Length < 2) { throw new CryptographicException(); } // Use the message algorithm headers to determine what cipher algorithm and // MAC algorithm are going to be used. Since the same Key Derivation // Functions (KDFs) are being used in Decrypt as Encrypt, the keys are also // the same. AeCipher aeCipher = (AeCipher)cipherText[0]; AeMac aeMac = (AeMac)cipherText[1]; using (SymmetricAlgorithm cipher = GetCipher(aeCipher, keyEnc)) using (HMAC tagGenerator = GetMac(aeMac, keyMac)) { int blockSizeInBytes = cipher.BlockSize / 8; int tagSizeInBytes = tagGenerator.HashSize / 8; int headerSizeInBytes = 2; int tagOffset = headerSizeInBytes; int ivOffset = tagOffset + tagSizeInBytes; int cipherTextOffset = ivOffset + blockSizeInBytes; int cipherTextLength = cipherText.Length - cipherTextOffset; int minLen = cipherTextOffset + blockSizeInBytes; // Again, the minimum length is still assumed to be public knowledge, // nothing has leaked out yet. The minimum length couldn't just be calculated // without reading the header. if (cipherText.Length < minLen) { throw new CryptographicException(); } // It's very important that the MAC be calculated and verified before // proceeding to decrypt the ciphertext, as this prevents any sort of // information leaking out to an attacker. // // Don't include the tag in the calculation, though. var data = new byte[cipherText.Length - tagSizeInBytes]; // First, everything before the tag (the cipher and MAC algorithm ids) Buffer.BlockCopy(cipherText, 0, data, 0, tagOffset); // Skip the data before the tag and the tag, then read everything that remains. Buffer.BlockCopy(cipherText, (tagOffset + tagSizeInBytes), data, tagOffset, cipherText.Length - (tagOffset + tagSizeInBytes)); tagGenerator.AppendData(data); byte[] generatedTag = tagGenerator.GetHashAndReset(); byte[] expectedPayload = new byte[tagSizeInBytes]; Buffer.BlockCopy(cipherText, tagOffset, expectedPayload, 0, tagSizeInBytes); if (!CryptographicEquals( generatedTag, 0, cipherText, tagOffset, tagSizeInBytes)) { // Assuming every tampered message (of the same length) took the same // amount of time to process, we can now safely say // "this data makes no sense" without giving anything away. throw new CryptographicException("Mismatch in signed data"); } // Restore the IV into the symmetricAlgorithm instance. byte[] iv = new byte[blockSizeInBytes]; Buffer.BlockCopy(cipherText, ivOffset, iv, 0, iv.Length); cipher.IV = iv; using (ICryptoTransform decryptor = cipher.CreateDecryptor()) { return(Transform( decryptor, cipherText, cipherTextOffset, cipherTextLength)); } } }