internal static RsaPaddingProcessor OpenProcessor(HashAlgorithmName hashAlgorithmName) { return(s_lookup.GetOrAdd( hashAlgorithmName, static hashAlgorithmName => { using (IncrementalHash hasher = IncrementalHash.CreateHash(hashAlgorithmName)) { // SHA-2-512 is the biggest we expect Span <byte> stackDest = stackalloc byte[512 / 8]; ReadOnlyMemory <byte> digestInfoPrefix; if (hashAlgorithmName == HashAlgorithmName.MD5) { digestInfoPrefix = s_digestInfoMD5; } else if (hashAlgorithmName == HashAlgorithmName.SHA1) { digestInfoPrefix = s_digestInfoSha1; } else if (hashAlgorithmName == HashAlgorithmName.SHA256) { digestInfoPrefix = s_digestInfoSha256; } else if (hashAlgorithmName == HashAlgorithmName.SHA384) { digestInfoPrefix = s_digestInfoSha384; } else if (hashAlgorithmName == HashAlgorithmName.SHA512) { digestInfoPrefix = s_digestInfoSha512; } else { Debug.Fail("Unknown digest algorithm"); throw new CryptographicException(); } if (hasher.TryGetHashAndReset(stackDest, out int bytesWritten)) { return new RsaPaddingProcessor(hashAlgorithmName, bytesWritten, digestInfoPrefix); } byte[] big = hasher.GetHashAndReset(); return new RsaPaddingProcessor(hashAlgorithmName, big.Length, digestInfoPrefix); } })); }
private void Mgf1(IncrementalHash hasher, ReadOnlySpan <byte> mgfSeed, Span <byte> mask) { Span <byte> writePtr = mask; int count = 0; Span <byte> bigEndianCount = stackalloc byte[sizeof(int)]; while (writePtr.Length > 0) { hasher.AppendData(mgfSeed); BinaryPrimitives.WriteInt32BigEndian(bigEndianCount, count); hasher.AppendData(bigEndianCount); if (writePtr.Length >= _hLen) { if (!hasher.TryGetHashAndReset(writePtr, out int bytesWritten)) { Debug.Fail($"TryGetHashAndReset failed with sufficient space"); throw new CryptographicException(); } Debug.Assert(bytesWritten == _hLen); writePtr = writePtr.Slice(bytesWritten); } else { Span <byte> tmp = stackalloc byte[_hLen]; if (!hasher.TryGetHashAndReset(tmp, out int bytesWritten)) { Debug.Fail($"TryGetHashAndReset failed with sufficient space"); throw new CryptographicException(); } Debug.Assert(bytesWritten == _hLen); tmp.Slice(0, writePtr.Length).CopyTo(writePtr); break; } count++; } }
internal static RsaPaddingProcessor OpenProcessor(HashAlgorithmName hashAlgorithmName) { return(s_lookup.GetOrAdd( hashAlgorithmName, alg => { using (IncrementalHash hasher = IncrementalHash.CreateHash(hashAlgorithmName)) { // SHA-2-512 is the biggest we expect Span <byte> stackDest = stackalloc byte[512 / 8]; if (hasher.TryGetHashAndReset(stackDest, out int bytesWritten)) { return new RsaPaddingProcessor(hashAlgorithmName, bytesWritten); } byte[] big = hasher.GetHashAndReset(); return new RsaPaddingProcessor(hashAlgorithmName, big.Length); } })); }
internal static byte[] DeriveKeyFromHash( ECDiffieHellmanPublicKey otherPartyPublicKey, HashAlgorithmName hashAlgorithm, ReadOnlySpan <byte> secretPrepend, ReadOnlySpan <byte> secretAppend, DeriveSecretAgreement deriveSecretAgreement) { Debug.Assert(otherPartyPublicKey != null); Debug.Assert(!string.IsNullOrEmpty(hashAlgorithm.Name)); using (IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm)) { hash.AppendData(secretPrepend); byte[]? secretAgreement = deriveSecretAgreement(otherPartyPublicKey, hash); // We want the side effect, and it should not have returned the answer. Debug.Assert(secretAgreement == null); hash.AppendData(secretAppend); return(hash.GetHashAndReset()); } }
public IncrementalMD5NetStandard16() { _incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.MD5); }
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 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 SHA1CryptoServiceProvider() { _incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); }
private static void Expand(HashAlgorithmName hashAlgorithmName, int hashLength, ReadOnlySpan <byte> prk, Span <byte> output, ReadOnlySpan <byte> info) { Debug.Assert(HashLength(hashAlgorithmName) == hashLength); if (prk.Length < hashLength) { throw new ArgumentException(SR.Format(SR.Cryptography_Prk_TooSmall, hashLength), nameof(prk)); } byte counter = 0; var counterSpan = new Span <byte>(ref counter); Span <byte> t = Span <byte> .Empty; Span <byte> remainingOutput = output; const int MaxStackInfoBuffer = 64; Span <byte> tempInfoBuffer = stackalloc byte[MaxStackInfoBuffer]; scoped ReadOnlySpan <byte> infoBuffer; byte[]? rentedTempInfoBuffer = null; if (output.Overlaps(info)) { if (info.Length > MaxStackInfoBuffer) { rentedTempInfoBuffer = CryptoPool.Rent(info.Length); tempInfoBuffer = rentedTempInfoBuffer; } tempInfoBuffer = tempInfoBuffer.Slice(0, info.Length); info.CopyTo(tempInfoBuffer); infoBuffer = tempInfoBuffer; } else { infoBuffer = info; } using (IncrementalHash hmac = IncrementalHash.CreateHMAC(hashAlgorithmName, prk)) { for (int i = 1; ; i++) { hmac.AppendData(t); hmac.AppendData(infoBuffer); counter = (byte)i; hmac.AppendData(counterSpan); if (remainingOutput.Length >= hashLength) { t = remainingOutput.Slice(0, hashLength); remainingOutput = remainingOutput.Slice(hashLength); GetHashAndReset(hmac, t); } else { if (remainingOutput.Length > 0) { Debug.Assert(hashLength <= 512 / 8, "hashLength is larger than expected, consider increasing this value or using regular allocation"); Span <byte> lastChunk = stackalloc byte[hashLength]; GetHashAndReset(hmac, lastChunk); lastChunk.Slice(0, remainingOutput.Length).CopyTo(remainingOutput); } break; } } } if (rentedTempInfoBuffer is not null) { CryptoPool.Return(rentedTempInfoBuffer, clearSize: info.Length); } }
public SHA256CryptoServiceProvider() { _incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); HashSizeValue = HashSizeBits; }
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 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); } } }
public MD5CryptoServiceProvider() { _incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.MD5); }
/// <summary> /// Constructor. /// </summary> /// <param name="key">Password string</param> /// <param name="saltBytes">Random bytes, length depends on encryption strength. /// 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.</param> /// <param name="blockSize">The encryption strength, in bytes eg 16 for 128 bits.</param> /// <param name="writeMode">True when creating a zip, false when reading. For the AuthCode.</param> /// public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMode) { if (blockSize!=16&&blockSize!=32) // 24 valid for AES but not supported by Winzip throw new Exception("Invalid blocksize "+blockSize+". Must be 16 or 32."); if (saltBytes.Length!=blockSize/2) throw new Exception("Invalid salt len. Must be "+blockSize/2+" for blocksize "+blockSize); // initialise the encryption buffer and buffer pos _blockSize=blockSize; _encryptBuffer=new byte[_blockSize]; _encrPos=ENCRYPT_BLOCK; // Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c var pdb=new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS); var rm=new RijndaelImplementation(); rm.Mode=CipherMode.ECB; // No feedback from cipher for CTR mode _counterNonce=new byte[_blockSize]; byte[] byteKey1=pdb.GetBytes(_blockSize); byte[] byteKey2=pdb.GetBytes(_blockSize); _encryptor=rm.CreateEncryptor(byteKey1, byteKey2); _pwdVerifier=pdb.GetBytes(PWD_VER_LENGTH); // #if !OS_WINDOWS incrementalHash = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, byteKey2); #else _hmacsha1=new HMACSHA1(byteKey2); #endif _writeMode=writeMode; }
public MD5CryptoServiceProvider() { _incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.MD5); HashSizeValue = HashSizeInBits; }
public Gost3411CryptoServiceProvider() { _incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.Gost3411); HashSizeValue = HashSizeBits; }
public SHA1CryptoServiceProvider() { _incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); HashSizeValue = HashSizeBits; }
private static unsafe void PHash( HashAlgorithmName algorithmName, ReadOnlySpan <byte> secret, ReadOnlySpan <byte> prfLabel, ReadOnlySpan <byte> prfSeed, int hashOutputSize, Span <byte> ret) { // https://tools.ietf.org/html/rfc4346#section-5 // // P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + // HMAC_hash(secret, A(2) + seed) + // HMAC_hash(secret, A(3) + seed) + ... // // A(0) = seed // A(i) = HMAC_hash(secret, A(i-1)) // // This is called via PRF, which turns (label || seed) into seed. Span <byte> retSpan = ret; using (IncrementalHash hasher = IncrementalHash.CreateHMAC(algorithmName, secret)) { Span <byte> a = stackalloc byte[hashOutputSize]; Span <byte> p = stackalloc byte[hashOutputSize]; // A(1) hasher.AppendData(prfLabel); hasher.AppendData(prfSeed); if (!hasher.TryGetHashAndReset(a, out int bytesWritten) || bytesWritten != hashOutputSize) { throw new CryptographicException(); } while (true) { // HMAC_hash(secret, A(i) || seed) => p hasher.AppendData(a); hasher.AppendData(prfLabel); hasher.AppendData(prfSeed); if (!hasher.TryGetHashAndReset(p, out bytesWritten) || bytesWritten != hashOutputSize) { throw new CryptographicException(); } int len = Math.Min(p.Length, retSpan.Length); p.Slice(0, len).CopyTo(retSpan); retSpan = retSpan.Slice(len); if (retSpan.Length == 0) { return; } // Build the next A(i) hasher.AppendData(a); if (!hasher.TryGetHashAndReset(a, out bytesWritten) || bytesWritten != hashOutputSize) { throw new CryptographicException(); } } } }
private byte[] DeriveSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey, IncrementalHash hasher) { if (!(otherPartyPublicKey is ECDiffieHellmanSecurityTransformsPublicKey secTransPubKey)) { secTransPubKey = new ECDiffieHellmanSecurityTransformsPublicKey(otherPartyPublicKey.ExportParameters()); } try { SafeSecKeyRefHandle otherPublic = secTransPubKey.KeyHandle; if (Interop.AppleCrypto.EccGetKeySizeInBits(otherPublic) != KeySize) { throw new ArgumentException( SR.Cryptography_ArgECDHKeySizeMismatch, nameof(otherPartyPublicKey)); } SafeSecKeyRefHandle thisPrivate = GetKeys().PrivateKey; if (thisPrivate == null) { throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); } // Since Apple only supports secp256r1, secp384r1, and secp521r1; and 521 fits in // 66 bytes ((521 + 7) / 8), the Span path will always succeed. Span <byte> secretSpan = stackalloc byte[66]; byte[] secret = Interop.AppleCrypto.EcdhKeyAgree( thisPrivate, otherPublic, secretSpan, out int bytesWritten); // Either we wrote to the span or we returned an array, but not both, and not neither. // ("neither" would have thrown) Debug.Assert( (bytesWritten == 0) != (secret == null), $"bytesWritten={bytesWritten}, (secret==null)={secret == null}"); if (hasher == null) { return(secret ?? secretSpan.Slice(0, bytesWritten).ToArray()); } if (secret == null) { hasher.AppendData(secretSpan.Slice(0, bytesWritten)); } else { hasher.AppendData(secret); Array.Clear(secret, 0, secret.Length); } return(null); } finally { if (!ReferenceEquals(otherPartyPublicKey, secTransPubKey)) { secTransPubKey.Dispose(); } } }
internal MD5SHA1IncrementalHasher() { _md5 = IncrementalHash.CreateHash(HashAlgorithmName.MD5); _sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); }
private static unsafe void PHash( HashAlgorithmName algorithmName, ReadOnlySpan <byte> secret, ReadOnlySpan <byte> prfLabel, ReadOnlySpan <byte> prfSeed, int hashOutputSize, Span <byte> ret) { // https://tools.ietf.org/html/rfc4346#section-5 // // P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + // HMAC_hash(secret, A(2) + seed) + // HMAC_hash(secret, A(3) + seed) + ... // // A(0) = seed // A(i) = HMAC_hash(secret, A(i-1)) // // This is called via PRF, which turns (label || seed) into seed. byte[] secretTmp = new byte[secret.Length]; // Keep secretTmp pinned the whole time it has a secret in it, so it // doesn't get copied around during heap compaction. fixed(byte *pinnedSecretTmp = secretTmp) { secret.CopyTo(secretTmp); try { Span <byte> retSpan = ret; using (IncrementalHash hasher = IncrementalHash.CreateHMAC(algorithmName, secretTmp)) { Span <byte> a = stackalloc byte[hashOutputSize]; Span <byte> p = stackalloc byte[hashOutputSize]; // A(1) hasher.AppendData(prfLabel); hasher.AppendData(prfSeed); if (!hasher.TryGetHashAndReset(a, out int bytesWritten) || bytesWritten != hashOutputSize) { throw new CryptographicException(); } while (true) { // HMAC_hash(secret, A(i) || seed) => p hasher.AppendData(a); hasher.AppendData(prfLabel); hasher.AppendData(prfSeed); if (!hasher.TryGetHashAndReset(p, out bytesWritten) || bytesWritten != hashOutputSize) { throw new CryptographicException(); } int len = Math.Min(p.Length, retSpan.Length); p.Slice(0, len).CopyTo(retSpan); retSpan = retSpan.Slice(len); if (retSpan.Length == 0) { return; } // Build the next A(i) hasher.AppendData(a); if (!hasher.TryGetHashAndReset(a, out bytesWritten) || bytesWritten != hashOutputSize) { throw new CryptographicException(); } } } } finally { Array.Clear(secretTmp, 0, secretTmp.Length); } } }
internal IncrementalHashHasher(HashAlgorithmName hashAlgorithmName) { _incrementalHash = IncrementalHash.CreateHash(hashAlgorithmName); }
/// <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 = ArrayPool<byte>.Shared.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) { Array.Clear(rented, 0, secretLength); ArrayPool<byte>.Shared.Return(rented); } } }