423 lines
23 KiB
C#
423 lines
23 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using Microsoft.AspNetCore.Cryptography;
|
|
using Microsoft.AspNetCore.Cryptography.Cng;
|
|
using Microsoft.AspNetCore.Cryptography.SafeHandles;
|
|
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
|
|
using Microsoft.AspNetCore.DataProtection.Cng.Internal;
|
|
using Microsoft.AspNetCore.DataProtection.SP800_108;
|
|
|
|
namespace Microsoft.AspNetCore.DataProtection.Cng
|
|
{
|
|
// An encryptor which does Encrypt(CBC) + HMAC using the Windows CNG (BCrypt*) APIs.
|
|
// The payloads produced by this encryptor should be compatible with the payloads
|
|
// produced by the managed Encrypt(CBC) + HMAC encryptor.
|
|
internal unsafe sealed class CbcAuthenticatedEncryptor : CngAuthenticatedEncryptorBase
|
|
{
|
|
// Even when IVs are chosen randomly, CBC is susceptible to IV collisions within a single
|
|
// key. For a 64-bit block cipher (like 3DES), we'd expect a collision after 2^32 block
|
|
// encryption operations, which a high-traffic web server might perform in mere hours.
|
|
// AES and other 128-bit block ciphers are less susceptible to this due to the larger IV
|
|
// space, but unfortunately some organizations require older 64-bit block ciphers. To address
|
|
// the collision issue, we'll feed 128 bits of entropy to the KDF when performing subkey
|
|
// generation. This creates >= 192 bits total entropy for each operation, so we shouldn't
|
|
// expect a collision until >= 2^96 operations. Even 2^80 operations still maintains a <= 2^-32
|
|
// probability of collision, and this is acceptable for the expected KDK lifetime.
|
|
private const uint KEY_MODIFIER_SIZE_IN_BYTES = 128 / 8;
|
|
|
|
private readonly byte[] _contextHeader;
|
|
private readonly IBCryptGenRandom _genRandom;
|
|
private readonly BCryptAlgorithmHandle _hmacAlgorithmHandle;
|
|
private readonly uint _hmacAlgorithmDigestLengthInBytes;
|
|
private readonly uint _hmacAlgorithmSubkeyLengthInBytes;
|
|
private readonly ISP800_108_CTR_HMACSHA512Provider _sp800_108_ctr_hmac_provider;
|
|
private readonly BCryptAlgorithmHandle _symmetricAlgorithmHandle;
|
|
private readonly uint _symmetricAlgorithmBlockSizeInBytes;
|
|
private readonly uint _symmetricAlgorithmSubkeyLengthInBytes;
|
|
|
|
public CbcAuthenticatedEncryptor(Secret keyDerivationKey, BCryptAlgorithmHandle symmetricAlgorithmHandle, uint symmetricAlgorithmKeySizeInBytes, BCryptAlgorithmHandle hmacAlgorithmHandle, IBCryptGenRandom genRandom = null)
|
|
{
|
|
_genRandom = genRandom ?? BCryptGenRandomImpl.Instance;
|
|
_sp800_108_ctr_hmac_provider = SP800_108_CTR_HMACSHA512Util.CreateProvider(keyDerivationKey);
|
|
_symmetricAlgorithmHandle = symmetricAlgorithmHandle;
|
|
_symmetricAlgorithmBlockSizeInBytes = symmetricAlgorithmHandle.GetCipherBlockLength();
|
|
_symmetricAlgorithmSubkeyLengthInBytes = symmetricAlgorithmKeySizeInBytes;
|
|
_hmacAlgorithmHandle = hmacAlgorithmHandle;
|
|
_hmacAlgorithmDigestLengthInBytes = hmacAlgorithmHandle.GetHashDigestLength();
|
|
_hmacAlgorithmSubkeyLengthInBytes = _hmacAlgorithmDigestLengthInBytes; // for simplicity we'll generate HMAC subkeys with a length equal to the digest length
|
|
|
|
// Argument checking on the algorithms and lengths passed in to us
|
|
AlgorithmAssert.IsAllowableSymmetricAlgorithmBlockSize(checked(_symmetricAlgorithmBlockSizeInBytes * 8));
|
|
AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked(_symmetricAlgorithmSubkeyLengthInBytes * 8));
|
|
AlgorithmAssert.IsAllowableValidationAlgorithmDigestSize(checked(_hmacAlgorithmDigestLengthInBytes * 8));
|
|
|
|
_contextHeader = CreateContextHeader();
|
|
}
|
|
|
|
private byte[] CreateContextHeader()
|
|
{
|
|
var 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 */
|
|
+ _hmacAlgorithmDigestLengthInBytes /* digest of HMACed empty string */)];
|
|
|
|
fixed (byte* pbRetVal = retVal)
|
|
{
|
|
byte* ptr = pbRetVal;
|
|
|
|
// First is the two-byte header
|
|
*(ptr++) = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
|
|
*(ptr++) = 0; // 0x00 = CBC encryption + HMAC authentication
|
|
|
|
// Next is information about the symmetric algorithm (key size followed by block size)
|
|
BitHelpers.WriteTo(ref ptr, _symmetricAlgorithmSubkeyLengthInBytes);
|
|
BitHelpers.WriteTo(ref ptr, _symmetricAlgorithmBlockSizeInBytes);
|
|
|
|
// Next is information about the HMAC algorithm (key size followed by digest size)
|
|
BitHelpers.WriteTo(ref ptr, _hmacAlgorithmSubkeyLengthInBytes);
|
|
BitHelpers.WriteTo(ref ptr, _hmacAlgorithmDigestLengthInBytes);
|
|
|
|
// See the design document for an explanation of the following code.
|
|
var tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes];
|
|
fixed (byte* pbTempKeys = tempKeys)
|
|
{
|
|
byte dummy;
|
|
|
|
// Derive temporary keys for encryption + HMAC.
|
|
using (var provider = SP800_108_CTR_HMACSHA512Util.CreateEmptyProvider())
|
|
{
|
|
provider.DeriveKey(
|
|
pbLabel: &dummy,
|
|
cbLabel: 0,
|
|
pbContext: &dummy,
|
|
cbContext: 0,
|
|
pbDerivedKey: pbTempKeys,
|
|
cbDerivedKey: (uint)tempKeys.Length);
|
|
}
|
|
|
|
// At this point, tempKeys := { K_E || K_H }.
|
|
byte* pbSymmetricEncryptionSubkey = pbTempKeys;
|
|
byte* pbHmacSubkey = &pbTempKeys[_symmetricAlgorithmSubkeyLengthInBytes];
|
|
|
|
// Encrypt a zero-length input string with an all-zero IV and copy the ciphertext to the return buffer.
|
|
using (var symmetricKeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
|
|
{
|
|
fixed (byte* pbIV = new byte[_symmetricAlgorithmBlockSizeInBytes] /* will be zero-initialized */)
|
|
{
|
|
DoCbcEncrypt(
|
|
symmetricKeyHandle: symmetricKeyHandle,
|
|
pbIV: pbIV,
|
|
pbInput: &dummy,
|
|
cbInput: 0,
|
|
pbOutput: ptr,
|
|
cbOutput: _symmetricAlgorithmBlockSizeInBytes);
|
|
}
|
|
}
|
|
ptr += _symmetricAlgorithmBlockSizeInBytes;
|
|
|
|
// MAC a zero-length input string and copy the digest to the return buffer.
|
|
using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
|
|
{
|
|
hashHandle.HashData(
|
|
pbInput: &dummy,
|
|
cbInput: 0,
|
|
pbHashDigest: ptr,
|
|
cbHashDigest: _hmacAlgorithmDigestLengthInBytes);
|
|
}
|
|
|
|
ptr += _hmacAlgorithmDigestLengthInBytes;
|
|
CryptoUtil.Assert(ptr - pbRetVal == retVal.Length, "ptr - pbRetVal == retVal.Length");
|
|
}
|
|
}
|
|
|
|
// retVal := { version || chainingMode || symAlgKeySize || symAlgBlockSize || hmacAlgKeySize || hmacAlgDigestSize || E("") || MAC("") }.
|
|
return retVal;
|
|
}
|
|
|
|
protected override byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData)
|
|
{
|
|
// Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
|
|
if (cbCiphertext < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes))
|
|
{
|
|
throw Error.CryptCommon_PayloadInvalid();
|
|
}
|
|
|
|
// Assumption: pbCipherText := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) }
|
|
|
|
var cbEncryptedData = checked(cbCiphertext - (KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes));
|
|
|
|
// Calculate offsets
|
|
byte* pbKeyModifier = pbCiphertext;
|
|
byte* pbIV = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
|
|
byte* pbEncryptedData = &pbIV[_symmetricAlgorithmBlockSizeInBytes];
|
|
byte* pbActualHmac = &pbEncryptedData[cbEncryptedData];
|
|
|
|
// Use the KDF to recreate the symmetric encryption and HMAC subkeys
|
|
// We'll need a temporary buffer to hold them
|
|
var cbTempSubkeys = checked(_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes);
|
|
byte* pbTempSubkeys = stackalloc byte[checked((int)cbTempSubkeys)];
|
|
try
|
|
{
|
|
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
|
|
pbLabel: pbAdditionalAuthenticatedData,
|
|
cbLabel: cbAdditionalAuthenticatedData,
|
|
contextHeader: _contextHeader,
|
|
pbContext: pbKeyModifier,
|
|
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
|
|
pbDerivedKey: pbTempSubkeys,
|
|
cbDerivedKey: cbTempSubkeys);
|
|
|
|
// Calculate offsets
|
|
byte* pbSymmetricEncryptionSubkey = pbTempSubkeys;
|
|
byte* pbHmacSubkey = &pbTempSubkeys[_symmetricAlgorithmSubkeyLengthInBytes];
|
|
|
|
// First, perform an explicit integrity check over (iv | encryptedPayload) to ensure the
|
|
// data hasn't been tampered with. The integrity check is also implicitly performed over
|
|
// keyModifier since that value was provided to the KDF earlier.
|
|
using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
|
|
{
|
|
if (!ValidateHash(hashHandle, pbIV, _symmetricAlgorithmBlockSizeInBytes + cbEncryptedData, pbActualHmac))
|
|
{
|
|
throw Error.CryptCommon_PayloadInvalid();
|
|
}
|
|
}
|
|
|
|
// If the integrity check succeeded, decrypt the payload.
|
|
using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
|
|
{
|
|
return DoCbcDecrypt(decryptionSubkeyHandle, pbIV, pbEncryptedData, cbEncryptedData);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// Buffer contains sensitive key material; delete.
|
|
UnsafeBufferUtil.SecureZeroMemory(pbTempSubkeys, cbTempSubkeys);
|
|
}
|
|
}
|
|
|
|
public override void Dispose()
|
|
{
|
|
_sp800_108_ctr_hmac_provider.Dispose();
|
|
|
|
// We don't want to dispose of the underlying algorithm instances because they
|
|
// might be reused.
|
|
}
|
|
|
|
// 'pbIV' must be a pointer to a buffer equal in length to the symmetric algorithm block size.
|
|
private byte[] DoCbcDecrypt(BCryptKeyHandle symmetricKeyHandle, byte* pbIV, byte* pbInput, uint cbInput)
|
|
{
|
|
// BCryptDecrypt mutates the provided IV; we need to clone it to prevent mutation of the original value
|
|
byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
|
|
UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
|
|
|
|
// First, figure out how large an output buffer we require.
|
|
// Ideally we'd be able to transform the last block ourselves and strip
|
|
// off the padding before creating the return value array, but we don't
|
|
// know the actual padding scheme being used under the covers (we can't
|
|
// assume PKCS#7). So unfortunately we're stuck with the temporary buffer.
|
|
// (Querying the output size won't mutate the IV.)
|
|
uint dwEstimatedDecryptedByteCount;
|
|
var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
|
|
hKey: symmetricKeyHandle,
|
|
pbInput: pbInput,
|
|
cbInput: cbInput,
|
|
pPaddingInfo: null,
|
|
pbIV: pbClonedIV,
|
|
cbIV: _symmetricAlgorithmBlockSizeInBytes,
|
|
pbOutput: null,
|
|
cbOutput: 0,
|
|
pcbResult: out dwEstimatedDecryptedByteCount,
|
|
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
|
|
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
|
|
|
|
var decryptedPayload = new byte[dwEstimatedDecryptedByteCount];
|
|
uint dwActualDecryptedByteCount;
|
|
fixed (byte* pbDecryptedPayload = decryptedPayload)
|
|
{
|
|
byte dummy;
|
|
|
|
// Perform the actual decryption.
|
|
ntstatus = UnsafeNativeMethods.BCryptDecrypt(
|
|
hKey: symmetricKeyHandle,
|
|
pbInput: pbInput,
|
|
cbInput: cbInput,
|
|
pPaddingInfo: null,
|
|
pbIV: pbClonedIV,
|
|
cbIV: _symmetricAlgorithmBlockSizeInBytes,
|
|
pbOutput: (pbDecryptedPayload != null) ? pbDecryptedPayload : &dummy, // CLR won't pin zero-length arrays
|
|
cbOutput: dwEstimatedDecryptedByteCount,
|
|
pcbResult: out dwActualDecryptedByteCount,
|
|
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
|
|
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
|
|
}
|
|
|
|
// Decryption finished!
|
|
CryptoUtil.Assert(dwActualDecryptedByteCount <= dwEstimatedDecryptedByteCount, "dwActualDecryptedByteCount <= dwEstimatedDecryptedByteCount");
|
|
if (dwActualDecryptedByteCount == dwEstimatedDecryptedByteCount)
|
|
{
|
|
// payload takes up the entire buffer
|
|
return decryptedPayload;
|
|
}
|
|
else
|
|
{
|
|
// payload takes up only a partial buffer
|
|
var resizedDecryptedPayload = new byte[dwActualDecryptedByteCount];
|
|
Buffer.BlockCopy(decryptedPayload, 0, resizedDecryptedPayload, 0, resizedDecryptedPayload.Length);
|
|
return resizedDecryptedPayload;
|
|
}
|
|
}
|
|
|
|
// 'pbIV' must be a pointer to a buffer equal in length to the symmetric algorithm block size.
|
|
private void DoCbcEncrypt(BCryptKeyHandle symmetricKeyHandle, byte* pbIV, byte* pbInput, uint cbInput, byte* pbOutput, uint cbOutput)
|
|
{
|
|
// BCryptEncrypt mutates the provided IV; we need to clone it to prevent mutation of the original value
|
|
byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
|
|
UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
|
|
|
|
uint dwEncryptedBytes;
|
|
var ntstatus = UnsafeNativeMethods.BCryptEncrypt(
|
|
hKey: symmetricKeyHandle,
|
|
pbInput: pbInput,
|
|
cbInput: cbInput,
|
|
pPaddingInfo: null,
|
|
pbIV: pbClonedIV,
|
|
cbIV: _symmetricAlgorithmBlockSizeInBytes,
|
|
pbOutput: pbOutput,
|
|
cbOutput: cbOutput,
|
|
pcbResult: out dwEncryptedBytes,
|
|
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
|
|
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
|
|
|
|
// Need to make sure we didn't underrun the buffer - means caller passed a bad value
|
|
CryptoUtil.Assert(dwEncryptedBytes == cbOutput, "dwEncryptedBytes == cbOutput");
|
|
}
|
|
|
|
protected override byte[] EncryptImpl(byte* pbPlaintext, uint cbPlaintext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer)
|
|
{
|
|
// This buffer will be used to hold the symmetric encryption and HMAC subkeys
|
|
// used in the generation of this payload.
|
|
var cbTempSubkeys = checked(_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes);
|
|
byte* pbTempSubkeys = stackalloc byte[checked((int)cbTempSubkeys)];
|
|
|
|
try
|
|
{
|
|
// Randomly generate the key modifier and IV.
|
|
var cbKeyModifierAndIV = checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes);
|
|
byte* pbKeyModifierAndIV = stackalloc byte[checked((int)cbKeyModifierAndIV)];
|
|
_genRandom.GenRandom(pbKeyModifierAndIV, cbKeyModifierAndIV);
|
|
|
|
// Calculate offsets
|
|
byte* pbKeyModifier = pbKeyModifierAndIV;
|
|
byte* pbIV = &pbKeyModifierAndIV[KEY_MODIFIER_SIZE_IN_BYTES];
|
|
|
|
// Use the KDF to generate a new symmetric encryption and HMAC subkey
|
|
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
|
|
pbLabel: pbAdditionalAuthenticatedData,
|
|
cbLabel: cbAdditionalAuthenticatedData,
|
|
contextHeader: _contextHeader,
|
|
pbContext: pbKeyModifier,
|
|
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
|
|
pbDerivedKey: pbTempSubkeys,
|
|
cbDerivedKey: cbTempSubkeys);
|
|
|
|
// Calculate offsets
|
|
byte* pbSymmetricEncryptionSubkey = pbTempSubkeys;
|
|
byte* pbHmacSubkey = &pbTempSubkeys[_symmetricAlgorithmSubkeyLengthInBytes];
|
|
|
|
using (var symmetricKeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
|
|
{
|
|
// We can't assume PKCS#7 padding (maybe the underlying provider is really using CTS),
|
|
// so we need to query the padded output size before we can allocate the return value array.
|
|
var cbOutputCiphertext = GetCbcEncryptedOutputSizeWithPadding(symmetricKeyHandle, pbPlaintext, cbPlaintext);
|
|
|
|
// Allocate return value array and start copying some data
|
|
var retVal = new byte[checked(cbPreBuffer + KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + cbOutputCiphertext + _hmacAlgorithmDigestLengthInBytes + cbPostBuffer)];
|
|
fixed (byte* pbRetVal = retVal)
|
|
{
|
|
// Calculate offsets
|
|
byte* pbOutputKeyModifier = &pbRetVal[cbPreBuffer];
|
|
byte* pbOutputIV = &pbOutputKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
|
|
byte* pbOutputCiphertext = &pbOutputIV[_symmetricAlgorithmBlockSizeInBytes];
|
|
byte* pbOutputHmac = &pbOutputCiphertext[cbOutputCiphertext];
|
|
|
|
UnsafeBufferUtil.BlockCopy(from: pbKeyModifierAndIV, to: pbOutputKeyModifier, byteCount: cbKeyModifierAndIV);
|
|
|
|
// retVal will eventually contain { preBuffer | keyModifier | iv | encryptedData | HMAC(iv | encryptedData) | postBuffer }
|
|
// At this point, retVal := { preBuffer | keyModifier | iv | _____ | _____ | postBuffer }
|
|
|
|
DoCbcEncrypt(
|
|
symmetricKeyHandle: symmetricKeyHandle,
|
|
pbIV: pbIV,
|
|
pbInput: pbPlaintext,
|
|
cbInput: cbPlaintext,
|
|
pbOutput: pbOutputCiphertext,
|
|
cbOutput: cbOutputCiphertext);
|
|
|
|
// At this point, retVal := { preBuffer | keyModifier | iv | encryptedData | _____ | postBuffer }
|
|
|
|
// Compute the HMAC over the IV and the ciphertext (prevents IV tampering).
|
|
// The HMAC is already implicitly computed over the key modifier since the key
|
|
// modifier is used as input to the KDF.
|
|
using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
|
|
{
|
|
hashHandle.HashData(
|
|
pbInput: pbOutputIV,
|
|
cbInput: checked(_symmetricAlgorithmBlockSizeInBytes + cbOutputCiphertext),
|
|
pbHashDigest: pbOutputHmac,
|
|
cbHashDigest: _hmacAlgorithmDigestLengthInBytes);
|
|
}
|
|
|
|
// At this point, retVal := { preBuffer | keyModifier | iv | encryptedData | HMAC(iv | encryptedData) | postBuffer }
|
|
// And we're done!
|
|
return retVal;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// Buffer contains sensitive material; delete it.
|
|
UnsafeBufferUtil.SecureZeroMemory(pbTempSubkeys, cbTempSubkeys);
|
|
}
|
|
}
|
|
|
|
private uint GetCbcEncryptedOutputSizeWithPadding(BCryptKeyHandle symmetricKeyHandle, byte* pbInput, uint cbInput)
|
|
{
|
|
// ok for this memory to remain uninitialized since nobody depends on it
|
|
byte* pbIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
|
|
|
|
// Calling BCryptEncrypt with a null output pointer will cause it to return the total number
|
|
// of bytes required for the output buffer.
|
|
uint dwResult;
|
|
var ntstatus = UnsafeNativeMethods.BCryptEncrypt(
|
|
hKey: symmetricKeyHandle,
|
|
pbInput: pbInput,
|
|
cbInput: cbInput,
|
|
pPaddingInfo: null,
|
|
pbIV: pbIV,
|
|
cbIV: _symmetricAlgorithmBlockSizeInBytes,
|
|
pbOutput: null,
|
|
cbOutput: 0,
|
|
pcbResult: out dwResult,
|
|
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
|
|
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
|
|
|
|
return dwResult;
|
|
}
|
|
|
|
// 'pbExpectedDigest' must point to a '_hmacAlgorithmDigestLengthInBytes'-length buffer
|
|
private bool ValidateHash(BCryptHashHandle hashHandle, byte* pbInput, uint cbInput, byte* pbExpectedDigest)
|
|
{
|
|
byte* pbActualDigest = stackalloc byte[checked((int)_hmacAlgorithmDigestLengthInBytes)];
|
|
hashHandle.HashData(pbInput, cbInput, pbActualDigest, _hmacAlgorithmDigestLengthInBytes);
|
|
return CryptoUtil.TimeConstantBuffersAreEqual(pbExpectedDigest, pbActualDigest, _hmacAlgorithmDigestLengthInBytes);
|
|
}
|
|
}
|
|
}
|