using System;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
namespace Org.BouncyCastle.Crypto.Engines{
/**
* this does your basic ElGamal algorithm.
*/
public class ElGamalEngine
: IAsymmetricBlockCipher
{
private ElGamalKeyParameters key;
private SecureRandom random;
private bool forEncryption;
private int bitSize;
public string AlgorithmName
{
get { return "ElGamal"; }
}
/**
* initialise the ElGamal engine.
*
* @param forEncryption true if we are encrypting, false otherwise.
* @param param the necessary ElGamal key parameters.
*/
public void Init(
bool forEncryption,
ICipherParameters parameters)
{
if (parameters is ParametersWithRandom)
{
ParametersWithRandom p = (ParametersWithRandom) parameters;
this.key = (ElGamalKeyParameters) p.Parameters;
this.random = p.Random;
}
else
{
this.key = (ElGamalKeyParameters) parameters;
this.random = new SecureRandom();
}
this.forEncryption = forEncryption;
this.bitSize = key.Parameters.P.BitLength;
if (forEncryption)
{
if (!(key is ElGamalPublicKeyParameters))
{
throw new ArgumentException("ElGamalPublicKeyParameters are required for encryption.");
}
}
else
{
if (!(key is ElGamalPrivateKeyParameters))
{
throw new ArgumentException("ElGamalPrivateKeyParameters are required for decryption.");
}
}
}
/**
* Return the maximum size for an input block to this engine.
* For ElGamal this is always one byte less than the size of P on
* encryption, and twice the length as the size of P on decryption.
*
* @return maximum size for an input block.
*/
public int GetInputBlockSize()
{
if (forEncryption)
{
return (bitSize - 1) / 8;
}
return 2 * ((bitSize + 7) / 8);
}
/**
* Return the maximum size for an output block to this engine.
* For ElGamal this is always one byte less than the size of P on
* decryption, and twice the length as the size of P on encryption.
*
* @return maximum size for an output block.
*/
public int GetOutputBlockSize()
{
if (forEncryption)
{
return 2 * ((bitSize + 7) / 8);
}
return (bitSize - 1) / 8;
}
/**
* Process a single block using the basic ElGamal algorithm.
*
* @param in the input array.
* @param inOff the offset into the input buffer where the data starts.
* @param length the length of the data to be processed.
* @return the result of the ElGamal process.
* @exception DataLengthException the input block is too large.
*/
public byte[] ProcessBlock(
byte[] input,
int inOff,
int length)
{
if (key == null)
throw new InvalidOperationException("ElGamal engine not initialised");
int maxLength = forEncryption
? (bitSize - 1 + 7) / 8
: GetInputBlockSize();
if (length > maxLength)
throw new DataLengthException("input too large for ElGamal cipher.\n");
BigInteger p = key.Parameters.P;
byte[] output;
if (key is ElGamalPrivateKeyParameters) // decryption
{
int halfLength = length / 2;
BigInteger gamma = new BigInteger(1, input, inOff, halfLength);
BigInteger phi = new BigInteger(1, input, inOff + halfLength, halfLength);
ElGamalPrivateKeyParameters priv = (ElGamalPrivateKeyParameters) key;
// a shortcut, which generally relies on p being prime amongst other things.
// if a problem with this shows up, check the p and g values!
BigInteger m = gamma.ModPow(p.Subtract(BigInteger.One).Subtract(priv.X), p).Multiply(phi).Mod(p);
output = m.ToByteArrayUnsigned();
}
else // encryption
{
BigInteger tmp = new BigInteger(1, input, inOff, length);
if (tmp.BitLength >= p.BitLength)
throw new DataLengthException("input too large for ElGamal cipher.\n");
ElGamalPublicKeyParameters pub = (ElGamalPublicKeyParameters) key;
BigInteger pSub2 = p.Subtract(BigInteger.Two);
// TODO In theory, a series of 'k', 'g.ModPow(k, p)' and 'y.ModPow(k, p)' can be pre-calculated
BigInteger k;
do
{
k = new BigInteger(p.BitLength, random);
}
while (k.SignValue == 0 || k.CompareTo(pSub2) > 0);
BigInteger g = key.Parameters.G;
BigInteger gamma = g.ModPow(k, p);
BigInteger phi = tmp.Multiply(pub.Y.ModPow(k, p)).Mod(p);
output = new byte[this.GetOutputBlockSize()];
// TODO Add methods to allow writing BigInteger to existing byte array?
byte[] out1 = gamma.ToByteArrayUnsigned();
byte[] out2 = phi.ToByteArrayUnsigned();
out1.CopyTo(output, output.Length / 2 - out1.Length);
out2.CopyTo(output, output.Length - out2.Length);
}
return output;
}
}
}
|