public override bool TryFinalizeHashAndReset(Span <byte> destination, out int bytesWritten) { if (destination.Length < _hashSize) { bytesWritten = 0; return(false); } NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hHash, destination, _hashSize, 0); if (ntStatus != NTSTATUS.STATUS_SUCCESS) { throw Interop.BCrypt.CreateCryptographicException(ntStatus); } bytesWritten = _hashSize; ResetHashObject(); return(true); }
private static byte[] ExportKeyBlob(SafeBCryptKeyHandle bCryptKeyHandle, CngKeyBlobFormat blobFormat) { string blobFormatString = blobFormat.Format; int numBytesNeeded = 0; NTSTATUS ntStatus = Interop.BCrypt.BCryptExportKey(bCryptKeyHandle, IntPtr.Zero, blobFormatString, null, 0, out numBytesNeeded, 0); if (ntStatus != NTSTATUS.STATUS_SUCCESS) { throw new CryptographicException(Interop.Kernel32.GetMessage((int)ntStatus)); } byte[] keyBlob = new byte[numBytesNeeded]; ntStatus = Interop.BCrypt.BCryptExportKey(bCryptKeyHandle, IntPtr.Zero, blobFormatString, keyBlob, keyBlob.Length, out numBytesNeeded, 0); if (ntStatus != NTSTATUS.STATUS_SUCCESS) { throw new CryptographicException(Interop.Kernel32.GetMessage((int)ntStatus)); } Array.Resize(ref keyBlob, numBytesNeeded); return(keyBlob); }
public override void Reset() { if (_reusable && !_running) { return; } DestroyHash(); BCryptCreateHashFlags flags = _reusable ? BCryptCreateHashFlags.BCRYPT_HASH_REUSABLE_FLAG : BCryptCreateHashFlags.None; SafeBCryptHashHandle hHash; NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(_hAlgorithm, out hHash, IntPtr.Zero, 0, _key, _key == null ? 0 : _key.Length, flags); if (ntStatus != NTSTATUS.STATUS_SUCCESS) { throw Interop.BCrypt.CreateCryptographicException(ntStatus); } _hHash = hHash; }
private static byte[] CreateCryptoRandomByteArray(int byteLength) { // We need to fill a byte array with cryptographically-strong random bytes, but we can't reference // System.Security.Cryptography.RandomNumberGenerator.dll due to layering. Instead, we just // call to BCryptGenRandom directly, which is all that RandomNumberGenerator does. var arr = new byte[byteLength]; Interop.BCrypt.NTSTATUS status = Interop.BCrypt.BCryptGenRandom(arr, arr.Length); if (status == Interop.BCrypt.NTSTATUS.STATUS_SUCCESS) { return(arr); } else if (status == Interop.BCrypt.NTSTATUS.STATUS_NO_MEMORY) { throw new OutOfMemoryException(); } else { Debug.Fail("BCryptGenRandom should only fail due to OOM or invalid args / handle inputs."); throw new InvalidOperationException(); } }
internal HashProviderCng(string hashAlgId, ReadOnlySpan <byte> key, bool isHmac) { BCryptOpenAlgorithmProviderFlags dwFlags = BCryptOpenAlgorithmProviderFlags.None; if (isHmac) { _key = key.ToArray(); dwFlags |= BCryptOpenAlgorithmProviderFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG; } _hAlgorithm = Interop.BCrypt.BCryptAlgorithmCache.GetCachedBCryptAlgorithmHandle(hashAlgId, dwFlags, out _hashSize); // Win7 won't set hHash to a valid handle, Win8+ will; and both will set _hHash. // So keep hHash trapped in this scope to prevent (mis-)use of it. { SafeBCryptHashHandle hHash; NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(_hAlgorithm, out hHash, IntPtr.Zero, 0, key, key == null ? 0 : key.Length, BCryptCreateHashFlags.BCRYPT_HASH_REUSABLE_FLAG); if (ntStatus == NTSTATUS.STATUS_INVALID_PARAMETER) { hHash.Dispose(); // If we got here, we're running on a downlevel OS (pre-Win8) that doesn't support reusable CNG hash objects. Fall back to creating a // new HASH object each time. Reset(); } else if (ntStatus != NTSTATUS.STATUS_SUCCESS) { hHash.Dispose(); throw Interop.BCrypt.CreateCryptographicException(ntStatus); } else { _hHash = hHash; _reusable = true; } } }
private static unsafe void GetCryptoRandomBytes(byte *bytes, int byteCount) { // We need to fill a byte array with cryptographically-strong random bytes, but we can't reference // System.Security.Cryptography.RandomNumberGenerator.dll due to layering. Instead, we just // call to BCryptGenRandom directly, which is all that RandomNumberGenerator does. Debug.Assert(bytes != null); Debug.Assert(byteCount >= 0); Interop.BCrypt.NTSTATUS status = Interop.BCrypt.BCryptGenRandom(bytes, byteCount); if (status == Interop.BCrypt.NTSTATUS.STATUS_SUCCESS) { return; } else if (status == Interop.BCrypt.NTSTATUS.STATUS_NO_MEMORY) { throw new OutOfMemoryException(); } else { Debug.Fail("BCryptGenRandom should only fail due to OOM or invalid args / handle inputs."); throw new InvalidOperationException(); } }
protected sealed override bool ReleaseHandle() { NTSTATUS ntStatus = Interop.BCrypt.BCryptDestroySecret(handle); return(ntStatus == NTSTATUS.STATUS_SUCCESS); }
protected sealed override bool ReleaseHandle() { NTSTATUS ntStatus = Interop.BCrypt.BCryptCloseAlgorithmProvider(handle, 0); return(ntStatus == NTSTATUS.STATUS_SUCCESS); }
private static unsafe void FillKeyDerivation( ReadOnlySpan <byte> password, ReadOnlySpan <byte> salt, int iterations, string hashAlgorithmName, Span <byte> destination) { SafeBCryptKeyHandle keyHandle; int hashBlockSizeBytes = GetHashBlockSize(hashAlgorithmName); // stackalloc 0 to let compiler know this cannot escape. scoped Span <byte> clearSpan; scoped ReadOnlySpan <byte> symmetricKeyMaterial; int symmetricKeyMaterialLength; if (password.IsEmpty) { // CNG won't accept a null pointer for the password. symmetricKeyMaterial = stackalloc byte[1]; symmetricKeyMaterialLength = 0; clearSpan = default; } else if (password.Length <= hashBlockSizeBytes) { // Password is small enough to use as-is. symmetricKeyMaterial = password; symmetricKeyMaterialLength = password.Length; clearSpan = default; } else { // RFC 2104: "The key for HMAC can be of any length (keys longer than B bytes are // first hashed using H). // We denote by B the byte-length of such // blocks (B=64 for all the above mentioned examples of hash functions) // // Windows' PBKDF2 will do this up to a point. To ensure we accept arbitrary inputs for // PBKDF2, we do the hashing ourselves. Span <byte> hashBuffer = stackalloc byte[512 / 8]; // 64 bytes is SHA512, the largest digest handled. int hashBufferSize; switch (hashAlgorithmName) { case HashAlgorithmNames.SHA1: hashBufferSize = SHA1.HashData(password, hashBuffer); break; case HashAlgorithmNames.SHA256: hashBufferSize = SHA256.HashData(password, hashBuffer); break; case HashAlgorithmNames.SHA384: hashBufferSize = SHA384.HashData(password, hashBuffer); break; case HashAlgorithmNames.SHA512: hashBufferSize = SHA512.HashData(password, hashBuffer); break; default: Debug.Fail($"Unexpected hash algorithm '{hashAlgorithmName}'"); throw new CryptographicException(); } clearSpan = hashBuffer.Slice(0, hashBufferSize); symmetricKeyMaterial = clearSpan; symmetricKeyMaterialLength = hashBufferSize; } Debug.Assert(symmetricKeyMaterial.Length > 0); NTSTATUS generateKeyStatus; if (Interop.BCrypt.PseudoHandlesSupported) { fixed(byte *pSymmetricKeyMaterial = symmetricKeyMaterial) { generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey( (nuint)BCryptAlgPseudoHandle.BCRYPT_PBKDF2_ALG_HANDLE, out keyHandle, pbKeyObject: IntPtr.Zero, cbKeyObject: 0, pSymmetricKeyMaterial, symmetricKeyMaterialLength, dwFlags: 0); } } else { if (s_pbkdf2AlgorithmHandle is null) { NTSTATUS openStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider( out SafeBCryptAlgorithmHandle pbkdf2AlgorithmHandle, Internal.NativeCrypto.BCryptNative.AlgorithmName.Pbkdf2, null, BCryptOpenAlgorithmProviderFlags.None); if (openStatus != NTSTATUS.STATUS_SUCCESS) { pbkdf2AlgorithmHandle.Dispose(); CryptographicOperations.ZeroMemory(clearSpan); throw Interop.BCrypt.CreateCryptographicException(openStatus); } // This might race, and that's okay. Worst case the algorithm is opened // more than once, and the ones that lost will get cleaned up during collection. Interlocked.CompareExchange(ref s_pbkdf2AlgorithmHandle, pbkdf2AlgorithmHandle, null); } fixed(byte *pSymmetricKeyMaterial = symmetricKeyMaterial) { generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey( s_pbkdf2AlgorithmHandle, out keyHandle, pbKeyObject: IntPtr.Zero, cbKeyObject: 0, pSymmetricKeyMaterial, symmetricKeyMaterialLength, dwFlags: 0); } } CryptographicOperations.ZeroMemory(clearSpan); if (generateKeyStatus != NTSTATUS.STATUS_SUCCESS) { keyHandle.Dispose(); throw Interop.BCrypt.CreateCryptographicException(generateKeyStatus); } Debug.Assert(!keyHandle.IsInvalid); ulong kdfIterations = (ulong)iterations; // Previously asserted to be positive. using (keyHandle) fixed(char *pHashAlgorithmName = hashAlgorithmName) fixed(byte *pSalt = salt) fixed(byte *pDestination = destination) { Span <BCryptBuffer> buffers = stackalloc BCryptBuffer[3]; buffers[0].BufferType = CngBufferDescriptors.KDF_ITERATION_COUNT; buffers[0].pvBuffer = (IntPtr)(&kdfIterations); buffers[0].cbBuffer = sizeof(ulong); buffers[1].BufferType = CngBufferDescriptors.KDF_SALT; buffers[1].pvBuffer = (IntPtr)pSalt; buffers[1].cbBuffer = salt.Length; buffers[2].BufferType = CngBufferDescriptors.KDF_HASH_ALGORITHM; buffers[2].pvBuffer = (IntPtr)pHashAlgorithmName; // C# spec: "A char* value produced by fixing a string instance always points to a null-terminated string" buffers[2].cbBuffer = checked ((hashAlgorithmName.Length + 1) * sizeof(char)); // Add null terminator. fixed(BCryptBuffer *pBuffers = buffers) { Interop.BCrypt.BCryptBufferDesc bufferDesc; bufferDesc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION; bufferDesc.cBuffers = buffers.Length; bufferDesc.pBuffers = (IntPtr)pBuffers; NTSTATUS deriveStatus = Interop.BCrypt.BCryptKeyDerivation( keyHandle, &bufferDesc, pDestination, destination.Length, out uint resultLength, dwFlags: 0); if (deriveStatus != NTSTATUS.STATUS_SUCCESS) { throw Interop.BCrypt.CreateCryptographicException(deriveStatus); } if (destination.Length != resultLength) { Debug.Fail("PBKDF2 resultLength != destination.Length"); throw new CryptographicException(); } } } }