Skip to content

Commit

Permalink
new constructor from an externally provided safe prime + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bazzilic committed May 25, 2022
1 parent ea5aa6e commit bcc2204
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 29 deletions.
92 changes: 63 additions & 29 deletions src/Aprismatic.ElGamal/ElGamal.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using Aprismatic.ElGamal.Homomorphism;
Expand All @@ -19,12 +20,40 @@ public class ElGamal : AsymmetricAlgorithm
public int CiphertextLength => keyStruct.CiphertextLength;


#region Constructors & Key Generaion
// TODO: Constructors should allow to specify MaxPlaintextBits
public ElGamal(int keySize, int precomputedQueueSize = 10) // TODO: Constructor should probably optionally accept an RNG
{
LegalKeySizesValue = new[] { new KeySizes(384, 1088, 8) };
KeySizeValue = keySize; // TODO: Validate that key is of legal size
keyStruct = CreateKeyPair(ElGamalKeyDefaults.DefaultMaxPlaintextBits);
KeySizeValue = keySize;

if (!LegalKeySizesValue.Any(x => x.MinSize <= KeySizeValue && KeySizeValue <= x.MaxSize && (KeySizeValue - x.MinSize) % x.SkipSize == 0))
throw new ArgumentException("Key size is not supported by this algorithm.");

using var rng = RandomNumberGenerator.Create();
var P = BigInteger.Zero.GenSafePseudoPrime(KeySizeValue, 16, rng); // Generate a large safe prime number P of key length KeySizeValue

keyStruct = CreateKeyPair(P, ElGamalKeyDefaults.DefaultMaxPlaintextBits, rng);

Encryptor = new ElGamalEncryptor(keyStruct, precomputedQueueSize);
Decryptor = new ElGamalDecryptor(keyStruct);
}

public ElGamal(BigInteger safePrimeModulus, int precomputedQueueSize = 10)
{
LegalKeySizesValue = new[] { new KeySizes(384, 1088, 8) };
KeySizeValue = safePrimeModulus.BitCount();

if (!LegalKeySizesValue.Any(x => x.MinSize <= KeySizeValue && KeySizeValue <= x.MaxSize && (KeySizeValue - x.MinSize) % x.SkipSize == 0))
throw new ArgumentException("Key size is not supported by this algorithm.");

using var rng = RandomNumberGenerator.Create();

if (! (safePrimeModulus.IsProbablePrime(4, rng) && ((safePrimeModulus - BigInteger.One) >> 1).IsProbablePrime(4, rng)) )
throw new ArgumentException("Provided prime is not a safe prime.");

keyStruct = CreateKeyPair(safePrimeModulus, ElGamalKeyDefaults.DefaultMaxPlaintextBits, rng);

Encryptor = new ElGamalEncryptor(keyStruct, precomputedQueueSize);
Decryptor = new ElGamalDecryptor(keyStruct);
}
Expand All @@ -41,7 +70,10 @@ public ElGamal(ElGamalParameters prms, int precomputedQueueSize = 10) // TODO: C
prms.MaxPlaintextBits
);

KeySizeValue = keyStruct.PLength * 8; // TODO: Validate that key is of legal size
KeySizeValue = keyStruct.P.BitCount();

if (!LegalKeySizesValue.Any(x => x.MinSize <= KeySizeValue && KeySizeValue <= x.MaxSize && (KeySizeValue - x.MinSize) % x.SkipSize == 0))
throw new ArgumentException("Key size is not supported by this algorithm.");

Encryptor = new ElGamalEncryptor(keyStruct, precomputedQueueSize);
Decryptor = new ElGamalDecryptor(keyStruct);
Expand All @@ -50,17 +82,12 @@ public ElGamal(ElGamalParameters prms, int precomputedQueueSize = 10) // TODO: C
public ElGamal(string xml, int precomputedQueueSize = 10) : this(ElGamalParameters.FromXml(xml), precomputedQueueSize)
{ }

private ElGamalKeyStruct CreateKeyPair(int maxptbits) // TODO: This method should probably move to KeyStruct
private ElGamalKeyStruct CreateKeyPair(BigInteger P, int maxptbits, RandomNumberGenerator rng) // TODO: This method should probably move to KeyStruct
{
// Good reading on the topic: https://ibm.github.io/system-security-research-updates/2021/07/20/insecurity-elgamal-pt1

BigInteger P, G, Y, X;
BigInteger G, Y, X;
var bitwo = new BigInteger(2);

using var rng = RandomNumberGenerator.Create();

// Generate a large safe prime number P of key length KeySizeValue
P = BigInteger.Zero.GenSafePseudoPrime(KeySizeValue, 16, rng);
var PminusOne = P - BigInteger.One;
var Q = PminusOne / bitwo;

Expand All @@ -81,26 +108,9 @@ private ElGamalKeyStruct CreateKeyPair(int maxptbits) // TODO: This method shoul

return new ElGamalKeyStruct(P, G, Y, X, maxptbits);
}
#endregion

// TODO: Consider moving Encode and Decode to a separate class library or to Homomorphism. This way, plaintext operations can be moved down to Homomorphism library
public BigInteger Encode(BigInteger message) // TODO: Add tests now that this method is public
{
if (BigInteger.Abs(message) > keyStruct.MaxEncryptableValue)
throw new ArgumentException($"Numerator or denominator of the fraction to encrypt are too large; should be |m| < 2^{keyStruct.MaxPlaintextBits - 1}");

if (message.Sign < 0)
return keyStruct.MaxRawPlaintext + message + BigInteger.One;
return message;
}

public BigInteger Decode(BigInteger encodedMessage) // TODO: Add tests now that this method is public
{
encodedMessage %= keyStruct.MaxRawPlaintext + BigInteger.One;
if (encodedMessage > keyStruct.MaxEncryptableValue)
return encodedMessage - keyStruct.MaxRawPlaintext - BigInteger.One;
return encodedMessage;
}

#region Encryption & Decryprtion
public byte[] EncryptData(BigFraction message)
{
var ctbs = keyStruct.CiphertextBlocksize;
Expand All @@ -125,6 +135,27 @@ public BigFraction DecryptData(ReadOnlySpan<byte> data)

return res;
}
#endregion

#region Homomorphic Properties
// TODO: Consider moving Encode and Decode to a separate class library or to Homomorphism. This way, plaintext operations can be moved down to Homomorphism library
public BigInteger Encode(BigInteger message) // TODO: Add tests now that this method is public
{
if (BigInteger.Abs(message) > keyStruct.MaxEncryptableValue)
throw new ArgumentException($"Numerator or denominator of the fraction to encrypt are too large; should be |m| < 2^{keyStruct.MaxPlaintextBits - 1}");

if (message.Sign < 0)
return keyStruct.MaxRawPlaintext + message + BigInteger.One;
return message;
}

public BigInteger Decode(BigInteger encodedMessage) // TODO: Add tests now that this method is public
{
encodedMessage %= keyStruct.MaxRawPlaintext + BigInteger.One;
if (encodedMessage > keyStruct.MaxEncryptableValue)
return encodedMessage - keyStruct.MaxRawPlaintext - BigInteger.One;
return encodedMessage;
}

public byte[] Multiply(ReadOnlySpan<byte> first, ReadOnlySpan<byte> second) => ElGamalHomomorphism.MultiplyFractions(first, second, keyStruct.P.ToByteArray());

Expand Down Expand Up @@ -191,14 +222,17 @@ public void PlaintextPowBigInteger(ReadOnlySpan<byte> first, BigInteger exp_bi,
a_bi.TryWriteBytes(writeTo[..halfblock], out _);
b_bi.TryWriteBytes(writeTo[halfblock..], out _);
}
#endregion

#region Serialization
public ElGamalParameters ExportParameters(bool includePrivateParams) => keyStruct.ExportParameters(includePrivateParams);

public override string ToXmlString(bool includePrivateParameters)
{
var prms = ExportParameters(includePrivateParameters);
return prms.ToXml(includePrivateParameters);
}
#endregion

public new void Dispose() => Encryptor.Dispose();
}
Expand Down
49 changes: 49 additions & 0 deletions test/ElGamalTests/KeyStruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,55 @@ public void TestKeyStruct()
Assert.True(X > 1);
Assert.True(X < P - 1);
}

// invalid key sizes
for (var i = 0; i < Globals.Iterations; i++)
{
var keySize = minKeySize - 1;
Assert.Throws<ArgumentException>(() => new ElGamal(keySize));
var p = BigInteger.One.GenRandomBits(keySize, rng);
Assert.Throws<ArgumentException>(() => new ElGamal(p));

keySize = maxKeySize + 1;
Assert.Throws<ArgumentException>(() => new ElGamal(keySize));
p = BigInteger.One.GenRandomBits(keySize, rng);
Assert.Throws<ArgumentException>(() => new ElGamal(p));

keySize = minKeySize + 1;
Assert.Throws<ArgumentException>(() => new ElGamal(keySize));
p = BigInteger.One.GenRandomBits(keySize, rng);
Assert.Throws<ArgumentException>(() => new ElGamal(p));

keySize = maxKeySize - 1;
Assert.Throws<ArgumentException>(() => new ElGamal(keySize));
p = BigInteger.One.GenRandomBits(keySize, rng);
Assert.Throws<ArgumentException>(() => new ElGamal(p));
}

// existing prime
for (var i = 0; i < Globals.Iterations; i++)
{
var p = BigInteger.One;
do
p = BigInteger.One.GenPseudoPrime(minKeySize, 8, rng);
while (((p-1)/2).IsProbablePrime(8, rng)); // make p NOT a safe prime

Assert.Throws<ArgumentException>(() => new ElGamal(p));

p = p.GenSafePseudoPrime(minKeySize, 8, rng);
var eg = new ElGamal(p);

Assert.Equal(eg.P, p);
Assert.Equal(eg.KeySize, p.BitCount());
Assert.Equal(p.BitCount(), eg.PLength * 8);

var prms = eg.ExportParameters(true);
var X = new BigInteger(prms.X);
Assert.True(X > 1);
Assert.True(X < p - 1);

Assert.Throws<ArgumentException>(() => new ElGamal(p + 1)); // not a prime
}
}
}
}

0 comments on commit bcc2204

Please sign in to comment.