概述
RSA算法是当今互联网安全的基石,广泛应用于HTTPS、数字签名和密钥交换。与AES等对称加密不同,RSA使用一对数学上关联但不同的密钥:公钥用于加密,私钥用于解密。本文将用Java实现完整的RSA算法,涵盖大整数运算、素数生成、密钥生成、加密解密和数字签名。
数学原理
RSA的安全性基于大整数分解难题:将两个大素数的乘积分解回原来的素数,在计算上极其困难。
密钥生成步骤
- 随机选择两个大素数 p 和 q
- 计算 n = p × q(模数)
- 计算欧拉函数 φ(n) = (p-1)(q-1)
- 选择公钥指数 e,满足 1 < e < φ(n) 且 gcd(e, φ(n)) = 1
- 计算私钥指数 d,满足 e × d ≡ 1 (mod φ(n))
加密与解密
- 加密:c = m^e mod n
- 解密:m = c^d mod n
Java实现
大整数工具类
import java.math.BigInteger;
import java.security.SecureRandom;
public class RSA {
// 密钥长度(比特)
private static final int KEY_SIZE = 1024;
private static final SecureRandom random = new SecureRandom();
private BigInteger n; // 模数
private BigInteger e; // 公钥指数
private BigInteger d; // 私钥指数
private BigInteger p, q; // 素数因子
private BigInteger phi; // 欧拉函数
public RSA() {
generateKeys();
}
/**
* 生成RSA密钥对
*/
public void generateKeys() {
// 1. 生成两个大素数
p = generatePrime(KEY_SIZE / 2);
q = generatePrime(KEY_SIZE / 2);
// 确保 p != q
while (p.equals(q)) {
q = generatePrime(KEY_SIZE / 2);
}
// 2. 计算模数 n = p * q
n = p.multiply(q);
// 3. 计算欧拉函数 φ(n) = (p-1)(q-1)
phi = p.subtract(BigInteger.ONE)
.multiply(q.subtract(BigInteger.ONE));
// 4. 选择公钥指数 e(常用 65537 = 2^16 + 1)
e = new BigInteger("65537");
while (phi.gcd(e).intValue() > 1) {
e = e.add(BigInteger.TWO);
}
// 5. 计算私钥指数 d = e^(-1) mod φ(n)
d = e.modInverse(phi);
}
/**
* Miller-Rabin素性测试生成大素数
*/
private BigInteger generatePrime(int bitLength) {
while (true) {
BigInteger candidate = new BigInteger(bitLength, random);
// 确保是奇数且最高位为1
candidate = candidate.setBit(0).setBit(bitLength - 1);
if (isProbablePrime(candidate, 20)) {
return candidate;
}
}
}
/**
* Miller-Rabin素性测试
* @param n 待测试的数
* @param k 测试轮数,错误概率 < 4^(-k)
*/
public boolean isProbablePrime(BigInteger n, int k) {
if (n.compareTo(BigInteger.TWO) < 0) return false;
if (n.equals(BigInteger.TWO) || n.equals(BigInteger.valueOf(3))) return true;
if (n.mod(BigInteger.TWO).equals(BigInteger.ZERO)) return false;
// 将 n-1 分解为 2^r * d
BigInteger d = n.subtract(BigInteger.ONE);
int r = 0;
while (d.mod(BigInteger.TWO).equals(BigInteger.ZERO)) {
d = d.divide(BigInteger.TWO);
r++;
}
// 进行k轮测试
for (int i = 0; i < k; i++) {
BigInteger a = randomBigInteger(BigInteger.TWO, n.subtract(BigInteger.TWO));
BigInteger x = a.modPow(d, n);
if (x.equals(BigInteger.ONE) || x.equals(n.subtract(BigInteger.ONE))) {
continue;
}
boolean continueOuter = false;
for (int j = 0; j < r - 1; j++) {
x = x.modPow(BigInteger.TWO, n);
if (x.equals(n.subtract(BigInteger.ONE))) {
continueOuter = true;
break;
}
}
if (continueOuter) continue;
return false;
}
return true;
}
private BigInteger randomBigInteger(BigInteger min, BigInteger max) {
BigInteger range = max.subtract(min).add(BigInteger.ONE);
BigInteger result;
do {
result = new BigInteger(range.bitLength(), random);
} while (result.compareTo(range) >= 0);
return result.add(min);
}
加密解密与签名
/**
* 公钥加密:c = m^e mod n
* @param message 明文(必须小于n)
*/
public BigInteger encrypt(BigInteger message) {
if (message.compareTo(n) >= 0) {
throw new IllegalArgumentException("Message must be less than n");
}
return message.modPow(e, n);
}
/**
* 私钥解密:m = c^d mod n
*/
public BigInteger decrypt(BigInteger ciphertext) {
return ciphertext.modPow(d, n);
}
/**
* 使用中国剩余定理(CRT)加速解密
* 速度比直接 modPow 快约4倍
*/
public BigInteger decryptCRT(BigInteger ciphertext) {
// dp = d mod (p-1), dq = d mod (q-1)
BigInteger dp = d.mod(p.subtract(BigInteger.ONE));
BigInteger dq = d.mod(q.subtract(BigInteger.ONE));
// qInv = q^(-1) mod p
BigInteger qInv = q.modInverse(p);
// m1 = c^dp mod p, m2 = c^dq mod q
BigInteger m1 = ciphertext.modPow(dp, p);
BigInteger m2 = ciphertext.modPow(dq, q);
// h = qInv * (m1 - m2) mod p
BigInteger h = qInv.multiply(m1.subtract(m2)).mod(p);
// m = m2 + h * q
return m2.add(h.multiply(q));
}
/**
* 数字签名:使用私钥对消息哈希签名
*/
public BigInteger sign(byte[] message) {
BigInteger hash = hashToBigInteger(message);
return hash.modPow(d, n);
}
/**
* 签名验证:使用公钥验证签名
*/
public boolean verify(byte[] message, BigInteger signature) {
BigInteger hash = hashToBigInteger(message);
BigInteger decryptedHash = signature.modPow(e, n);
return hash.equals(decryptedHash);
}
/**
* 简单哈希函数(实际应使用SHA-256)
*/
private BigInteger hashToBigInteger(byte[] message) {
long hash = 0;
for (byte b : message) {
hash = ((hash << 5) - hash) + (b & 0xFF);
hash = hash & 0x7FFFFFFFFFFFFFFFL;
}
return BigInteger.valueOf(hash).mod(n);
}
// Getters
public BigInteger getN() { return n; }
public BigInteger getE() { return e; }
public BigInteger getD() { return d; }
完整测试
public static void main(String[] args) {
System.out.println("=== RSA算法演示 ===");
// 生成密钥
RSA rsa = new RSA();
System.out.println("模数 n 位数: " + rsa.getN().bitLength());
System.out.println("公钥 e: " + rsa.getE());
// 加密测试
String original = "Hello RSA!";
BigInteger message = new BigInteger(original.getBytes());
System.out.println("原始消息: " + original);
BigInteger encrypted = rsa.encrypt(message);
System.out.println("加密后: " + encrypted.toString(16).substring(0, 32) + "...");
// 解密测试
BigInteger decrypted = rsa.decrypt(encrypted);
String recovered = new String(decrypted.toByteArray());
System.out.println("解密后: " + recovered);
// CRT加速对比
long start = System.nanoTime();
for (int i = 0; i < 100; i++) {
rsa.decrypt(encrypted);
}
long normalTime = System.nanoTime() - start;
start = System.nanoTime();
for (int i = 0; i < 100; i++) {
rsa.decryptCRT(encrypted);
}
long crtTime = System.nanoTime() - start;
System.out.println("\n=== 性能对比(100次解密)===");
System.out.println("标准解密: " + (normalTime / 1000000) + " ms");
System.out.println("CRT加速: " + (crtTime / 1000000) + " ms");
System.out.println("加速比: " + String.format("%.2fx", (double)normalTime / crtTime));
// 数字签名测试
String contract = "我同意该条款";
BigInteger signature = rsa.sign(contract.getBytes());
boolean valid = rsa.verify(contract.getBytes(), signature);
System.out.println("\n=== 数字签名 ===");
System.out.println("签名验证: " + (valid ? "通过" : "失败"));
}
}
算法分析
安全性参数
| 密钥长度 | 推荐用途 | 安全级别 |
|---|---|---|
| 1024位 | 已不推荐 | ~80位 |
| 2048位 | 标准推荐 | ~112位 |
| 4096位 | 高安全场景 | ~140位 |
时间复杂度
| 操作 | 时间复杂度 |
|---|---|
| 密钥生成 | O(k⁴) 其中k为位数(受素数测试支配) |
| 加密 | O(k³) 使用快速模幂 |
| 解密(标准) | O(k³) |
| 解密(CRT) | O(k³/4) 约4倍加速 |
实际应用注意事项
- 填充方案: never使用裸RSA,必须配合OAEP或PKCS#1 v1.5填充
- 消息长度:RSA直接加密的消息长度不能超过密钥长度减去填充开销
- 实际用法:通常用RSA加密AES密钥,再用AES加密实际数据(混合加密)
- 随机数安全:素数生成依赖高质量的随机数源
总结
RSA是现代密码学的里程碑算法。本文从素数生成、密钥派生到加密解密和CRT优化,完整呈现了RSA的Java实现。理解RSA有助于把握非对称密码学的本质:利用数学难题构建单向函数,从而实现安全的密钥分发和数字签名。