RSA加密算法实战:用Java手把手实现非对称加密(附完整代码)

从零构建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的流程就可以概括为:

  1. 选两个大质数p和q
  2. 计算 n = p * q 和 φ(n) = (p-1)(q-1)
  3. 选择一个与φ(n)互质的整数e作为公钥
  4. 计算e关于φ(n)的模逆元d作为私钥
  5. 加密:密文 C = M^e mod n
  6. 解密:明文 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值