Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt Hashes.HMACSHA256() so that it uses native hashes when they are… #965

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions NBitcoin/BIP39/Mnemonic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,19 @@ 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());
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
Expand Down
7 changes: 1 addition & 6 deletions NBitcoin/BouncyCastle/crypto/digests/GeneralDigest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
}
}
2 changes: 0 additions & 2 deletions NBitcoin/BouncyCastle/crypto/digests/RipeMD160Digest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,6 @@ internal override void ProcessBlock()
X[i] = 0;
}
}
#if !NO_BC
public override IMemoable Copy()
{
return new RipeMD160Digest(this);
Expand All @@ -602,7 +601,6 @@ public override void Reset(IMemoable other)

CopyIn(d);
}
#endif
}

}
2 changes: 0 additions & 2 deletions NBitcoin/BouncyCastle/crypto/digests/Sha1Digest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -282,7 +281,6 @@ public override void Reset(IMemoable other)

CopyIn(d);
}
#endif
}
}
#endif
2 changes: 0 additions & 2 deletions NBitcoin/BouncyCastle/crypto/digests/Sha256Digest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -348,6 +347,5 @@ public override void Reset(IMemoable other)

CopyIn(d);
}
#endif
}
}
2 changes: 1 addition & 1 deletion NBitcoin/Crypto/Cryptsharp/SCrypt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
53 changes: 51 additions & 2 deletions NBitcoin/Crypto/Hashes.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -806,6 +807,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<byte> data, Span<byte> output, out int outputLength)
{
Expand All @@ -823,6 +825,7 @@ public static bool HMACSHA512(byte[] key, ReadOnlySpan<byte> data, Span<byte> ou
return true;
}
#endif

public static byte[] HMACSHA256(byte[] key, byte[] data)
{
var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new Sha256Digest());
Expand All @@ -833,23 +836,69 @@ 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)
{
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
public static bool HMACSHA512(byte[] key, ReadOnlySpan<byte> data, Span<byte> output, out int outputLength)
{
outputLength = 0;
var mac = new NBitcoin.BouncyCastle.Crypto.Macs.HMac(new Sha512Digest());
var macSize = mac.GetMacSize();
if (output.Length < macSize)
return false;
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

public static byte[] HMACSHA256(byte[] key, byte[] data)
{
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)
{
return new HMACSHA512(key).ComputeHash(data);
}

#if HAS_SPAN
public static bool HMACSHA512(byte[] key, ReadOnlySpan<byte> data, Span<byte> output, out int outputLength)
{
using var hmac = new HMACSHA512(key);
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<byte> data, Span<byte> output)
{
Expand Down
63 changes: 63 additions & 0 deletions NBitcoin/Crypto/NativeDigests/ManagedSha256Digest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using NBitcoin.BouncyCastle.Crypto;
using System;

namespace NBitcoin.Crypto.NativeDigests
{
#if !NETSTANDARD1X && !NONATIVEHASH

using System.Security.Cryptography;

/// <summary>
/// A wrapper around the native SHA256, implements BouncyCastle's IDigest interface in order
/// to be compatible with BouncyCastle's HMac implementation.
/// </summary>
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you instead rely on NBitcoin.Secp256k1.SHA256 class which properly implement DoFinal and BlockUpdate and is widely tested and memory efficient?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I'll take care of it tonight.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NicolasDorier I found the NBitcoin.Secpk1.SHA class, but it doesn't look like it implements the IDigest interface, nor the BlockUpdate/DoFinal methods.

Copy link
Collaborator

@NicolasDorier NicolasDorier Feb 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not saying to use it instead of ManagedSha256Digest, but to use it rather than that nativeSha256

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @NicolasDorier, I understand now. There is no NBitcoin.Secp512k1.SHA512 equivalent in the Secpk1 project though (to go along with the SHA256). Besides, wouldn't it be great if NBitcoin could rely on the framework's builtins?

It's not clear to me if DoFinal() and BlockUpdate() are BouncyCastle-specific interface methods, or if they are a more wider known concept. I'm not sure how to implement them correctly. I was hoping someone here would review it, and fill in the gaps for me.

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();
}
}
#endif
}
62 changes: 62 additions & 0 deletions NBitcoin/Crypto/NativeDigests/ManagedSha512Digest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using NBitcoin.BouncyCastle.Crypto;
using System;

namespace NBitcoin.Crypto.NativeDigests
{
#if !NETSTANDARD1X && !NONATIVEHASH
using System.Security.Cryptography;

/// <summary>
/// A wrapper around the native SHA512, implements BouncyCastle's IDigest interface in order
/// to be compatible with BouncyCastle's HMac implementation.
/// </summary>
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();
}
}
#endif
}
6 changes: 6 additions & 0 deletions NBitcoin/NBitcoin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@
<Compile Include="BouncyCastle\math\BigInteger.cs"></Compile>
<Compile Include="BouncyCastle\util\Arrays.cs"></Compile>
<Compile Include="BouncyCastle\util\Platform.cs"></Compile>
<Compile Include="BouncyCastle\util\IMemoable.cs"></Compile>
<Compile Include="BouncyCastle\crypto\IDigest.cs"></Compile>
<Compile Include="BouncyCastle\crypto\IMac.cs"></Compile>
<Compile Include="BouncyCastle\crypto\ICipherParameters.cs"></Compile>
<Compile Include="BouncyCastle\crypto\macs\HMac.cs"></Compile>
<Compile Include="BouncyCastle\crypto\parameters\KeyParameter.cs"></Compile>
<Compile Include="BouncyCastle\crypto\digests\RipeMD160Digest.cs"></Compile>
<Compile Include="BouncyCastle\crypto\digests\Sha1Digest.cs"></Compile>
<Compile Include="BouncyCastle\crypto\digests\Sha256Digest.cs"></Compile>
Expand Down
2 changes: 1 addition & 1 deletion NBitcoin/Secp256k1/Schnorr/ECPrivKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public bool TryGetNonce(Span<byte> nonce32, ReadOnlySpan<byte> msg32, ReadOnlySp
#endif
class SchnorrNonceFunction : INonceFunction
{
byte[]? data = null;
readonly byte[]? data = null;
public SchnorrNonceFunction(byte[]? nonceData = null)
{
this.data = nonceData;
Expand Down