Implemented key handling
This commit is contained in:
parent
0707aa9cdc
commit
744bcd217f
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Ed25519;
|
||||
@ -30,7 +31,7 @@ namespace Ed25519_Tests
|
||||
try
|
||||
{
|
||||
Signer.Sign(message, privateKey, publicKey);
|
||||
Assert.That(false);
|
||||
Assert.Fail("Should not be reached!");
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
@ -126,6 +127,127 @@ namespace Ed25519_Tests
|
||||
Assert.That(validationResult, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyGeneratorWithoutPassword()
|
||||
{
|
||||
var privateKey = Signer.GeneratePrivateKey();
|
||||
var publicKey = privateKey.ExtractPublicKey();
|
||||
|
||||
Assert.That(privateKey.Length, Is.EqualTo(32));
|
||||
Assert.That(publicKey.Length, Is.EqualTo(32));
|
||||
Assert.That(privateKey.ToArray(), Is.Not.EqualTo(publicKey.ToArray()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyGeneratorWithPassword()
|
||||
{
|
||||
var password = "test password";
|
||||
var privateKeyEncrypted = Signer.GeneratePrivateKey(password);
|
||||
var privateKeyDecrypted = privateKeyEncrypted.DecryptPrivateKey(password);
|
||||
var publicKey = privateKeyDecrypted.ExtractPublicKey();
|
||||
|
||||
Assert.That(privateKeyEncrypted.Length, Is.GreaterThan(32));
|
||||
Assert.That(privateKeyDecrypted.Length, Is.EqualTo(32));
|
||||
Assert.That(publicKey.Length, Is.EqualTo(32));
|
||||
Assert.That(privateKeyEncrypted.ToArray(), Is.Not.EqualTo(privateKeyDecrypted.ToArray()));
|
||||
Assert.That(privateKeyEncrypted.ToArray(), Is.Not.EqualTo(publicKey.ToArray()));
|
||||
Assert.That(privateKeyDecrypted.ToArray(), Is.Not.EqualTo(publicKey.ToArray()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleKeysWithoutPassword()
|
||||
{
|
||||
var privateKey1 = Signer.GeneratePrivateKey();
|
||||
var privateKey2 = Signer.GeneratePrivateKey();
|
||||
var privateKey3 = Signer.GeneratePrivateKey();
|
||||
|
||||
Assert.That(privateKey1.ToArray(), Is.Not.EqualTo(privateKey2.ToArray()));
|
||||
Assert.That(privateKey1.ToArray(), Is.Not.EqualTo(privateKey3.ToArray()));
|
||||
Assert.That(privateKey2.ToArray(), Is.Not.EqualTo(privateKey3.ToArray()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleKeysWithPassword()
|
||||
{
|
||||
var password = "test password";
|
||||
var privateKey1 = Signer.GeneratePrivateKey(password);
|
||||
var privateKey2 = Signer.GeneratePrivateKey(password);
|
||||
var privateKey3 = Signer.GeneratePrivateKey(password);
|
||||
|
||||
Assert.That(privateKey1.ToArray(), Is.Not.EqualTo(privateKey2.ToArray()));
|
||||
Assert.That(privateKey1.ToArray(), Is.Not.EqualTo(privateKey3.ToArray()));
|
||||
Assert.That(privateKey2.ToArray(), Is.Not.EqualTo(privateKey3.ToArray()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPublicKeyFromRandomData()
|
||||
{
|
||||
var privateKey = new byte[] { 0x00, 0xac, 0x48 }.AsSpan();
|
||||
var publicKey = privateKey.ExtractPublicKey();
|
||||
|
||||
Assert.That(privateKey.Length, Is.EqualTo(3));
|
||||
Assert.That(publicKey.Length, Is.EqualTo(0));
|
||||
Assert.That(publicKey.IsEmpty, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWritingKeys()
|
||||
{
|
||||
var tempFilePrivate = Path.GetTempFileName();
|
||||
var tempFilePublic = Path.GetTempFileName();
|
||||
|
||||
try
|
||||
{
|
||||
var privateKey = Signer.GeneratePrivateKey();
|
||||
var publicKey = privateKey.ExtractPublicKey();
|
||||
|
||||
privateKey.WriteKey(tempFilePrivate);
|
||||
publicKey.WriteKey(tempFilePublic);
|
||||
|
||||
var reloadPrivateKey = Signer.LoadKey(tempFilePrivate);
|
||||
var reloadPublicKey = Signer.LoadKey(tempFilePublic);
|
||||
|
||||
Assert.That(reloadPublicKey.Length, Is.EqualTo(32));
|
||||
Assert.That(reloadPrivateKey.Length, Is.EqualTo(32));
|
||||
|
||||
Assert.That(reloadPublicKey.ToArray(), Is.EqualTo(publicKey.ToArray()));
|
||||
Assert.That(reloadPrivateKey.ToArray(), Is.EqualTo(privateKey.ToArray()));
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempFilePublic);
|
||||
File.Delete(tempFilePrivate);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExtractPublicKeyFromEncryptedPrivateKey()
|
||||
{
|
||||
var privateKeyEncrypted = Signer.GeneratePrivateKey("secret password");
|
||||
var privateKeyDecrypted = privateKeyEncrypted.DecryptPrivateKey("secret password");
|
||||
var publicKeyFromDecrypted = privateKeyDecrypted.ExtractPublicKey();
|
||||
var publicKeyFromEncrypted = privateKeyEncrypted.ExtractPublicKey();
|
||||
|
||||
Assert.That(publicKeyFromDecrypted.ToArray(), Is.Not.EqualTo(publicKeyFromEncrypted.ToArray()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSigningBehaviourPrivateKeyWithPassword()
|
||||
{
|
||||
var privateKeyEncrypted = Signer.GeneratePrivateKey("secret password");
|
||||
var privateKeyDecrypted = privateKeyEncrypted.DecryptPrivateKey("secret password");
|
||||
var publicKey = privateKeyDecrypted.ExtractPublicKey();
|
||||
var message = Encoding.UTF8.GetBytes("This is a test message.");
|
||||
var signaturePrivateKeyDecrypted = Signer.Sign(message, privateKeyDecrypted, publicKey);
|
||||
var signaturePrivateKeyEncrypted = Signer.Sign(message, privateKeyEncrypted[..32], publicKey); // Note: The encrypted private key is 64 bytes long!
|
||||
var validationDecrypted = Signer.Validate(signaturePrivateKeyDecrypted, message, publicKey);
|
||||
var validationEncrypted = Signer.Validate(signaturePrivateKeyEncrypted, message, publicKey);
|
||||
|
||||
Assert.That(validationDecrypted, Is.True); // This is the intended behaviour. The public key was derived from the decrypted private key.
|
||||
Assert.That(validationEncrypted, Is.False); // This should fail, because the public key does not match the *encrypted* private key!
|
||||
Assert.That(signaturePrivateKeyEncrypted.ToArray(), Is.Not.EqualTo(signaturePrivateKeyDecrypted.ToArray()));
|
||||
}
|
||||
|
||||
// See https://tools.ietf.org/html/rfc8032#section-7.1
|
||||
[Test]
|
||||
public void TestRFC8032Test01EmptyMessage()
|
||||
|
@ -24,4 +24,8 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Encrypter" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Encrypter;
|
||||
|
||||
namespace Ed25519
|
||||
{
|
||||
@ -108,8 +109,16 @@ namespace Ed25519
|
||||
return 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>
|
||||
public static ReadOnlySpan<byte> ExtractPublicKey(this ReadOnlySpan<byte> privateKey)
|
||||
{
|
||||
if(privateKey.Length != 32)
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
|
||||
var hash = privateKey.ComputeHash();
|
||||
var a = Constants.TWO_POW_BIT_LENGTH_MINUS_TWO;
|
||||
for (var i = 3; i < Constants.BIT_LENGTH - 2; i++)
|
||||
@ -125,9 +134,39 @@ namespace Ed25519
|
||||
return bigA.EncodePoint();
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static ReadOnlySpan<byte> ExtractPublicKey(this Span<byte> privateKey)
|
||||
{
|
||||
return new ReadOnlySpan<byte>(privateKey.ToArray()).ExtractPublicKey();
|
||||
}
|
||||
|
||||
/// <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 void WriteKey(this ReadOnlySpan<byte> key, string filename)
|
||||
{
|
||||
File.WriteAllBytes(filename, key.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 ReadOnlySpan<byte> DecryptPrivateKey(this ReadOnlySpan<byte> privateKey, string password)
|
||||
{
|
||||
using var inputStream = new MemoryStream(privateKey.ToArray());
|
||||
using var outputStream = new MemoryStream();
|
||||
CryptoProcessor.Decrypt(inputStream, outputStream, password).Wait();
|
||||
|
||||
return outputStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,49 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Encrypter;
|
||||
|
||||
namespace Ed25519
|
||||
{
|
||||
public static class Signer
|
||||
{
|
||||
/// <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 ReadOnlySpan<byte> GeneratePrivateKey(string password = "")
|
||||
{
|
||||
var privateKey = new Span<byte>(new byte[32]);
|
||||
RandomNumberGenerator.Create().GetBytes(privateKey);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
using var inputStream = new MemoryStream(privateKey.ToArray(), false);
|
||||
using var outputStream = new MemoryStream();
|
||||
|
||||
CryptoProcessor.Encrypt(inputStream, outputStream, password).Wait();
|
||||
privateKey = new Span<byte>(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>
|
||||
public static ReadOnlySpan<byte> LoadKey(string filename) => !File.Exists(filename) ? ReadOnlySpan<byte>.Empty : File.ReadAllBytes(filename);
|
||||
|
||||
public static ReadOnlySpan<byte> Sign(ReadOnlySpan<byte> message, ReadOnlySpan<byte> privateKey, ReadOnlySpan<byte> publicKey)
|
||||
{
|
||||
if(privateKey.Length != Constants.BIT_LENGTH / 8)
|
||||
throw new ArgumentException($"Private key length is wrong. Got {publicKey.Length} instead of {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}.");
|
||||
|
Loading…
Reference in New Issue
Block a user