// '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"); }
public static void DeriveKeyWithContextHeader(this ISP800_108_CTR_HMACSHA512Provider provider, byte *pbLabel, uint cbLabel, byte[] contextHeader, byte *pbContext, uint cbContext, byte *pbDerivedKey, uint cbDerivedKey) { uint cbCombinedContext = checked ((uint)contextHeader.Length + cbContext); // Try allocating the combined context on the stack to avoid temporary managed objects; only fall back to heap if buffers are too large. byte[] heapAllocatedCombinedContext = (cbCombinedContext > Constants.MAX_STACKALLOC_BYTES) ? new byte[cbCombinedContext] : null; fixed(byte *pbHeapAllocatedCombinedContext = heapAllocatedCombinedContext) { byte *pbCombinedContext = pbHeapAllocatedCombinedContext; if (pbCombinedContext == null) { byte *pbStackAllocatedCombinedContext = stackalloc byte[(int)cbCombinedContext]; // will be released when frame pops pbCombinedContext = pbStackAllocatedCombinedContext; } fixed(byte *pbContextHeader = contextHeader) { UnsafeBufferUtil.BlockCopy(from: pbContextHeader, to: pbCombinedContext, byteCount: contextHeader.Length); } UnsafeBufferUtil.BlockCopy(from: pbContext, to: &pbCombinedContext[contextHeader.Length], byteCount: cbContext); // At this point, combinedContext := { contextHeader || context } provider.DeriveKey(pbLabel, cbLabel, pbCombinedContext, cbCombinedContext, pbDerivedKey, cbDerivedKey); } }
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); } } } }
public SecureLocalAllocHandle Duplicate() { SecureLocalAllocHandle duplicateHandle = Allocate(_cb); UnsafeBufferUtil.BlockCopy(from: this, to: duplicateHandle, length: _cb); return(duplicateHandle); }
private static SecureLocalAllocHandle Protect(byte *pbPlaintext, uint cbPlaintext) { // If we're not running on a platform that supports CryptProtectMemory, // shove the plaintext directly into a LocalAlloc handle. Ideally we'd // mark this memory page as non-pageable, but this is fraught with peril. if (!OSVersionUtil.IsWindows()) { SecureLocalAllocHandle handle = SecureLocalAllocHandle.Allocate((IntPtr) checked ((int)cbPlaintext)); UnsafeBufferUtil.BlockCopy(from: pbPlaintext, to: handle, byteCount: cbPlaintext); return(handle); } // We need to make sure we're a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE. uint numTotalBytesToAllocate = cbPlaintext; uint numBytesPaddingRequired = CRYPTPROTECTMEMORY_BLOCK_SIZE - (numTotalBytesToAllocate % CRYPTPROTECTMEMORY_BLOCK_SIZE); if (numBytesPaddingRequired == CRYPTPROTECTMEMORY_BLOCK_SIZE) { numBytesPaddingRequired = 0; // we're already a proper multiple of the block size } checked { numTotalBytesToAllocate += numBytesPaddingRequired; } CryptoUtil.Assert(numTotalBytesToAllocate % CRYPTPROTECTMEMORY_BLOCK_SIZE == 0, "numTotalBytesToAllocate % CRYPTPROTECTMEMORY_BLOCK_SIZE == 0"); // Allocate and copy plaintext data; padding is uninitialized / undefined. SecureLocalAllocHandle encryptedMemoryHandle = SecureLocalAllocHandle.Allocate((IntPtr)numTotalBytesToAllocate); UnsafeBufferUtil.BlockCopy(from: pbPlaintext, to: encryptedMemoryHandle, byteCount: cbPlaintext); // Finally, CryptProtectMemory the whole mess. if (numTotalBytesToAllocate != 0) { MemoryProtection.CryptProtectMemory(encryptedMemoryHandle, byteCount: numTotalBytesToAllocate); } return(encryptedMemoryHandle); }
/// <summary> /// Returns a Secret comprised entirely of random bytes retrieved from /// a cryptographically secure RNG. /// </summary> public static Secret Random(int numBytes) { if (numBytes < 0) { throw Error.Common_ValueMustBeNonNegative(nameof(numBytes)); } if (numBytes == 0) { byte dummy; return(new Secret(&dummy, 0)); } else { // Don't use CNG if we're not on Windows. if (!OSVersionUtil.IsWindows()) { return(new Secret(ManagedGenRandomImpl.Instance.GenRandom(numBytes))); } byte[] bytes = new byte[numBytes]; fixed(byte *pbBytes = bytes) { try { BCryptUtil.GenRandom(pbBytes, (uint)numBytes); return(new Secret(pbBytes, numBytes)); } finally { UnsafeBufferUtil.SecureZeroMemory(pbBytes, numBytes); } } } }
private void UnprotectInto(byte *pbBuffer) { // If we're not running on a platform that supports CryptProtectMemory, // the handle contains plaintext bytes. if (!OSVersionUtil.IsWindows()) { UnsafeBufferUtil.BlockCopy(from: _localAllocHandle, to: pbBuffer, byteCount: _plaintextLength); return; } if (_plaintextLength % CRYPTPROTECTMEMORY_BLOCK_SIZE == 0) { // Case 1: Secret length is an exact multiple of the block size. Copy directly to the buffer and decrypt there. UnsafeBufferUtil.BlockCopy(from: _localAllocHandle, to: pbBuffer, byteCount: _plaintextLength); MemoryProtection.CryptUnprotectMemory(pbBuffer, _plaintextLength); } else { // Case 2: Secret length is not a multiple of the block size. We'll need to duplicate the data and // perform the decryption in the duplicate buffer, then copy the plaintext data over. using (var duplicateHandle = _localAllocHandle.Duplicate()) { MemoryProtection.CryptUnprotectMemory(duplicateHandle, checked ((uint)duplicateHandle.Length)); UnsafeBufferUtil.BlockCopy(from: duplicateHandle, to: pbBuffer, byteCount: _plaintextLength); } } }
private static BCryptKeyHandle ImportKey(byte *pbKdk, uint cbKdk) { // The MS implementation of SP800_108_CTR_HMAC has a limit on the size of the key it can accept. // If the incoming key is too long, we'll hash it using SHA512 to bring it back to a manageable // length. This transform is appropriate since SP800_108_CTR_HMAC is just a glorified HMAC under // the covers, and the HMAC algorithm allows hashing the key using the underlying PRF if the key // is greater than the PRF's block length. const uint SHA512_BLOCK_SIZE_IN_BYTES = 1024 / 8; const uint SHA512_DIGEST_SIZE_IN_BYTES = 512 / 8; if (cbKdk > SHA512_BLOCK_SIZE_IN_BYTES) { // Hash key. byte *pbHashedKey = stackalloc byte[(int)SHA512_DIGEST_SIZE_IN_BYTES]; try { using (var hashHandle = CachedAlgorithmHandles.SHA512.CreateHash()) { hashHandle.HashData(pbKdk, cbKdk, pbHashedKey, SHA512_DIGEST_SIZE_IN_BYTES); } return(CachedAlgorithmHandles.SP800_108_CTR_HMAC.GenerateSymmetricKey(pbHashedKey, SHA512_DIGEST_SIZE_IN_BYTES)); } finally { UnsafeBufferUtil.SecureZeroMemory(pbHashedKey, SHA512_DIGEST_SIZE_IN_BYTES); } } else { // Use key directly. return(CachedAlgorithmHandles.SP800_108_CTR_HMAC.GenerateSymmetricKey(pbKdk, cbKdk)); } }
/// <summary> /// Creates a new Secret from another secret object. /// </summary> public Secret(ISecret secret) { if (secret == null) { throw new ArgumentNullException(nameof(secret)); } Secret other = secret as Secret; if (other != null) { // Fast-track: simple deep copy scenario. this._localAllocHandle = other._localAllocHandle.Duplicate(); this._plaintextLength = other._plaintextLength; } else { // Copy the secret to a temporary managed buffer, then protect the buffer. // We pin the temp buffer and zero it out when we're finished to limit exposure of the secret. byte[] tempPlaintextBuffer = new byte[secret.Length]; fixed(byte *pbTempPlaintextBuffer = tempPlaintextBuffer) { try { secret.WriteSecretIntoBuffer(new ArraySegment <byte>(tempPlaintextBuffer)); _localAllocHandle = Protect(pbTempPlaintextBuffer, (uint)tempPlaintextBuffer.Length); _plaintextLength = (uint)tempPlaintextBuffer.Length; } finally { UnsafeBufferUtil.SecureZeroMemory(pbTempPlaintextBuffer, tempPlaintextBuffer.Length); } } } }
protected override byte[] EncryptImpl(byte *pbPlaintext, uint cbPlaintext, byte *pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer) { // 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 (cbPreBuffer + KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + cbPlaintext + TAG_SIZE_IN_BYTES + cbPostBuffer)]; fixed(byte *pbRetVal = retVal) { // Calculate offsets byte *pbKeyModifier = &pbRetVal[cbPreBuffer]; byte *pbNonce = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES]; byte *pbEncryptedData = &pbNonce[NONCE_SIZE_IN_BYTES]; byte *pbAuthTag = &pbEncryptedData[cbPlaintext]; // Randomly generate the key modifier and nonce _genRandom.GenRandom(pbKeyModifier, KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES); // 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 byte *pbSymmetricEncryptionSubkey = 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: pbSymmetricEncryptionSubkey, cbDerivedKey: _symmetricAlgorithmSubkeyLengthInBytes); // Perform the encryption operation DoGcmEncrypt( pbKey: pbSymmetricEncryptionSubkey, cbKey: _symmetricAlgorithmSubkeyLengthInBytes, pbNonce: pbNonce, pbPlaintextData: pbPlaintext, cbPlaintextData: cbPlaintext, pbEncryptedData: pbEncryptedData, pbTag: pbAuthTag); // At this point, retVal := { preBuffer | keyModifier | nonce | encryptedData | authenticationTag | postBuffer } // And we're done! return(retVal); } finally { // The buffer contains key material, so delete it. UnsafeBufferUtil.SecureZeroMemory(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes); } } }
public void SecureZeroMemory_IntPtrLength() { // Arrange long x = 0x0123456789ABCDEF; // Act UnsafeBufferUtil.SecureZeroMemory((byte *)&x, length: (IntPtr)sizeof(long)); // Assert Assert.Equal(0, x); }
public void SecureZeroMemory_ULongLength() { // Arrange long x = 0x0123456789ABCDEF; // Act UnsafeBufferUtil.SecureZeroMemory((byte *)&x, byteCount: (ulong)sizeof(long)); // Assert Assert.Equal(0, x); }
public void DeriveKey(byte *pbLabel, uint cbLabel, byte *pbContext, uint cbContext, byte *pbDerivedKey, uint cbDerivedKey) { const uint SHA512_DIGEST_SIZE_IN_BYTES = 512 / 8; byte * pbHashDigest = stackalloc byte[(int)SHA512_DIGEST_SIZE_IN_BYTES]; // NOTE: pbDerivedKey and cbDerivedKey are modified as data is copied to the output buffer. // this will be zero-inited byte[] tempInputBuffer = new byte[checked ( sizeof(int) /* [i] */ + cbLabel /* Label */ + 1 /* 0x00 */ + cbContext /* Context */ + sizeof(int) /* [L] */)]; fixed(byte *pbTempInputBuffer = tempInputBuffer) { // Step 1: Calculate all necessary offsets into the temp input & output buffer. byte *pbTempInputCounter = pbTempInputBuffer; byte *pbTempInputLabel = &pbTempInputCounter[sizeof(int)]; byte *pbTempInputContext = &pbTempInputLabel[cbLabel + 1 /* 0x00 */]; byte *pbTempInputBitlengthIndicator = &pbTempInputContext[cbContext]; // Step 2: Copy Label and Context into the temp input buffer. UnsafeBufferUtil.BlockCopy(from: pbLabel, to: pbTempInputLabel, byteCount: cbLabel); UnsafeBufferUtil.BlockCopy(from: pbContext, to: pbTempInputContext, byteCount: cbContext); // Step 3: copy [L] into last part of data to be hashed, big-endian BitHelpers.WriteTo(pbTempInputBitlengthIndicator, checked (cbDerivedKey * 8)); // Step 4: iterate until all desired bytes have been generated for (uint i = 1; cbDerivedKey > 0; i++) { // Step 4a: Copy [i] into the first part of data to be hashed, big-endian BitHelpers.WriteTo(pbTempInputCounter, i); // Step 4b: Hash. Win7 doesn't allow reusing hash algorithm objects after the final hash // has been computed, so we'll just keep calling DuplicateHash on the original // hash handle. This offers a slight performance increase over allocating a new hash // handle for each iteration. We don't need to mess with any of this on Win8 since on // that platform we use BCryptKeyDerivation directly, which offers superior performance. using (var hashHandle = _hashHandle.DuplicateHash()) { hashHandle.HashData(pbTempInputBuffer, (uint)tempInputBuffer.Length, pbHashDigest, SHA512_DIGEST_SIZE_IN_BYTES); } // Step 4c: Copy bytes from the temporary buffer to the output buffer. uint numBytesToCopy = Math.Min(cbDerivedKey, SHA512_DIGEST_SIZE_IN_BYTES); UnsafeBufferUtil.BlockCopy(from: pbHashDigest, to: pbDerivedKey, byteCount: numBytesToCopy); pbDerivedKey += numBytesToCopy; cbDerivedKey -= numBytesToCopy; } } }
public void BlockCopy_PtrToPtr_UIntLength() { // Arrange long x = 0x0123456789ABCDEF; long y = 0; // Act UnsafeBufferUtil.BlockCopy(from: &x, to: &y, byteCount: (uint)sizeof(long)); // Assert Assert.Equal(x, y); }
private static byte[] ProtectWithDpapiNGCore(NCryptDescriptorHandle protectionDescriptorHandle, byte *pbData, uint cbData) { Debug.Assert(protectionDescriptorHandle != null); Debug.Assert(pbData != null); // Perform the encryption operation, putting the protected data into LocalAlloc-allocated memory. LocalAllocHandle protectedData; uint cbProtectedData; var ntstatus = UnsafeNativeMethods.NCryptProtectSecret( hDescriptor: protectionDescriptorHandle, dwFlags: NCRYPT_SILENT_FLAG, pbData: pbData, cbData: cbData, pMemPara: IntPtr.Zero, hWnd: IntPtr.Zero, ppbProtectedBlob: out protectedData, pcbProtectedBlob: out cbProtectedData); UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus); CryptoUtil.AssertSafeHandleIsValid(protectedData); // Copy the data from LocalAlloc-allocated memory into a managed memory buffer. using (protectedData) { var retVal = new byte[cbProtectedData]; if (cbProtectedData > 0) { fixed(byte *pbRetVal = retVal) { var handleAcquired = false; #if NETSTANDARD2_0 RuntimeHelpers.PrepareConstrainedRegions(); #endif try { protectedData.DangerousAddRef(ref handleAcquired); UnsafeBufferUtil.BlockCopy(from: (void *)protectedData.DangerousGetHandle(), to: pbRetVal, byteCount: cbProtectedData); } finally { if (handleAcquired) { protectedData.DangerousRelease(); } } } } return(retVal); } }
internal static Secret UnprotectWithDpapiCore(byte *pbProtectedData, uint cbProtectedData, byte *pbOptionalEntropy, uint cbOptionalEntropy) { byte dummy; // provides a valid memory address if the secret or entropy has zero length var dataIn = new DATA_BLOB() { cbData = cbProtectedData, pbData = (pbProtectedData != null) ? pbProtectedData : &dummy }; var entropy = new DATA_BLOB() { cbData = cbOptionalEntropy, pbData = (pbOptionalEntropy != null) ? pbOptionalEntropy : &dummy }; var dataOut = default(DATA_BLOB); #if NETSTANDARD2_0 RuntimeHelpers.PrepareConstrainedRegions(); #endif try { var success = UnsafeNativeMethods.CryptUnprotectData( pDataIn: &dataIn, ppszDataDescr: IntPtr.Zero, pOptionalEntropy: &entropy, pvReserved: IntPtr.Zero, pPromptStruct: IntPtr.Zero, dwFlags: CRYPTPROTECT_UI_FORBIDDEN, pDataOut: &dataOut); if (!success) { var errorCode = Marshal.GetLastWin32Error(); throw new CryptographicException(errorCode); } return(new Secret(dataOut.pbData, checked ((int)dataOut.cbData))); } finally { // Zero and free memory so that we don't leak secrets. // FreeHGlobal actually calls LocalFree. if (dataOut.pbData != null) { UnsafeBufferUtil.SecureZeroMemory(dataOut.pbData, dataOut.cbData); Marshal.FreeHGlobal((IntPtr)dataOut.pbData); } } }
private static Secret UnprotectWithDpapiNGCore(byte *pbData, uint cbData) { Debug.Assert(pbData != null); // First, decrypt the payload into LocalAlloc-allocated memory. LocalAllocHandle unencryptedPayloadHandle; uint cbUnencryptedPayload; var ntstatus = UnsafeNativeMethods.NCryptUnprotectSecret( phDescriptor: IntPtr.Zero, dwFlags: NCRYPT_SILENT_FLAG, pbProtectedBlob: pbData, cbProtectedBlob: cbData, pMemPara: IntPtr.Zero, hWnd: IntPtr.Zero, ppbData: out unencryptedPayloadHandle, pcbData: out cbUnencryptedPayload); UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus); CryptoUtil.AssertSafeHandleIsValid(unencryptedPayloadHandle); // Copy the data from LocalAlloc-allocated memory into a CryptProtectMemory-protected buffer. // There's a small window between NCryptUnprotectSecret returning and the call to PrepareConstrainedRegions // below where the AppDomain could rudely unload. This won't leak memory (due to the SafeHandle), but it // will cause the secret not to be zeroed out before the memory is freed. We won't worry about this since // the window is extremely small and AppDomain unloads should not happen here in practice. using (unencryptedPayloadHandle) { var handleAcquired = false; #if NETSTANDARD2_0 RuntimeHelpers.PrepareConstrainedRegions(); #endif try { unencryptedPayloadHandle.DangerousAddRef(ref handleAcquired); return(new Secret((byte *)unencryptedPayloadHandle.DangerousGetHandle(), checked ((int)cbUnencryptedPayload))); } finally { if (handleAcquired) { UnsafeBufferUtil.SecureZeroMemory((byte *)unencryptedPayloadHandle.DangerousGetHandle(), cbUnencryptedPayload); unencryptedPayloadHandle.DangerousRelease(); } } } }
public void BlockCopy_PtrToHandle() { // Arrange const string expected = "Hello there!"; int cbExpected = expected.Length * sizeof(char); var testHandle = LocalAlloc(cbExpected); // Act fixed(char *pExpected = expected) { UnsafeBufferUtil.BlockCopy(from: pExpected, to: testHandle, byteCount: (uint)cbExpected); } // Assert string actual = new string((char *)testHandle.DangerousGetHandle(), 0, expected.Length); GC.KeepAlive(testHandle); Assert.Equal(expected, actual); }
private static BCryptKeyHandle PasswordToPbkdfKeyHandle(string password, BCryptAlgorithmHandle pbkdf2AlgHandle, KeyDerivationPrf prf) { byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers // Convert password string to bytes. // Allocate on the stack whenever we can to save allocations. int cbPasswordBuffer = Encoding.UTF8.GetMaxByteCount(password.Length); fixed(byte *pbHeapAllocatedPasswordBuffer = (cbPasswordBuffer > Constants.MAX_STACKALLOC_BYTES)?new byte[cbPasswordBuffer] : null) { byte *pbPasswordBuffer = pbHeapAllocatedPasswordBuffer; if (pbPasswordBuffer == null) { if (cbPasswordBuffer == 0) { pbPasswordBuffer = &dummy; } else { byte *pbStackAllocPasswordBuffer = stackalloc byte[cbPasswordBuffer]; // will be released when the frame unwinds pbPasswordBuffer = pbStackAllocPasswordBuffer; } } try { int cbPasswordBufferUsed; // we're not filling the entire buffer, just a partial buffer fixed(char *pszPassword = password) { cbPasswordBufferUsed = Encoding.UTF8.GetBytes(pszPassword, password.Length, pbPasswordBuffer, cbPasswordBuffer); } return(PasswordToPbkdfKeyHandleStep2(pbkdf2AlgHandle, pbPasswordBuffer, (uint)cbPasswordBufferUsed, prf)); } finally { UnsafeBufferUtil.SecureZeroMemory(pbPasswordBuffer, cbPasswordBuffer); } } }
public void BlockCopy_HandleToPtr() { // Arrange const string expected = "Hello there!"; int cbExpected = expected.Length * sizeof(char); var controlHandle = LocalAlloc(cbExpected); for (int i = 0; i < expected.Length; i++) { ((char *)controlHandle.DangerousGetHandle())[i] = expected[i]; } char *dest = stackalloc char[expected.Length]; // Act UnsafeBufferUtil.BlockCopy(from: controlHandle, to: dest, byteCount: (uint)cbExpected); // Assert string actual = new string(dest, 0, expected.Length); Assert.Equal(expected, actual); }
public void BlockCopy_HandleToHandle() { // Arrange const string expected = "Hello there!"; int cbExpected = expected.Length * sizeof(char); var controlHandle = LocalAlloc(cbExpected); for (int i = 0; i < expected.Length; i++) { ((char *)controlHandle.DangerousGetHandle())[i] = expected[i]; } var testHandle = LocalAlloc(cbExpected); // Act UnsafeBufferUtil.BlockCopy(from: controlHandle, to: testHandle, length: (IntPtr)cbExpected); // Assert string actual = new string((char *)testHandle.DangerousGetHandle(), 0, expected.Length); GC.KeepAlive(testHandle); Assert.Equal(expected, actual); }
// Creates a provider from the given secret. public static ISP800_108_CTR_HMACSHA512Provider CreateProvider(Secret kdk) { uint secretLengthInBytes = checked ((uint)kdk.Length); if (secretLengthInBytes == 0) { return(CreateEmptyProvider()); } else { fixed(byte *pbPlaintextSecret = new byte[secretLengthInBytes]) { try { kdk.WriteSecretIntoBuffer(pbPlaintextSecret, checked ((int)secretLengthInBytes)); return(CreateProvider(pbPlaintextSecret, secretLengthInBytes)); } finally { UnsafeBufferUtil.SecureZeroMemory(pbPlaintextSecret, secretLengthInBytes); } } } }
public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested) { Debug.Assert(password != null); Debug.Assert(salt != null); Debug.Assert(iterationCount > 0); Debug.Assert(numBytesRequested > 0); byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers // Don't dispose of this algorithm instance; it is cached and reused! var algHandle = PrfToCachedCngAlgorithmInstance(prf); // Convert password string to bytes. // Allocate on the stack whenever we can to save allocations. int cbPasswordBuffer = Encoding.UTF8.GetMaxByteCount(password.Length); fixed(byte *pbHeapAllocatedPasswordBuffer = (cbPasswordBuffer > Constants.MAX_STACKALLOC_BYTES)?new byte[cbPasswordBuffer] : null) { byte *pbPasswordBuffer = pbHeapAllocatedPasswordBuffer; if (pbPasswordBuffer == null) { if (cbPasswordBuffer == 0) { pbPasswordBuffer = &dummy; } else { byte *pbStackAllocPasswordBuffer = stackalloc byte[cbPasswordBuffer]; // will be released when the frame unwinds pbPasswordBuffer = pbStackAllocPasswordBuffer; } } try { int cbPasswordBufferUsed; // we're not filling the entire buffer, just a partial buffer fixed(char *pszPassword = password) { cbPasswordBufferUsed = Encoding.UTF8.GetBytes(pszPassword, password.Length, pbPasswordBuffer, cbPasswordBuffer); } fixed(byte *pbHeapAllocatedSalt = salt) { byte *pbSalt = (pbHeapAllocatedSalt != null) ? pbHeapAllocatedSalt : &dummy; byte[] retVal = new byte[numBytesRequested]; fixed(byte *pbRetVal = retVal) { int ntstatus = UnsafeNativeMethods.BCryptDeriveKeyPBKDF2( hPrf: algHandle, pbPassword: pbPasswordBuffer, cbPassword: (uint)cbPasswordBufferUsed, pbSalt: pbSalt, cbSalt: (uint)salt.Length, cIterations: (ulong)iterationCount, pbDerivedKey: pbRetVal, cbDerivedKey: (uint)retVal.Length, dwFlags: 0); UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus); } return(retVal); } } finally { UnsafeBufferUtil.SecureZeroMemory(pbPasswordBuffer, cbPasswordBuffer); } } }
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); } } }
// Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you. protected override bool ReleaseHandle() { UnsafeBufferUtil.SecureZeroMemory((byte *)handle, _cb); // compiler won't optimize this away return(base.ReleaseHandle()); }
private byte[] CreateContextHeader() { var retVal = new byte[checked ( 1 /* KDF alg */ + 1 /* chaining mode */ + sizeof(uint) /* sym alg key size */ + sizeof(uint) /* GCM nonce size */ + sizeof(uint) /* sym alg block size */ + sizeof(uint) /* GCM tag size */ + TAG_SIZE_IN_BYTES /* tag of GCM-encrypted 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++) = 1; // 0x01 = GCM encryption + authentication // Next is information about the symmetric algorithm (key size, nonce size, block size, tag size) BitHelpers.WriteTo(ref ptr, _symmetricAlgorithmSubkeyLengthInBytes); BitHelpers.WriteTo(ref ptr, NONCE_SIZE_IN_BYTES); BitHelpers.WriteTo(ref ptr, TAG_SIZE_IN_BYTES); // block size = tag size BitHelpers.WriteTo(ref ptr, TAG_SIZE_IN_BYTES); // See the design document for an explanation of the following code. var tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes]; fixed(byte *pbTempKeys = tempKeys) { byte dummy; // Derive temporary key for encryption. using (var provider = SP800_108_CTR_HMACSHA512Util.CreateEmptyProvider()) { provider.DeriveKey( pbLabel: &dummy, cbLabel: 0, pbContext: &dummy, cbContext: 0, pbDerivedKey: pbTempKeys, cbDerivedKey: (uint)tempKeys.Length); } // Encrypt a zero-length input string with an all-zero nonce and copy the tag to the return buffer. byte *pbNonce = stackalloc byte[(int)NONCE_SIZE_IN_BYTES]; UnsafeBufferUtil.SecureZeroMemory(pbNonce, NONCE_SIZE_IN_BYTES); DoGcmEncrypt( pbKey: pbTempKeys, cbKey: _symmetricAlgorithmSubkeyLengthInBytes, pbNonce: pbNonce, pbPlaintextData: &dummy, cbPlaintextData: 0, pbEncryptedData: &dummy, pbTag: ptr); } ptr += TAG_SIZE_IN_BYTES; CryptoUtil.Assert(ptr - pbRetVal == retVal.Length, "ptr - pbRetVal == retVal.Length"); } // retVal := { version || chainingMode || symAlgKeySize || nonceSize || symAlgBlockSize || symAlgTagSize || TAG-of-E("") }. return(retVal); }
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); } }
// '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); } }
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); } }