forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathElGamalCipherTest.java
More file actions
145 lines (115 loc) · 6.27 KB
/
ElGamalCipherTest.java
File metadata and controls
145 lines (115 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package com.thealgorithms.ciphers;
import java.math.BigInteger;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
/**
* Unit tests for ElGamalCipher.
* Includes property-based testing (homomorphism), probabilistic checks,
* and boundary validation.
*/
class ElGamalCipherTest {
private static ElGamalCipher.KeyPair sharedKeys;
@BeforeAll
static void setup() {
// Generate 256-bit keys for efficient unit testing
sharedKeys = ElGamalCipher.generateKeys(256);
}
@Test
@DisplayName("Test Key Generation Validity")
void testKeyGeneration() {
Assertions.assertNotNull(sharedKeys.p());
Assertions.assertNotNull(sharedKeys.g());
Assertions.assertNotNull(sharedKeys.x());
Assertions.assertNotNull(sharedKeys.y());
// Verify generator bounds: 1 < g < p
Assertions.assertTrue(sharedKeys.g().compareTo(BigInteger.ONE) > 0);
Assertions.assertTrue(sharedKeys.g().compareTo(sharedKeys.p()) < 0);
// Verify private key bounds: 1 < x < p-1
Assertions.assertTrue(sharedKeys.x().compareTo(BigInteger.ONE) > 0);
Assertions.assertTrue(sharedKeys.x().compareTo(sharedKeys.p().subtract(BigInteger.ONE)) < 0);
}
@Test
@DisplayName("Security Check: Probabilistic Encryption")
void testSemanticSecurity() {
// Encrypting the same message twice MUST yield different ciphertexts
// due to the random ephemeral key 'k'.
BigInteger message = new BigInteger("123456789");
ElGamalCipher.CipherText c1 = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
ElGamalCipher.CipherText c2 = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
// Check that the ephemeral keys (and thus 'a' components) were different
Assertions.assertNotEquals(c1.a(), c2.a(), "Ciphertexts must be randomized (Semantic Security violation)");
Assertions.assertNotEquals(c1.b(), c2.b());
// But both must decrypt to the original message
Assertions.assertEquals(ElGamalCipher.decrypt(c1, sharedKeys.x(), sharedKeys.p()), message);
Assertions.assertEquals(ElGamalCipher.decrypt(c2, sharedKeys.x(), sharedKeys.p()), message);
}
@ParameterizedTest
@MethodSource("provideMessages")
@DisplayName("Parameterized Test: Encrypt and Decrypt various messages")
void testEncryptDecrypt(String messageStr) {
BigInteger message = new BigInteger(messageStr.getBytes());
// Skip if message exceeds the test key size (256 bits)
if (message.compareTo(sharedKeys.p()) >= 0) {
return;
}
ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p());
Assertions.assertEquals(message, decrypted, "Decrypted BigInteger must match original");
Assertions.assertEquals(messageStr, new String(decrypted.toByteArray()), "Decrypted string must match original");
}
static Stream<String> provideMessages() {
return Stream.of("Hello World", "TheAlgorithms", "A", "1234567890", "!@#$%^&*()");
}
@Test
@DisplayName("Edge Case: Message equals 0")
void testMessageZero() {
BigInteger zero = BigInteger.ZERO;
ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(zero, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p());
Assertions.assertEquals(zero, decrypted, "Should successfully encrypt/decrypt zero");
}
@Test
@DisplayName("Edge Case: Message equals p-1")
void testMessageMaxBound() {
BigInteger pMinus1 = sharedKeys.p().subtract(BigInteger.ONE);
ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(pMinus1, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p());
Assertions.assertEquals(pMinus1, decrypted, "Should successfully encrypt/decrypt p-1");
}
@Test
@DisplayName("Negative Test: Message >= p should fail")
void testMessageTooLarge() {
BigInteger tooLarge = sharedKeys.p();
Assertions.assertThrows(IllegalArgumentException.class, () -> ElGamalCipher.encrypt(tooLarge, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()));
}
@Test
@DisplayName("Negative Test: Decrypt with wrong private key")
void testWrongKeyDecryption() {
BigInteger message = new BigInteger("99999");
ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
// Generate a fake private key
BigInteger wrongX = sharedKeys.x().add(BigInteger.ONE);
BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, wrongX, sharedKeys.p());
Assertions.assertNotEquals(message, decrypted, "Decryption with wrong key must yield incorrect result");
}
@Test
@DisplayName("Property Test: Multiplicative Homomorphism")
void testHomomorphism() {
BigInteger m1 = new BigInteger("50");
BigInteger m2 = BigInteger.TEN; // Fix: Replaced new BigInteger("10") with BigInteger.TEN
ElGamalCipher.CipherText c1 = ElGamalCipher.encrypt(m1, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
ElGamalCipher.CipherText c2 = ElGamalCipher.encrypt(m2, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
// Multiply ciphertexts component-wise: (a1*a2, b1*b2)
BigInteger aNew = c1.a().multiply(c2.a()).mod(sharedKeys.p());
BigInteger bNew = c1.b().multiply(c2.b()).mod(sharedKeys.p());
ElGamalCipher.CipherText cCombined = new ElGamalCipher.CipherText(aNew, bNew);
BigInteger decrypted = ElGamalCipher.decrypt(cCombined, sharedKeys.x(), sharedKeys.p());
BigInteger expected = m1.multiply(m2).mod(sharedKeys.p());
Assertions.assertEquals(expected, decrypted, "Cipher must satisfy multiplicative homomorphism");
}
}