从零构建RSA加密引擎:一个Java开发者的深度实践笔记
我记得第一次接触非对称加密,是在一个需要安全传输用户凭证的项目里。当时团队讨论了半天该用哪种方案,有人提议直接用现成的库,但作为技术负责人,我坚持要有人真正理解底层原理——毕竟,如果你不知道锁是怎么工作的,又怎么能确保它真的安全?RSA就是这样一把“锁”,而今天我要分享的,就是如何用Java亲手打造这把锁的每一个零件。
这篇文章面向的是已经熟悉Java基础语法、对加密概念有所了解但想深入实践的开发者。我不会只给你一堆API调用示例,而是带你从数学原理开始,一步步推导,最终用代码实现完整的RSA加密解密流程。你会发现,那些看似神秘的“密钥生成”、“模幂运算”,其实都是可以亲手实现的算法。更重要的是,通过这个过程,你会对现代密码学的基石有更直观、更深刻的理解。
1. 重新认识RSA:不只是“公钥加密”
很多人对RSA的第一印象就是“公钥加密,私钥解密”。这没错,但过于简化了。RSA的安全性建立在一个巧妙的数学难题上:大整数的质因数分解在计算上的不可行性。换句话说,给你两个很大的质数p和q,算出它们的乘积n很容易;但反过来,给你这个很大的n,要找出原来的p和q,以目前计算机的能力,需要天文数字的时间。
1.1 核心数学组件拆解
在动手写代码前,我们必须先理解几个关键的数学概念,它们构成了RSA算法的骨架:
- 质数(Prime Number):大于1的自然数,且只能被1和自身整除。在RSA中,我们选择两个非常大的、随机的质数作为算法的“种子”。
- 模运算(Modular Arithmetic):也就是我们常说的“求余数”。RSA的加密和解密本质上都是在模n下的运算,这是保证算法单向性的关键。
- 欧拉函数 φ(n):表示小于n且与n互质的正整数的个数。当n是两个质数p和q的乘积时,φ(n) = (p-1)(q-1)。这个函数在密钥生成中扮演着核心角色。
- 模逆元(Modular Inverse):对于整数e和m,如果存在整数d使得
(e * d) % m == 1,那么d就是e在模m下的逆元。在RSA中,私钥d就是公钥e关于φ(n)的模逆元。
理解这些概念后,RSA的流程就可以概括为:
- 选两个大质数p和q
- 计算 n = p * q 和 φ(n) = (p-1)(q-1)
- 选择一个与φ(n)互质的整数e作为公钥
- 计算e关于φ(n)的模逆元d作为私钥
- 加密:密文 C = M^e mod n
- 解密:明文 M = C^d mod n
其中M是明文(需要先转换为整数),C是密文。
1.2 为什么RSA是安全的?
这里有一个常见的误解:RSA算法本身是公开的,所以不安全。恰恰相反,正是算法的公开性才保证了其安全性。攻击者知道一切——知道n,知道e,甚至知道加密的数学公式。但他们不知道p和q,因此无法计算φ(n),也就无法推导出私钥d。只要质数足够大(目前推荐2048位以上),用现有计算机暴力分解n需要的时间可能超过宇宙的年龄。
注意:本文旨在教学原理和实现。实际生产环境中,绝对不要使用自己编写的加密算法用于真实的数据保护。请使用经过严格审计和广泛测试的成熟库,如Java的
java.security包。自己实现的版本可能存在侧信道攻击等安全漏洞。
2. 搭建开发环境与质数生成
我们从一个干净的Java项目开始。我习惯用Maven管理依赖,但这里我们只用到标准库,所以一个简单的Java项目即可。
2.1 质数生成:算法的起点
RSA的第一步是生成两个大质数。在现实中,我们使用概率性素性测试算法(如米勒-拉宾测试),因为它们对于大数比确定性测试快得多。Java的BigInteger类提供了现成的方法,但为了理解,我们先自己实现一个简单的素性测试。
import java.math.BigInteger;
import java.util.Random;
public class SimplePrimeGenerator {
/**
* 一个简单的素性测试(费马小定理基础版,仅用于教学)
* 注意:这不是加密强度的测试,实际应用请使用BigInteger.probablePrime
*/
public static boolean isProbablePrime(BigInteger num, int certainty) {
if (num.compareTo(BigInteger.ONE) <= 0) {
return false;
}
if (num.equals(BigInteger.TWO)) {
return true;
}
if (num.mod(BigInteger.TWO).equals(BigInteger.ZERO)) {
return false;
}
Random rnd = new Random();
// 进行多次费马测试
for (int i = 0; i < certainty; i++) {
BigInteger a;
do {
a = new BigInteger(num.bitLength() - 1, rnd);
} while (a.compareTo(BigInteger.ONE) <= 0);
// 费马小定理:如果p是质数,且a不是p的倍数,则 a^(p-1) ≡ 1 (mod p)
if (!a.modPow(num.subtract(BigInteger.ONE), num).equals(BigInteger.ONE)) {
return false; // 一定是合数
}
}
return true; // 可能是质数
}
public static BigInteger generatePrime(int bitLength) {
Random rnd = new Random();
BigInteger prime;
do {
// 生成一个奇数
prime = new BigInteger(bitLength, rnd);
prime = prime.setBit(0); // 确保是奇数
} while (!isProbablePrime(prime, 5)); // 进行5次测试
return prime;
}
public static void main(String[] args) {
System.out.println("生成一个128位的质数(用于演示,实际RSA需要1024位以上):");
BigInteger p = generatePrime(128);
System.out.println("p = " + p.toString(16)); // 以16进制输出
System.out.println("位数: " + p.bitLength());
}
}
运行这个程序,你会得到一个128位的质数。但在真正的RSA中,我们需要更大的数:
| 安全等级 | 推荐的RSA密钥长度(位) | 等效对称密钥长度 | 预计被破解时间(假设) |
|---|---|---|---|
| 基础 | 1024 | 80 | 已不推荐,可能被破解 |
| 标准 | 2048 | 112 | 未来许多年安全 |
| 较高 | 3072 | 128 | 长期安全 |
| 极高 | 4096 | 192 |

&spm=1001.2101.3001.5002&articleId=154221670&d=1&t=3&u=adbab3f3860845cabf365a7219f40082)
8359

被折叠的 条评论
为什么被折叠?



