private static SafeSecKeyRefHandle ImportKey(RSAParameters parameters) { AsnWriter keyWriter; bool hasPrivateKey; if (parameters.D != null) { keyWriter = RSAKeyFormatHelper.WritePkcs1PrivateKey(parameters); hasPrivateKey = true; } else { keyWriter = RSAKeyFormatHelper.WritePkcs1PublicKey(parameters); hasPrivateKey = false; } byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); if (!keyWriter.TryEncode(rented, out int written)) { Debug.Fail("TryEncode failed with a pre-allocated buffer"); throw new InvalidOperationException(); } // Explicitly clear the inner buffer keyWriter.Reset(); try { return(Interop.AppleCrypto.CreateDataKey( rented.AsSpan(0, written), Interop.AppleCrypto.PAL_KeyAlgorithm.RSA, isPublic: !hasPrivateKey)); } finally { CryptoPool.Return(rented, written); } }
public virtual bool VerifyData(ReadOnlySpan <byte> data, ReadOnlySpan <byte> signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); for (int i = 256; ; i = checked (i * 2)) { int hashLength = 0; byte[] hash = CryptoPool.Rent(i); try { if (TryHashData(data, hash, hashAlgorithm, out hashLength)) { return(VerifyHash(new ReadOnlySpan <byte>(hash, 0, hashLength), signature, hashAlgorithm, padding)); } } finally { CryptoPool.Return(hash, hashLength); } } }
public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) { if (data == null) { throw new ArgumentNullException(nameof(data)); } if (padding == null) { throw new ArgumentNullException(nameof(padding)); } Interop.Crypto.RsaPadding rsaPadding = GetInteropPadding(padding, out RsaPaddingProcessor? oaepProcessor); SafeRsaHandle key = GetKey(); int rsaSize = Interop.Crypto.RsaSize(key); byte[]? buf = null; Span <byte> destination = default; try { buf = CryptoPool.Rent(rsaSize); destination = new Span <byte>(buf, 0, rsaSize); if (!TryDecrypt(key, data, destination, rsaPadding, oaepProcessor, out int bytesWritten)) { Debug.Fail($"{nameof(TryDecrypt)} should not return false for RSA_size buffer"); throw new CryptographicException(); } return(destination.Slice(0, bytesWritten).ToArray()); } finally { CryptographicOperations.ZeroMemory(destination); CryptoPool.Return(buf !, clearSize: 0); } }
public int TransformFinal(ReadOnlySpan <byte> input, Span <byte> output) { #if DEBUG if (_isFinalized) { Debug.Fail("Cipher was reused without being reset."); throw new CryptographicException(); } _isFinalized = true; #endif Debug.Assert((input.Length % PaddingSizeInBytes) == 0); Debug.Assert(input.Length <= output.Length); int written = 0; if (input.Overlaps(output, out int offset) && offset != 0) { byte[] rented = CryptoPool.Rent(output.Length); try { written = ProcessFinalBlock(input, rented); rented.AsSpan(0, written).CopyTo(output); } finally { CryptoPool.Return(rented, clearSize: written); } } else { written = ProcessFinalBlock(input, output); } return(written); }
public override unsafe bool TryExportSubjectPublicKeyInfo(Span <byte> destination, out int bytesWritten) { // The PKCS1 RSAPublicKey format is just the modulus (KeySize bits) and Exponent (usually 3 bytes), // with each field having up to 7 bytes of overhead and then up to 6 extra bytes of overhead for the // SEQUENCE tag. // // So KeySize / 4 is ideally enough to start. int rentSize = KeySize / 4; while (true) { byte[] rented = CryptoPool.Rent(rentSize); rentSize = rented.Length; int pkcs1Size = 0; fixed(byte *rentPtr = rented) { try { if (!TryExportRSAPublicKey(rented, out pkcs1Size)) { rentSize = checked (rentSize * 2); continue; } using (AsnWriter writer = RSAKeyFormatHelper.WriteSubjectPublicKeyInfo(rented.AsSpan(0, pkcs1Size))) { return(writer.TryEncode(destination, out bytesWritten)); } } finally { CryptoPool.Return(rented, pkcs1Size); } } } }
private static SafeSecKeyRefHandle ImportKey(ECParameters parameters) { AsnWriter keyWriter; bool hasPrivateKey; if (parameters.D != null) { keyWriter = EccKeyFormatHelper.WriteECPrivateKey(parameters); hasPrivateKey = true; } else { keyWriter = EccKeyFormatHelper.WriteSubjectPublicKeyInfo(parameters); hasPrivateKey = false; } byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); if (!keyWriter.TryEncode(rented, out int written)) { Debug.Fail("TryEncode failed with a pre-allocated buffer"); throw new InvalidOperationException(); } // Explicitly clear the inner buffer keyWriter.Reset(); try { return(Interop.AppleCrypto.ImportEphemeralKey(rented.AsSpan(0, written), hasPrivateKey)); } finally { CryptoPool.Return(rented, written); } }
public static unsafe bool TryExportToEncryptedPem <T>( T arg, ReadOnlySpan <char> password, PbeParameters pbeParameters, TryExportEncryptedKeyAction <T> exporter, Span <char> destination, out int charsWritten) { int bufferSize = 4096; while (true) { byte[] buffer = CryptoPool.Rent(bufferSize); int bytesWritten = 0; bufferSize = buffer.Length; // Fixed to prevent GC moves. fixed(byte *bufferPtr = buffer) { try { if (exporter(arg, password, pbeParameters, buffer, out bytesWritten)) { Span <byte> writtenSpan = new Span <byte>(buffer, 0, bytesWritten); return(PemEncoding.TryWrite(PemLabels.EncryptedPkcs8PrivateKey, writtenSpan, destination, out charsWritten)); } } finally { CryptoPool.Return(buffer, bytesWritten); } bufferSize = checked (bufferSize * 2); } } }
private static SafeSecKeyRefHandle ImportLegacyPrivateKey(ref ECParameters parameters) { AsnWriter keyWriter = EccKeyFormatHelper.WriteECPrivateKey(parameters); byte[] rented = CryptoPool.Rent(keyWriter.GetEncodedLength()); if (!keyWriter.TryEncode(rented, out int written)) { Debug.Fail("TryEncode failed with a pre-allocated buffer"); throw new InvalidOperationException(); } // Explicitly clear the inner buffer keyWriter.Reset(); try { return(Interop.AppleCrypto.ImportEphemeralKey(rented.AsSpan(0, written), true)); } finally { CryptoPool.Return(rented, written); } }
private bool TrySignHash( ReadOnlySpan <byte> hash, Span <byte> destination, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding, bool allocateSignature, out int bytesWritten, out byte[]?signature) { Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name)); Debug.Assert(padding != null); signature = null; if (padding == RSASignaturePadding.Pkcs1 && padding == RSASignaturePadding.Pss) { throw PaddingModeNotSupported(); } RsaPaddingProcessor processor = RsaPaddingProcessor.OpenProcessor(hashAlgorithm); SafeRsaHandle rsa = GetKey(); int bytesRequired = Interop.AndroidCrypto.RsaSize(rsa); if (allocateSignature) { Debug.Assert(destination.Length == 0); signature = new byte[bytesRequired]; destination = signature; } if (destination.Length < bytesRequired) { bytesWritten = 0; return(false); } byte[] encodedRented = CryptoPool.Rent(bytesRequired); Span <byte> encodedBytes = new Span <byte>(encodedRented, 0, bytesRequired); if (padding.Mode == RSASignaturePaddingMode.Pkcs1) { processor.PadPkcs1Signature(hash, encodedBytes); } else if (padding.Mode == RSASignaturePaddingMode.Pss) { processor.EncodePss(hash, encodedBytes, KeySize); } else { Debug.Fail("Padding mode should be checked prior to this point."); throw PaddingModeNotSupported(); } int ret = Interop.AndroidCrypto.RsaSignPrimitive(encodedBytes, destination, rsa); CryptoPool.Return(encodedRented, bytesRequired); CheckReturn(ret); Debug.Assert( ret == bytesRequired, $"RsaSignPrimitive returned {ret} when {bytesRequired} was expected"); bytesWritten = ret; return(true); }
private bool TrySignHash( ReadOnlySpan <byte> hash, Span <byte> destination, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding, bool allocateSignature, out int bytesWritten, out byte[] signature) { Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name)); Debug.Assert(padding != null); signature = null; // Do not factor out getting _key.Value, since the key creation should not happen on // invalid padding modes. if (padding.Mode == RSASignaturePaddingMode.Pkcs1) { int algorithmNid = GetAlgorithmNid(hashAlgorithm); SafeRsaHandle rsa = _key.Value; int bytesRequired = Interop.Crypto.RsaSize(rsa); if (allocateSignature) { Debug.Assert(destination.Length == 0); signature = new byte[bytesRequired]; destination = signature; } if (destination.Length < bytesRequired) { bytesWritten = 0; return(false); } if (!Interop.Crypto.RsaSign(algorithmNid, hash, hash.Length, destination, out int signatureSize, rsa)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } Debug.Assert( signatureSize == bytesRequired, $"RSA_sign reported signatureSize was {signatureSize}, when {bytesRequired} was expected"); bytesWritten = signatureSize; return(true); } else if (padding.Mode == RSASignaturePaddingMode.Pss) { RsaPaddingProcessor processor = RsaPaddingProcessor.OpenProcessor(hashAlgorithm); SafeRsaHandle rsa = _key.Value; int bytesRequired = Interop.Crypto.RsaSize(rsa); if (allocateSignature) { Debug.Assert(destination.Length == 0); signature = new byte[bytesRequired]; destination = signature; } if (destination.Length < bytesRequired) { bytesWritten = 0; return(false); } byte[] pssRented = CryptoPool.Rent(bytesRequired); Span <byte> pssBytes = new Span <byte>(pssRented, 0, bytesRequired); processor.EncodePss(hash, pssBytes, KeySize); int ret = Interop.Crypto.RsaSignPrimitive(pssBytes, destination, rsa); CryptoPool.Return(pssRented, bytesRequired); CheckReturn(ret); Debug.Assert( ret == bytesRequired, $"RSA_private_encrypt returned {ret} when {bytesRequired} was expected"); bytesWritten = ret; return(true); } throw PaddingModeNotSupported(); }
internal bool DepadOaep( ReadOnlySpan <byte> source, Span <byte> destination, out int bytesWritten) { // https://tools.ietf.org/html/rfc3447#section-7.1.2 using (IncrementalHash hasher = IncrementalHash.CreateHash(_hashAlgorithmName)) { Span <byte> lHash = stackalloc byte[_hLen]; if (!hasher.TryGetHashAndReset(lHash, out int hLen2) || hLen2 != _hLen) { Debug.Fail("TryGetHashAndReset failed with exact-size destination"); throw new CryptographicException(); } int y = source[0]; ReadOnlySpan <byte> maskedSeed = source.Slice(1, _hLen); ReadOnlySpan <byte> maskedDB = source.Slice(1 + _hLen); Span <byte> seed = stackalloc byte[_hLen]; // seedMask = MGF(maskedDB, hLen) Mgf1(hasher, maskedDB, seed); // seed = seedMask XOR maskedSeed Xor(seed, maskedSeed); byte[] tmp = CryptoPool.Rent(source.Length); try { Span <byte> dbMask = new Span <byte>(tmp, 0, maskedDB.Length); // dbMask = MGF(seed, k - hLen - 1) Mgf1(hasher, seed, dbMask); // DB = dbMask XOR maskedDB Xor(dbMask, maskedDB); ReadOnlySpan <byte> lHashPrime = dbMask.Slice(0, _hLen); int separatorPos = int.MaxValue; for (int i = dbMask.Length - 1; i >= _hLen; i--) { // if dbMask[i] is 1, val is 0. otherwise val is [01,FF] byte dbMinus1 = (byte)(dbMask[i] - 1); int val = dbMinus1; // if val is 0: FFFFFFFF & FFFFFFFF => FFFFFFFF // if val is any other byte value, val-1 will be in the range 00000000 to 000000FE, // and so the high bit will not be set. val = (~val & (val - 1)) >> 31; // if val is 0: separator = (0 & i) | (~0 & separator) => separator // else: separator = (~0 & i) | (0 & separator) => i // // Net result: non-branching "if (dbMask[i] == 1) separatorPos = i;" separatorPos = (val & i) | (~val & separatorPos); } bool lHashMatches = CryptographicOperations.FixedTimeEquals(lHash, lHashPrime); bool yIsZero = y == 0; bool separatorMadeSense = separatorPos < dbMask.Length; // This intentionally uses non-short-circuiting operations to hide the timing // differential between the three failure cases bool shouldContinue = lHashMatches & yIsZero & separatorMadeSense; if (!shouldContinue) { throw new CryptographicException(SR.Cryptography_OAEP_Decryption_Failed); } Span <byte> message = dbMask.Slice(separatorPos + 1); if (message.Length <= destination.Length) { message.CopyTo(destination); bytesWritten = message.Length; return(true); } else { bytesWritten = 0; return(false); } } finally { CryptoPool.Return(tmp, source.Length); } } }
public override bool VerifyHash(ReadOnlySpan <byte> hash, ReadOnlySpan <byte> signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); ThrowIfDisposed(); if (padding == RSASignaturePadding.Pkcs1) { Interop.AppleCrypto.PAL_HashAlgorithm palAlgId = PalAlgorithmFromAlgorithmName(hashAlgorithm, out _); return(Interop.AppleCrypto.VerifySignature( GetKeys().PublicKey, hash, signature, palAlgId, Interop.AppleCrypto.PAL_SignatureAlgorithm.RsaPkcs1)); } else if (padding.Mode == RSASignaturePaddingMode.Pss) { SafeSecKeyRefHandle publicKey = GetKeys().PublicKey; int keySize = KeySize; int rsaSize = RsaPaddingProcessor.BytesRequiredForBitCount(keySize); if (signature.Length != rsaSize) { return(false); } if (hash.Length != RsaPaddingProcessor.HashLength(hashAlgorithm)) { 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(RsaPaddingProcessor.VerifyPss(hashAlgorithm, hash, unwrapped, keySize)); } finally { CryptographicOperations.ZeroMemory(unwrapped); CryptoPool.Return(rented, clearSize: 0); } } throw new CryptographicException(SR.Cryptography_InvalidPaddingMode); }
public void Dispose() { CryptoPool.Return(this.memory, this.clearAll ? -1 : 0); }
private void EncryptCore( ReadOnlySpan <byte> nonce, ReadOnlySpan <byte> plaintext, Span <byte> ciphertext, Span <byte> tag, ReadOnlySpan <byte> associatedData = default) { if (!Interop.Crypto.CipherSetTagLength(_ctxHandle, tag.Length)) { throw new CryptographicException(); } Interop.Crypto.EvpCipherSetKeyAndIV( _ctxHandle, Span <byte> .Empty, nonce, Interop.Crypto.EvpCipherDirection.Encrypt); if (associatedData.Length != 0) { Interop.Crypto.CipherUpdateAAD(_ctxHandle, associatedData); } byte[]? rented = null; try { scoped Span <byte> ciphertextAndTag; // Arbitrary limit. const int StackAllocMax = 128; if (checked (ciphertext.Length + tag.Length) <= StackAllocMax) { ciphertextAndTag = stackalloc byte[ciphertext.Length + tag.Length]; } else { rented = CryptoPool.Rent(ciphertext.Length + tag.Length); ciphertextAndTag = new Span <byte>(rented, 0, ciphertext.Length + tag.Length); } if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, ciphertextAndTag, out int ciphertextBytesWritten, plaintext)) { throw new CryptographicException(); } if (!Interop.Crypto.EvpCipherFinalEx( _ctxHandle, ciphertextAndTag.Slice(ciphertextBytesWritten), out int bytesWritten)) { throw new CryptographicException(); } ciphertextBytesWritten += bytesWritten; // NOTE: Android appends tag to the end of the ciphertext in case of CCM/GCM and "encryption" mode if (ciphertextBytesWritten != ciphertextAndTag.Length) { Debug.Fail($"GCM encrypt wrote {ciphertextBytesWritten} of {ciphertextAndTag.Length} bytes."); throw new CryptographicException(); } ciphertextAndTag[..ciphertext.Length].CopyTo(ciphertext);
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); } } }
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); }
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)); } if (padding == RSASignaturePadding.Pkcs1 && padding == RSASignaturePadding.Pss) { throw PaddingModeNotSupported(); } RsaPaddingProcessor processor = RsaPaddingProcessor.OpenProcessor(hashAlgorithm); SafeRsaHandle rsa = GetKey(); int requiredBytes = Interop.AndroidCrypto.RsaSize(rsa); if (signature.Length != requiredBytes) { return(false); } if (hash.Length != processor.HashLength) { return(false); } byte[] rented = CryptoPool.Rent(requiredBytes); Span <byte> unwrapped = new Span <byte>(rented, 0, requiredBytes); try { int ret = Interop.AndroidCrypto.RsaVerificationPrimitive(signature, unwrapped, rsa); CheckReturn(ret); if (ret == 0) { // Return value of 0 from RsaVerificationPrimitive indicates the signature could not be decrypted. return(false); } Debug.Assert( ret == requiredBytes, $"RsaVerificationPrimitive returned {ret} when {requiredBytes} was expected"); if (padding == RSASignaturePadding.Pkcs1) { byte[] repadRent = CryptoPool.Rent(unwrapped.Length); Span <byte> repadded = repadRent.AsSpan(0, requiredBytes); processor.PadPkcs1Signature(hash, repadded); bool valid = CryptographicOperations.FixedTimeEquals(repadded, unwrapped); CryptoPool.Return(repadRent, requiredBytes); return(valid); } else if (padding == RSASignaturePadding.Pss) { return(processor.VerifyPss(hash, unwrapped, KeySize)); } else { Debug.Fail("Padding mode should be checked prior to this point."); throw PaddingModeNotSupported(); } } finally { CryptoPool.Return(rented, requiredBytes); } throw PaddingModeNotSupported(); }
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 (data.Length == 0) { 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); } } return(Interop.AppleCrypto.TryRsaEncrypt( GetKeys().PublicKey, data, destination, padding, out bytesWritten)); }
private void EncryptCore( ReadOnlySpan <byte> nonce, ReadOnlySpan <byte> plaintext, Span <byte> ciphertext, Span <byte> tag, ReadOnlySpan <byte> associatedData = default) { Interop.Crypto.EvpCipherSetKeyAndIV( _ctxHandle, Span <byte> .Empty, nonce, Interop.Crypto.EvpCipherDirection.Encrypt); if (!associatedData.IsEmpty) { Interop.Crypto.CipherUpdateAAD(_ctxHandle, associatedData); } byte[]? rented = null; int ciphertextAndTagLength = checked (ciphertext.Length + tag.Length); try { // Arbitrary limit. const int StackAllocMax = 128; Span <byte> ciphertextAndTag = stackalloc byte[StackAllocMax]; if (ciphertextAndTagLength > StackAllocMax) { rented = CryptoPool.Rent(ciphertextAndTagLength); ciphertextAndTag = rented; } ciphertextAndTag = ciphertextAndTag.Slice(0, ciphertextAndTagLength); if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, ciphertextAndTag, out int ciphertextBytesWritten, plaintext)) { throw new CryptographicException(); } if (!Interop.Crypto.EvpCipherFinalEx( _ctxHandle, ciphertextAndTag.Slice(ciphertextBytesWritten), out int bytesWritten)) { throw new CryptographicException(); } ciphertextBytesWritten += bytesWritten; // NOTE: Android appends tag to the end of the ciphertext in case of ChaCha20Poly1305 and "encryption" mode if (ciphertextBytesWritten != ciphertextAndTagLength) { Debug.Fail($"ChaCha20Poly1305 encrypt wrote {ciphertextBytesWritten} of {ciphertextAndTagLength} bytes."); throw new CryptographicException(); } ciphertextAndTag.Slice(0, ciphertext.Length).CopyTo(ciphertext); ciphertextAndTag.Slice(ciphertext.Length).CopyTo(tag); } finally { if (rented is not null) { CryptoPool.Return(rented, clearSize: ciphertextAndTagLength); } } }
// 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 static unsafe bool OneShotDecrypt( ILiteSymmetricCipher cipher, PaddingMode paddingMode, ReadOnlySpan <byte> input, Span <byte> output, out int bytesWritten) { if (input.Length % cipher.PaddingSizeInBytes != 0) { throw new CryptographicException(SR.Cryptography_PartialBlock); } // If there is no padding that needs to be removed, and the output buffer is large enough to hold // the resulting plaintext, we can decrypt directly in to the output buffer. // We do not do this for modes that require padding removal. // // This is not done for padded ciphertexts because we don't know if the padding is valid // until it's been decrypted. We don't want to decrypt in to a user-supplied buffer and then throw // a padding exception after we've already filled the user buffer with plaintext. We should only // release the plaintext to the caller once we know the padding is valid. if (!SymmetricPadding.DepaddingRequired(paddingMode)) { if (output.Length >= input.Length) { bytesWritten = cipher.TransformFinal(input, output); return(true); } // If no padding is going to be removed, we know the buffer is too small and we can bail out. bytesWritten = 0; return(false); } byte[] rentedBuffer = CryptoPool.Rent(input.Length); Span <byte> buffer = rentedBuffer.AsSpan(0, input.Length); Span <byte> decryptedBuffer = default; fixed(byte *pBuffer = buffer) { try { int transformWritten = cipher.TransformFinal(input, buffer); decryptedBuffer = buffer.Slice(0, transformWritten); // This intentionally passes in BlockSizeInBytes instead of PaddingSizeInBytes. This is so that // "extra padded" CFB data can still be decrypted. The .NET Framework always padded CFB8 to the // block size, not the feedback size. We want the one-shot to be able to continue to decrypt // those ciphertexts, so for CFB8 we are more lenient on the number of allowed padding bytes. int unpaddedLength = SymmetricPadding.GetPaddingLength(decryptedBuffer, paddingMode, cipher.BlockSizeInBytes); // validates padding if (unpaddedLength > output.Length) { bytesWritten = 0; return(false); } decryptedBuffer.Slice(0, unpaddedLength).CopyTo(output); bytesWritten = unpaddedLength; return(true); } finally { CryptographicOperations.ZeroMemory(decryptedBuffer); CryptoPool.Return(rentedBuffer, clearSize: 0); // ZeroMemory clears the part of the buffer that was written to. } } }
public override bool TrySignHash(ReadOnlySpan <byte> hash, Span <byte> destination, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding, out int bytesWritten) { ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); ArgumentNullException.ThrowIfNull(padding); ThrowIfDisposed(); bool pssPadding = padding.Mode switch { RSASignaturePaddingMode.Pss => true, RSASignaturePaddingMode.Pkcs1 => false, _ => 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 (!pssPadding) { 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.TryCreateSignature( keys.PrivateKey, hash, destination, palAlgId, Interop.AppleCrypto.PAL_SignatureAlgorithm.RsaPkcs1, 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); RsaPaddingProcessor.EncodePss(hashAlgorithm, hash, buf, keySize); try { return(Interop.AppleCrypto.TryRsaSignaturePrimitive(keys.PrivateKey, buf, destination, out bytesWritten)); } finally { CryptographicOperations.ZeroMemory(buf); CryptoPool.Return(rented, clearSize: 0); } }
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); }
/// <summary> /// Get the secret agreement generated between two parties /// </summary> private byte[]? DeriveSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey, IncrementalHash?hasher) { Debug.Assert(otherPartyPublicKey != null); // Ensure that this ECDH object contains a private key by attempting a parameter export // which will throw an OpenSslCryptoException if no private key is available ECParameters thisKeyExplicit = ExportExplicitParameters(true); bool thisIsNamed = Interop.Crypto.EcKeyHasCurveName(_key.Value); ECDiffieHellmanOpenSslPublicKey?otherKey = otherPartyPublicKey as ECDiffieHellmanOpenSslPublicKey; bool disposeOtherKey = false; if (otherKey == null) { disposeOtherKey = true; ECParameters otherParameters = thisIsNamed ? otherPartyPublicKey.ExportParameters() : otherPartyPublicKey.ExportExplicitParameters(); otherKey = new ECDiffieHellmanOpenSslPublicKey(otherParameters); } bool otherIsNamed = otherKey.HasCurveName; SafeEvpPKeyHandle?ourKey = null; SafeEvpPKeyHandle?theirKey = null; byte[]? rented = null; int secretLength = 0; try { if (otherKey.KeySize != KeySize) { throw new ArgumentException(SR.Cryptography_ArgECDHKeySizeMismatch, nameof(otherPartyPublicKey)); } if (otherIsNamed == thisIsNamed) { ourKey = _key.UpRefKeyHandle(); theirKey = otherKey.DuplicateKeyHandle(); } else if (otherIsNamed) { ourKey = _key.UpRefKeyHandle(); using (ECOpenSsl tmp = new ECOpenSsl(otherKey.ExportExplicitParameters())) { theirKey = tmp.UpRefKeyHandle(); } } else { using (ECOpenSsl tmp = new ECOpenSsl(thisKeyExplicit)) { ourKey = tmp.UpRefKeyHandle(); } theirKey = otherKey.DuplicateKeyHandle(); } using (SafeEvpPKeyCtxHandle ctx = Interop.Crypto.EvpPKeyCtxCreate(ourKey, theirKey, out uint secretLengthU)) { if (ctx == null || ctx.IsInvalid || secretLengthU == 0 || secretLengthU > int.MaxValue) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } secretLength = (int)secretLengthU; // Indicate that secret can hold stackallocs from nested scopes Span <byte> secret = stackalloc byte[0]; // Arbitrary limit. But it covers secp521r1, which is the biggest common case. const int StackAllocMax = 66; if (secretLength > StackAllocMax) { rented = CryptoPool.Rent(secretLength); secret = new Span <byte>(rented, 0, secretLength); } else { secret = stackalloc byte[secretLength]; } Interop.Crypto.EvpPKeyDeriveSecretAgreement(ctx, secret); if (hasher == null) { return(secret.ToArray()); } else { hasher.AppendData(secret); return(null); } } } finally { theirKey?.Dispose(); ourKey?.Dispose(); if (disposeOtherKey) { otherKey.Dispose(); } if (rented != null) { CryptoPool.Return(rented, secretLength); } } }
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 { CryptoPool.Return(decrypted); } } } } catch (AsnContentException e) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } }
private static bool TryDecrypt( SafeRsaHandle key, ReadOnlySpan <byte> data, Span <byte> destination, Interop.AndroidCrypto.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.AndroidCrypto.RsaPadding.NoPadding) == (rsaPaddingProcessor != null)); // Caller should have already checked this. Debug.Assert(!key.IsInvalid); int rsaSize = Interop.AndroidCrypto.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 = CryptoPool.Rent(rsaSize); decryptBuf = paddingBuf; } try { int returnValue = Interop.AndroidCrypto.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); CryptoPool.Return(paddingBuf, clearSize: 0); } } }
public int Transform(ReadOnlySpan <byte> input, Span <byte> output) { Debug.Assert(input.Length > 0); Debug.Assert((input.Length % PaddingSizeInBytes) == 0); int numBytesWritten = 0; // NCryptEncrypt and NCryptDecrypt can do in place encryption, but if the buffers overlap // the offset must be zero. In that case, we need to copy to a temporary location. if (input.Overlaps(output, out int offset) && offset != 0) { byte[] rented = CryptoPool.Rent(output.Length); try { numBytesWritten = NCryptTransform(input, rented); rented.AsSpan(0, numBytesWritten).CopyTo(output); } finally { CryptoPool.Return(rented, clearSize: numBytesWritten); } } else { numBytesWritten = NCryptTransform(input, output); } if (numBytesWritten != input.Length) { // CNG gives us no way to tell NCryptDecrypt() that we're decrypting the final block, nor is it performing any // padding /depadding for us. So there's no excuse for a provider to hold back output for "future calls." Though // this isn't technically our problem to detect, we might as well detect it now for easier diagnosis. throw new CryptographicException(SR.Cryptography_UnexpectedTransformTruncation); } return(numBytesWritten); int NCryptTransform(ReadOnlySpan <byte> input, Span <byte> output) { int bytesWritten; // The Handle property duplicates the handle. using (SafeNCryptKeyHandle keyHandle = _key.Handle) { unsafe { ErrorCode errorCode = _encrypting ? Interop.NCrypt.NCryptEncrypt(keyHandle, input, input.Length, null, output, output.Length, out bytesWritten, AsymmetricPaddingMode.None) : Interop.NCrypt.NCryptDecrypt(keyHandle, input, input.Length, null, output, output.Length, out bytesWritten, AsymmetricPaddingMode.None); if (errorCode != ErrorCode.ERROR_SUCCESS) { throw errorCode.ToCryptographicException(); } } } return(bytesWritten); } }
private void EncryptCore( ReadOnlySpan <byte> nonce, ReadOnlySpan <byte> plaintext, Span <byte> ciphertext, Span <byte> tag, ReadOnlySpan <byte> associatedData = default) { // Convert key length to bits. using (SafeEvpCipherCtxHandle ctx = Interop.Crypto.EvpCipherCreatePartial(GetCipher(_key.Length * 8))) { if (ctx.IsInvalid) { throw new CryptographicException(); } if (!Interop.Crypto.CipherSetTagLength(ctx, tag.Length)) { throw new CryptographicException(); } Interop.Crypto.CipherSetNonceLength(ctx, nonce.Length); Interop.Crypto.EvpCipherSetKeyAndIV(ctx, _key, nonce, Interop.Crypto.EvpCipherDirection.Encrypt); if (associatedData.Length != 0) { Interop.Crypto.CipherUpdateAAD(ctx, associatedData); } byte[]? rented = null; try { Span <byte> ciphertextAndTag = stackalloc byte[0]; // Arbitrary limit. const int StackAllocMax = 128; if (checked (ciphertext.Length + tag.Length) <= StackAllocMax) { ciphertextAndTag = stackalloc byte[ciphertext.Length + tag.Length]; } else { rented = CryptoPool.Rent(ciphertext.Length + tag.Length); ciphertextAndTag = new Span <byte>(rented, 0, ciphertext.Length + tag.Length); } if (!Interop.Crypto.EvpCipherUpdate(ctx, ciphertextAndTag, out int ciphertextBytesWritten, plaintext)) { throw new CryptographicException(); } if (!Interop.Crypto.EvpCipherFinalEx( ctx, ciphertextAndTag.Slice(ciphertextBytesWritten), out int bytesWritten)) { throw new CryptographicException(); } ciphertextBytesWritten += bytesWritten; // NOTE: Android appends tag to the end of the ciphertext in case of CCM/GCM and "encryption" mode if (ciphertextBytesWritten != ciphertextAndTag.Length) { Debug.Fail($"CCM encrypt wrote {ciphertextBytesWritten} of {ciphertextAndTag.Length} bytes."); throw new CryptographicException(); } ciphertextAndTag[..ciphertext.Length].CopyTo(ciphertext);
/// <summary> /// Get the secret agreement generated between two parties /// </summary> private byte[]? DeriveSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey, IncrementalHash?hasher) { Debug.Assert(otherPartyPublicKey != null); // Ensure that this ECDH object contains a private key by attempting a parameter export // which will throw an OpenSslCryptoException if no private key is available ECParameters thisKeyExplicit = ExportExplicitParameters(true); bool thisIsNamed = Interop.AndroidCrypto.EcKeyHasCurveName(_key.Value); ECDiffieHellmanAndroidPublicKey?otherKey = otherPartyPublicKey as ECDiffieHellmanAndroidPublicKey; bool disposeOtherKey = false; if (otherKey == null) { disposeOtherKey = true; ECParameters otherParameters = thisIsNamed ? otherPartyPublicKey.ExportParameters() : otherPartyPublicKey.ExportExplicitParameters(); otherKey = new ECDiffieHellmanAndroidPublicKey(otherParameters); } bool otherIsNamed = otherKey.HasCurveName; SafeEcKeyHandle?ourKey = null; SafeEcKeyHandle?theirKey = null; byte[]? rented = null; // Calculate secretLength in bytes. int secretLength = AsymmetricAlgorithmHelpers.BitsToBytes(KeySize); try { if (otherKey.KeySize != KeySize) { throw new ArgumentException(SR.Cryptography_ArgECDHKeySizeMismatch, nameof(otherPartyPublicKey)); } if (otherIsNamed == thisIsNamed) { ourKey = _key.UpRefKeyHandle(); theirKey = otherKey.DuplicateKeyHandle(); } else if (otherIsNamed) { ourKey = _key.UpRefKeyHandle(); using (ECAndroid tmp = new ECAndroid(otherKey.ExportExplicitParameters())) { theirKey = tmp.UpRefKeyHandle(); } } else { using (ECAndroid tmp = new ECAndroid(thisKeyExplicit)) { ourKey = tmp.UpRefKeyHandle(); } theirKey = otherKey.DuplicateKeyHandle(); } // Indicate that secret can hold stackallocs from nested scopes Span <byte> secret = stackalloc byte[0]; // Arbitrary limit. But it covers secp521r1, which is the biggest common case. const int StackAllocMax = 66; if (secretLength > StackAllocMax) { rented = CryptoPool.Rent(secretLength); secret = new Span <byte>(rented, 0, secretLength); } else { secret = stackalloc byte[secretLength]; } if (!Interop.AndroidCrypto.EcdhDeriveKey(ourKey, theirKey, secret, out int usedBufferLength)) { throw new CryptographicException(); } Debug.Assert(secretLength == usedBufferLength, $"Expected secret length {secretLength} does not match actual secret length {usedBufferLength}."); if (hasher == null) { return(secret.ToArray()); } else { hasher.AppendData(secret); return(null); } } finally { theirKey?.Dispose(); ourKey?.Dispose(); if (disposeOtherKey) { otherKey.Dispose(); } if (rented != null) { CryptoPool.Return(rented, secretLength); } } }