private static BCryptKeyHandle PasswordToPbkdfKeyHandleStep2(BCryptAlgorithmHandle pbkdf2AlgHandle, byte *pbPassword, uint cbPassword, KeyDerivationPrf prf) { const uint PBKDF2_MAX_KEYLENGTH_IN_BYTES = 2048; // GetSupportedKeyLengths() on a Win8 box; value should never be lowered in any future version of Windows if (cbPassword <= PBKDF2_MAX_KEYLENGTH_IN_BYTES) { // Common case: the password is small enough to be consumed directly by the PBKDF2 algorithm. return(pbkdf2AlgHandle.GenerateSymmetricKey(pbPassword, cbPassword)); } else { // Rare case: password is very long; we must hash manually. // PBKDF2 uses the PRFs in HMAC mode, and when the HMAC input key exceeds the hash function's // block length the key is hashed and run back through the key initialization function. BCryptAlgorithmHandle prfAlgorithmHandle; // cached; don't dispose switch (prf) { case KeyDerivationPrf.Sha1: prfAlgorithmHandle = CachedAlgorithmHandles.SHA1; break; case KeyDerivationPrf.Sha256: prfAlgorithmHandle = CachedAlgorithmHandles.SHA256; break; case KeyDerivationPrf.Sha512: prfAlgorithmHandle = CachedAlgorithmHandles.SHA512; break; default: throw CryptoUtil.Fail("Unrecognized PRF."); } // Final sanity check: don't hash the password if the HMAC key initialization function wouldn't have done it for us. if (cbPassword <= prfAlgorithmHandle.GetHashBlockLength() /* in bytes */) { return(pbkdf2AlgHandle.GenerateSymmetricKey(pbPassword, cbPassword)); } // Hash the password and use the hash as input to PBKDF2. uint cbPasswordDigest = prfAlgorithmHandle.GetHashDigestLength(); CryptoUtil.Assert(cbPasswordDigest > 0, "cbPasswordDigest > 0"); fixed(byte *pbPasswordDigest = new byte[cbPasswordDigest]) { try { using (var hashHandle = prfAlgorithmHandle.CreateHash()) { hashHandle.HashData(pbPassword, cbPassword, pbPasswordDigest, cbPasswordDigest); } return(pbkdf2AlgHandle.GenerateSymmetricKey(pbPasswordDigest, cbPasswordDigest)); } finally { UnsafeBufferUtil.SecureZeroMemory(pbPasswordDigest, cbPasswordDigest); } } } }
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, nonce, and tag if (cbCiphertext < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES) { throw Error.CryptCommon_PayloadInvalid(); } // Assumption: pbCipherText := { keyModifier || nonce || encryptedData || authenticationTag } var cbPlaintext = checked (cbCiphertext - (KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES)); var retVal = new byte[cbPlaintext]; fixed(byte *pbRetVal = retVal) { // Calculate offsets byte *pbKeyModifier = pbCiphertext; byte *pbNonce = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES]; byte *pbEncryptedData = &pbNonce[NONCE_SIZE_IN_BYTES]; byte *pbAuthTag = &pbEncryptedData[cbPlaintext]; // Use the KDF to recreate the symmetric block cipher key // We'll need a temporary buffer to hold the symmetric encryption subkey byte *pbSymmetricDecryptionSubkey = stackalloc byte[checked ((int)_symmetricAlgorithmSubkeyLengthInBytes)]; try { _sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader( pbLabel: pbAdditionalAuthenticatedData, cbLabel: cbAdditionalAuthenticatedData, contextHeader: _contextHeader, pbContext: pbKeyModifier, cbContext: KEY_MODIFIER_SIZE_IN_BYTES, pbDerivedKey: pbSymmetricDecryptionSubkey, cbDerivedKey: _symmetricAlgorithmSubkeyLengthInBytes); // Perform the decryption operation using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricDecryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes)) { byte dummy; byte *pbPlaintext = (pbRetVal != null) ? pbRetVal : &dummy; // CLR doesn't like pinning empty buffers BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo; BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Init(out authInfo); authInfo.pbNonce = pbNonce; authInfo.cbNonce = NONCE_SIZE_IN_BYTES; authInfo.pbTag = pbAuthTag; authInfo.cbTag = TAG_SIZE_IN_BYTES; // The call to BCryptDecrypt will also validate the authentication tag uint cbDecryptedBytesWritten; var ntstatus = UnsafeNativeMethods.BCryptDecrypt( hKey: decryptionSubkeyHandle, pbInput: pbEncryptedData, cbInput: cbPlaintext, pPaddingInfo: &authInfo, pbIV: null, // IV not used; nonce provided in pPaddingInfo cbIV: 0, pbOutput: pbPlaintext, cbOutput: cbPlaintext, pcbResult: out cbDecryptedBytesWritten, dwFlags: 0); UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus); CryptoUtil.Assert(cbDecryptedBytesWritten == cbPlaintext, "cbDecryptedBytesWritten == cbPlaintext"); // At this point, retVal := { decryptedPayload } // And we're done! return(retVal); } } finally { // The buffer contains key material, so delete it. UnsafeBufferUtil.SecureZeroMemory(pbSymmetricDecryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes); } } }