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()));
    }
        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);
            }
        }
Beispiel #3
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);
            }
        }
        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[] 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);
            }
        }