Skip to content

Commit b891ca0

Browse files
committed
refactor: Enhance docs, add more tests in RSA
1 parent e499d3b commit b891ca0

File tree

2 files changed

+116
-22
lines changed

2 files changed

+116
-22
lines changed

src/main/java/com/thealgorithms/ciphers/RSA.java

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,116 @@
44
import java.security.SecureRandom;
55

66
/**
7-
* @author Nguyen Duy Tiep on 23-Oct-17.
7+
* RSA is an asymmetric cryptographic algorithm used for secure data encryption and decryption.
8+
* It relies on a pair of keys: a public key (used for encryption) and a private key
9+
* (used for decryption). The algorithm is based on the difficulty of factoring large prime numbers.
10+
*
11+
* This implementation includes key generation, encryption, and decryption methods that can handle both
12+
* text-based messages and BigInteger inputs. For more details on RSA:
13+
* <a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)">RSA Cryptosystem - Wikipedia</a>.
14+
*
15+
* Example Usage:
16+
* <pre>
17+
* RSA rsa = new RSA(1024);
18+
* String encryptedMessage = rsa.encrypt("Hello RSA!");
19+
* String decryptedMessage = rsa.decrypt(encryptedMessage);
20+
* System.out.println(decryptedMessage); // Output: Hello RSA!
21+
* </pre>
22+
*
23+
* Note: The key size directly affects the security and performance of the RSA algorithm.
24+
* Larger keys are more secure but slower to compute.
25+
*
26+
* @author Nguyen Duy Tiep
27+
* @version 23-Oct-17
828
*/
929
public class RSA {
1030

1131
private BigInteger modulus;
1232
private BigInteger privateKey;
1333
private BigInteger publicKey;
1434

35+
/**
36+
* Constructor that generates RSA keys with the specified number of bits.
37+
*
38+
* @param bits The bit length of the keys to be generated. Common sizes include 512, 1024, 2048, etc.
39+
*/
1540
public RSA(int bits) {
1641
generateKeys(bits);
1742
}
1843

1944
/**
20-
* @return encrypted message
45+
* Encrypts a text message using the RSA public key.
46+
*
47+
* @param message The plaintext message to be encrypted.
48+
* @throws IllegalArgumentException If the message is empty.
49+
* @return The encrypted message represented as a String.
2150
*/
2251
public synchronized String encrypt(String message) {
52+
if (message.isEmpty()) {
53+
throw new IllegalArgumentException("Message is empty");
54+
}
2355
return (new BigInteger(message.getBytes())).modPow(publicKey, modulus).toString();
2456
}
2557

2658
/**
27-
* @return encrypted message as big integer
59+
* Encrypts a BigInteger message using the RSA public key.
60+
*
61+
* @param message The plaintext message as a BigInteger.
62+
* @return The encrypted message as a BigInteger.
2863
*/
2964
public synchronized BigInteger encrypt(BigInteger message) {
3065
return message.modPow(publicKey, modulus);
3166
}
3267

3368
/**
34-
* @return plain message
69+
* Decrypts an encrypted message (as String) using the RSA private key.
70+
*
71+
* @param encryptedMessage The encrypted message to be decrypted, represented as a String.
72+
* @throws IllegalArgumentException If the message is empty.
73+
* @return The decrypted plaintext message as a String.
3574
*/
3675
public synchronized String decrypt(String encryptedMessage) {
76+
if (encryptedMessage.isEmpty()) {
77+
throw new IllegalArgumentException("Message is empty");
78+
}
3779
return new String((new BigInteger(encryptedMessage)).modPow(privateKey, modulus).toByteArray());
3880
}
3981

4082
/**
41-
* @return plain message as big integer
83+
* Decrypts an encrypted BigInteger message using the RSA private key.
84+
*
85+
* @param encryptedMessage The encrypted message as a BigInteger.
86+
* @return The decrypted plaintext message as a BigInteger.
4287
*/
4388
public synchronized BigInteger decrypt(BigInteger encryptedMessage) {
4489
return encryptedMessage.modPow(privateKey, modulus);
4590
}
4691

4792
/**
48-
* Generate a new public and private key set.
93+
* Generates a new RSA key pair (public and private keys) with the specified bit length.
94+
* Steps:
95+
* 1. Generate two large prime numbers p and q.
96+
* 2. Compute the modulus n = p * q.
97+
* 3. Compute Euler's totient function: φ(n) = (p-1) * (q-1).
98+
* 4. Choose a public key e (starting from 3) that is coprime with φ(n).
99+
* 5. Compute the private key d as the modular inverse of e mod φ(n).
100+
* The public key is (e, n) and the private key is (d, n).
101+
*
102+
* @param bits The bit length of the keys to be generated.
49103
*/
50104
public final synchronized void generateKeys(int bits) {
51-
SecureRandom r = new SecureRandom();
52-
BigInteger p = new BigInteger(bits / 2, 100, r);
53-
BigInteger q = new BigInteger(bits / 2, 100, r);
105+
SecureRandom random = new SecureRandom();
106+
BigInteger p = new BigInteger(bits / 2, 100, random);
107+
BigInteger q = new BigInteger(bits / 2, 100, random);
54108
modulus = p.multiply(q);
55109

56-
BigInteger m = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
110+
BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
57111

58112
publicKey = BigInteger.valueOf(3L);
59-
60-
while (m.gcd(publicKey).intValue() > 1) {
113+
while (phi.gcd(publicKey).intValue() > 1) {
61114
publicKey = publicKey.add(BigInteger.TWO);
62115
}
63116

64-
privateKey = publicKey.modInverse(m);
117+
privateKey = publicKey.modInverse(phi);
65118
}
66119
}
Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,64 @@
11
package com.thealgorithms.ciphers;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
45

6+
import java.math.BigInteger;
57
import org.junit.jupiter.api.Test;
68

79
class RSATest {
810

9-
RSA rsa = new RSA(1024);
11+
private final RSA rsa = new RSA(1024);
1012

1113
@Test
12-
void testRSA() {
13-
// given
14-
String textToEncrypt = "Such secure";
14+
void testEncryptDecryptString() {
15+
String originalMessage = "Such secure";
16+
String encryptedMessage = rsa.encrypt(originalMessage);
17+
String decryptedMessage = rsa.decrypt(encryptedMessage);
18+
assertEquals(originalMessage, decryptedMessage);
19+
}
20+
21+
@Test
22+
void testEncryptDecryptBigInteger() {
23+
BigInteger originalMessage = new BigInteger("12345678901234567890");
24+
BigInteger encryptedMessage = rsa.encrypt(originalMessage);
25+
BigInteger decryptedMessage = rsa.decrypt(encryptedMessage);
26+
assertEquals(originalMessage, decryptedMessage);
27+
}
1528

16-
// when
17-
String cipherText = rsa.encrypt(textToEncrypt);
18-
String decryptedText = rsa.decrypt(cipherText);
29+
@Test
30+
void testEmptyMessage() {
31+
String originalMessage = "";
32+
assertThrows(IllegalArgumentException.class, () -> rsa.encrypt(originalMessage));
33+
assertThrows(IllegalArgumentException.class, () -> rsa.decrypt(originalMessage));
34+
}
35+
36+
@Test
37+
void testDifferentKeySizes() {
38+
// Testing with 512-bit RSA keys
39+
RSA smallRSA = new RSA(512);
40+
String originalMessage = "Test with smaller key";
1941

20-
// then
21-
assertEquals("Such secure", decryptedText);
42+
String encryptedMessage = smallRSA.encrypt(originalMessage);
43+
String decryptedMessage = smallRSA.decrypt(encryptedMessage);
44+
45+
assertEquals(originalMessage, decryptedMessage);
46+
47+
// Testing with 2048-bit RSA keys
48+
RSA largeRSA = new RSA(2048);
49+
String largeOriginalMessage = "Test with larger key";
50+
51+
String largeEncryptedMessage = largeRSA.encrypt(largeOriginalMessage);
52+
String largeDecryptedMessage = largeRSA.decrypt(largeEncryptedMessage);
53+
54+
assertEquals(largeOriginalMessage, largeDecryptedMessage);
55+
}
56+
57+
@Test
58+
void testSpecialCharacters() {
59+
String originalMessage = "Hello, RSA! @2024#";
60+
String encryptedMessage = rsa.encrypt(originalMessage);
61+
String decryptedMessage = rsa.decrypt(encryptedMessage);
62+
assertEquals(originalMessage, decryptedMessage);
2263
}
2364
}

0 commit comments

Comments
 (0)