From 7101bd162bf6c32a1e3629dd2d0c416a6fe7c2bc Mon Sep 17 00:00:00 2001 From: jorn vanloofsvelt Date: Sat, 30 Jan 2021 16:22:41 +0100 Subject: [PATCH 1/4] Adapt Hashes.HMACSHA256() so that it uses native hashes when they are present, even if native HMAC is not. If there is no native HMACSHA256, but there is native SHA256, then use Bouncy Castle's HMAC implementation in combination with the native Sha256 digest (instead of Bouncy Castle's SHA256 implementation). --- NBitcoin/BIP39/Mnemonic.cs | 4 ++ .../crypto/digests/GeneralDigest.cs | 7 +-- .../crypto/digests/RipeMD160Digest.cs | 2 - .../BouncyCastle/crypto/digests/Sha1Digest.cs | 2 - .../crypto/digests/Sha256Digest.cs | 2 - NBitcoin/Crypto/Cryptsharp/SCrypt.cs | 2 +- NBitcoin/Crypto/Hashes.cs | 43 +++++++++++++- .../NativeDigests/ManagedSha256Digest.cs | 59 +++++++++++++++++++ .../NativeDigests/ManagedSha512Digest.cs | 59 +++++++++++++++++++ NBitcoin/NBitcoin.csproj | 6 ++ NBitcoin/Secp256k1/Schnorr/ECPrivKey.cs | 2 +- 11 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs create mode 100644 NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs diff --git a/NBitcoin/BIP39/Mnemonic.cs b/NBitcoin/BIP39/Mnemonic.cs index f574fd9cd3..1fb15438ef 100644 --- a/NBitcoin/BIP39/Mnemonic.cs +++ b/NBitcoin/BIP39/Mnemonic.cs @@ -173,7 +173,11 @@ public byte[] DeriveSeed(string passphrase = null) var salt = Concat(NoBOMUTF8.GetBytes("mnemonic"), Normalize(passphrase)); var bytes = Normalize(_Mnemonic); #if NO_NATIVE_HMACSHA512 +#if NONATIVEHASH var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new NBitcoin.BouncyCastle.Crypto.Digests.Sha512Digest()); +#else + var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new Crypto.digests.ManagedSha512Digest()); +#endif mac.Init(new NBitcoin.BouncyCastle.Crypto.Parameters.KeyParameter(bytes)); return Pbkdf2.ComputeDerivedKey(mac, salt, 2048, 64); #elif NO_NATIVE_RFC2898_HMACSHA512 diff --git a/NBitcoin/BouncyCastle/crypto/digests/GeneralDigest.cs b/NBitcoin/BouncyCastle/crypto/digests/GeneralDigest.cs index 98cf940a8d..20e5084318 100644 --- a/NBitcoin/BouncyCastle/crypto/digests/GeneralDigest.cs +++ b/NBitcoin/BouncyCastle/crypto/digests/GeneralDigest.cs @@ -8,10 +8,7 @@ namespace NBitcoin.BouncyCastle.Crypto.Digests * base implementation of MD4 family style digest as outlined in * "Handbook of Applied Cryptography", pages 344 - 347. */ - internal abstract class GeneralDigest -#if !NO_BC - : IDigest, IMemoable -#endif + internal abstract class GeneralDigest : IDigest, IMemoable { private const int BYTE_LENGTH = 64; @@ -133,9 +130,7 @@ public abstract string AlgorithmName } public abstract int GetDigestSize(); public abstract int DoFinal(byte[] output, int outOff); -#if !NO_BC public abstract IMemoable Copy(); public abstract void Reset(IMemoable t); -#endif } } diff --git a/NBitcoin/BouncyCastle/crypto/digests/RipeMD160Digest.cs b/NBitcoin/BouncyCastle/crypto/digests/RipeMD160Digest.cs index 3d152c1ce2..9732b75186 100644 --- a/NBitcoin/BouncyCastle/crypto/digests/RipeMD160Digest.cs +++ b/NBitcoin/BouncyCastle/crypto/digests/RipeMD160Digest.cs @@ -590,7 +590,6 @@ internal override void ProcessBlock() X[i] = 0; } } -#if !NO_BC public override IMemoable Copy() { return new RipeMD160Digest(this); @@ -602,7 +601,6 @@ public override void Reset(IMemoable other) CopyIn(d); } -#endif } } diff --git a/NBitcoin/BouncyCastle/crypto/digests/Sha1Digest.cs b/NBitcoin/BouncyCastle/crypto/digests/Sha1Digest.cs index c5b652acad..2acc52a7b3 100644 --- a/NBitcoin/BouncyCastle/crypto/digests/Sha1Digest.cs +++ b/NBitcoin/BouncyCastle/crypto/digests/Sha1Digest.cs @@ -270,7 +270,6 @@ internal override void ProcessBlock() xOff = 0; Array.Clear(X, 0, 16); } -#if !NO_BC public override IMemoable Copy() { return new Sha1Digest(this); @@ -282,7 +281,6 @@ public override void Reset(IMemoable other) CopyIn(d); } -#endif } } #endif diff --git a/NBitcoin/BouncyCastle/crypto/digests/Sha256Digest.cs b/NBitcoin/BouncyCastle/crypto/digests/Sha256Digest.cs index 34a57d762c..38b0373e75 100644 --- a/NBitcoin/BouncyCastle/crypto/digests/Sha256Digest.cs +++ b/NBitcoin/BouncyCastle/crypto/digests/Sha256Digest.cs @@ -336,7 +336,6 @@ private static uint Theta1( 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; -#if !NO_BC public override IMemoable Copy() { return new Sha256Digest(this); @@ -348,6 +347,5 @@ public override void Reset(IMemoable other) CopyIn(d); } -#endif } } diff --git a/NBitcoin/Crypto/Cryptsharp/SCrypt.cs b/NBitcoin/Crypto/Cryptsharp/SCrypt.cs index 06851da8db..bd2b691b12 100644 --- a/NBitcoin/Crypto/Cryptsharp/SCrypt.cs +++ b/NBitcoin/Crypto/Cryptsharp/SCrypt.cs @@ -18,10 +18,10 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #endregion #if !NO_BC -using NBitcoin.BouncyCastle.Crypto.Parameters; using NBitcoin.BouncyCastle.Security; #endif using NBitcoin.Crypto.Internal; +using NBitcoin.BouncyCastle.Crypto.Parameters; using System; #if !USEBC using System.Security.Cryptography; diff --git a/NBitcoin/Crypto/Hashes.cs b/NBitcoin/Crypto/Hashes.cs index a404937299..068d16804c 100644 --- a/NBitcoin/Crypto/Hashes.cs +++ b/NBitcoin/Crypto/Hashes.cs @@ -12,6 +12,8 @@ #if !NONATIVEHASH using System.Security.Cryptography; +using NBitcoin.Crypto.digests; // use managed SHA implementations +using NBitcoin.BouncyCastle.Crypto.Parameters; // we'll need this to create HMACs based o the managed SHAs. #endif namespace NBitcoin.Crypto @@ -806,6 +808,7 @@ public static byte[] HMACSHA512(byte[] key, byte[] data) mac.DoFinal(result, 0); return result; } + #if HAS_SPAN public static bool HMACSHA512(byte[] key, ReadOnlySpan data, Span output, out int outputLength) { @@ -823,6 +826,7 @@ public static bool HMACSHA512(byte[] key, ReadOnlySpan data, Span ou return true; } #endif + public static byte[] HMACSHA256(byte[] key, byte[] data) { var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new Sha256Digest()); @@ -833,11 +837,46 @@ public static byte[] HMACSHA256(byte[] key, byte[] data) return result; } -#else +#elif NO_NATIVE_HMACSHA512 // There is a native hash, but no native HMAC. + public static byte[] HMACSHA512(byte[] key, byte[] data) + { + var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new ManagedSha512Digest()); + mac.Init(new KeyParameter(key)); + mac.BlockUpdate(data, 0, data.Length); + byte[] result = new byte[mac.GetMacSize()]; + mac.DoFinal(result, 0); + return result; + } + +#if HAS_SPAN + public static bool HMACSHA512(byte[] key, ReadOnlySpan data, Span output, out int outputLength) + { + var outputBuffer = HMACSHA512(key, data.ToArray()); + outputLength = output.Length; + + if (outputLength > output.Length) + return false; + + outputBuffer.CopyTo(output); + return true; + } +#endif + + public static byte[] HMACSHA256(byte[] key, byte[] data) + { + var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new ManagedSha256Digest()); + mac.Init(new KeyParameter(key)); + mac.BlockUpdate(data, 0, data.Length); + byte[] result = new byte[mac.GetMacSize()]; + mac.DoFinal(result, 0); + return result; + } +#else // There is no native hash and no native HMAC public static byte[] HMACSHA512(byte[] key, byte[] data) { return new HMACSHA512(key).ComputeHash(data); } + #if HAS_SPAN public static bool HMACSHA512(byte[] key, ReadOnlySpan data, Span output, out int outputLength) { @@ -845,11 +884,13 @@ public static bool HMACSHA512(byte[] key, ReadOnlySpan data, Span ou return hmac.TryComputeHash(data, output, out outputLength); } #endif + public static byte[] HMACSHA256(byte[] key, byte[] data) { return new HMACSHA256(key).ComputeHash(data); } #endif + #if HAS_SPAN public static void BIP32Hash(byte[] chainCode, uint nChild, byte header, Span data, Span output) { diff --git a/NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs b/NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs new file mode 100644 index 0000000000..0440e0fdac --- /dev/null +++ b/NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs @@ -0,0 +1,59 @@ +using NBitcoin.BouncyCastle.Crypto; +using System; +using System.Security.Cryptography; + +namespace NBitcoin.Crypto.digests +{ + /// + /// A wrapper around the native SHA256, implements BouncyCastle's IDigest interface in order + /// to be compatible with BouncyCastle's HMac implementation. + /// + internal class ManagedSha256Digest : IDigest, IDisposable + { + private const int DigestLength = 32; + SHA256Managed nativeSha256 = null; + byte[] input; + + public ManagedSha256Digest() + { + Reset(); + } + + public string AlgorithmName => "SHA-256"; + + public void BlockUpdate(byte[] input, int inOff, int length) + { + this.input = new byte[length]; + Array.Copy(input, inOff, this.input, 0, length); + } + + public int DoFinal(byte[] output, int outOff) + { + var hash = nativeSha256.ComputeHash(input, 0, input.Length); + Array.Copy(hash, 0, output, outOff, hash.Length); + Reset(); + return DigestLength; + } + + public int GetByteLength() => 64; + + public int GetDigestSize() => DigestLength; + + public void Reset() + { + nativeSha256?.Dispose(); + nativeSha256 = new SHA256Managed(); + input = null; + } + + public void Update(byte input) + { + throw new NotImplementedException("Code monkey didn't expect you would need this."); + } + + public void Dispose() + { + nativeSha256?.Dispose(); + } + } +} diff --git a/NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs b/NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs new file mode 100644 index 0000000000..f254b1f126 --- /dev/null +++ b/NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs @@ -0,0 +1,59 @@ +using NBitcoin.BouncyCastle.Crypto; +using System; +using System.Security.Cryptography; + +namespace NBitcoin.Crypto.digests +{ + /// + /// A wrapper around the native SHA512, implements BouncyCastle's IDigest interface in order + /// to be compatible with BouncyCastle's HMac implementation. + /// + internal class ManagedSha512Digest : IDigest, IDisposable + { + private const int DigestLength = 64; + SHA512Managed nativeSha512 = null; + byte[] input; + + public ManagedSha512Digest() + { + Reset(); + } + + public string AlgorithmName => "SHA-512"; + + public void BlockUpdate(byte[] input, int inOff, int length) + { + this.input = new byte[length]; + Array.Copy(input, inOff, this.input, 0, length); + } + + public int DoFinal(byte[] output, int outOff) + { + var hash = nativeSha512.ComputeHash(input, 0, input.Length); + Array.Copy(hash, 0, output, outOff, hash.Length); + Reset(); + return DigestLength; + } + + public int GetByteLength() => 64; + + public int GetDigestSize() => DigestLength; + + public void Reset() + { + nativeSha512?.Dispose(); + nativeSha512 = new SHA512Managed(); + input = null; + } + + public void Update(byte input) + { + throw new NotImplementedException("Code monkey didn't expect you would need this."); + } + + public void Dispose() + { + nativeSha512?.Dispose(); + } + } +} diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index 32f4c0656d..90d15f934e 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -87,6 +87,12 @@ + + + + + + diff --git a/NBitcoin/Secp256k1/Schnorr/ECPrivKey.cs b/NBitcoin/Secp256k1/Schnorr/ECPrivKey.cs index 887edf23bf..4b98f5dc3a 100644 --- a/NBitcoin/Secp256k1/Schnorr/ECPrivKey.cs +++ b/NBitcoin/Secp256k1/Schnorr/ECPrivKey.cs @@ -101,7 +101,7 @@ public bool TryGetNonce(Span nonce32, ReadOnlySpan msg32, ReadOnlySp #endif class SchnorrNonceFunction : INonceFunction { - byte[]? data = null; + readonly byte[]? data = null; public SchnorrNonceFunction(byte[]? nonceData = null) { this.data = nonceData; From e8b64ad1c35c89d5d1b26ed2236bce3e70388ffa Mon Sep 17 00:00:00 2001 From: jorn vanloofsvelt Date: Sat, 30 Jan 2021 18:31:53 +0100 Subject: [PATCH 2/4] Fix using statements according to target platform. Add guards (compilation symbols) to conditionally add using statements according to the target framework. --- NBitcoin/Crypto/Hashes.cs | 5 ++--- NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs | 8 ++++++-- NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs | 7 +++++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/NBitcoin/Crypto/Hashes.cs b/NBitcoin/Crypto/Hashes.cs index 068d16804c..29da270895 100644 --- a/NBitcoin/Crypto/Hashes.cs +++ b/NBitcoin/Crypto/Hashes.cs @@ -1,8 +1,9 @@ #if !NO_BC -using NBitcoin.BouncyCastle.Crypto.Parameters; using NBitcoin.BouncyCastle.Security; #endif +using NBitcoin.BouncyCastle.Crypto.Parameters; using NBitcoin.BouncyCastle.Crypto.Digests; +using NBitcoin.Crypto.NativeDigests; // Managed digests, compatible with BC's HMac algorithm. using System; using System.Collections.Generic; using System.IO; @@ -12,8 +13,6 @@ #if !NONATIVEHASH using System.Security.Cryptography; -using NBitcoin.Crypto.digests; // use managed SHA implementations -using NBitcoin.BouncyCastle.Crypto.Parameters; // we'll need this to create HMACs based o the managed SHAs. #endif namespace NBitcoin.Crypto diff --git a/NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs b/NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs index 0440e0fdac..446511d3c2 100644 --- a/NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs +++ b/NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs @@ -1,9 +1,12 @@ using NBitcoin.BouncyCastle.Crypto; using System; -using System.Security.Cryptography; -namespace NBitcoin.Crypto.digests +namespace NBitcoin.Crypto.NativeDigests { +#if !NETSTANDARD1X && !NONATIVEHASH + + using System.Security.Cryptography; + /// /// A wrapper around the native SHA256, implements BouncyCastle's IDigest interface in order /// to be compatible with BouncyCastle's HMac implementation. @@ -56,4 +59,5 @@ public void Dispose() nativeSha256?.Dispose(); } } +#endif } diff --git a/NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs b/NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs index f254b1f126..458c7c154e 100644 --- a/NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs +++ b/NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs @@ -1,9 +1,11 @@ using NBitcoin.BouncyCastle.Crypto; using System; -using System.Security.Cryptography; -namespace NBitcoin.Crypto.digests +namespace NBitcoin.Crypto.NativeDigests { +#if !NETSTANDARD1X && !NONATIVEHASH + using System.Security.Cryptography; + /// /// A wrapper around the native SHA512, implements BouncyCastle's IDigest interface in order /// to be compatible with BouncyCastle's HMac implementation. @@ -56,4 +58,5 @@ public void Dispose() nativeSha512?.Dispose(); } } +#endif } From eca59f5b244bfae5351760f2d190ca0f915dfdfe Mon Sep 17 00:00:00 2001 From: jorn vanloofsvelt Date: Mon, 1 Feb 2021 19:29:02 +0100 Subject: [PATCH 3/4] Properly dispose of managed digest instances. --- NBitcoin/BIP39/Mnemonic.cs | 12 +++++++++--- NBitcoin/Crypto/Hashes.cs | 30 ++++++++++++++++++------------ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/NBitcoin/BIP39/Mnemonic.cs b/NBitcoin/BIP39/Mnemonic.cs index 1fb15438ef..52a6e67fee 100644 --- a/NBitcoin/BIP39/Mnemonic.cs +++ b/NBitcoin/BIP39/Mnemonic.cs @@ -175,11 +175,17 @@ public byte[] DeriveSeed(string passphrase = null) #if NO_NATIVE_HMACSHA512 #if NONATIVEHASH var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new NBitcoin.BouncyCastle.Crypto.Digests.Sha512Digest()); -#else - var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new Crypto.digests.ManagedSha512Digest()); -#endif mac.Init(new NBitcoin.BouncyCastle.Crypto.Parameters.KeyParameter(bytes)); return Pbkdf2.ComputeDerivedKey(mac, salt, 2048, 64); +#else + using (var sha512 = new Crypto.NativeDigests.ManagedSha512Digest()) + { + var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(sha512); + mac.Init(new NBitcoin.BouncyCastle.Crypto.Parameters.KeyParameter(bytes)); + return Pbkdf2.ComputeDerivedKey(mac, salt, 2048, 64); + } +#endif + #elif NO_NATIVE_RFC2898_HMACSHA512 return NBitcoin.Crypto.Pbkdf2.ComputeDerivedKey(new System.Security.Cryptography.HMACSHA512(bytes), salt, 2048, 64); #else diff --git a/NBitcoin/Crypto/Hashes.cs b/NBitcoin/Crypto/Hashes.cs index 29da270895..8860051a9f 100644 --- a/NBitcoin/Crypto/Hashes.cs +++ b/NBitcoin/Crypto/Hashes.cs @@ -839,12 +839,15 @@ public static byte[] HMACSHA256(byte[] key, byte[] data) #elif NO_NATIVE_HMACSHA512 // There is a native hash, but no native HMAC. public static byte[] HMACSHA512(byte[] key, byte[] data) { - var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new ManagedSha512Digest()); - mac.Init(new KeyParameter(key)); - mac.BlockUpdate(data, 0, data.Length); - byte[] result = new byte[mac.GetMacSize()]; - mac.DoFinal(result, 0); - return result; + using (var sha512 = new ManagedSha512Digest()) + { + var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(sha512); + mac.Init(new KeyParameter(key)); + mac.BlockUpdate(data, 0, data.Length); + byte[] result = new byte[mac.GetMacSize()]; + mac.DoFinal(result, 0); + return result; + } } #if HAS_SPAN @@ -863,12 +866,15 @@ public static bool HMACSHA512(byte[] key, ReadOnlySpan data, Span ou public static byte[] HMACSHA256(byte[] key, byte[] data) { - var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new ManagedSha256Digest()); - mac.Init(new KeyParameter(key)); - mac.BlockUpdate(data, 0, data.Length); - byte[] result = new byte[mac.GetMacSize()]; - mac.DoFinal(result, 0); - return result; + using (var sha256 = new ManagedSha256Digest()) + { + var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(sha256); + mac.Init(new KeyParameter(key)); + mac.BlockUpdate(data, 0, data.Length); + byte[] result = new byte[mac.GetMacSize()]; + mac.DoFinal(result, 0); + return result; + } } #else // There is no native hash and no native HMAC public static byte[] HMACSHA512(byte[] key, byte[] data) From 8b1e49d67828426cb11084118df6b3d65f540564 Mon Sep 17 00:00:00 2001 From: jorn vanloofsvelt Date: Wed, 3 Feb 2021 22:14:53 +0100 Subject: [PATCH 4/4] Fix HMACSHA512 for NONATIVEHASH && HAS_SPAN --- NBitcoin/Crypto/Hashes.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/NBitcoin/Crypto/Hashes.cs b/NBitcoin/Crypto/Hashes.cs index 8860051a9f..606433f656 100644 --- a/NBitcoin/Crypto/Hashes.cs +++ b/NBitcoin/Crypto/Hashes.cs @@ -853,14 +853,17 @@ public static byte[] HMACSHA512(byte[] key, byte[] data) #if HAS_SPAN public static bool HMACSHA512(byte[] key, ReadOnlySpan data, Span output, out int outputLength) { - var outputBuffer = HMACSHA512(key, data.ToArray()); - outputLength = output.Length; - - if (outputLength > output.Length) + outputLength = 0; + var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new Sha512Digest()); + var macSize = mac.GetMacSize(); + if (output.Length < macSize) return false; - - outputBuffer.CopyTo(output); - return true; + mac.Init(new KeyParameter(key)); + mac.BlockUpdate(data.ToArray(), 0, data.Length); + byte[] result = new byte[macSize]; + mac.DoFinal(result, 0); + result.CopyTo(result); + outputLength = result.Length; } #endif