RSA 的公钥 (e, n) 挂在网上随便看,私钥 d 却藏得住,秘密只有一条:两个质数相乘谁都会,把乘积拆回质数很难。
我在页面上用 p=61、q=53 走了一遍,算出 n=3233、φ(n)=3120、e=17、d=2753。
把 65 加密成 2790,再用 d 解回 65,一次不差。
换成 9998000099 这个 n,试除法要试 49995 次才拆出 99989 和 99991。

在线案例与源码:atomgit.com/deli007/dem... 成品只有一个 index.html,零依赖、不联网,双击就能用。文里的数字我都在页面上点过一遍,又用一份 Python 脚本独立复算过。
一、它是什么
这是一个在线的 RSA 加密实验室,把密钥生成、加密解密、试除攻击、签名验签拆成四个面板。
| 面板 | 干什么 | 你能拿到什么 |
|---|---|---|
| ① 密钥生成 | 输入两个不相等的质数 | n、φ(n)、e、d,以及扩展欧几里得求 d 的每一步 |
| ② 加密 / 解密 | 输入数字或短文本 | 逐字节的密文与还原结果对照表 |
| ③ 攻击演示 | 输入一个 n | 质因数、试除次数、耗时,以及位数-耗时曲线 |
| ④ 签名 / 验签 | 输入消息 m | 签名值、验签结论、篡改后的失败提示 |
四块拼起来正好是一条完整的链路:先造钥匙,再用钥匙,最后看钥匙在什么条件下会被撬开。
二、准备环境:进入码道 Web
浏览器打开码道 Web 版:devcloud.cn-north-4.huaweicloud.com/chat?source... ,登录后直接在对话框里写需求。
码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。
这一轮我把要算的东西全部点名写进了需求。
- 密钥生成要给出 n、φ(n)、e、d,并逐步列出扩展欧几里得求 d 的过程。
- 加密要逐字节做,幂运算的中间结果要能看见。
- 攻击面板要统计试除次数和耗时,并画一条随位数增长的耗时曲线。
- 签名面板要带篡改演示,改一个数字就得验不过。
- p、q 必须是不相等的质数,e 必须与 φ(n) 互质,非法输入给中文提示并保留上一次的有效结果。
最后两条决定了这个工具有没有可以动手验证的抓手。
三、为什么两个质数相乘容易、拆开难
RSA 的全部安全性,押在一件事上:乘法是单向门。
61 乘 53,我用眼睛就能算出是 3233。
反过来,给你 3233,问它是哪两个质数乘出来的,就只能一个一个试。
试到 53 才撞上,前面 26 次全是白做。
质数越大,这个白算的次数按平方根往上涨,密码学的难度就长在这里。
四、密钥怎么来:n、φ、e、d 四个量
面板① 把四个量一次算齐。
| 符号 | 含义 | 61 与 53 这一组 |
|---|---|---|
| n | 两个质数的乘积 | 3233 |
| φ(n) | (p-1)×(q-1) | 3120 |
| e | 公钥指数,需与 φ(n) 互质 | 17 |
| d | 私钥指数,e 在模 φ(n) 下的逆元 | 2753 |
d 不是猜出来的,是扩展欧几里得算出来的。
页面把每一步的商、余数和系数都列了出来,最后一行给出 x = -367。
取 d = -367 mod 3120 = 2753,代回去验一遍:17 × 2753 = 46801,46801 mod 3120 = 1。
页面上那行绿框写得很直白:公钥 = (e=17, n=3233),私钥 = (d=2753, n=3233),已校验 e·d ≡ 1 (mod φ) 成立。
五、加密为什么能还原:65 变 2790 再变回 65
加密就是一次模幂:c = m 的 e 次方 mod n。
解密是同一件事,只是把指数换成 d:m' = c 的 d 次方 mod n。
我拿教科书上最有名的那组数点了一遍,m = 65。
65 的 17 次方再 mod 3233,得到 2790。
2790 的 2753 次方再 mod 3233,得到 65,正好回到原点。
页面用的是平方-乘算法,指数按二进制拆开,每一步平方、每一步乘 base 的结果都列在表里。
所以算 2753 次幂只需要十几次乘法,而不是真的乘 2753 次。
六、逐字节加密:Hello RSA 变成一串数字
短文本模式是按字节加密的。
「Hello RSA」共 9 个字节,每个字节单独做一次模幂。

| 序号 | 原字节 m | 密文 c | 还原 m' | 字符 |
|---|---|---|---|---|
| 0 | 72 | 3000 | 72 | H |
| 1 | 101 | 1313 | 101 | e |
| 2 | 108 | 745 | 108 | l |
能无损还原有个前提:每个字节必须小于 n。
n=3233 远大于字节上限 255,所以这一组不会溢出。
换一组小 n 就不一定,比如 17 × 11 = 187,超过 187 的字节就还原不回来。
七、攻击演示:n 越大,试除越慢
面板③ 用的是最笨也最直观的攻击方式:从 2 开始一个一个试。
我把 n 填成 9998000099,点「分解质因数」。

页面给出:质因数 99989 × 99991,试除 49995 次,耗时 2.100 毫秒,还自己验算了一遍乘积。
9998000099 才 10 位十进制数,试除次数已经五万量级。
换成 RSA 实际用的 2048 位,试除次数要涨到 10 的 300 次方量级。

曲线下面附了同一轮的样本表,位数从 4 位涨到 15 位。
| n 的来源 | 十进制位数 | 试除次数 | 耗时 (ms) |
|---|---|---|---|
| 61×53 | 4 | 27 | 0.000 |
| 1009×1013 | 7 | 505 | 0.000 |
| 100003×100019 | 11 | 50002 | 1.900 |
| 1000003×1000033 | 13 | 500002 | 12.600 |
| 9999991×10000019 | 15 | 4999996 | 125.000 |
每加 2 位十进制数,试除次数大约乘 10,耗时跟着涨,两次采样之间差了 9.9 倍。
有两处得说清楚,不然会误读这张表。
位数小于 9 的几行耗时全是 0.000,不是真的不用时间,而是毫秒计时器分辨不出来。
同一段代码我前后跑了两轮,15 位那一行一次是 128.1 毫秒,一次是 125.0 毫秒,差 2.4%。
所以这张表只能看趋势,别拿单个小数当基准。
八、签名与篡改:改一个数字就验不过
面板④ 用私钥 d 签名,用公钥 e 验签。
我把 m 填成 2024,点「用 d 签名」。
页面给出签名 s = 1658,再用 e 验签得 m' = 2024,判定「验签通过」。
接着把签名改成 s+1 = 1659,模拟一次篡改。
验签结果变成 m'' = 876,和 2024 对不上,页面直接给出中文提示:签名被篡改,消息完整性校验未通过,拒绝接受该签名。
改动只有 1,签名就废了,这就是签名能证明「内容没被动过」的原因。
九、验证与踩坑:53 项断言独立复算
页面上的数字我不敢直接抄进文章,就另写了一份纯 Python 脚本重算。
脚本从 index.html 里读那六组预设质数,把质数判定、扩展欧几里得、模幂、试除法各实现一遍,再逐条断言。
powershell
python verify_rsa.py
53 项断言全部通过,我在现场逐条对过。
复算里有四条值得记。
第一条,六组预设质数算出的 d 都满足 e·d ≡ 1 (mod φ),比如 17/11 这组 d=113,61/53 这组 d=2753。
第二条,17 × 11 = 187 这组小 n,我把 m 从 0 到 186 全试了一遍,187 次签名验签全部通过。
第三条,签名 +1 的篡改在小 n 上穷举 187 次没有一次漏过,换成 3233 这组做 20 万次随机篡改,被误判通过的次数还是 0。
第四条,试除次数和页面对得上:3233 要试 27 次,9998000099 要试 49995 次。
踩到的真实问题记在这里。
第一版曲线画出来之后,y 轴标题「耗时 (对数刻度)」压在刻度文字身上,两行字叠在一起看不清。
原因是画布左边距只留了 56 像素,刻度文字右对齐到 56 处,长标签就被挤到了标题头上。
我把左边距改成 92 像素,y 轴标题挪到 x=8,重画之后就不打架了。
另一处是上面提过的计时抖动,属于工具本身的精度边界,没改。
十、这个工具验不了什么
边界要写清楚,不然容易当成生产代码用。
- 小质数只是为了看清步骤,真实 RSA 的 n 是 2048 位,本工具算不动,也不打算算。
- 这是教科书版 RSA,没有填充。同样的明文永远得到同样的密文,真实使用必须加 OAEP 这类填充。
- 加密只支持 m < n 的单个整数或短文本,不做分块,超范围直接报错。
- 攻击演示只有试除法一种,没有实现 Pollard rho 这类更快的因数分解。
- 毫秒计时只在位数较大时才可信,小 n 的数字当噪声看。
- 全程不联网、不上传、无第三方依赖,p、q 和明文只留在本地内存。
最后一条最要紧:能算清步骤不等于能当加密库用,也不等于这些数字离开浏览器还安全。
十一、直接下载和试玩
Demo 的源码、本文档和五张截图都在公开仓库里。
- Demo Park 仓库:atomgit.com/deli007/dem...
- 本案例目录:atomgit.com/deli007/dem...
把 index.html 拷到本地双击就能用,想换质数直接改输入框。
十二、本地复现与下一步
本地跑三步。
powershell
git clone https://atomgit.com/deli007/demo_park.git
cd demo_park\codearts-rsa-crypto-lab
python -m http.server 8080
浏览器打开 http://localhost:8080/index.html。
亲手点三下:先点预设 61 / 53 看 e·d ≡ 1 那行校验条,再把 e 改成 15 看它怎么报不互质,最后到面板③ 分解 9998000099 看试除次数和耗时。

下一步我想补两件事:把 Pollard rho 加进攻击面板,和试除法做同屏对照;再把 OAEP 填充的原理也做成可单步的演示,免得读者以为加填充只是形式。
十三、使用码道体会
- 先把公式钉死,再要画面。n、φ(n)、e、d 的定义和校验关系我都在需求里点名,生成出来的结果才对得上教科书。
- 要「中间过程」。我只说要透明,把扩展欧几里得每一步列出来,页面就给出了商、余数、系数三列,而不是只丢一个 d。
- 要「可验证的抓手」。每个面板我都配了一条能动手改的输入:改 e、改 n、改签名,能动的才是能验的。
- 失败路径单独写一段。两类错误的中文提示原文我都留进了文章,读者才有得对照。
- 一句需求别塞太多。这一轮拆成五条短句,生成的结构就没跑偏。
- 生成的界面要自己盯一眼。坐标轴重叠这种问题不会报错,只看截图才发现得了。
十四、总结
RSA 的公钥随便给,因为从 (e, n) 反推出 d,得先把 n 分解回两个质数。
61 × 53 这种小 n,试除 27 次就拆开了。
9998000099 要试 49995 次,花了 2.1 毫秒;而 2048 位已经远远超出任何暴力尝试的范围。
我拿 65 走完 65 → 2790 → 65,又拿 2024 走完签名、验签和篡改失败,还用一份独立脚本复算了 53 项断言。
想自己验证,clone 下来开个静态服务器,或者直接打开那个 index.html。