private static void TestManagedKeyDerivation(byte[] kdk, byte[] label, byte[] contextHeader, byte[] context, int numDerivedBytes, string expectedDerivedSubkeyAsBase64) { var labelSegment = new ArraySegment <byte>(new byte[label.Length + 10], 3, label.Length); Buffer.BlockCopy(label, 0, labelSegment.Array, labelSegment.Offset, labelSegment.Count); var contextSegment = new ArraySegment <byte>(new byte[context.Length + 10], 5, context.Length); Buffer.BlockCopy(context, 0, contextSegment.Array, contextSegment.Offset, contextSegment.Count); var derivedSubkeySegment = new ArraySegment <byte>(new byte[numDerivedBytes + 10], 4, numDerivedBytes); ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader(kdk, labelSegment, contextHeader, contextSegment, bytes => new HMACSHA512(bytes), derivedSubkeySegment); Assert.Equal(expectedDerivedSubkeyAsBase64, Convert.ToBase64String(derivedSubkeySegment.AsStandaloneArray())); }
private byte[] CreateContextHeader() { var EMPTY_ARRAY = new byte[0]; var EMPTY_ARRAY_SEGMENT = new ArraySegment <byte>(EMPTY_ARRAY); byte[] retVal = new byte[checked ( 1 /* KDF alg */ + 1 /* chaining mode */ + sizeof(uint) /* sym alg key size */ + sizeof(uint) /* sym alg block size */ + sizeof(uint) /* hmac alg key size */ + sizeof(uint) /* hmac alg digest size */ + _symmetricAlgorithmBlockSizeInBytes /* ciphertext of encrypted empty string */ + _validationAlgorithmDigestLengthInBytes /* digest of HMACed empty string */)]; int idx = 0; // First is the two-byte header retVal[idx++] = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF retVal[idx++] = 0; // 0x00 = CBC encryption + HMAC authentication // Next is information about the symmetric algorithm (key size followed by block size) BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmSubkeyLengthInBytes); BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmBlockSizeInBytes); // Next is information about the keyed hash algorithm (key size followed by digest size) BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmSubkeyLengthInBytes); BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmDigestLengthInBytes); // See the design document for an explanation of the following code. byte[] tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes + _validationAlgorithmSubkeyLengthInBytes]; ManagedSP800_108_CTR_HMACSHA512.DeriveKeys( kdk: EMPTY_ARRAY, label: EMPTY_ARRAY_SEGMENT, context: EMPTY_ARRAY_SEGMENT, prfFactory: _kdkPrfFactory, output: new ArraySegment <byte>(tempKeys)); // At this point, tempKeys := { K_E || K_H }. // Encrypt a zero-length input string with an all-zero IV and copy the ciphertext to the return buffer. using (var symmetricAlg = CreateSymmetricAlgorithm()) { using (var cryptoTransform = symmetricAlg.CreateEncryptor( rgbKey: new ArraySegment <byte>(tempKeys, 0, _symmetricAlgorithmSubkeyLengthInBytes).AsStandaloneArray(), rgbIV: new byte[_symmetricAlgorithmBlockSizeInBytes])) { byte[] ciphertext = cryptoTransform.TransformFinalBlock(EMPTY_ARRAY, 0, 0); CryptoUtil.Assert(ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes, "ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes"); Buffer.BlockCopy(ciphertext, 0, retVal, idx, ciphertext.Length); } } idx += _symmetricAlgorithmBlockSizeInBytes; // MAC a zero-length input string and copy the digest to the return buffer. using (var hashAlg = CreateValidationAlgorithm(new ArraySegment <byte>(tempKeys, _symmetricAlgorithmSubkeyLengthInBytes, _validationAlgorithmSubkeyLengthInBytes).AsStandaloneArray())) { byte[] digest = hashAlg.ComputeHash(EMPTY_ARRAY); CryptoUtil.Assert(digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes, "digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes"); Buffer.BlockCopy(digest, 0, retVal, idx, digest.Length); } idx += _validationAlgorithmDigestLengthInBytes; CryptoUtil.Assert(idx == retVal.Length, "idx == retVal.Length"); // retVal := { version || chainingMode || symAlgKeySize || symAlgBlockSize || macAlgKeySize || macAlgDigestSize || E("") || MAC("") }. return(retVal); }
public byte[] Encrypt(ArraySegment <byte> plaintext, ArraySegment <byte> additionalAuthenticatedData) { plaintext.Validate(); additionalAuthenticatedData.Validate(); try { var outputStream = new MemoryStream(); // Step 1: Generate a random key modifier and IV for this operation. // Both will be equal to the block size of the block cipher algorithm. byte[] keyModifier = _genRandom.GenRandom(KEY_MODIFIER_SIZE_IN_BYTES); byte[] iv = _genRandom.GenRandom(_symmetricAlgorithmBlockSizeInBytes); // Step 2: Copy the key modifier and the IV to the output stream since they'll act as a header. outputStream.Write(keyModifier, 0, keyModifier.Length); outputStream.Write(iv, 0, iv.Length); // At this point, outputStream := { keyModifier || IV }. // Step 3: Decrypt the KDK, and use it to generate new encryption and HMAC keys. // We pin all unencrypted keys to limit their exposure via GC relocation. byte[] decryptedKdk = new byte[_keyDerivationKey.Length]; byte[] encryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes]; byte[] validationSubkey = new byte[_validationAlgorithmSubkeyLengthInBytes]; byte[] derivedKeysBuffer = new byte[checked (encryptionSubkey.Length + validationSubkey.Length)]; fixed(byte *__unused__1 = decryptedKdk) fixed(byte *__unused__2 = encryptionSubkey) fixed(byte *__unused__3 = validationSubkey) fixed(byte *__unused__4 = derivedKeysBuffer) { try { _keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment <byte>(decryptedKdk)); ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader( kdk: decryptedKdk, label: additionalAuthenticatedData, contextHeader: _contextHeader, context: new ArraySegment <byte>(keyModifier), prfFactory: _kdkPrfFactory, output: new ArraySegment <byte>(derivedKeysBuffer)); Buffer.BlockCopy(derivedKeysBuffer, 0, encryptionSubkey, 0, encryptionSubkey.Length); Buffer.BlockCopy(derivedKeysBuffer, encryptionSubkey.Length, validationSubkey, 0, validationSubkey.Length); // Step 4: Perform the encryption operation. using (var symmetricAlgorithm = CreateSymmetricAlgorithm()) using (var cryptoTransform = symmetricAlgorithm.CreateEncryptor(encryptionSubkey, iv)) using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write)) { cryptoStream.Write(plaintext.Array, plaintext.Offset, plaintext.Count); cryptoStream.FlushFinalBlock(); // At this point, outputStream := { keyModifier || IV || ciphertext } // Step 5: Calculate the digest over the IV and ciphertext. // We don't need to calculate the digest over the key modifier since that // value has already been mixed into the KDF used to generate the MAC key. using (var validationAlgorithm = CreateValidationAlgorithm(validationSubkey)) { #if !DOTNET5_4 // As an optimization, avoid duplicating the underlying buffer if we're on desktop CLR. byte[] underlyingBuffer = outputStream.GetBuffer(); #else byte[] underlyingBuffer = outputStream.ToArray(); #endif byte[] mac = validationAlgorithm.ComputeHash(underlyingBuffer, KEY_MODIFIER_SIZE_IN_BYTES, checked ((int)outputStream.Length - KEY_MODIFIER_SIZE_IN_BYTES)); outputStream.Write(mac, 0, mac.Length); // At this point, outputStream := { keyModifier || IV || ciphertext || MAC(IV || ciphertext) } // And we're done! return(outputStream.ToArray()); } } } finally { // delete since these contain secret material Array.Clear(decryptedKdk, 0, decryptedKdk.Length); Array.Clear(encryptionSubkey, 0, encryptionSubkey.Length); Array.Clear(validationSubkey, 0, validationSubkey.Length); Array.Clear(derivedKeysBuffer, 0, derivedKeysBuffer.Length); } } } catch (Exception ex) when(ex.RequiresHomogenization()) { // Homogenize all exceptions to CryptographicException. throw Error.CryptCommon_GenericError(ex); } }
public byte[] Decrypt(ArraySegment <byte> protectedPayload, ArraySegment <byte> additionalAuthenticatedData) { protectedPayload.Validate(); additionalAuthenticatedData.Validate(); // Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC if (protectedPayload.Count < checked (KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _validationAlgorithmDigestLengthInBytes)) { throw Error.CryptCommon_PayloadInvalid(); } // Assumption: protectedPayload := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) } try { // Step 1: Extract the key modifier and IV from the payload. int keyModifierOffset; // position in protectedPayload.Array where key modifier begins int ivOffset; // position in protectedPayload.Array where key modifier ends / IV begins int ciphertextOffset; // position in protectedPayload.Array where IV ends / ciphertext begins int macOffset; // position in protectedPayload.Array where ciphertext ends / MAC begins int eofOffset; // position in protectedPayload.Array where MAC ends checked { keyModifierOffset = protectedPayload.Offset; ivOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES; ciphertextOffset = ivOffset + _symmetricAlgorithmBlockSizeInBytes; } ArraySegment <byte> keyModifier = new ArraySegment <byte>(protectedPayload.Array, keyModifierOffset, ivOffset - keyModifierOffset); byte[] iv = new byte[_symmetricAlgorithmBlockSizeInBytes]; Buffer.BlockCopy(protectedPayload.Array, ivOffset, iv, 0, iv.Length); // Step 2: Decrypt the KDK and use it to restore the original encryption and MAC keys. // We pin all unencrypted keys to limit their exposure via GC relocation. byte[] decryptedKdk = new byte[_keyDerivationKey.Length]; byte[] decryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes]; byte[] validationSubkey = new byte[_validationAlgorithmSubkeyLengthInBytes]; byte[] derivedKeysBuffer = new byte[checked (decryptionSubkey.Length + validationSubkey.Length)]; fixed(byte *__unused__1 = decryptedKdk) fixed(byte *__unused__2 = decryptionSubkey) fixed(byte *__unused__3 = validationSubkey) fixed(byte *__unused__4 = derivedKeysBuffer) { try { _keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment <byte>(decryptedKdk)); ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader( kdk: decryptedKdk, label: additionalAuthenticatedData, contextHeader: _contextHeader, context: keyModifier, prfFactory: _kdkPrfFactory, output: new ArraySegment <byte>(derivedKeysBuffer)); Buffer.BlockCopy(derivedKeysBuffer, 0, decryptionSubkey, 0, decryptionSubkey.Length); Buffer.BlockCopy(derivedKeysBuffer, decryptionSubkey.Length, validationSubkey, 0, validationSubkey.Length); // Step 3: Calculate the correct MAC for this payload. // correctHash := MAC(IV || ciphertext) byte[] correctHash; using (var hashAlgorithm = CreateValidationAlgorithm(validationSubkey)) { checked { eofOffset = protectedPayload.Offset + protectedPayload.Count; macOffset = eofOffset - _validationAlgorithmDigestLengthInBytes; } correctHash = hashAlgorithm.ComputeHash(protectedPayload.Array, ivOffset, macOffset - ivOffset); } // Step 4: Validate the MAC provided as part of the payload. if (!CryptoUtil.TimeConstantBuffersAreEqual(correctHash, 0, correctHash.Length, protectedPayload.Array, macOffset, eofOffset - macOffset)) { throw Error.CryptCommon_PayloadInvalid(); // integrity check failure } // Step 5: Decipher the ciphertext and return it to the caller. using (var symmetricAlgorithm = CreateSymmetricAlgorithm()) using (var cryptoTransform = symmetricAlgorithm.CreateDecryptor(decryptionSubkey, iv)) { var outputStream = new MemoryStream(); using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write)) { cryptoStream.Write(protectedPayload.Array, ciphertextOffset, macOffset - ciphertextOffset); cryptoStream.FlushFinalBlock(); // At this point, outputStream := { plaintext }, and we're done! return(outputStream.ToArray()); } } } finally { // delete since these contain secret material Array.Clear(decryptedKdk, 0, decryptedKdk.Length); Array.Clear(decryptionSubkey, 0, decryptionSubkey.Length); Array.Clear(validationSubkey, 0, validationSubkey.Length); Array.Clear(derivedKeysBuffer, 0, derivedKeysBuffer.Length); } } } catch (Exception ex) when(ex.RequiresHomogenization()) { // Homogenize all exceptions to CryptographicException. throw Error.CryptCommon_GenericError(ex); } }
public byte[] Decrypt(ArraySegment <byte> ciphertext, ArraySegment <byte> additionalAuthenticatedData) { ciphertext.Validate(); additionalAuthenticatedData.Validate(); // Argument checking: input must at the absolute minimum contain a key modifier, nonce, and tag if (ciphertext.Count < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES) { throw Error.CryptCommon_PayloadInvalid(); } // Assumption: pbCipherText := { keyModifier || nonce || encryptedData || authenticationTag } var plaintextBytes = ciphertext.Count - (KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES); var plaintext = new byte[plaintextBytes]; try { // Step 1: Extract the key modifier from the payload. int keyModifierOffset; // position in ciphertext.Array where key modifier begins int nonceOffset; // position in ciphertext.Array where key modifier ends / nonce begins int encryptedDataOffset; // position in ciphertext.Array where nonce ends / encryptedData begins int tagOffset; // position in ciphertext.Array where encrypted data ends checked { keyModifierOffset = ciphertext.Offset; nonceOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES; encryptedDataOffset = nonceOffset + NONCE_SIZE_IN_BYTES; tagOffset = encryptedDataOffset + plaintextBytes; } var keyModifier = new ArraySegment <byte>(ciphertext.Array !, keyModifierOffset, KEY_MODIFIER_SIZE_IN_BYTES); // Step 2: Decrypt the KDK and use it to restore the original encryption and MAC keys. // We pin all unencrypted keys to limit their exposure via GC relocation. var decryptedKdk = new byte[_keyDerivationKey.Length]; var derivedKey = new byte[_derivedkeySizeInBytes]; fixed(byte *__unused__1 = decryptedKdk) fixed(byte *__unused__2 = derivedKey) { try { _keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment <byte>(decryptedKdk)); ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader( kdk: decryptedKdk, label: additionalAuthenticatedData, contextHeader: _contextHeader, context: keyModifier, prfFactory: _kdkPrfFactory, output: new ArraySegment <byte>(derivedKey)); // Perform the decryption operation var nonce = new Span <byte>(ciphertext.Array, nonceOffset, NONCE_SIZE_IN_BYTES); var tag = new Span <byte>(ciphertext.Array, tagOffset, TAG_SIZE_IN_BYTES); var encrypted = new Span <byte>(ciphertext.Array, encryptedDataOffset, plaintextBytes); using var aes = new AesGcm(derivedKey); aes.Decrypt(nonce, encrypted, tag, plaintext); return(plaintext); } finally { // delete since these contain secret material Array.Clear(decryptedKdk, 0, decryptedKdk.Length); Array.Clear(derivedKey, 0, derivedKey.Length); } } } catch (Exception ex) when(ex.RequiresHomogenization()) { // Homogenize all exceptions to CryptographicException. throw Error.CryptCommon_GenericError(ex); } }
public byte[] Encrypt(ArraySegment <byte> plaintext, ArraySegment <byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize) { plaintext.Validate(); additionalAuthenticatedData.Validate(); try { // Allocate a buffer to hold the key modifier, nonce, encrypted data, and tag. // In GCM, the encrypted output will be the same length as the plaintext input. var retVal = new byte[checked (preBufferSize + KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plaintext.Count + TAG_SIZE_IN_BYTES + postBufferSize)]; int keyModifierOffset; // position in ciphertext.Array where key modifier begins int nonceOffset; // position in ciphertext.Array where key modifier ends / nonce begins int encryptedDataOffset; // position in ciphertext.Array where nonce ends / encryptedData begins int tagOffset; // position in ciphertext.Array where encrypted data ends checked { keyModifierOffset = plaintext.Offset + (int)preBufferSize; nonceOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES; encryptedDataOffset = nonceOffset + NONCE_SIZE_IN_BYTES; tagOffset = encryptedDataOffset + plaintext.Count; } // Randomly generate the key modifier and nonce var keyModifier = _genRandom.GenRandom(KEY_MODIFIER_SIZE_IN_BYTES); var nonceBytes = _genRandom.GenRandom(NONCE_SIZE_IN_BYTES); Buffer.BlockCopy(keyModifier, 0, retVal, (int)preBufferSize, keyModifier.Length); Buffer.BlockCopy(nonceBytes, 0, retVal, (int)preBufferSize + keyModifier.Length, nonceBytes.Length); // At this point, retVal := { preBuffer | keyModifier | nonce | _____ | _____ | postBuffer } // Use the KDF to generate a new symmetric block cipher key // We'll need a temporary buffer to hold the symmetric encryption subkey var decryptedKdk = new byte[_keyDerivationKey.Length]; var derivedKey = new byte[_derivedkeySizeInBytes]; fixed(byte *__unused__1 = decryptedKdk) fixed(byte *__unused__2 = derivedKey) { try { _keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment <byte>(decryptedKdk)); ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader( kdk: decryptedKdk, label: additionalAuthenticatedData, contextHeader: _contextHeader, context: keyModifier, prfFactory: _kdkPrfFactory, output: new ArraySegment <byte>(derivedKey)); // do gcm var nonce = new Span <byte>(retVal, nonceOffset, NONCE_SIZE_IN_BYTES); var tag = new Span <byte>(retVal, tagOffset, TAG_SIZE_IN_BYTES); var encrypted = new Span <byte>(retVal, encryptedDataOffset, plaintext.Count); using var aes = new AesGcm(derivedKey); aes.Encrypt(nonce, plaintext, encrypted, tag); // At this point, retVal := { preBuffer | keyModifier | nonce | encryptedData | authenticationTag | postBuffer } // And we're done! return(retVal); } finally { // delete since these contain secret material Array.Clear(decryptedKdk, 0, decryptedKdk.Length); Array.Clear(derivedKey, 0, derivedKey.Length); } } } catch (Exception ex) when(ex.RequiresHomogenization()) { // Homogenize all exceptions to CryptographicException. throw Error.CryptCommon_GenericError(ex); } }