1+ package com .thealgorithms .ciphers ;
2+
3+ import java .math .BigInteger ;
4+ import java .security .SecureRandom ;
5+ import java .util .Random ;
6+
7+ /**
8+ * ElGamal Encryption Algorithm Implementation
9+ *
10+ * ElGamal is an asymmetric key encryption algorithm for public-key cryptography
11+ * based on the Discrete Logarithm Problem (DLP). It provides semantic security
12+ * through randomization in the encryption process.
13+ *
14+ * Key Components:
15+ * - p: Large prime number (modulus)
16+ * - g: Generator of the multiplicative group modulo p
17+ * - x: Private key (random integer)
18+ * - y: Public key where y = g^x mod p
19+ *
20+ * Encryption: For message m, choose random k and compute:
21+ * c1 = g^k mod p
22+ * c2 = m * y^k mod p
23+ * Ciphertext = (c1, c2)
24+ *
25+ * Decryption: Recover m using:
26+ * m = c2 * (c1^x)^-1 mod p
27+ * where (c1^x)^-1 is the modular multiplicative inverse
28+ *
29+ * @author TheAlgorithms
30+ */
31+ public final class ElGamalCipher {
32+
33+ private ElGamalCipher () {
34+ // Utility class - prevent instantiation
35+ }
36+
37+ /**
38+ * Represents an ElGamal key pair containing public and private keys
39+ */
40+ public static class KeyPair {
41+ private final BigInteger p ; // Prime modulus
42+ private final BigInteger g ; // Generator
43+ private final BigInteger x ; // Private key
44+ private final BigInteger y ; // Public key (y = g^x mod p)
45+
46+ public KeyPair (BigInteger p , BigInteger g , BigInteger x , BigInteger y ) {
47+ this .p = p ;
48+ this .g = g ;
49+ this .x = x ;
50+ this .y = y ;
51+ }
52+
53+ public BigInteger getP () {
54+ return p ;
55+ }
56+
57+ public BigInteger getG () {
58+ return g ;
59+ }
60+
61+ public BigInteger getPrivateKey () {
62+ return x ;
63+ }
64+
65+ public BigInteger getPublicKey () {
66+ return y ;
67+ }
68+ }
69+
70+ /**
71+ * Represents an ElGamal ciphertext as a pair (c1, c2)
72+ */
73+ public static class Ciphertext {
74+ private final BigInteger c1 ;
75+ private final BigInteger c2 ;
76+
77+ public Ciphertext (BigInteger c1 , BigInteger c2 ) {
78+ this .c1 = c1 ;
79+ this .c2 = c2 ;
80+ }
81+
82+ public BigInteger getC1 () {
83+ return c1 ;
84+ }
85+
86+ public BigInteger getC2 () {
87+ return c2 ;
88+ }
89+
90+ @ Override
91+ public String toString () {
92+ return "Ciphertext{c1=" + c1 + ", c2=" + c2 + "}" ;
93+ }
94+ }
95+
96+ /**
97+ * Generates ElGamal key pair with specified bit length
98+ *
99+ * Steps:
100+ * 1. Generate a large prime p
101+ * 2. Find a generator g of the multiplicative group mod p
102+ * 3. Choose random private key x in range [2, p-2]
103+ * 4. Compute public key y = g^x mod p
104+ *
105+ * @param bitLength The bit length for the prime (e.g., 512, 1024, 2048)
106+ * @return KeyPair containing (p, g, x, y)
107+ */
108+ public static KeyPair generateKeys (int bitLength ) {
109+ SecureRandom random = new SecureRandom ();
110+
111+ // Generate a large prime p
112+ BigInteger p = BigInteger .probablePrime (bitLength , random );
113+
114+ // Find a generator g (simplified: use a small generator that works for most primes)
115+ // In practice, we often use g = 2 or find a primitive root
116+ BigInteger g = findGenerator (p , random );
117+
118+ // Generate private key x: random number in range [2, p-2]
119+ BigInteger x ;
120+ do {
121+ x = new BigInteger (bitLength - 1 , random );
122+ } while (x .compareTo (BigInteger .TWO ) < 0 || x .compareTo (p .subtract (BigInteger .TWO )) > 0 );
123+
124+ // Calculate public key y = g^x mod p
125+ BigInteger y = g .modPow (x , p );
126+
127+ return new KeyPair (p , g , x , y );
128+ }
129+
130+ /**
131+ * Finds a generator for the multiplicative group modulo p
132+ * Simplified approach: tries small values until finding a suitable generator
133+ *
134+ * @param p The prime modulus
135+ * @param random Random number generator
136+ * @return A generator g
137+ */
138+ private static BigInteger findGenerator (BigInteger p , Random random ) {
139+ // Simplified: use 2 as generator (works for most safe primes)
140+ // For production, should verify g is a primitive root
141+ BigInteger g = BigInteger .valueOf (2 );
142+
143+ // If 2 doesn't work, try other small values
144+ while (g .compareTo (p ) < 0 ) {
145+ // Check if g is a valid generator (simplified check)
146+ if (g .modPow (p .subtract (BigInteger .ONE ), p ).equals (BigInteger .ONE )) {
147+ return g ;
148+ }
149+ g = g .add (BigInteger .ONE );
150+ }
151+
152+ return BigInteger .valueOf (2 ); // Fallback
153+ }
154+
155+ /**
156+ * Encrypts a message using ElGamal encryption
157+ *
158+ * Process:
159+ * 1. Choose random k in range [2, p-2]
160+ * 2. Compute c1 = g^k mod p
161+ * 3. Compute c2 = m * y^k mod p
162+ * 4. Return ciphertext (c1, c2)
163+ *
164+ * The random k ensures semantic security - same message encrypted
165+ * multiple times produces different ciphertexts
166+ *
167+ * @param message The plaintext message as BigInteger (must be < p)
168+ * @param keyPair The key pair containing public parameters
169+ * @return Ciphertext (c1, c2)
170+ */
171+ public static Ciphertext encrypt (BigInteger message , KeyPair keyPair ) {
172+ if (message .compareTo (keyPair .getP ()) >= 0 ) {
173+ throw new IllegalArgumentException ("Message must be less than modulus p" );
174+ }
175+
176+ SecureRandom random = new SecureRandom ();
177+ BigInteger p = keyPair .getP ();
178+ BigInteger g = keyPair .getG ();
179+ BigInteger y = keyPair .getPublicKey ();
180+
181+ // Choose random k in range [2, p-2]
182+ BigInteger k ;
183+ do {
184+ k = new BigInteger (p .bitLength () - 1 , random );
185+ } while (k .compareTo (BigInteger .TWO ) < 0 || k .compareTo (p .subtract (BigInteger .TWO )) > 0 );
186+
187+ // Compute c1 = g^k mod p
188+ BigInteger c1 = g .modPow (k , p );
189+
190+ // Compute c2 = m * y^k mod p
191+ BigInteger c2 = message .multiply (y .modPow (k , p )).mod (p );
192+
193+ return new Ciphertext (c1 , c2 );
194+ }
195+
196+ /**
197+ * Decrypts a ciphertext using ElGamal decryption
198+ *
199+ * Process:
200+ * 1. Compute s = c1^x mod p (shared secret)
201+ * 2. Compute s^-1 (modular multiplicative inverse of s)
202+ * 3. Recover m = c2 * s^-1 mod p
203+ *
204+ * Mathematical proof:
205+ * c2 * (c1^x)^-1 = (m * y^k) * (g^(k*x))^-1
206+ * = (m * g^(k*x)) * (g^(k*x))^-1
207+ * = m
208+ *
209+ * @param ciphertext The ciphertext (c1, c2)
210+ * @param keyPair The key pair containing private key
211+ * @return Decrypted plaintext message
212+ */
213+ public static BigInteger decrypt (Ciphertext ciphertext , KeyPair keyPair ) {
214+ BigInteger c1 = ciphertext .getC1 ();
215+ BigInteger c2 = ciphertext .getC2 ();
216+ BigInteger x = keyPair .getPrivateKey ();
217+ BigInteger p = keyPair .getP ();
218+
219+ // Compute s = c1^x mod p
220+ BigInteger s = c1 .modPow (x , p );
221+
222+ // Compute s^-1 mod p (modular multiplicative inverse)
223+ BigInteger sInverse = s .modInverse (p );
224+
225+ // Recover message: m = c2 * s^-1 mod p
226+ BigInteger message = c2 .multiply (sInverse ).mod (p );
227+
228+ return message ;
229+ }
230+
231+ /**
232+ * Converts a string to BigInteger for encryption
233+ *
234+ * @param text The input string
235+ * @return BigInteger representation
236+ */
237+ public static BigInteger stringToBigInteger (String text ) {
238+ byte [] bytes = text .getBytes ();
239+ return new BigInteger (1 , bytes );
240+ }
241+
242+ /**
243+ * Converts BigInteger back to string after decryption
244+ *
245+ * @param number The BigInteger to convert
246+ * @return Original string
247+ */
248+ public static String bigIntegerToString (BigInteger number ) {
249+ byte [] bytes = number .toByteArray ();
250+ // Handle sign byte if present
251+ if (bytes [0 ] == 0 && bytes .length > 1 ) {
252+ byte [] tmp = new byte [bytes .length - 1 ];
253+ System .arraycopy (bytes , 1 , tmp , 0 , tmp .length );
254+ bytes = tmp ;
255+ }
256+ return new String (bytes );
257+ }
258+
259+ /**
260+ * Main method demonstrating ElGamal encryption and decryption
261+ */
262+ public static void main (String [] args ) {
263+ System .out .println ("=== ElGamal Encryption Algorithm Demo ===\n " );
264+
265+ // Example 1: Encrypting a small integer
266+ System .out .println ("Example 1: Encrypting a small integer" );
267+ System .out .println ("--------------------------------------" );
268+
269+ // Generate keys with 512-bit prime (use 1024 or 2048 for production)
270+ System .out .println ("Generating keys (512-bit)..." );
271+ KeyPair keyPair = generateKeys (512 );
272+
273+ System .out .println ("Prime (p): " + keyPair .getP ());
274+ System .out .println ("Generator (g): " + keyPair .getG ());
275+ System .out .println ("Private key (x): " + keyPair .getPrivateKey ());
276+ System .out .println ("Public key (y): " + keyPair .getPublicKey ());
277+
278+ // Message to encrypt
279+ BigInteger message = BigInteger .valueOf (12345 );
280+ System .out .println ("\n Original message: " + message );
281+
282+ // Encrypt
283+ Ciphertext ciphertext = encrypt (message , keyPair );
284+ System .out .println ("Encrypted: " + ciphertext );
285+
286+ // Decrypt
287+ BigInteger decrypted = decrypt (ciphertext , keyPair );
288+ System .out .println ("Decrypted message: " + decrypted );
289+
290+ // Verify
291+ System .out .println ("Decryption successful: " + message .equals (decrypted ));
292+
293+ // Example 2: Demonstrating semantic security
294+ System .out .println ("\n \n Example 2: Demonstrating Semantic Security" );
295+ System .out .println ("------------------------------------------" );
296+ System .out .println ("Same message encrypted twice produces different ciphertexts:" );
297+
298+ Ciphertext ct1 = encrypt (message , keyPair );
299+ Ciphertext ct2 = encrypt (message , keyPair );
300+
301+ System .out .println ("Encryption 1: " + ct1 );
302+ System .out .println ("Encryption 2: " + ct2 );
303+ System .out .println ("Are ciphertexts different? " + !ct1 .getC1 ().equals (ct2 .getC1 ()));
304+
305+ // Both decrypt to same message
306+ System .out .println ("Both decrypt to: " + decrypt (ct1 , keyPair ) + " and " + decrypt (ct2 , keyPair ));
307+
308+ // Example 3: String encryption
309+ System .out .println ("\n \n Example 3: Encrypting a String" );
310+ System .out .println ("-------------------------------" );
311+
312+ String text = "Hello" ;
313+ System .out .println ("Original text: " + text );
314+
315+ BigInteger textAsNumber = stringToBigInteger (text );
316+ System .out .println ("Text as BigInteger: " + textAsNumber );
317+
318+ // Check if message is small enough
319+ if (textAsNumber .compareTo (keyPair .getP ()) < 0 ) {
320+ Ciphertext textCiphertext = encrypt (textAsNumber , keyPair );
321+ System .out .println ("Encrypted: " + textCiphertext );
322+
323+ BigInteger decryptedNumber = decrypt (textCiphertext , keyPair );
324+ String decryptedText = bigIntegerToString (decryptedNumber );
325+ System .out .println ("Decrypted text: " + decryptedText );
326+ System .out .println ("Match: " + text .equals (decryptedText ));
327+ } else {
328+ System .out .println ("Text too large for current key size. Use larger keys or split text." );
329+ }
330+ }
331+ }
0 commit comments