Implemented key handling
This commit is contained in:
parent
0707aa9cdc
commit
744bcd217f
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Ed25519;
|
using Ed25519;
|
||||||
@ -30,7 +31,7 @@ namespace Ed25519_Tests
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Signer.Sign(message, privateKey, publicKey);
|
Signer.Sign(message, privateKey, publicKey);
|
||||||
Assert.That(false);
|
Assert.Fail("Should not be reached!");
|
||||||
}
|
}
|
||||||
catch (ArgumentException e)
|
catch (ArgumentException e)
|
||||||
{
|
{
|
||||||
@ -126,6 +127,127 @@ namespace Ed25519_Tests
|
|||||||
Assert.That(validationResult, Is.True);
|
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
|
// See https://tools.ietf.org/html/rfc8032#section-7.1
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRFC8032Test01EmptyMessage()
|
public void TestRFC8032Test01EmptyMessage()
|
||||||
|
@ -24,4 +24,8 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Encrypter" Version="1.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Encrypter;
|
||||||
|
|
||||||
namespace Ed25519
|
namespace Ed25519
|
||||||
{
|
{
|
||||||
@ -108,8 +109,16 @@ namespace Ed25519
|
|||||||
return data[index / 8] >> (index % 8) & 1;
|
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)
|
public static ReadOnlySpan<byte> ExtractPublicKey(this ReadOnlySpan<byte> privateKey)
|
||||||
{
|
{
|
||||||
|
if(privateKey.Length != 32)
|
||||||
|
return ReadOnlySpan<byte>.Empty;
|
||||||
|
|
||||||
var hash = privateKey.ComputeHash();
|
var hash = privateKey.ComputeHash();
|
||||||
var a = Constants.TWO_POW_BIT_LENGTH_MINUS_TWO;
|
var a = Constants.TWO_POW_BIT_LENGTH_MINUS_TWO;
|
||||||
for (var i = 3; i < Constants.BIT_LENGTH - 2; i++)
|
for (var i = 3; i < Constants.BIT_LENGTH - 2; i++)
|
||||||
@ -125,9 +134,39 @@ namespace Ed25519
|
|||||||
return bigA.EncodePoint();
|
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)
|
public static ReadOnlySpan<byte> ExtractPublicKey(this Span<byte> privateKey)
|
||||||
{
|
{
|
||||||
return new ReadOnlySpan<byte>(privateKey.ToArray()).ExtractPublicKey();
|
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.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Encrypter;
|
||||||
|
|
||||||
namespace Ed25519
|
namespace Ed25519
|
||||||
{
|
{
|
||||||
public static class Signer
|
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)
|
public static ReadOnlySpan<byte> Sign(ReadOnlySpan<byte> message, ReadOnlySpan<byte> privateKey, ReadOnlySpan<byte> publicKey)
|
||||||
{
|
{
|
||||||
if(privateKey.Length != Constants.BIT_LENGTH / 8)
|
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)
|
if (publicKey.Length != Constants.BIT_LENGTH / 8)
|
||||||
throw new ArgumentException($"Public key length is wrong. Got {publicKey.Length} instead of {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