Skip to content

Commit 4dc2c6c

Browse files
committed
Add ElGamal encryption algorithm implementation - Implemented key generation with configurable bit length - Added encryption and decryption methods - Included semantic security demonstration - Added string conversion utilities - Comprehensive documentation and examples - Resolves #6934
1 parent 3169178 commit 4dc2c6c

File tree

1 file changed

+331
-0
lines changed

1 file changed

+331
-0
lines changed
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
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("\nOriginal 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\nExample 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\nExample 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

Comments
 (0)