RSA 为什么公开公钥也解不开?2 个质数拆开实测一遍

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 的源码、本文档和五张截图都在公开仓库里。

把 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。

相关推荐
Linye2 小时前
告别“PRD 与原型两张皮”!我们把可交互原生组件直接嵌进了需求文档,还能双向回写 Markdown
前端
ModyQyW2 小时前
vite-plugin-uni-pages 0.6:统一扫描规则,开发期少重启
前端·uni-app
门思科技2 小时前
把 ThinkLink 接进 Home Assistant 与 Node-RED:一条 MQTT 转发器能走通的路
前端
没事写点啥2 小时前
WeKnora 回答溯源
前端·后端·ai编程
LuHang3 小时前
Vue3 大屏项目 MQTT 实战:机场与无人机 OSD 实时数据总线
前端
2401_885885043 小时前
芬兰语音通知接口怎样对接?语音提醒接入流程
android·前端·python·php·语音识别
铁皮饭盒3 小时前
这6个小模型各有绝活, 且都可cpu/浏览器端运行
前端·后端
徐小夕3 小时前
JitKnow AI知识库 4.7 发布|多文档推理+拖拽 Agent + 技能库,打造更懂业务的 AI 知识中台
前端·vue.js·github
小poop4 小时前
从“雷峰塔”到“对象”:JavaScript基础语法入门指南
前端·javascript