//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.Security.Cryptography { using System; using System.IO; using System.Security.Cryptography; /****************************************************************** * !! WARNING !! * * This class contains cryptographic code. If you make changes to * * this class, please have it reviewed by the appropriate people. * ******************************************************************/ // Uses .NET Framework classes to encrypt (SymmetricAlgorithm) and sign (KeyedHashAlgorithm) data. // // [PROTECT] // INPUT: clearData // OUTPUT: protectedData // ALGORITHM: // protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData)) // // [UNPROTECT] // INPUT: protectedData // OUTPUT: clearData // ALGORITHM: // 1) Assume protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData)) // 2) Validate the signature over the payload and strip it from the end // 3) Strip off the IV from the beginning of the payload // 4) Decrypt what remains of the payload, and return it as clearData internal sealed class NetFXCryptoService : ICryptoService { private readonly ICryptoAlgorithmFactory _cryptoAlgorithmFactory; private readonly CryptographicKey _encryptionKey; private readonly bool _predictableIV; private readonly CryptographicKey _validationKey; public NetFXCryptoService(ICryptoAlgorithmFactory cryptoAlgorithmFactory, CryptographicKey encryptionKey, CryptographicKey validationKey, bool predictableIV = false) { _cryptoAlgorithmFactory = cryptoAlgorithmFactory; _encryptionKey = encryptionKey; _validationKey = validationKey; _predictableIV = predictableIV; } public byte[] Protect(byte[] clearData) { // The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures. checked { // These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block. using (SymmetricAlgorithm encryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) { // Initialize the algorithm with the specified key and an appropriate IV encryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial(); if (_predictableIV) { // The caller wanted the output to be predictable (e.g. for caching), so we'll create an // appropriate IV directly from the input buffer. The IV length is equal to the block size. encryptionAlgorithm.IV = CryptoUtil.CreatePredictableIV(clearData, encryptionAlgorithm.BlockSize); } else { // If the caller didn't ask for a predictable IV, just let the algorithm itself choose one. encryptionAlgorithm.GenerateIV(); } byte[] iv = encryptionAlgorithm.IV; using (MemoryStream memStream = new MemoryStream()) { memStream.Write(iv, 0, iv.Length); // At this point: // memStream := IV // Write the encrypted payload to the memory stream. using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor()) { using (CryptoStream cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write)) { cryptoStream.Write(clearData, 0, clearData.Length); cryptoStream.FlushFinalBlock(); // At this point: // memStream := IV || Enc(Kenc, IV, clearData) // These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block. using (KeyedHashAlgorithm signingAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) { // Initialize the algorithm with the specified key signingAlgorithm.Key = _validationKey.GetKeyMaterial(); // Compute the signature byte[] signature = signingAlgorithm.ComputeHash(memStream.GetBuffer(), 0, (int)memStream.Length); // At this point: // memStream := IV || Enc(Kenc, IV, clearData) // signature := Sign(Kval, IV || Enc(Kenc, IV, clearData)) // Append the signature to the encrypted payload memStream.Write(signature, 0, signature.Length); // At this point: // memStream := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData)) // Algorithm complete byte[] protectedData = memStream.ToArray(); return protectedData; } } } } } } } public byte[] Unprotect(byte[] protectedData) { // The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures. checked { // We want to check that the input is in the form: // protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData)) // Definitions used in this method: // encryptedPayload := Enc(Kenc, IV, clearData) // signature := Sign(Kval, IV || encryptedPayload) // These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block. using (SymmetricAlgorithm decryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) { decryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial(); // These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block. using (KeyedHashAlgorithm validationAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) { validationAlgorithm.Key = _validationKey.GetKeyMaterial(); // First, we need to verify that protectedData is even long enough to contain // the required components (IV, encryptedPayload, signature). int ivByteCount = decryptionAlgorithm.BlockSize / 8; // IV length is equal to the block size int signatureByteCount = validationAlgorithm.HashSize / 8; int encryptedPayloadByteCount = protectedData.Length - ivByteCount - signatureByteCount; if (encryptedPayloadByteCount <= 0) { // protectedData doesn't meet minimum length requirements return null; } // If that check passes, we need to detect payload tampering. // Compute the signature over the IV and encrypted payload // computedSignature := Sign(Kval, IV || encryptedPayload) byte[] computedSignature = validationAlgorithm.ComputeHash(protectedData, 0, ivByteCount + encryptedPayloadByteCount); if (!CryptoUtil.BuffersAreEqual( buffer1: protectedData, buffer1Offset: ivByteCount + encryptedPayloadByteCount, buffer1Count: signatureByteCount, buffer2: computedSignature, buffer2Offset: 0, buffer2Count: computedSignature.Length)) { // the computed signature didn't match the incoming signature, which is a sign of payload tampering return null; } // At this point, we're certain that we generated the signature over this payload, // so we can go ahead with decryption. // Populate the IV from the incoming stream byte[] iv = new byte[ivByteCount]; Buffer.BlockCopy(protectedData, 0, iv, 0, iv.Length); decryptionAlgorithm.IV = iv; // Write the decrypted payload to the memory stream. using (MemoryStream memStream = new MemoryStream()) { using (ICryptoTransform decryptor = decryptionAlgorithm.CreateDecryptor()) { using (CryptoStream cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Write)) { cryptoStream.Write(protectedData, ivByteCount, encryptedPayloadByteCount); cryptoStream.FlushFinalBlock(); // At this point // memStream := clearData byte[] clearData = memStream.ToArray(); return clearData; } } } } } } } } }