summary refs log tree commit diff
path: root/Crypto/src/crypto/engines/ElGamalEngine.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Crypto/src/crypto/engines/ElGamalEngine.cs')
-rw-r--r--Crypto/src/crypto/engines/ElGamalEngine.cs178
1 files changed, 178 insertions, 0 deletions
diff --git a/Crypto/src/crypto/engines/ElGamalEngine.cs b/Crypto/src/crypto/engines/ElGamalEngine.cs
new file mode 100644
index 000000000..3d256a087
--- /dev/null
+++ b/Crypto/src/crypto/engines/ElGamalEngine.cs
@@ -0,0 +1,178 @@
+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;
+		}
+	}
+}