示例#1
0
        // '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");
        }
示例#2
0
        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);
                    }
                }
            }
        }
示例#4
0
        public SecureLocalAllocHandle Duplicate()
        {
            SecureLocalAllocHandle duplicateHandle = Allocate(_cb);

            UnsafeBufferUtil.BlockCopy(from: this, to: duplicateHandle, length: _cb);
            return(duplicateHandle);
        }
示例#5
0
        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);
        }
示例#6
0
        /// <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);
                    }
                }
            }
        }
示例#7
0
        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);
                }
            }
        }
示例#8
0
        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));
            }
        }
示例#9
0
        /// <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);
                    }
                }
            }
        }
示例#10
0
    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);
            }
        }
    }
示例#11
0
    public void SecureZeroMemory_IntPtrLength()
    {
        // Arrange
        long x = 0x0123456789ABCDEF;

        // Act
        UnsafeBufferUtil.SecureZeroMemory((byte *)&x, length: (IntPtr)sizeof(long));

        // Assert
        Assert.Equal(0, x);
    }
示例#12
0
    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;
                }
            }
        }
示例#14
0
    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);
    }
示例#15
0
    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);
        }
    }
示例#16
0
    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);
            }
        }
    }
示例#17
0
    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();
                }
            }
        }
    }
示例#18
0
    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);
                }
            }
        }
示例#20
0
    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);
    }
示例#21
0
    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);
    }
示例#22
0
        // 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);
                    }
                }
            }
        }
示例#23
0
    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);
            }
        }
    }
示例#24
0
    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);
            }
        }
    }
示例#25
0
 // 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());
 }
示例#26
0
    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);
    }
示例#27
0
        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);
            }
        }
示例#28
0
        // '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);
            }
        }
示例#29
0
        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);
            }
        }