Enhance docs, add more tests in RSA (#5898)

This commit is contained in:
Hardik Pawar
2024-10-26 20:20:32 +05:30
committed by GitHub
parent 3de202b953
commit 4d85c61c37
2 changed files with 116 additions and 22 deletions

View File

@ -4,7 +4,27 @@ import java.math.BigInteger;
import java.security.SecureRandom; import java.security.SecureRandom;
/** /**
* @author Nguyen Duy Tiep on 23-Oct-17. * RSA is an asymmetric cryptographic algorithm used for secure data encryption and decryption.
* It relies on a pair of keys: a public key (used for encryption) and a private key
* (used for decryption). The algorithm is based on the difficulty of factoring large prime numbers.
*
* This implementation includes key generation, encryption, and decryption methods that can handle both
* text-based messages and BigInteger inputs. For more details on RSA:
* <a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)">RSA Cryptosystem - Wikipedia</a>.
*
* Example Usage:
* <pre>
* RSA rsa = new RSA(1024);
* String encryptedMessage = rsa.encrypt("Hello RSA!");
* String decryptedMessage = rsa.decrypt(encryptedMessage);
* System.out.println(decryptedMessage); // Output: Hello RSA!
* </pre>
*
* Note: The key size directly affects the security and performance of the RSA algorithm.
* Larger keys are more secure but slower to compute.
*
* @author Nguyen Duy Tiep
* @version 23-Oct-17
*/ */
public class RSA { public class RSA {
@ -12,55 +32,88 @@ public class RSA {
private BigInteger privateKey; private BigInteger privateKey;
private BigInteger publicKey; private BigInteger publicKey;
/**
* Constructor that generates RSA keys with the specified number of bits.
*
* @param bits The bit length of the keys to be generated. Common sizes include 512, 1024, 2048, etc.
*/
public RSA(int bits) { public RSA(int bits) {
generateKeys(bits); generateKeys(bits);
} }
/** /**
* @return encrypted message * Encrypts a text message using the RSA public key.
*
* @param message The plaintext message to be encrypted.
* @throws IllegalArgumentException If the message is empty.
* @return The encrypted message represented as a String.
*/ */
public synchronized String encrypt(String message) { public synchronized String encrypt(String message) {
if (message.isEmpty()) {
throw new IllegalArgumentException("Message is empty");
}
return (new BigInteger(message.getBytes())).modPow(publicKey, modulus).toString(); return (new BigInteger(message.getBytes())).modPow(publicKey, modulus).toString();
} }
/** /**
* @return encrypted message as big integer * Encrypts a BigInteger message using the RSA public key.
*
* @param message The plaintext message as a BigInteger.
* @return The encrypted message as a BigInteger.
*/ */
public synchronized BigInteger encrypt(BigInteger message) { public synchronized BigInteger encrypt(BigInteger message) {
return message.modPow(publicKey, modulus); return message.modPow(publicKey, modulus);
} }
/** /**
* @return plain message * Decrypts an encrypted message (as String) using the RSA private key.
*
* @param encryptedMessage The encrypted message to be decrypted, represented as a String.
* @throws IllegalArgumentException If the message is empty.
* @return The decrypted plaintext message as a String.
*/ */
public synchronized String decrypt(String encryptedMessage) { public synchronized String decrypt(String encryptedMessage) {
if (encryptedMessage.isEmpty()) {
throw new IllegalArgumentException("Message is empty");
}
return new String((new BigInteger(encryptedMessage)).modPow(privateKey, modulus).toByteArray()); return new String((new BigInteger(encryptedMessage)).modPow(privateKey, modulus).toByteArray());
} }
/** /**
* @return plain message as big integer * Decrypts an encrypted BigInteger message using the RSA private key.
*
* @param encryptedMessage The encrypted message as a BigInteger.
* @return The decrypted plaintext message as a BigInteger.
*/ */
public synchronized BigInteger decrypt(BigInteger encryptedMessage) { public synchronized BigInteger decrypt(BigInteger encryptedMessage) {
return encryptedMessage.modPow(privateKey, modulus); return encryptedMessage.modPow(privateKey, modulus);
} }
/** /**
* Generate a new public and private key set. * Generates a new RSA key pair (public and private keys) with the specified bit length.
* Steps:
* 1. Generate two large prime numbers p and q.
* 2. Compute the modulus n = p * q.
* 3. Compute Euler's totient function: φ(n) = (p-1) * (q-1).
* 4. Choose a public key e (starting from 3) that is coprime with φ(n).
* 5. Compute the private key d as the modular inverse of e mod φ(n).
* The public key is (e, n) and the private key is (d, n).
*
* @param bits The bit length of the keys to be generated.
*/ */
public final synchronized void generateKeys(int bits) { public final synchronized void generateKeys(int bits) {
SecureRandom r = new SecureRandom(); SecureRandom random = new SecureRandom();
BigInteger p = new BigInteger(bits / 2, 100, r); BigInteger p = new BigInteger(bits / 2, 100, random);
BigInteger q = new BigInteger(bits / 2, 100, r); BigInteger q = new BigInteger(bits / 2, 100, random);
modulus = p.multiply(q); modulus = p.multiply(q);
BigInteger m = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE)); BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
publicKey = BigInteger.valueOf(3L); publicKey = BigInteger.valueOf(3L);
while (phi.gcd(publicKey).intValue() > 1) {
while (m.gcd(publicKey).intValue() > 1) {
publicKey = publicKey.add(BigInteger.TWO); publicKey = publicKey.add(BigInteger.TWO);
} }
privateKey = publicKey.modInverse(m); privateKey = publicKey.modInverse(phi);
} }
} }

View File

@ -1,23 +1,64 @@
package com.thealgorithms.ciphers; package com.thealgorithms.ciphers;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.math.BigInteger;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class RSATest { class RSATest {
RSA rsa = new RSA(1024); private final RSA rsa = new RSA(1024);
@Test @Test
void testRSA() { void testEncryptDecryptString() {
// given String originalMessage = "Such secure";
String textToEncrypt = "Such secure"; String encryptedMessage = rsa.encrypt(originalMessage);
String decryptedMessage = rsa.decrypt(encryptedMessage);
assertEquals(originalMessage, decryptedMessage);
}
// when @Test
String cipherText = rsa.encrypt(textToEncrypt); void testEncryptDecryptBigInteger() {
String decryptedText = rsa.decrypt(cipherText); BigInteger originalMessage = new BigInteger("12345678901234567890");
BigInteger encryptedMessage = rsa.encrypt(originalMessage);
BigInteger decryptedMessage = rsa.decrypt(encryptedMessage);
assertEquals(originalMessage, decryptedMessage);
}
// then @Test
assertEquals("Such secure", decryptedText); void testEmptyMessage() {
String originalMessage = "";
assertThrows(IllegalArgumentException.class, () -> rsa.encrypt(originalMessage));
assertThrows(IllegalArgumentException.class, () -> rsa.decrypt(originalMessage));
}
@Test
void testDifferentKeySizes() {
// Testing with 512-bit RSA keys
RSA smallRSA = new RSA(512);
String originalMessage = "Test with smaller key";
String encryptedMessage = smallRSA.encrypt(originalMessage);
String decryptedMessage = smallRSA.decrypt(encryptedMessage);
assertEquals(originalMessage, decryptedMessage);
// Testing with 2048-bit RSA keys
RSA largeRSA = new RSA(2048);
String largeOriginalMessage = "Test with larger key";
String largeEncryptedMessage = largeRSA.encrypt(largeOriginalMessage);
String largeDecryptedMessage = largeRSA.decrypt(largeEncryptedMessage);
assertEquals(largeOriginalMessage, largeDecryptedMessage);
}
@Test
void testSpecialCharacters() {
String originalMessage = "Hello, RSA! @2024#";
String encryptedMessage = rsa.encrypt(originalMessage);
String decryptedMessage = rsa.decrypt(encryptedMessage);
assertEquals(originalMessage, decryptedMessage);
} }
} }