// Conveniently, Encrypt() and Decrypt() are identical save for the actual P/Invoke call to CNG. Thus, both // array-based APIs invoke this common helper with the "encrypt" parameter determining whether encryption or decryption is done. private unsafe byte[] EncryptOrDecrypt(byte[] data, RSAEncryptionPadding padding, bool encrypt) { if (data == null) { throw new ArgumentNullException(nameof(data)); } if (padding == null) { throw new ArgumentNullException(nameof(padding)); } int modulusSizeInBytes = RsaPaddingProcessor.BytesRequiredForBitCount(KeySize); if (!encrypt && data.Length != modulusSizeInBytes) { throw new CryptographicException(SR.Cryptography_RSA_DecryptWrongSize); } if (encrypt && padding.Mode == RSAEncryptionPaddingMode.Pkcs1 && data.Length > modulusSizeInBytes - Pkcs1PaddingOverhead) { throw new CryptographicException( SR.Format(SR.Cryptography_Encryption_MessageTooLong, modulusSizeInBytes - Pkcs1PaddingOverhead)); } using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle()) { if (encrypt && data.Length == 0) { byte[] rented = CryptoPool.Rent(modulusSizeInBytes); Span <byte> paddedMessage = new Span <byte>(rented, 0, modulusSizeInBytes); try { if (padding == RSAEncryptionPadding.Pkcs1) { RsaPaddingProcessor.PadPkcs1Encryption(data, paddedMessage); } else if (padding.Mode == RSAEncryptionPaddingMode.Oaep) { RsaPaddingProcessor processor = RsaPaddingProcessor.OpenProcessor(padding.OaepHashAlgorithm); processor.PadOaep(data, paddedMessage); } else { throw new CryptographicException(SR.Cryptography_UnsupportedPaddingMode); } return(EncryptOrDecrypt(keyHandle, paddedMessage, AsymmetricPaddingMode.NCRYPT_NO_PADDING_FLAG, null, encrypt)); } finally { CryptographicOperations.ZeroMemory(paddedMessage); CryptoPool.Return(rented, clearSize: 0); } } switch (padding.Mode) { case RSAEncryptionPaddingMode.Pkcs1: return(EncryptOrDecrypt(keyHandle, data, AsymmetricPaddingMode.NCRYPT_PAD_PKCS1_FLAG, null, encrypt)); case RSAEncryptionPaddingMode.Oaep: IntPtr namePtr = Marshal.StringToHGlobalUni(padding.OaepHashAlgorithm.Name); try { var paddingInfo = new BCRYPT_OAEP_PADDING_INFO() { pszAlgId = namePtr, // It would nice to put randomized data here but RSAEncryptionPadding does not at this point provide support for this. pbLabel = IntPtr.Zero, cbLabel = 0, }; return(EncryptOrDecrypt(keyHandle, data, AsymmetricPaddingMode.NCRYPT_PAD_OAEP_FLAG, &paddingInfo, encrypt)); } finally { Marshal.FreeHGlobal(namePtr); } default: throw new CryptographicException(SR.Cryptography_UnsupportedPaddingMode); } } }
public override RSAParameters ExportParameters(bool includePrivateParameters) { SecKeyPair keys = GetKeys(); if (includePrivateParameters && keys.PrivateKey == null) { throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); } bool gotKeyBlob = Interop.AppleCrypto.TrySecKeyCopyExternalRepresentation( includePrivateParameters ? keys.PrivateKey ! : keys.PublicKey, out byte[] keyBlob); if (!gotKeyBlob) { return(ExportParametersFromLegacyKey(keys, includePrivateParameters)); } try { if (!includePrivateParameters) { // When exporting a key handle opened from a certificate, it seems to // export as a PKCS#1 blob instead of an X509 SubjectPublicKeyInfo blob. // So, check for that. // NOTE: It doesn't affect macOS Mojave when SecCertificateCopyKey API // is used. RSAParameters key; AsnReader reader = new AsnReader(keyBlob, AsnEncodingRules.BER); AsnReader sequenceReader = reader.ReadSequence(); if (sequenceReader.PeekTag().Equals(Asn1Tag.Integer)) { AlgorithmIdentifierAsn ignored = default; RSAKeyFormatHelper.ReadRsaPublicKey(keyBlob, ignored, out key); } else { RSAKeyFormatHelper.ReadSubjectPublicKeyInfo( keyBlob, out int localRead, out key); Debug.Assert(localRead == keyBlob.Length); } return(key); } else { AlgorithmIdentifierAsn ignored = default; RSAKeyFormatHelper.FromPkcs1PrivateKey( keyBlob, ignored, out RSAParameters key); return(key); } } finally { CryptographicOperations.ZeroMemory(keyBlob); } }
private async Task <int> ReadAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken, bool useAsync) { // read <= count bytes from the input stream, transforming as we go. // Basic idea: first we deliver any bytes we already have in the // _OutputBuffer, because we know they're good. Then, if asked to deliver // more bytes, we read & transform a block at a time until either there are // no bytes ready or we've delivered enough. int bytesToDeliver = count; int currentOutputIndex = offset; if (_outputBufferIndex != 0) { // we have some already-transformed bytes in the output buffer if (_outputBufferIndex <= count) { Buffer.BlockCopy(_outputBuffer, 0, buffer, offset, _outputBufferIndex); bytesToDeliver -= _outputBufferIndex; currentOutputIndex += _outputBufferIndex; int toClear = _outputBuffer.Length - _outputBufferIndex; CryptographicOperations.ZeroMemory(new Span <byte>(_outputBuffer, _outputBufferIndex, toClear)); _outputBufferIndex = 0; } else { Buffer.BlockCopy(_outputBuffer, 0, buffer, offset, count); Buffer.BlockCopy(_outputBuffer, count, _outputBuffer, 0, _outputBufferIndex - count); _outputBufferIndex -= count; int toClear = _outputBuffer.Length - _outputBufferIndex; CryptographicOperations.ZeroMemory(new Span <byte>(_outputBuffer, _outputBufferIndex, toClear)); return(count); } } // _finalBlockTransformed == true implies we're at the end of the input stream // if we got through the previous if block then _OutputBufferIndex = 0, meaning // we have no more transformed bytes to give // so return count-bytesToDeliver, the amount we were able to hand back // eventually, we'll just always return 0 here because there's no more to read if (_finalBlockTransformed) { return(count - bytesToDeliver); } // ok, now loop until we've delivered enough or there's nothing available int amountRead = 0; int numOutputBytes; // OK, see first if it's a multi-block transform and we can speed up things int blocksToProcess = bytesToDeliver / _outputBlockSize; if (blocksToProcess > 1 && _transform.CanTransformMultipleBlocks) { int numWholeBlocksInBytes = blocksToProcess * _inputBlockSize; byte[] tempInputBuffer = ArrayPool <byte> .Shared.Rent(numWholeBlocksInBytes); byte[] tempOutputBuffer = null; try { amountRead = useAsync ? await _stream.ReadAsync(new Memory <byte>(tempInputBuffer, _inputBufferIndex, numWholeBlocksInBytes - _inputBufferIndex), cancellationToken) : // ConfigureAwait not needed, as useAsync is only true if we're already on a TP thread _stream.Read(tempInputBuffer, _inputBufferIndex, numWholeBlocksInBytes - _inputBufferIndex); int totalInput = _inputBufferIndex + amountRead; // If there's still less than a block, copy the new data into the hold buffer and move to the slow read. if (totalInput < _inputBlockSize) { Buffer.BlockCopy(tempInputBuffer, _inputBufferIndex, _inputBuffer, _inputBufferIndex, amountRead); _inputBufferIndex = totalInput; } else { // Copy any held data into tempInputBuffer now that we know we're proceeding Buffer.BlockCopy(_inputBuffer, 0, tempInputBuffer, 0, _inputBufferIndex); CryptographicOperations.ZeroMemory(new Span <byte>(_inputBuffer, 0, _inputBufferIndex)); amountRead += _inputBufferIndex; _inputBufferIndex = 0; // Make amountRead an integral multiple of _InputBlockSize int numWholeReadBlocks = amountRead / _inputBlockSize; int numWholeReadBlocksInBytes = numWholeReadBlocks * _inputBlockSize; int numIgnoredBytes = amountRead - numWholeReadBlocksInBytes; if (numIgnoredBytes != 0) { _inputBufferIndex = numIgnoredBytes; Buffer.BlockCopy(tempInputBuffer, numWholeReadBlocksInBytes, _inputBuffer, 0, numIgnoredBytes); } tempOutputBuffer = ArrayPool <byte> .Shared.Rent(numWholeReadBlocks *_outputBlockSize); numOutputBytes = _transform.TransformBlock(tempInputBuffer, 0, numWholeReadBlocksInBytes, tempOutputBuffer, 0); Buffer.BlockCopy(tempOutputBuffer, 0, buffer, currentOutputIndex, numOutputBytes); // Clear what was written while we know how much that was CryptographicOperations.ZeroMemory(new Span <byte>(tempOutputBuffer, 0, numOutputBytes)); ArrayPool <byte> .Shared.Return(tempOutputBuffer); tempOutputBuffer = null; bytesToDeliver -= numOutputBytes; currentOutputIndex += numOutputBytes; } } finally { // If we rented and then an exception happened we don't know how much was written to, // clear the whole thing and return it. if (tempOutputBuffer != null) { CryptographicOperations.ZeroMemory(tempOutputBuffer); ArrayPool <byte> .Shared.Return(tempOutputBuffer); tempOutputBuffer = null; } CryptographicOperations.ZeroMemory(new Span <byte>(tempInputBuffer, 0, numWholeBlocksInBytes)); ArrayPool <byte> .Shared.Return(tempInputBuffer); tempInputBuffer = null; } } // try to fill _InputBuffer so we have something to transform while (bytesToDeliver > 0) { while (_inputBufferIndex < _inputBlockSize) { amountRead = useAsync ? await _stream.ReadAsync(new Memory <byte>(_inputBuffer, _inputBufferIndex, _inputBlockSize - _inputBufferIndex), cancellationToken) : // ConfigureAwait not needed, as useAsync is only true if we're already on a TP thread _stream.Read(_inputBuffer, _inputBufferIndex, _inputBlockSize - _inputBufferIndex); // first, check to see if we're at the end of the input stream if (amountRead == 0) { goto ProcessFinalBlock; } _inputBufferIndex += amountRead; } numOutputBytes = _transform.TransformBlock(_inputBuffer, 0, _inputBlockSize, _outputBuffer, 0); _inputBufferIndex = 0; if (bytesToDeliver >= numOutputBytes) { Buffer.BlockCopy(_outputBuffer, 0, buffer, currentOutputIndex, numOutputBytes); CryptographicOperations.ZeroMemory(new Span <byte>(_outputBuffer, 0, numOutputBytes)); currentOutputIndex += numOutputBytes; bytesToDeliver -= numOutputBytes; } else { Buffer.BlockCopy(_outputBuffer, 0, buffer, currentOutputIndex, bytesToDeliver); _outputBufferIndex = numOutputBytes - bytesToDeliver; Buffer.BlockCopy(_outputBuffer, bytesToDeliver, _outputBuffer, 0, _outputBufferIndex); int toClear = _outputBuffer.Length - _outputBufferIndex; CryptographicOperations.ZeroMemory(new Span <byte>(_outputBuffer, _outputBufferIndex, toClear)); return(count); } } return(count); ProcessFinalBlock: // if so, then call TransformFinalBlock to get whatever is left byte[] finalBytes = _transform.TransformFinalBlock(_inputBuffer, 0, _inputBufferIndex); // now, since _OutputBufferIndex must be 0 if we're in the while loop at this point, // reset it to be what we just got back _outputBuffer = finalBytes; _outputBufferIndex = finalBytes.Length; // set the fact that we've transformed the final block _finalBlockTransformed = true; // now, return either everything we just got or just what's asked for, whichever is smaller if (bytesToDeliver < _outputBufferIndex) { Buffer.BlockCopy(_outputBuffer, 0, buffer, currentOutputIndex, bytesToDeliver); _outputBufferIndex -= bytesToDeliver; Buffer.BlockCopy(_outputBuffer, bytesToDeliver, _outputBuffer, 0, _outputBufferIndex); int toClear = _outputBuffer.Length - _outputBufferIndex; CryptographicOperations.ZeroMemory(new Span <byte>(_outputBuffer, _outputBufferIndex, toClear)); return(count); } else { Buffer.BlockCopy(_outputBuffer, 0, buffer, currentOutputIndex, _outputBufferIndex); bytesToDeliver -= _outputBufferIndex; _outputBufferIndex = 0; CryptographicOperations.ZeroMemory(_outputBuffer); return(count - bytesToDeliver); } }
private async Task WriteAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken, bool useAsync) { // write <= count bytes to the output stream, transforming as we go. // Basic idea: using bytes in the _InputBuffer first, make whole blocks, // transform them, and write them out. Cache any remaining bytes in the _InputBuffer. int bytesToWrite = count; int currentInputIndex = offset; // if we have some bytes in the _InputBuffer, we have to deal with those first, // so let's try to make an entire block out of it if (_inputBufferIndex > 0) { if (count >= _inputBlockSize - _inputBufferIndex) { // we have enough to transform at least a block, so fill the input block Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferIndex, _inputBlockSize - _inputBufferIndex); currentInputIndex += (_inputBlockSize - _inputBufferIndex); bytesToWrite -= (_inputBlockSize - _inputBufferIndex); _inputBufferIndex = _inputBlockSize; // Transform the block and write it out } else { // not enough to transform a block, so just copy the bytes into the _InputBuffer // and return Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferIndex, count); _inputBufferIndex += count; return; } } // If the OutputBuffer has anything in it, write it out if (_outputBufferIndex > 0) { if (useAsync) { await _stream.WriteAsync(new ReadOnlyMemory <byte>(_outputBuffer, 0, _outputBufferIndex), cancellationToken); // ConfigureAwait not needed, as useAsync is only true if we're already on a TP thread } else { _stream.Write(_outputBuffer, 0, _outputBufferIndex); } _outputBufferIndex = 0; } // At this point, either the _InputBuffer is full, empty, or we've already returned. // If full, let's process it -- we now know the _OutputBuffer is empty int numOutputBytes; if (_inputBufferIndex == _inputBlockSize) { numOutputBytes = _transform.TransformBlock(_inputBuffer, 0, _inputBlockSize, _outputBuffer, 0); // write out the bytes we just got if (useAsync) { await _stream.WriteAsync(new ReadOnlyMemory <byte>(_outputBuffer, 0, numOutputBytes), cancellationToken); // ConfigureAwait not needed, as useAsync is only true if we're already on a TP thread } else { _stream.Write(_outputBuffer, 0, numOutputBytes); } // reset the _InputBuffer _inputBufferIndex = 0; } while (bytesToWrite > 0) { if (bytesToWrite >= _inputBlockSize) { // We have at least an entire block's worth to transform int numWholeBlocks = bytesToWrite / _inputBlockSize; // If the transform will handle multiple blocks at once, do that if (_transform.CanTransformMultipleBlocks && numWholeBlocks > 1) { int numWholeBlocksInBytes = numWholeBlocks * _inputBlockSize; byte[] tempOutputBuffer = ArrayPool <byte> .Shared.Rent(numWholeBlocks *_outputBlockSize); numOutputBytes = 0; try { numOutputBytes = _transform.TransformBlock(buffer, currentInputIndex, numWholeBlocksInBytes, tempOutputBuffer, 0); if (useAsync) { await _stream.WriteAsync(new ReadOnlyMemory <byte>(tempOutputBuffer, 0, numOutputBytes), cancellationToken); // ConfigureAwait not needed, as useAsync is only true if we're already on a TP thread } else { _stream.Write(tempOutputBuffer, 0, numOutputBytes); } currentInputIndex += numWholeBlocksInBytes; bytesToWrite -= numWholeBlocksInBytes; } finally { CryptographicOperations.ZeroMemory(new Span <byte>(tempOutputBuffer, 0, numOutputBytes)); ArrayPool <byte> .Shared.Return(tempOutputBuffer); tempOutputBuffer = null; } } else { // do it the slow way numOutputBytes = _transform.TransformBlock(buffer, currentInputIndex, _inputBlockSize, _outputBuffer, 0); if (useAsync) { await _stream.WriteAsync(new ReadOnlyMemory <byte>(_outputBuffer, 0, numOutputBytes), cancellationToken); // ConfigureAwait not needed, as useAsync is only true if we're already on a TP thread } else { _stream.Write(_outputBuffer, 0, numOutputBytes); } currentInputIndex += _inputBlockSize; bytesToWrite -= _inputBlockSize; } } else { // In this case, we don't have an entire block's worth left, so store it up in the // input buffer, which by now must be empty. Buffer.BlockCopy(buffer, currentInputIndex, _inputBuffer, 0, bytesToWrite); _inputBufferIndex += bytesToWrite; return; } } return; }
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. Span <byte> clearSpan = stackalloc byte[0]; ReadOnlySpan <byte> symmetricKeyMaterial = stackalloc byte[0]; 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(); } } } }
public override bool TryDecrypt( ReadOnlySpan <byte> data, Span <byte> destination, RSAEncryptionPadding padding, out int bytesWritten) { if (padding == null) { throw new ArgumentNullException(nameof(padding)); } ValidatePadding(padding); SafeEvpPKeyHandle key = GetKey(); int keySizeBytes = Interop.Crypto.EvpPKeySize(key); // OpenSSL requires that the decryption buffer be at least as large as EVP_PKEY_size. // So if the destination is too small, use a temporary buffer so we can match // Windows behavior of succeeding so long as the buffer can hold the final output. if (destination.Length < keySizeBytes) { // RSA up through 4096 bits use a stackalloc Span <byte> tmp = stackalloc byte[512]; byte[]? rent = null; if (keySizeBytes > tmp.Length) { rent = CryptoPool.Rent(keySizeBytes); tmp = rent; } int written = Decrypt(key, data, tmp, padding); bool ret; if (destination.Length < written) { bytesWritten = 0; ret = false; } else { tmp.Slice(0, written).CopyTo(destination); bytesWritten = written; ret = true; } // Whether a stackalloc or a rented array, clear our copy of // the decrypted content. CryptographicOperations.ZeroMemory(tmp.Slice(0, written)); if (rent != null) { // Already cleared. CryptoPool.Return(rent, clearSize: 0); } return(ret); } bytesWritten = Decrypt(key, data, destination, padding); return(true); }
void ILiteSymmetricCipher.Reset(ReadOnlySpan <byte> iv) => throw new NotImplementedException(); // never invoked private void Reset() { CryptographicOperations.ZeroMemory(_lastBlockBuffer); _lastBlockBuffer = null; }
public override bool VerifyHash(ReadOnlySpan <byte> hash, ReadOnlySpan <byte> signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) { if (string.IsNullOrEmpty(hashAlgorithm.Name)) { throw HashAlgorithmNameNullOrEmpty(); } if (padding == null) { throw new ArgumentNullException(nameof(padding)); } ThrowIfDisposed(); if (padding == RSASignaturePadding.Pkcs1) { Interop.AppleCrypto.PAL_HashAlgorithm palAlgId = PalAlgorithmFromAlgorithmName(hashAlgorithm, out int expectedSize); return(Interop.AppleCrypto.VerifySignature(GetKeys().PublicKey, hash, signature, palAlgId)); } else if (padding.Mode == RSASignaturePaddingMode.Pss) { RsaPaddingProcessor processor = RsaPaddingProcessor.OpenProcessor(hashAlgorithm); SafeSecKeyRefHandle publicKey = GetKeys().PublicKey; int keySize = KeySize; int rsaSize = RsaPaddingProcessor.BytesRequiredForBitCount(keySize); if (signature.Length != rsaSize) { return(false); } if (hash.Length != processor.HashLength) { return(false); } byte[] rented = CryptoPool.Rent(rsaSize); Span <byte> unwrapped = new Span <byte>(rented, 0, rsaSize); try { if (!Interop.AppleCrypto.TryRsaVerificationPrimitive( publicKey, signature, unwrapped, out int bytesWritten)) { Debug.Fail($"TryRsaVerificationPrimitive with a pre-allocated buffer"); throw new CryptographicException(); } Debug.Assert(bytesWritten == rsaSize); return(processor.VerifyPss(hash, unwrapped, keySize)); } finally { CryptographicOperations.ZeroMemory(unwrapped); CryptoPool.Return(rented, clearSize: 0); } } throw new CryptographicException(SR.Cryptography_InvalidPaddingMode); }
internal void PadOaep( ReadOnlySpan <byte> source, Span <byte> destination) { // https://tools.ietf.org/html/rfc3447#section-7.1.1 byte[]? dbMask = null; Span <byte> dbMaskSpan = Span <byte> .Empty; try { // Since the biggest known _hLen is 512/8 (64) and destination.Length is 0 or more, // this shouldn't underflow without something having severely gone wrong. int maxInput = checked (destination.Length - _hLen - _hLen - 2); // 1(a) does not apply, we do not allow custom label values. // 1(b) if (source.Length > maxInput) { throw new CryptographicException( SR.Format(SR.Cryptography_Encryption_MessageTooLong, maxInput)); } // The final message (step 2(i)) will be // 0x00 || maskedSeed (hLen long) || maskedDB (rest of the buffer) Span <byte> seed = destination.Slice(1, _hLen); Span <byte> db = destination.Slice(1 + _hLen); using (IncrementalHash hasher = IncrementalHash.CreateHash(_hashAlgorithmName)) { // DB = lHash || PS || 0x01 || M Span <byte> lHash = db.Slice(0, _hLen); Span <byte> mDest = db.Slice(db.Length - source.Length); Span <byte> ps = db.Slice(_hLen, db.Length - _hLen - 1 - mDest.Length); Span <byte> psEnd = db.Slice(_hLen + ps.Length, 1); // 2(a) lHash = Hash(L), where L is the empty string. if (!hasher.TryGetHashAndReset(lHash, out int hLen2) || hLen2 != _hLen) { Debug.Fail("TryGetHashAndReset failed with exact-size destination"); throw new CryptographicException(); } // 2(b) generate a padding string of all zeros equal to the amount of unused space. ps.Clear(); // 2(c) psEnd[0] = 0x01; // still 2(c) source.CopyTo(mDest); // 2(d) RandomNumberGenerator.Fill(seed); // 2(e) dbMask = CryptoPool.Rent(db.Length); dbMaskSpan = new Span <byte>(dbMask, 0, db.Length); Mgf1(hasher, seed, dbMaskSpan); // 2(f) Xor(db, dbMaskSpan); // 2(g) Span <byte> seedMask = stackalloc byte[_hLen]; Mgf1(hasher, db, seedMask); // 2(h) Xor(seed, seedMask); // 2(i) destination[0] = 0; } } catch (Exception e) when(!(e is CryptographicException)) { Debug.Fail("Bad exception produced from OAEP padding: " + e); throw new CryptographicException(); } finally { if (dbMask != null) { CryptographicOperations.ZeroMemory(dbMaskSpan); CryptoPool.Return(dbMask, clearSize: 0); } } }
private static bool TryDecrypt( SafeRsaHandle key, ReadOnlySpan <byte> data, Span <byte> destination, Interop.Crypto.RsaPadding rsaPadding, RsaPaddingProcessor rsaPaddingProcessor, out int bytesWritten) { // If rsaPadding is PKCS1 or OAEP-SHA1 then no depadding method should be present. // If rsaPadding is NoPadding then a depadding method should be present. Debug.Assert( (rsaPadding == Interop.Crypto.RsaPadding.NoPadding) == (rsaPaddingProcessor != null)); // Caller should have already checked this. Debug.Assert(!key.IsInvalid); int rsaSize = Interop.Crypto.RsaSize(key); if (data.Length != rsaSize) { throw new CryptographicException(SR.Cryptography_RSA_DecryptWrongSize); } if (destination.Length < rsaSize) { bytesWritten = 0; return(false); } Span <byte> decryptBuf = destination; byte[] paddingBuf = null; if (rsaPaddingProcessor != null) { paddingBuf = ArrayPool <byte> .Shared.Rent(rsaSize); decryptBuf = paddingBuf; } try { int returnValue = Interop.Crypto.RsaPrivateDecrypt(data.Length, data, decryptBuf, key, rsaPadding); CheckReturn(returnValue); if (rsaPaddingProcessor != null) { return(rsaPaddingProcessor.DepadOaep(paddingBuf, destination, out bytesWritten)); } else { // If the padding mode is RSA_NO_PADDING then the size of the decrypted block // will be RSA_size. If any padding was used, then some amount (determined by the padding algorithm) // will have been reduced, and only returnValue bytes were part of the decrypted // body. Either way, we can just use returnValue, but some additional bytes may have been overwritten // in the destination span. bytesWritten = returnValue; } return(true); } finally { if (paddingBuf != null) { // DecryptBuf is paddingBuf if paddingBuf is not null, erase it before returning it. // If paddingBuf IS null then decryptBuf was destination, and shouldn't be cleared. CryptographicOperations.ZeroMemory(decryptBuf); ArrayPool <byte> .Shared.Return(paddingBuf); } } }
internal bool VerifyPss(ReadOnlySpan <byte> mHash, ReadOnlySpan <byte> em, int keySize) { // https://tools.ietf.org/html/rfc3447#section-9.1.2 int emBits = keySize - 1; int emLen = BytesRequiredForBitCount(emBits); if (mHash.Length != _hLen) { return(false); } Debug.Assert(em.Length >= emLen); // In this implementation, sLen is restricted to hLen. int sLen = _hLen; // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. if (emLen < _hLen + sLen + 2) { return(false); } // 4. If the last byte is not 0xBC, output "inconsistent" and stop. if (em[em.Length - 1] != 0xBC) { return(false); } // 5. maskedDB is the leftmost emLen - hLen -1 bytes, H is the next hLen bytes. int dbLen = emLen - _hLen - 1; ReadOnlySpan <byte> maskedDb = em.Slice(0, dbLen); ReadOnlySpan <byte> h = em.Slice(dbLen, _hLen); // 6. If the unused bits aren't zero, output "inconsistent" and stop. int unusedBits = 8 * emLen - emBits; byte usedBitsMask = (byte)(0xFF >> unusedBits); if ((maskedDb[0] & usedBitsMask) != maskedDb[0]) { return(false); } // 7. dbMask = MGF(H, emLen - hLen - 1) byte[] dbMaskRented = CryptoPool.Rent(maskedDb.Length); Span <byte> dbMask = new Span <byte>(dbMaskRented, 0, maskedDb.Length); try { using (IncrementalHash hasher = IncrementalHash.CreateHash(_hashAlgorithmName)) { Mgf1(hasher, h, dbMask); // 8. DB = maskedDB XOR dbMask Xor(dbMask, maskedDb); // 9. Set the unused bits of DB to 0 dbMask[0] &= usedBitsMask; // 10 ("a"): If the emLen - hLen - sLen - 2 leftmost bytes are not 0, // output "inconsistent" and stop. // // Since signature verification is a public key operation there's no need to // use fixed time equality checking here. for (int i = emLen - _hLen - sLen - 2 - 1; i >= 0; --i) { if (dbMask[i] != 0) { return(false); } } // 10 ("b") If the octet at position emLen - hLen - sLen - 1 (under a 1-indexed scheme) // is not 0x01, output "inconsistent" and stop. if (dbMask[emLen - _hLen - sLen - 2] != 0x01) { return(false); } // 11. Let salt be the last sLen octets of DB. ReadOnlySpan <byte> salt = dbMask.Slice(dbMask.Length - sLen); // 12/13. Let H' = Hash(eight zeros || mHash || salt) hasher.AppendData(EightZeros); hasher.AppendData(mHash); hasher.AppendData(salt); Span <byte> hPrime = stackalloc byte[_hLen]; if (!hasher.TryGetHashAndReset(hPrime, out int hLen2) || hLen2 != _hLen) { Debug.Fail("TryGetHashAndReset failed with exact-size destination"); throw new CryptographicException(); } // 14. If H = H' output "consistent". Otherwise, output "inconsistent" // // Since this is a public key operation, no need to provide fixed time // checking. return(h.SequenceEqual(hPrime)); } } finally { CryptographicOperations.ZeroMemory(dbMask); CryptoPool.Return(dbMaskRented, clearSize: 0); } }
internal void EncodePss(ReadOnlySpan <byte> mHash, Span <byte> destination, int keySize) { // https://tools.ietf.org/html/rfc3447#section-9.1.1 int emBits = keySize - 1; int emLen = BytesRequiredForBitCount(emBits); if (mHash.Length != _hLen) { throw new CryptographicException(SR.Cryptography_SignHash_WrongSize); } // In this implementation, sLen is restricted to the length of the input hash. int sLen = _hLen; // 3. if emLen < hLen + sLen + 2, encoding error. // // sLen = hLen in this implementation. if (emLen < 2 + _hLen + sLen) { throw new CryptographicException(SR.Cryptography_KeyTooSmall); } // Set any leading bytes to zero, since that will be required for the pending // RSA operation. destination.Slice(0, destination.Length - emLen).Clear(); // 12. Let EM = maskedDB || H || 0xbc (H has length hLen) Span <byte> em = destination.Slice(destination.Length - emLen, emLen); int dbLen = emLen - _hLen - 1; Span <byte> db = em.Slice(0, dbLen); Span <byte> hDest = em.Slice(dbLen, _hLen); em[emLen - 1] = 0xBC; byte[] dbMaskRented = CryptoPool.Rent(dbLen); Span <byte> dbMask = new Span <byte>(dbMaskRented, 0, dbLen); using (IncrementalHash hasher = IncrementalHash.CreateHash(_hashAlgorithmName)) { // 4. Generate a random salt of length sLen Span <byte> salt = stackalloc byte[sLen]; RandomNumberGenerator.Fill(salt); // 5. Let M' = an octet string of 8 zeros concat mHash concat salt // 6. Let H = Hash(M') hasher.AppendData(EightZeros); hasher.AppendData(mHash); hasher.AppendData(salt); if (!hasher.TryGetHashAndReset(hDest, out int hLen2) || hLen2 != _hLen) { Debug.Fail("TryGetHashAndReset failed with exact-size destination"); throw new CryptographicException(); } // 7. Generate PS as zero-valued bytes of length emLen - sLen - hLen - 2. // 8. Let DB = PS || 0x01 || salt int psLen = emLen - sLen - _hLen - 2; db.Slice(0, psLen).Clear(); db[psLen] = 0x01; salt.CopyTo(db.Slice(psLen + 1)); // 9. Let dbMask = MGF(H, emLen - hLen - 1) Mgf1(hasher, hDest, dbMask); // 10. Let maskedDB = DB XOR dbMask Xor(db, dbMask); // 11. Set the "unused" bits in the leftmost byte of maskedDB to 0. int unusedBits = 8 * emLen - emBits; if (unusedBits != 0) { byte mask = (byte)(0xFF >> unusedBits); db[0] &= mask; } } CryptographicOperations.ZeroMemory(dbMask); CryptoPool.Return(dbMaskRented, clearSize: 0); }
internal static unsafe Pkcs8Response ImportEncryptedPkcs8PrivateKey( ReadOnlySpan <char> password, ReadOnlySpan <byte> source, out int bytesRead) { try { AsnDecoder.ReadEncodedValue( source, AsnEncodingRules.BER, out _, out _, out int len); source = source.Slice(0, len); fixed(byte *ptr = &MemoryMarshal.GetReference(source)) { using (MemoryManager <byte> manager = new PointerMemoryManager <byte>(ptr, source.Length)) { try { bytesRead = len; return(ImportPkcs8(source, password)); } catch (CryptographicException) { } ArraySegment <byte> decrypted = KeyFormatHelper.DecryptPkcs8( password, manager.Memory.Slice(0, len), out int innerRead); Span <byte> decryptedSpan = decrypted; try { if (innerRead != len) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } bytesRead = len; return(ImportPkcs8(decryptedSpan)); } catch (CryptographicException e) { AsnWriter?pkcs8ZeroPublicKey = RewritePkcs8ECPrivateKeyWithZeroPublicKey(decryptedSpan); if (pkcs8ZeroPublicKey == null) { throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); } try { bytesRead = len; return(ImportPkcs8(pkcs8ZeroPublicKey)); } catch (CryptographicException) { throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e); } } finally { CryptographicOperations.ZeroMemory(decryptedSpan); CryptoPool.Return(decrypted.Array !, clearSize: 0); } } } } catch (AsnContentException e) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } }
public override bool TryEncrypt(ReadOnlySpan <byte> data, Span <byte> destination, RSAEncryptionPadding padding, out int bytesWritten) { if (padding == null) { throw new ArgumentNullException(nameof(padding)); } ThrowIfDisposed(); int rsaSize = RsaPaddingProcessor.BytesRequiredForBitCount(KeySize); if (destination.Length < rsaSize) { bytesWritten = 0; return(false); } if (padding == RSAEncryptionPadding.Pkcs1 && data.Length > 0) { const int Pkcs1PaddingOverhead = 11; int maxAllowed = rsaSize - Pkcs1PaddingOverhead; if (data.Length > maxAllowed) { throw new CryptographicException( SR.Format(SR.Cryptography_Encryption_MessageTooLong, maxAllowed)); } return(Interop.AppleCrypto.TryRsaEncrypt( GetKeys().PublicKey, data, destination, padding, out bytesWritten)); } RsaPaddingProcessor processor; switch (padding.Mode) { case RSAEncryptionPaddingMode.Pkcs1: processor = null; break; case RSAEncryptionPaddingMode.Oaep: processor = RsaPaddingProcessor.OpenProcessor(padding.OaepHashAlgorithm); break; default: throw new CryptographicException(SR.Cryptography_InvalidPaddingMode); } byte[] rented = CryptoPool.Rent(rsaSize); Span <byte> tmp = new Span <byte>(rented, 0, rsaSize); try { if (processor != null) { processor.PadOaep(data, tmp); } else { Debug.Assert(padding.Mode == RSAEncryptionPaddingMode.Pkcs1); RsaPaddingProcessor.PadPkcs1Encryption(data, tmp); } return(Interop.AppleCrypto.TryRsaEncryptionPrimitive( GetKeys().PublicKey, tmp, destination, out bytesWritten)); } finally { CryptographicOperations.ZeroMemory(tmp); CryptoPool.Return(rented, clearSize: 0); } }
public override bool TryDecrypt( ReadOnlySpan <byte> data, Span <byte> destination, RSAEncryptionPadding padding, out int bytesWritten) { ArgumentNullException.ThrowIfNull(padding); Interop.AndroidCrypto.RsaPadding rsaPadding = GetInteropPadding(padding); SafeRsaHandle key = GetKey(); int keySizeBytes = Interop.AndroidCrypto.RsaSize(key); // Android does not take a length value for the destination, so it can write out of bounds. // To prevent the OOB write, decrypt into a temporary buffer. if (destination.Length < keySizeBytes) { scoped Span <byte> tmp; byte[]? rent = null; // RSA up through 4096 stackalloc if (keySizeBytes <= 512) { tmp = stackalloc byte[keySizeBytes]; } else { rent = ArrayPool <byte> .Shared.Rent(keySizeBytes); tmp = rent; } bool ret = TryDecrypt(key, data, tmp, rsaPadding, out bytesWritten); if (ret) { tmp = tmp.Slice(0, bytesWritten); if (bytesWritten > destination.Length) { ret = false; bytesWritten = 0; } else { tmp.CopyTo(destination); } CryptographicOperations.ZeroMemory(tmp); } if (rent != null) { // Already cleared ArrayPool <byte> .Shared.Return(rent); } return(ret); } return(TryDecrypt(key, data, destination, rsaPadding, out bytesWritten)); }
public override bool TrySignHash(ReadOnlySpan <byte> hash, Span <byte> destination, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding, out int bytesWritten) { if (string.IsNullOrEmpty(hashAlgorithm.Name)) { throw HashAlgorithmNameNullOrEmpty(); } if (padding == null) { throw new ArgumentNullException(nameof(padding)); } ThrowIfDisposed(); RsaPaddingProcessor processor = null; if (padding.Mode == RSASignaturePaddingMode.Pss) { processor = RsaPaddingProcessor.OpenProcessor(hashAlgorithm); } else if (padding != RSASignaturePadding.Pkcs1) { throw new CryptographicException(SR.Cryptography_InvalidPaddingMode); } SecKeyPair keys = GetKeys(); if (keys.PrivateKey == null) { throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); } int keySize = KeySize; int rsaSize = RsaPaddingProcessor.BytesRequiredForBitCount(keySize); if (processor == null) { Interop.AppleCrypto.PAL_HashAlgorithm palAlgId = PalAlgorithmFromAlgorithmName(hashAlgorithm, out int expectedSize); if (hash.Length != expectedSize) { // Windows: NTE_BAD_DATA ("Bad Data.") // OpenSSL: RSA_R_INVALID_MESSAGE_LENGTH ("invalid message length") throw new CryptographicException( SR.Format( SR.Cryptography_BadHashSize_ForAlgorithm, hash.Length, expectedSize, hashAlgorithm.Name)); } if (destination.Length < rsaSize) { bytesWritten = 0; return(false); } return(Interop.AppleCrypto.TryGenerateSignature( keys.PrivateKey, hash, destination, palAlgId, out bytesWritten)); } Debug.Assert(padding.Mode == RSASignaturePaddingMode.Pss); if (destination.Length < rsaSize) { bytesWritten = 0; return(false); } byte[] rented = CryptoPool.Rent(rsaSize); Span <byte> buf = new Span <byte>(rented, 0, rsaSize); processor.EncodePss(hash, buf, keySize); try { return(Interop.AppleCrypto.TryRsaSignaturePrimitive(keys.PrivateKey, buf, destination, out bytesWritten)); } finally { CryptographicOperations.ZeroMemory(buf); CryptoPool.Return(rented, clearSize: 0); } }
internal static unsafe bool ExportPkcs8KeyBlob( bool allocate, SafeNCryptKeyHandle keyHandle, ReadOnlySpan <char> password, int kdfCount, Span <byte> destination, out int bytesWritten, out byte[] allocated) { using (SafeUnicodeStringHandle stringHandle = new SafeUnicodeStringHandle(password)) { fixed(byte *oidPtr = s_pkcs12TripleDesOidBytes) { Interop.NCrypt.NCryptBuffer *buffers = stackalloc Interop.NCrypt.NCryptBuffer[3]; Interop.NCrypt.PBE_PARAMS pbeParams = default; Span <byte> salt = new Span <byte>(pbeParams.rgbSalt, Interop.NCrypt.PBE_PARAMS.RgbSaltSize); RandomNumberGenerator.Fill(salt); pbeParams.Params.cbSalt = salt.Length; pbeParams.Params.iIterations = kdfCount; buffers[0] = new Interop.NCrypt.NCryptBuffer { BufferType = Interop.NCrypt.BufferType.PkcsSecret, cbBuffer = checked (2 * (password.Length + 1)), pvBuffer = stringHandle.DangerousGetHandle(), }; if (buffers[0].pvBuffer == IntPtr.Zero) { buffers[0].cbBuffer = 0; } buffers[1] = new Interop.NCrypt.NCryptBuffer { BufferType = Interop.NCrypt.BufferType.PkcsAlgOid, cbBuffer = s_pkcs12TripleDesOidBytes.Length, pvBuffer = (IntPtr)oidPtr, }; buffers[2] = new Interop.NCrypt.NCryptBuffer { BufferType = Interop.NCrypt.BufferType.PkcsAlgParam, cbBuffer = sizeof(Interop.NCrypt.PBE_PARAMS), pvBuffer = (IntPtr)(&pbeParams), }; Interop.NCrypt.NCryptBufferDesc desc = new Interop.NCrypt.NCryptBufferDesc { cBuffers = 3, pBuffers = (IntPtr)buffers, ulVersion = 0, }; Span <byte> empty = default; ErrorCode errorCode = Interop.NCrypt.NCryptExportKey( keyHandle, IntPtr.Zero, Interop.NCrypt.NCRYPT_PKCS8_PRIVATE_KEY_BLOB, ref desc, ref MemoryMarshal.GetReference(empty), 0, out int numBytesNeeded, 0); if (errorCode != ErrorCode.ERROR_SUCCESS) { throw errorCode.ToCryptographicException(); } allocated = null; if (allocate) { allocated = new byte[numBytesNeeded]; destination = allocated; } else if (numBytesNeeded > destination.Length) { bytesWritten = 0; return(false); } errorCode = Interop.NCrypt.NCryptExportKey( keyHandle, IntPtr.Zero, Interop.NCrypt.NCRYPT_PKCS8_PRIVATE_KEY_BLOB, ref desc, ref MemoryMarshal.GetReference(destination), destination.Length, out numBytesNeeded, 0); if (errorCode != ErrorCode.ERROR_SUCCESS) { throw errorCode.ToCryptographicException(); } if (allocate && numBytesNeeded != destination.Length) { byte[] trimmed = new byte[numBytesNeeded]; destination.Slice(0, numBytesNeeded).CopyTo(trimmed); CryptographicOperations.ZeroMemory(allocated.AsSpan(0, numBytesNeeded)); allocated = trimmed; } bytesWritten = numBytesNeeded; return(true); } } }
public override RSAParameters ExportParameters(bool includePrivateParameters) { // Apple requires all private keys to be exported encrypted, but since we're trying to export // as parsed structures we will need to decrypt it for the user. const string ExportPassword = "******"; SecKeyPair keys = GetKeys(); if (includePrivateParameters && keys.PrivateKey == null) { throw new CryptographicException(SR.Cryptography_OpenInvalidHandle); } byte[] keyBlob = Interop.AppleCrypto.SecKeyExport( includePrivateParameters ? keys.PrivateKey : keys.PublicKey, exportPrivate: includePrivateParameters, password: ExportPassword); try { if (!includePrivateParameters) { // When exporting a key handle opened from a certificate, it seems to // export as a PKCS#1 blob instead of an X509 SubjectPublicKeyInfo blob. // So, check for that. // NOTE: It doesn't affect macOS Mojave when SecCertificateCopyKey API // is used. RSAParameters key; AsnReader reader = new AsnReader(keyBlob, AsnEncodingRules.BER); AsnReader sequenceReader = reader.ReadSequence(); if (sequenceReader.PeekTag().Equals(Asn1Tag.Integer)) { AlgorithmIdentifierAsn ignored = default; RSAKeyFormatHelper.ReadRsaPublicKey(keyBlob, ignored, out key); } else { RSAKeyFormatHelper.ReadSubjectPublicKeyInfo( keyBlob, out int localRead, out key); Debug.Assert(localRead == keyBlob.Length); } return(key); } else { RSAKeyFormatHelper.ReadEncryptedPkcs8( keyBlob, ExportPassword, out int localRead, out RSAParameters key); return(key); } } finally { CryptographicOperations.ZeroMemory(keyBlob); } }
public void Dispose() { CryptographicOperations.ZeroMemory(_key); }