Beispiel #1
0
    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);
            }
        }
Beispiel #5
0
        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);
            }
        }
Beispiel #6
0
        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);
            }
        }