Added async methods

This commit is contained in:
Thorsten Sommer 2020-01-07 12:16:59 +01:00
parent b5f780343b
commit d1305e606c
4 changed files with 281 additions and 20 deletions

View File

@ -33,6 +33,28 @@ namespace Ed25519
return point;
}
public static EdPoint DecodePoint(byte[] pointBytes)
{
var y = new BigInteger(pointBytes) & Constants.U_N;
var x = y.RecoverX();
if ((x.IsEven ? 0 : 1) != pointBytes.GetBit(Constants.BIT_LENGTH - 1))
{
x = Constants.Q - x;
}
var point = new EdPoint
{
X = x,
Y = y,
};
if (!point.IsOnCurve())
throw new ArgumentException("Decoding point is not on curve");
return point;
}
public ReadOnlySpan<byte> EncodePoint()
{
var nout = this.Y.EncodeInt();

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Encrypter;
namespace Ed25519
@ -16,14 +17,36 @@ namespace Ed25519
return sha512.ComputeHash(data.ToArray());
}
internal static async Task<byte[]> ComputeHashAsync(this byte[] data)
{
return await Task.Run(() =>
{
using var sha512 = SHA512.Create();
return sha512.ComputeHash(data);
});
}
internal static ReadOnlySpan<byte> ComputeHash(this Stream inputStream)
{
inputStream.Seek(0, SeekOrigin.Begin);
if(inputStream.CanSeek)
inputStream.Seek(0, SeekOrigin.Begin);
using var sha512 = SHA512.Create();
return sha512.ComputeHash(inputStream);
}
internal static async Task<byte[]> ComputeHashAsync(this Stream inputStream)
{
return await Task.Run(() =>
{
if(inputStream.CanSeek)
inputStream.Seek(0, SeekOrigin.Begin);
using var sha512 = SHA512.Create();
return sha512.ComputeHash(inputStream);
});
}
internal static BigInteger Mod(this BigInteger number, BigInteger modulo)
{
var result = number % modulo;
@ -31,10 +54,7 @@ namespace Ed25519
return result;
}
internal static BigInteger Inv(this BigInteger number)
{
return number.ExpMod(Constants.QM2, Constants.Q);
}
internal static BigInteger Inv(this BigInteger number) => number.ExpMod(Constants.QM2, Constants.Q);
internal static BigInteger RecoverX(this BigInteger y)
{
@ -96,10 +116,9 @@ namespace Ed25519
return nout;
}
internal static BigInteger DecodeInt(this ReadOnlySpan<byte> data)
{
return new BigInteger(data) & Constants.U_N;
}
internal static BigInteger DecodeInt(this ReadOnlySpan<byte> data) => new BigInteger(data) & Constants.U_N;
internal static BigInteger DecodeInt(this byte[] data) => new BigInteger(data) & Constants.U_N;
internal static BigInteger HashInt(this MemoryStream data)
{
@ -120,16 +139,34 @@ namespace Ed25519
return hashSum;
}
internal static int GetBit(this ReadOnlySpan<byte> data, int index)
internal static async Task<BigInteger> HashIntAsync(this MemoryStream data)
{
return data[index / 8] >> (index % 8) & 1;
data.Seek(0, SeekOrigin.Begin);
var hash = await data.ComputeHashAsync();
var hashSum = BigInteger.Zero;
for (var i = 0; i < 2 * Constants.BIT_LENGTH; i++)
{
var bit = hash.GetBit(i);
if (bit != 0)
{
hashSum += Constants.TWO_POW_CACHE[i];
}
}
return hashSum;
}
internal static int GetBit(this ReadOnlySpan<byte> data, int index) => data[index / 8] >> (index % 8) & 1;
internal static int GetBit(this byte[] data, int index) => data[index / 8] >> (index % 8) & 1;
/// <summary>
/// Extracts the public key out of the given private key. The private key must be valid, i.e. must consist of 32 bytes.
/// </summary>
/// <param name="privateKey">The private key.</param>
/// <returns>The corresponding public key.</returns>
/// <returns>The corresponding public key. Returns an empty ReadOnlySpan, when the private key's length is incorrect.</returns>
public static ReadOnlySpan<byte> ExtractPublicKey(this ReadOnlySpan<byte> privateKey)
{
if(privateKey.Length != 32)
@ -155,9 +192,31 @@ namespace Ed25519
/// </summary>
/// <param name="privateKey">The private key.</param>
/// <returns>The corresponding public key.</returns>
public static ReadOnlySpan<byte> ExtractPublicKey(this Span<byte> privateKey)
public static ReadOnlySpan<byte> ExtractPublicKey(this Span<byte> privateKey) => new ReadOnlySpan<byte>(privateKey.ToArray()).ExtractPublicKey();
/// <summary>
/// Extracts the public key out of the given private key. The private key must be valid, i.e. must consist of 32 bytes.
/// </summary>
/// <param name="privateKey">The private key.</param>
/// <returns>The corresponding public key. Returns null, when the private key's length is incorrect.</returns>
public static async Task<byte[]> ExtractPublicKeyAsync(this byte[] privateKey)
{
return new ReadOnlySpan<byte>(privateKey.ToArray()).ExtractPublicKey();
if (privateKey.Length != 32)
return null;
var hash = await privateKey.ComputeHashAsync();
var a = Constants.TWO_POW_BIT_LENGTH_MINUS_TWO;
for (var i = 3; i < Constants.BIT_LENGTH - 2; i++)
{
var bit = hash.GetBit(i);
if (bit != 0)
{
a += Constants.TWO_POW_CACHE[i];
}
}
var bigA = Constants.B.ScalarMul(a);
return bigA.EncodePoint().ToArray();
}
/// <summary>
@ -165,10 +224,14 @@ namespace Ed25519
/// </summary>
/// <param name="key">The chosen key</param>
/// <param name="filename">The desired file</param>
public static void WriteKey(this ReadOnlySpan<byte> key, string filename)
{
File.WriteAllBytes(filename, key.ToArray());
}
public static void WriteKey(this ReadOnlySpan<byte> key, string filename) => File.WriteAllBytes(filename, key.ToArray());
/// <summary>
/// Writes a given key to a file.
/// </summary>
/// <param name="key">The chosen key</param>
/// <param name="filename">The desired file</param>
public static async Task WriteKeyAsync(this byte[] key, string filename) => await File.WriteAllBytesAsync(filename, key);
/// <summary>
/// Decrypts an encrypted private key.
@ -184,5 +247,20 @@ namespace Ed25519
return outputStream.ToArray();
}
/// <summary>
/// Decrypts an encrypted private key.
/// </summary>
/// <param name="privateKey">The encrypted private key.</param>
/// <param name="password">The matching password.</param>
/// <returns>The decrypted private key.</returns>
public static async Task<byte[]> DecryptPrivateKeyAsync(this byte[] privateKey, string password)
{
await using var inputStream = new MemoryStream(privateKey);
await using var outputStream = new MemoryStream();
await CryptoProcessor.Decrypt(inputStream, outputStream, password);
return outputStream.ToArray();
}
}
}

View File

@ -34,13 +34,42 @@ namespace Ed25519
return privateKey;
}
/// <summary>
/// Generates a random private key.
/// </summary>
/// <param name="password">An optional password to encrypt the key.</param>
/// <returns>The private key.</returns>
public static async Task<byte[]> GeneratePrivateKeyAsync(string password = "")
{
var privateKey = new byte[32];
await Task.Run(() => RandomNumberGenerator.Create().GetBytes(privateKey));
if (!string.IsNullOrWhiteSpace(password))
{
await using var inputStream = new MemoryStream(privateKey, false);
await using var outputStream = new MemoryStream();
await CryptoProcessor.Encrypt(inputStream, outputStream, password);
privateKey = outputStream.ToArray();
}
return privateKey;
}
/// <summary>
/// Loads a key (public or private key) from a file.
/// </summary>
/// <param name="filename">The entire path to the corresponding file.</param>
/// <returns>The desired key.</returns>
/// <returns>The desired key. Returns an empty ReadOnlySpan, when the file does not exist.</returns>
public static ReadOnlySpan<byte> LoadKey(string filename) => !File.Exists(filename) ? ReadOnlySpan<byte>.Empty : File.ReadAllBytes(filename);
/// <summary>
/// Loads a key (public or private key) from a file.
/// </summary>
/// <param name="filename">The entire path to the corresponding file.</param>
/// <returns>The desired key. Returns null, when the file does not exist.</returns>
public static async Task<byte[]> LoadKeyAsync(string filename) => !File.Exists(filename) ? null : await File.ReadAllBytesAsync(filename);
/// <summary>
/// Signs a message with the given private and public keys.
/// </summary>
@ -101,6 +130,66 @@ namespace Ed25519
}
}
/// <summary>
/// Signs a message with the given private and public keys.
/// </summary>
/// <param name="message">The message to sign.</param>
/// <param name="privateKey">The desired private key.</param>
/// <param name="publicKey">The corresponding public key.</param>
/// <returns>The derived signature. It's length is 64 bytes.</returns>
public static async Task<byte[]> SignAsync(byte[] message, byte[] privateKey, byte[] publicKey)
{
if (privateKey.Length != Constants.BIT_LENGTH / 8)
throw new ArgumentException($"Private key length is wrong. Got {privateKey.Length} instead of {Constants.BIT_LENGTH / 8}.");
if (publicKey.Length != Constants.BIT_LENGTH / 8)
throw new ArgumentException($"Public key length is wrong. Got {publicKey.Length} instead of {Constants.BIT_LENGTH / 8}.");
var privateKeyHash = await privateKey.ComputeHashAsync();
var privateKeyBits = Constants.TWO_POW_BIT_LENGTH_MINUS_TWO;
for (var i = 3; i < Constants.BIT_LENGTH - 2; i++)
{
var bit = privateKeyHash.GetBit(i);
if (bit != 0)
{
privateKeyBits += Constants.TWO_POW_CACHE[i];
}
}
BigInteger r;
await using (var rSub = new MemoryStream((Constants.BIT_LENGTH / 8) + message.Length))
{
await rSub.WriteAsync(privateKeyHash[(Constants.BIT_LENGTH / 8)..]);
await rSub.WriteAsync(message);
await rSub.FlushAsync();
r = await rSub.HashIntAsync();
}
var bigR = Constants.B.ScalarMul(r);
BigInteger s;
var encodedBigR = bigR.EncodePoint().ToArray();
await using (var sTemp = new MemoryStream(encodedBigR.Length + publicKey.Length + message.Length))
{
await sTemp.WriteAsync(encodedBigR);
await sTemp.WriteAsync(publicKey);
await sTemp.WriteAsync(message);
await sTemp.FlushAsync();
s = (r + await sTemp.HashIntAsync() * privateKeyBits).Mod(Constants.L);
}
await using (var nOut = new MemoryStream(64))
{
await nOut.WriteAsync(encodedBigR);
await nOut.WriteAsync(s.EncodeInt().ToArray());
await nOut.FlushAsync();
return nOut.ToArray();
}
}
/// <summary>
/// Validates a given signature by means of the given public key.
/// </summary>
@ -141,5 +230,46 @@ namespace Ed25519
return ra.X.Equals(rb.X) && ra.Y.Equals(rb.Y);
}
/// <summary>
/// Validates a given signature by means of the given public key.
/// </summary>
/// <param name="signature">The signature to validate.</param>
/// <param name="message">The corresponding message.</param>
/// <param name="publicKey">The used public key.</param>
/// <returns>Returns true when the combination of signature + message is valid.</returns>
public static async Task<bool> ValidateAsync(byte[] signature, byte[] message, byte[] publicKey)
{
if (signature.Length != Constants.BIT_LENGTH / 4)
throw new ArgumentException($"Signature length is wrong. Got {signature.Length} instead of {Constants.BIT_LENGTH / 4}.");
if (publicKey.Length != Constants.BIT_LENGTH / 8)
throw new ArgumentException($"Public key length is wrong. Got {publicKey.Length} instead of {Constants.BIT_LENGTH / 8}.");
var signatureSliceLeft = signature[..(Constants.BIT_LENGTH / 8)];
var pointSignatureLeft = EdPoint.DecodePoint(signatureSliceLeft);
var pointPublicKey = EdPoint.DecodePoint(publicKey);
var signatureSliceRight = signature[(Constants.BIT_LENGTH / 8)..];
var signatureRight = signatureSliceRight.DecodeInt();
var encodedSignatureLeftPoint = pointSignatureLeft.EncodePoint().ToArray();
BigInteger h;
await using (var sTemp = new MemoryStream(encodedSignatureLeftPoint.Length + publicKey.Length + message.Length))
{
await sTemp.WriteAsync(encodedSignatureLeftPoint);
await sTemp.WriteAsync(publicKey);
await sTemp.WriteAsync(message);
await sTemp.FlushAsync();
h = await sTemp.HashIntAsync();
}
var ra = Constants.B.ScalarMul(signatureRight);
var ah = pointPublicKey.ScalarMul(h);
var rb = pointSignatureLeft.Edwards(ah);
return ra.X.Equals(rb.X) && ra.Y.Equals(rb.Y);
}
}
}

View File

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
namespace Ed25519
@ -191,7 +192,7 @@ namespace Ed25519
}
[Test]
public void TestWritingKeys()
public void TestWritingAndLoadingKeys()
{
var tempFilePrivate = Path.GetTempFileName();
var tempFilePublic = Path.GetTempFileName();
@ -231,6 +232,22 @@ namespace Ed25519
Assert.That(publicKeyFromDecrypted.ToArray(), Is.Not.EqualTo(publicKeyFromEncrypted.ToArray()));
}
[Test]
public void TestExtractPublicKeyFromTooShortPrivateKey()
{
var privateKey= Signer.GeneratePrivateKey()[..6]; // Six first bytes from private key!
var publicKey = privateKey.ExtractPublicKey();
Assert.That(publicKey == ReadOnlySpan<byte>.Empty, Is.True);
}
[Test]
public async Task TestExtractPublicKeyFromTooShortPrivateKeyAsync()
{
var privateKey = (await Signer.GeneratePrivateKeyAsync())[..6]; // Six first bytes from private key!
var publicKey = await privateKey.ExtractPublicKeyAsync();
Assert.That(publicKey, Is.Null);
}
[Test]
public void TestSigningBehaviourPrivateKeyWithPassword()
{
@ -308,6 +325,20 @@ namespace Ed25519
Assert.That(true);
}
[Test]
public void TestLoadingNotExistingKey()
{
var key = Signer.LoadKey(Guid.NewGuid().ToString());
Assert.That(key == ReadOnlySpan<byte>.Empty, Is.True);
}
[Test]
public async Task TestLoadingNotExistingKeyAsync()
{
var key = await Signer.LoadKeyAsync(Guid.NewGuid().ToString());
Assert.That(key, Is.Null);
}
// See https://tools.ietf.org/html/rfc8032#section-7.1
[Test]
public void TestRFC8032Test01EmptyMessage()