这一章要解决什么
这一章回答三个问题:
- 我们到底要保护信息的什么?(不只是"别被看到"这么简单)
- 一个加密系统由哪几块拼成?
- 古人是怎么加密的?他们的办法为什么后来不管用了?
1.4 节的数学是全书地基——它看起来最枯燥,但后面每一章都在用它。建议在这一节多花时间。
1.1 信息要保护什么
生活场景
你去银行办事,走一遍流程:
- 进门,保安查身份证——确认你是不是你
- 进大厅后,你只能去普通柜台,进不了金库
- 你跟柜员说话,不希望旁边的人听见
- 你签的合同,事后不能被偷偷改动
- 你签完字,不能翻脸说"我没签"
这五件事,正好对应信息安全的五类安全服务。
| 服务 | 用一句话问 | 课件里的正式说法 |
| 认证(鉴别)服务 | "你是谁?我怎么信你就是你?" | 在网络交互过程中,对收发双方的身份及数据来源进行验证 |
| 访问控制服务 | "我能干什么?" | 防止未授权用户非法访问资源,包括用户身份认证和用户权限确认 |
| 数据保密性服务 | "别人能否偷听到?" | 防止数据在传输中被破解或泄露 |
| 数据完整性服务 | "传输中被改过没有?" | 防止数据在传输过程中被篡改 |
| 抗否认性服务 | "我想赖账,怎么办?" | 防止收发双方在操作后否认自己所做的操作 |
这五个问题,各自用什么技术解决?
大白话
课件的答案是:不同的服务,配不同的密码技术。
| 安全服务 | 用什么技术实现 |
| 保密性服务 | 加密 |
| 认证鉴别服务 | 密钥、数字签名 |
| 访问控制服务 | 密钥、权限 |
| 完整性服务 | 加密、数字摘要 |
| 抗否认性服务 | 数字签名 |
前后呼应
这张表是整门课的总纲。 后面每一章,本质上都在实现其中某一项:
- 第 2 章讲"加密"和"数字摘要"具体怎么实现
- 第 3.5 节讲"认证鉴别"怎么实现
- 第 2.5 节讲"数字签名"(抗否认性 + 完整性)怎么实现
三个"如何实现"(课件用图说明的场景)
场景一:保密性
Alice 给 Bob 发消息,路上有黑客在偷听(被动攻击)。
解决:加密。黑客截到的是密文,看不懂。
场景二:完整性和鉴别
Alice 算出一个"指纹" z = hk(m),跟着消息 m 一起发。
Bob 收到后,自己也对 m 算一遍指纹 y = hk(m)。
如果 y ≠ z,说明 m 在路上被改过了。(因为改动一点点,指纹就会大变)
解决:密钥 + 哈希函数。
场景三:抗否认性
Alice 说"我没发过这条消息",Bob 说"你发了"。谁对?
这就需要请一个仲裁者来判定。可仲裁者凭什么判?他需要一个只有 Alice 能生成、但谁都能验证的东西。
解决:数字签名。(注意这个巧妙的性质——"只有她能做,但谁都能验",这正是数字签名的精髓。)
自测:请把右边的技术连到左边的服务上
| 服务 | 答案 |
| 保密性服务 | 加密 |
| 认证鉴别服务 | 密钥、数字签名 |
| 访问控制服务 | 密钥、权限 |
| 完整性服务 | 加密、数字摘要 |
| 抗否认性服务 | 数字签名 |
记忆窍门:
"保密靠加密"、"完整靠摘要"、"抗赖靠签名"——三个最容易混的先记住。剩下的"认证"和"访问控制"都需要密钥(因为要先认出身份,才能谈权限)。
1.2 一个密码系统由什么组成
五要素
生活场景
回到开头那个寄信的例子:
Alice ──[明文 "Good luck…"] ──加密──→ [密文 "@**&%#$…"] ──公共网络──→ [密文] ──解密──→ [明文 "Good luck…"] ── Bob
拆开看,一共出现了五样东西。
| 要素 | 是什么 | 例子 |
| 明文 | 要保护的原始消息 | Good luck |
| 密文 | 加密后看不懂的样子 | @**&%#$ |
| 加解密算法 | 明文↔密文的变换规则 | "每个字母往后挪 N 位" |
| 密钥 | 算法里的可调参数,保密的关键 | N = 3 |
| 密钥空间 | 密钥所有可能取值的集合 | N 可以是 1~25,共 25 个 |
大白话
算法是"菜谱",密钥是"放多少盐"。
菜谱可以公开(大家都知道怎么做这道菜),但你放盐放多少,别人才不知道这道菜最后是什么味道——这就是密钥的作用。
密钥空间就是"盐可以放多少种量"。可选量越多,别人猜中的概率越低。
密码学发展的三个阶段
| 阶段 | 特征 |
| 1949 年以前 | 手工阶段,密码学是一门艺术。主要靠隐写术(隐形墨水、字符格式变化、图像) |
| 1949~1975 | 机械阶段,现代密码出现。1949 年香农提出"保密系统信息理论",提出:数据的安全基于密钥而不是算法 |
| 1976 年以后 | 计算机阶段,公钥密码出现。1976 年 Diffie & Hellman 提出不对称密钥密码;1977 年 RSA;90 年代 ECC |
前后呼应
为什么 1949 年香农那句话是分水岭?
因为在它之前,密码学家花大量精力在"怎么把算法藏好";在它之后,大家明白了——算法该公开,只需要守住密钥。整个现代密码学就建立在这一点上。
两种"安全",别搞混
| 理论安全(无条件安全) | 实际安全(计算安全) |
| 什么意思 | 攻击者无论截获多少密文,都得不到足够信息唯一决定明文 | 攻击者如果有无限资源,任何系统都能破;但在有限资源内破不了 |
| 要什么条件 | 密钥长度 ≥ 明文长度,而且密钥只用一次(一次一密) | 破它的代价 > 它能带来的收益 |
| 现实能不能做到 | 能,但用不起来(密钥太长、管理太麻烦,见 1.3 节 OTP) | 现实中全部用的是这种 |
大白话
理论安全 = "数学上不可能打开"。
实际安全 = "理论上能打开,但打开它花的钱比里面东西还贵,所以没人去开"。
很反直觉吧?——世界上没有绝对安全的密码系统,只有"划算不划算"。
两种分类维度(重点,容易考)
维度二:加密密钥公开不公开?
| 对称密码 | 公钥密码 |
| 密钥 | 加密密钥和解密密钥相同(或能互相推出) | 公钥和私钥不同,而且很难从公钥推出私钥 |
| 特点 | 快,但密钥管理复杂 | 密钥管理简单,但慢 |
| 代表 | DES、AES、SM4、RC4 | RSA、ECC、SM2、DH |
| 用途 | 加密消息 | 协商密钥、数字签名 |
| n 人互通的密钥数 | n(n−1)/2 | n 个私钥 |
大白话:用保险柜和邮箱来记
对称密码 = 保险柜。
你把文件放进保险柜、设密码锁上。取的时候要用同一个密码。
→ 所以密码必须保密;两个人用就得好商量一个密码(人一多,要商量很多对密码)。
公钥密码 = 邮箱。
谁都能往邮箱里投信(公钥公开),但只有本人有密码能开邮箱(私钥保密)。
→ 所以密钥管理简单(每人只需管好自己的邮箱密码)。
为什么对称密码的密钥数那么吓人?
动手算一算
假设 5 个人(A、B、C、D、E)要两两保密通信。
每对人需要一个共享密钥:A-B、A-C、A-D、A-E、B-C、B-D、B-E、C-D、C-E、D-E。
数一数:10 个。公式是 n(n−1)/2 = 5×4/2 = 10。
如果是 100 个人呢?100×99/2 = 4950 个密钥。每个人要记 99 个密钥——这根本管不过来。
换成公钥密码:100 个人,每人只要管好自己的 1 个私钥。答案就是"密钥管理简单"。
自测:为什么说公钥密码"密钥管理简单"?
因为公钥是公开的,不需要保密传输;只有私钥需要自己保管,一人一把。
对比一下:
- 对称密码:n 个人需要 n(n−1)/2 个密钥,而且每个都要"秘密地"送到对方手里
- 公钥密码:n 个人需要 n 个私钥,公钥随便贴在网上都行
"送密钥"这件事本身就是个大麻烦(见 0.4 节说的死循环),公钥密码直接绕开了它——所以叫"密钥管理简单"。
1.3 古典密码:换内容还是换位置
这一节要解决什么
古人没有电脑,他们的加密办法很朴素。但朴素不等于没用——它们包含了密码学最基本的两种思路,而且把它们搞懂,你就理解了"什么叫密码分析"。
另外这一节还埋了一堆数学基础(约简、同余、逆元),是 1.4 节的地基。
第一步:所有古典密码只有两种花样
为什么需要它
你可能觉得古典密码五花八门:凯撒密码、斯巴达密码棒、莫斯密码……但把它们摊开看,归根到底只有两种做法。
| 类型 | 做法 | 例子 |
| 代替密码 | 把明文里的字符换成别的字符(内容变了,位置没变) | 凯撒密码、莫斯密码 |
| 换位密码 | 把明文里的字符顺序打乱(内容没变,位置变了) | 斯巴达密码棒 |
大白话
拿一副扑克牌打比方:
- 代替 = 把花色全换掉(红桃→黑桃,黑桃→梅花…),但牌的顺序还是原来那样
- 换位 = 把牌洗乱,花色不变,但位置全变了
怎么一眼判断是哪种?
动手做:数字母
判断方法只有一句:数一数密文和明文的字母,是不是同一批?
- 是同一批(字母没变,只是顺序乱了)→ 换位
- 不是同一批(字母被换过了)→ 代替
练一练(别看答案,先自己判断)
| 明文 → 密文 | 是哪种? | 为什么 |
hello → khoor | | |
hello → lolelh | | |
computer → retupmoc | | |
secret → vhfuhw | | |
attackatdawn → dawnattackat | | |
看答案
| 明文 → 密文 | 是哪种 | 为什么 |
hello → khoor | 代替 | h→k, e→h, l→o… 字母被换过了 |
hello → lolelh | 换位 | 还是 h,e,l,l,o 那 5 个字母,只是顺序乱了 |
computer → retupmoc | 换位 | 字母完全相同,其实就是倒过来写(小心!看着陌生但字母没变) |
secret → vhfuhw | 代替 | s→v, e→h, c→f… 每个都往后挪了 3 位 |
attackatdawn → dawnattackat | 换位 | 同一批字母,把后 4 个挪到了前面 |
重点提醒:第 3 题最容易错。很多人看 retupmoc 觉得"这么陌生,字母肯定被换过了"——但"看着陌生" ≠ "字母变了"。
永远先数字母,别看观感。这一步只花 5 秒,能防住所有这类陷阱。
接下来:单表 vs 多表(这个区分很关键)
代替密码还能再分。区别在于:是一张表用到底,还是变着表用。
| 单表代替 | 多表代替 |
| 做法 | 一张替换表,从头用到尾 | 每隔几个字母就换一张表 |
| 效果 | 同一个字母永远变成同一个密文字母 | 同一个字母在不同位置会变成不同的密文字母 |
| 代表 | 移位密码、仿射密码 | Vigenère 密码、OTP |
为什么这个区分重要
因为单表代替有个致命弱点:它保留了字母的"出现频率"。
比如英文里 e 出现得最多。那么在单表代替的密文里,"出现最多的那个字母"大概率就对应 e。攻击者靠这个就能破译——这叫字母频率攻击。
多表代替就是来治这个病的:同一个 e 在不同位置变成不同的字母,频率被摊平了,统计就没用了。
前后呼应
记住"频率"这个词。第 1.3 节的 Vigenère 部分,我们会讲它怎么用"重合指数"(0.038 / 0.067 / 0.047 那三个数)重新把多表代替攻破。
① 移位密码(凯撒密码):最朴素的想法
生活场景
凯撒大帝要传军令,他想到的办法是:每个字母往后挪几位。
明文:i love china
每个字母挪 2 位 →
密文:k nqxg ejkpc
看着像乱码,但规律极其简单。
先做一件准备工作
要算数,得先把字母变成数字。英文字母 a~z 用 0~25 表示:
注意是从 0 开始,不是 1。为什么?因为后面要算"% 26",结果必须落在 0~25 这 26 个数字里,正好对应 26 个字母。如果从 1 开始,算到 26 会变成 0,0 不在 1~26 里,就漏了。
大白话
把字母的数字加上 k,如果超过 25 就"绕回来"。
所谓"绕回来",是因为字母表是一个环——z 后面又接回 a:
a b c d e … x y z → a b c …
(数到 z 就绕回 a)
动手做:跟课件算一遍
课件给的例子是 (24 + 3) = 27 % 26 = 1。拆开看:
- 数字 24 对应字母 y
- 24 + 3 = 27,超过 25 了
- 27 % 26 = 1(因为 27 − 26 = 1)
- 数字 1 对应字母 b
所以 y 加密后变成 b。用"绕回来"的说法验证:y 往后数 3 个 → z → a → b ✓
解密就反过来减:课件写 (1 − 3) = −2 % 26 = 24。
- 密文 1 对应 b
- 1 − 3 = −2,是负数
- −2 % 26 = 24(因为 −2 + 26 = 24)
- 数字 24 对应字母 y ✓ 还原了
小心坑:算出来是负数怎么办
课件这句 (1−3) = −2 % 26 = 24 里藏着一条全书通用的规则:
算出负数,就加模数变正。
为什么加 26 就行?因为 −2 和 24 相差正好 26,在"模 26"的世界里,这两个数是同一个:
−2 ≡ 24 (mod 26)
课件有句原话:"在密码学中:任何整数模 m 的约简都是非负数。"这个"约简"就是指这个动作。
这条规则你会在后面反复用到(比如求逆元、RSA 计算、欧拉定理降指数),一定要养成习惯。
移位密码的安全性问题
为什么需要它
移位密码的密钥 k 可以取 1~25,一共只有 25 种可能。
这意味着什么呢?攻击者最多试 25 次就能破译。拿张纸手算,一分钟都用不了。
这个"可选密钥的个数"就是密钥空间。课件点明了:
密钥空间决定了穷举攻击的难度。
所以课件紧接着问了一句:如何增大密钥空间,提高安全性?——这就引出了下一个算法。
自测:用密钥 k=3 加密 cat,结果是什么?
fdw
一步步来:
c = 2,2 + 3 = 5 → f
a = 0,0 + 3 = 3 → d
t = 19,19 + 3 = 22 → w
再解回来(密文 khoor,密钥 3):
- k = 10,10 − 3 = 7 →
h
- h = 7,7 − 3 = 4 →
e
- o = 14,14 − 3 = 11 →
l
- o = 14,14 − 3 = 11 →
l
- r = 17,17 − 3 = 14 →
o
得到 hello ✓
再练一个会出负数的:解 djx,密钥 5。
- d = 3,3 − 5 = −2 → −2 + 26 = 24 →
y
- j = 9,9 − 5 = 4 →
e
- x = 23,23 − 5 = 18 →
s
得到 yes ✓ —— 看到负数的处理了吧。
② 仿射密码:给密钥多加一个参数
为什么需要它
移位密码的问题:密钥空间只有 25,太容易被穷举。
仿射密码的思路非常直接:一个参数不够,我上两个参数。
| 公式 | 参数个数 |
| 移位密码 | y = (x + k) % 26 | 1 个(k) |
| 仿射密码 | y = (a·x + b) % 26 | 2 个(a, b) |
就是把 x + k 升级成了 a·x + b——先乘 a,再加 b。
(顺便:如果 a = 1,仿射密码就退化成了移位密码。)
为什么 a 不能随便选?(这是重点)
先看解密公式需要什么
解密要用到 a−1(a 的乘法逆元)。如果 a−1 不存在,就解不回来。
课件里讲过一个规则:乘法逆存在的条件是 (n, m) = 1,也就是两数互素。
所以 a 必须与 26 互素。
动手做:看看 a=2 会出什么事
假设 a = 2,加密规则 y = (2x + b) % 26。逐个数试一遍:
| 明文 x | 2x | y = 2x % 26 |
| 0 | 0 | 0 |
| 13 | 26 | 0 |
| 1 | 2 | 2 |
| 14 | 28 | 2 |
看出来了吗?x=0 和 x=13 加密后都是 0!
这意味着:收到密文 0,你分不清原文是 0 还是 13。信息丢了,救不回来。
根子在哪?因为 gcd(2, 26) = 2 ≠ 1,2 和 26 有公约数,所以"乘 2"这个操作不是一一对应的——多个输入挤到同一个输出去了。
26 个明文只产生了 13 种密文——一半的密文根本用不上。
大白话
好的加密必须"一对一":每个明文有唯一的密文,每个密文也能唯一地还原成明文。
a 与 26 互素,才能保证"乘 a"这个操作是漂亮的一一对应。
课件用的例子 a = 9,gcd(9, 26) = 1 ✓ 合法。
仿射密码的密钥空间
动手算一算
a 有多少种选法? 26 = 2 × 13,所以和 26 互素的数就是"不是 2 的倍数、也不是 13 的倍数"的数。
在 1~25 里数一数:
1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25
(就是除了 13 之外的奇数)共 12 个。
b 有多少种选法? b 可以是 0~25 任意一个,共 26 个。
所以总共 12 × 26 = 312 种。但其中 a=1、b=0 这一组是"什么都不做"(y = x),等于没加密,要去掉。
密钥空间 = 12 × 26 − 1 = 311
| 密钥空间 | 穷举要试几次 |
| 移位密码 | 25 | 25 次 |
| 仿射密码 | 311 | 311 次 |
大了 12 倍多。但说实话——311 次也就几分钟的事,还是不够安全。(后面 Vigenère 会把密钥空间真正拉起来。)
跟着课件算一遍
加密:明文 meet,密钥 (a,b) = (9,3)
加密式:y = (9x + 3) % 26
逐个算:
m = 12,(9×12 + 3) = 111,111 % 26 = 7 → h(因为 26×4 = 104,111−104 = 7)
课件给的结果是 meet me at midnight → hnns hn ds hxeqxfos,第一个字母确实是 h ✓
解密:那两个数(3 和 17)哪来的?
课件说这个密钥的解密式是 x = (3y + 17) % 26。这两个数不是拍脑袋来的,而是:
解密密钥 = (a−1, −a−1·b)
第一步:求 a−1 = 9−1 % 26
用 Euclid 算法(详细方法见 1.4 节,这里先看结果):
26 − 2×9 = 8
9 − 1×8 = 1 → 1 = 9 − 8 = 9 − (26 − 2×9) = 3×9 − 26
得到 3×9 − 1×26 = 1,所以 9−1 = 3。验算:9×3 = 27 = 26+1 ≡ 1 ✓
第二步:算 −a−1b = −(3×3) = −9
是负数,加 26 变正:−9 + 26 = 17
所以解密式是 x = (3y + 17) % 26。
验算:密文 h = 7,(3×7 + 17) = 38,38 % 26 = 12 → m ✓ 还原了
小心坑:加密密钥 ≠ 解密密钥
| 用途 | 密钥 |
| 加密 | (a, b) = (9, 3) |
| 解密 | (a−1, −a−1b) = (3, 17) |
两组数完全不同。考试如果问"解密密钥",填成加密的那组就错了。
(这个坑在 RSA 里也一样:加密用公钥 (e,n),签名用私钥 d。)
仿射密码怎么被攻击的(已知明文攻击)
如果攻击者知道了两组对应关系,就能反推密钥。课件给的公式:
a = (x − y)−1 · (E(x) − E(y)) % 26
b = (E(x) − a·x) % 26
思路其实很朴素:列两个方程,一相减,b 就被消掉了,只剩一个未知数 a。
7a + b ≡ 1 (mod 26)
12a + b ≡ 4 (mod 26)
────── 两式相减 ──────
5a ≡ 3 (mod 26)
然后求 a:两边乘 5−1(不能"除以 5",模运算没有除法)。这就是 1.4 节要练的手艺。
自测:已知 E(7)=1 且 E(12)=4,求加密密钥 (a,b)
第一步:两式相减消掉 b
7a + b ≡ 1 …(1)
12a + b ≡ 4 …(2)
(2)−(1):5a ≡ 3 (mod 26)
第二步:求 5−1 % 26
26 − 5×5 = 1 → 1 = 26 − 5×5 = (−5)×5 + 1×26
所以 5−1 = −5 = 21。验算:5×21 = 105 = 4×26+1 ✓
第三步:求 a
a = 21 × 3 = 63,63 % 26 = 11(63 − 52 = 11)。检查 gcd(11,26) = 1 ✓ 合法。
第四步:回代求 b
b = (1 − 11×7) % 26 = (1 − 77) % 26 = −76 % 26。−76 + 78 = 2。
答:加密密钥 (a, b) = (11, 2)
如果要解密密钥:再求 a−1 = 11−1 % 26 = 19(因 11×19 = 209 = 8×26+1),−a−1b = −38 ≡ 14。
所以解密密钥是 (19, 14),即 x = (19y + 14) % 26。
验算:D(1) = 33 % 26 = 7 ✓;D(4) = 90 % 26 = 12 ✓
③ Vigenère 密码:变着法儿换表
为什么需要它
回忆那个问题:单表代替保留了字母频率,所以能被统计攻破。
Vigenère 的思路是:别用一张表了,用一串密钥字母轮着换表。
先看个最小例子
明文字母 cat,密钥 key(三个字母)。
c 用密钥 k(=10)→ 2 + 10 = 12 → m
a 用密钥 e(=4)→ 0 + 4 = 4 → e
t 用密钥 y(=24)→ 19 + 24 = 43 % 26 = 17 → r
密文:mer
看明白了吗:每个位置用的移位量都不一样(10、4、24),而不是统一的 3。
那个 i%m 是什么意思
密钥不够长就循环使用。
密钥 key 长度是 3,但明文可能很长。那就把密钥一遍遍重复:
走到头就绕回开头,所以用 i % m 来取。
看它怎么实现"多表代替"
动手做:明文 meetme,密钥 key
明文: m e e t m e
密钥: k e y k e y ← key 循环
明文数字:12 4 4 19 12 4
密钥数字:10 4 24 10 4 24
结果: 22 8 2 3 16 2
密文:w i c d q c
重点看那三个 e(位置 1、2、5):
| 位置 | 明文 | 用的密钥字母 | 移位量 | 密文 |
| 1 | e | e | 4 | i |
| 2 | e | y | 24 | c |
| 5 | e | y | 24 | c |
同一个 e,在不同位置变成了不同的字母! 这就是"多表代替"——频率被摊平了。
小心坑:为什么"同一个字母变成不同密文"?
很多人的第一反应是"因为密钥循环使用"。这个答案是错的,它说的是另一件事。
真正的原因是:密钥里不同位置的字母不一样。
- 位置 1 用密钥字母 e → 移 4 位 →
e 变成 i
- 位置 2 用密钥字母 y → 移 24 位 →
e 变成 c
用的密钥字母不同,移位量就不同,结果自然不同。
那"密钥循环"导致什么? 正好相反——它导致"相同变成相同":位置 2 和位置 5 相隔 3(正好是密钥长度),它们用的都是密钥字母 y,所以 e 两次都变成 c。
而这就是 Vigenère 的死穴,下一段就讲。
Vigenère 的死穴:周期性
问题出在哪
课件原话:
"有周期性的弱点:若两个字符出现的间隔是密钥长度的倍数,则它们将以同样的方法加密。"
翻译成人话:相隔正好 m 个位置的两处,用的是同一个密钥字母,加密方式完全一样。
如果这两处的明文也恰好相同,密文就相同——规律又出现了,统计攻击又活过来了。
大白话比喻
Vigenère 本来想"每个字母换一张表",把频率摊平。但因为密钥循环,它实际上是"每 m 个字母,就把前面那套表重复一遍"。
就像一件迷彩服,花纹看着很乱,但你把图案平移 m 格,它自己就重合了。
攻击者只要找到这个 m,就能把"多表"拆成 m 个"单表",逐个击破。
破译三步走
① 找密钥长度 m → ② 按 m 分组 → ③ 每组当移位密码破
为什么第 ③ 步可行?因为按 m 分组后,每一组内部用的都是同一个密钥字母——那它就是一个普通的移位密码,用频率攻击就能破。
怎么找 m?Kasiski 方法
核心观察:密文里如果有一段重复出现,那这两段之间的距离,很可能是密钥长度的倍数。
为什么? 因为只有"距离是 m 的倍数"时,两处才"对齐"(用同一套密钥字母)。此时如果明文也相同,密文就相同。
用一个具体例子看。假设密钥 abc(m=3):
| 位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 密钥字母 | a | b | c | a | b | c | a | b | c |
看,位置 0、3、6 用的都是 a(相差 3,是 m 的倍数)。
现在明文 thecatthedog,里面 the 出现在位置 0 和位置 6:
位置 0-2:明文 t h e,密钥 a b c → 密文 t i g
位置 6-8:明文 t h e,密钥 a b c → 密文 t i g ← 一模一样!
攻击者看到密文里 tig 出现了两次,距离是 6。 于是他知道:m 一定是 6 的约数(2、3 或 6)。再找几处重复,取所有距离的公约数,就能锁定 m = 3。
小心坑
光"明文有重复"还不够,还要"距离对齐"。两个条件缺一不可。
如果 the 出现在位置 0 和位置 5(距离 5,不是 3 的倍数),那密钥字母不同(a 和 c),密文也不同,就不会产生重复。
另外:Kasiski 需要文本足够长。自然语言里重复片段很多(the、and、ing、tion…),文本一长就必然有重复。短文本用不了这招——那就换 Friedman 方法。
怎么找 m?Friedman 方法(换个思路)
Kasiski 靠"找具体片段",Friedman 靠"看整体统计"。核心工具叫重合指数。
什么叫重合指数? 就是一个概率问题:
从一段文字里随机抓两个字母,它们相同的概率是多少?
算法就是把 26 个字母各自的出现频率算出来,然后求"频率的平方和"。
| 文本类型 | 重合指数 | 为什么是这个值 |
| 完全随机的字符流 | ≈ 0.038 | 26 个字母概率都是 1/26,平方和 = 26 × (1/26)² = 1/26 ≈ 0.038 |
| 英文字符流 | ≈ 0.067 | 字母分布"扎堆"(e 最多,z、q 极少),所以撞上的概率更高 |
| Vigenère 密文 | ≈ 0.047 | 被搅匀了一半,介于两者之间 |
怎么用它找 m
猜一个密钥长度 m
↓
按 m 把密文分成 m 组
↓
算每组的重合指数,取平均
↓
看平均值靠近谁
如果 m 猜对了:每组内部用的是同一个密钥字母 → 每组相当于一个普通移位密码 → 而移位只是"整体平移",不改变字母分布的形状 → 所以每组还是"英文字符流" → 指数 ≈ 0.067 ✓
如果 m 猜错了:每组里混了不同密钥字母 → 等于被搅乱了 → 分布被摊平 → 指数下降,趋近 0.038 ✗
结论:把不同的 m 都试一遍,哪个 m 让各组重合指数的平均值最接近 0.067,哪个就是正确答案。
前后呼应
为什么"移位不改变分布"这么关键?—— 这正是后面 6.3 节 CFB 模式和 4.2 节安全多方计算都会用到的思路:平移/异或这类操作,只改变"位置"不改变"形状"。
另外,课件还提到"多轮加密":如果先用长度 m 的密钥加密,再用长度 n 的密钥加密,结果等于用长度 lcm(m, n) 的密钥加密一次。如果 m、n 互素,就等于 m×n。(所以多用几层不一定更安全,可能只是等价于一个更长的密钥。)
自测:为什么"猜对密钥长度后,每组重合指数会接近 0.067"?
因为猜对之后,每一组内部用的都是同一个密钥字母,所以每组就是一个普通移位密码;而移位只是给所有字母加同一个数,它只改变字母的"编号",不改变字母出现的"相对频率"——所以每一组仍然是标准的英文字符流,重合指数自然回到 0.067。
反过来,猜错了就会把不同密钥字母混在一组,相当于把字母随机平移,分布被摊平,指数就掉向 0.038。
④ OTP 密码:理论上的"终极方案"
为什么需要它
Vigenère 被攻破的根子在于"密钥太短、会循环"。那如果密钥和明文一样长,而且只用一次呢?
那就没有任何周期可言了——这就是 OTP(One-Time Pad,一次一密)。
大白话
注意那个密钥空间 26n —— n 是明文长度。明文越长,密钥空间指数级增长。这个强度是压倒性的。
课件说它"克服了 Vigenère 密码易受重合指数攻击的缺点",因为密钥不循环了,重合指数法就失效了。
它对应的是第 0 章讲的理论安全(无条件安全)——这是理论上唯一不可破的密码。
但它为什么用不了?
密钥要和明文一样长 → 传 1 万字的消息,就要 1 万字的密钥
密钥只能用一次 → 用完就扔,下次得重新生成、重新传递
密钥的产生、保存和传输成了天大的麻烦。
课件原话:"但密钥的产生、保存和传输非常重要。"
所以 OTP 是"理论上完美,实践上没法用"的典型代表。这也解释了第 0 章那句话:现实中所有系统用的都是"计算安全",而不是"理论安全"。
自测:OTP 的密钥空间为什么是 26n,而不是 26m?
因为 OTP 的密钥长度不是固定的,而是和明文一样长(n)。
对比一下:
- Vigenère:密钥是固定的 m 个字母,密钥空间 26m——密钥可以循环用
- OTP:密钥要和明文一样长(n 个字母),而且只能用一次,密钥空间 26n
所以明文的长度直接决定密钥空间——这是它强度极高的原因,也是它没法实用的原因(消息越长,密钥越难搞)。
⑤ 换位密码:不动内容,只动位置
为什么需要它
前面讲的全是"代替"(换内容)。换位走的是另一条路:字母一个都不改,只把顺序打乱。
共性
- 原理:将明文字符在消息内变换位置
- 标志:明文和密文一样长(一个不多一个不少)
- 例子:把文本翻转,
computersystems → SMETSYSRETUPMOC
有意思的性质:课件说"唯密文攻击可能得到多种不同的破译结果;如 live – evil – vile"。
因为 live、evil、vile 都是同样 4 个字母的排列,攻击者即使知道用的是这 4 个字母,也分不清原文到底是哪个。
列换位:按行填格子,按列读出来
动手做:跟课件走一遍
明文 canyouunderstand(16 个字母)。
第 1 步:按 4 个字符一行排列
c a n y
o u u n
d e r s
t a n d
第 2 步:按列读出来
第1列:c o d t → "codt"
第2列:a u e a → "auea"
第3列:n u r n → "nurn"
第4列:y n s d → "ynsd"
密文:codtaueanurnynsd (即 CODTAUEANURNYNSD)
第 3 步:解密就反过来 —— 把密文按 4 个一列排回去,再按行读出,就还原了。
一句话记住:加密 = 按行填、按列读;解密 = 按列填、按行读。
进阶版:加个关键词决定"先读哪一列"
课件还展示了一个带关键词的版本,用关键词 type 给每列贴标签:
列: 1 2 3 4
标签:t y p e
按标签的字母顺序排:e < p < t < y
所以读列的顺序是:第4列(e) → 第3列(p) → 第1列(t) → 第2列(y)
ynsd + nurn + codt + auea = YNSDNURNCODTAUEA
这个关键词就是密钥。不知道 type 这个词,就不知道按什么顺序读列。
矩阵换位:用"搬运表"指定每个位置搬到哪
先读懂那个矩阵
课件写的是:
f = (1 2 3 4 / 2 4 1 3)
这个写法要这样读——上排是原位置,下排是搬到哪:
逐列读:第 1 位的字符搬到第 2 位;第 2 位的搬到第 4 位;第 3 位的搬到第 1 位;第 4 位的搬到第 3 位。
就是一张"搬运指令表"。
动手做:加密 cany 这一组
| 原位置 | 字符 | 搬到哪 |
| 1 | c | f(1) = 2 |
| 2 | a | f(2) = 4 |
| 3 | n | f(3) = 1 |
| 4 | y | f(4) = 3 |
现在看密文的四个位置:
第1位:n(原来在第3位)
第2位:c(原来在第1位)
第3位:y(原来在第4位)
第4位:a(原来在第2位)
→ 密文组:n c y a
四组都这样处理:cany|ouun|ders|tand → ncya|uonu|rdse|ntda
密文:NCYAUONURDSENTDA ✓ 和课件一致
解密:用"逆矩阵"搬回去
课件给的解密矩阵:f−1 = (1 2 3 4 / 3 1 4 2)
拿密文第一组 n c y a 试:
第1位 n → 搬回明文第 3 位
第2位 c → 搬回明文第 1 位
第3位 y → 搬回明文第 4 位
第4位 a → 搬回明文第 2 位
→ 明文:c a n y ✓ 还原了
为什么能还原? 课件写:f ∘ f−1 = (1 2 3 4 / 1 2 3 4) —— 最后这个矩阵上下排一模一样,叫恒等置换:谁都待在原位、没动。所以"搬过去再搬回来"就复原了。
前后呼应
这个"搬运表"其实就是 1.4 节要讲的"置换"。
| 矩阵换位里的东西 | 对应 1.4 节的概念 |
| (1 2 3 4 / 2 4 1 3) | 一个置换 |
| f ∘ f−1 | 置换的复合 |
| (1 2 3 4 / 1 2 3 4) | 平凡置换(单位元) |
| f−1 | 逆置换 |
换位密码的本质,就是用置换来加密。这也是为什么 1.4 节要专门讲"置换群"。
课件补充:CTF 里常见的几种编码
- Base64:3 个 8 位字节转化为 4 个 6 位的字节,之后在 6 位前面补两个 0,形成 4 个 8 位字节。
- 猪圈密码(Pigpen Cipher / 九宫格密码 / 共济会密码):以格子为基础的简单替代式密码。
- 培根密码:明文字母被 5 个字符组成的序列替换。
- jjencode / aaencode:转换成只有特定符号的字符串 / 常见的网络表情。
1.4 数学基础:这一章是全书的地基
先说实话
这一节最容易劝退人。原因是课本习惯"先给定义、再给公式",而这些数学概念本身很抽象,一上来就顶不住。
但它的内容其实一点都不难,只是需要换个讲法。
我给这一节定的规矩是:每个概念都从一张图或一个具体数字开始,绝不先抛定义。
这一节为什么重要
第 2 章会直接把这些东西拿来用,而且不再解释。比如课件在讲 RC4 之后会说:
"线性同余发生器的周期为 a 在乘法群 Zp* 中的阶 l。"
这一句话里藏了三个本节的概念:乘法群、Zp*、阶。
所以这一节看不懂,第 2 章就一定是天书。 反过来,把这节的六个"零件"练熟,第 2 章就只是"换场景用零件"。
① 约简:一切从"转盘"开始
生活场景
你见过时钟吧?现在是 10 点,再过 5 个小时,是几点?
你会说"3 点"——而不是"15 点"。因为钟面上只有 12 个数字,走到 12 就绕回 1。
这个"绕回来"的动作,就是密码学里用得最多的运算:取余数(约简)。
为什么需要它
密码学要拿数字做运算,但结果不能无限大——得把它圈在一个固定范围里。
比如字母只有 26 个,那算出来的数字就必须落在 0~25 之内。所以每次算完都要"绕回来"。
动手做:负数怎么约简
求 −200 模 98 的约简。
方法:不断加 98,直到落进 0~97 的范围。
−200
−200 + 98 = −102 (还是负数,继续)
−102 + 98 = −4 (还是负的,继续)
−4 + 98 = 94 (落在 0~97 里了,停)
验算:94 − (−200) = 294 = 3 × 98,差是 98 的整数倍 ✓
换个说法理解:把模 98 想成一个 98 格的转盘。从 −200 出发往正方向走,走三步(每次 +98)就上了转盘。
小心坑
有人算成 −200 ÷ 98 余 −4,然后就把 −4 当答案了。不行——结果必须是非负的。
记一个万能公式:约简 = ((n % m) + m) % m。这样负数也能一步到位。
这条"负数加模数变正"的规则,会在全书反复出现(求逆元、RSA、欧拉定理降指数……)。养成习惯,看到负数就条件反射地加上模数。
自测:求 7 − 22 模 9 的约简
答:3
7 − 22 = −15
−15 + 9 = −6 (还是负,继续)
−6 + 9 = 3 (落在 0~8 里)
或者一步到位:−15 + 18 = 3(18 = 2×9)。验算:3 − (−15) = 18 = 2×9 ✓
② 同余:哪些数算"一伙的"
生活场景
时钟上,"15 点"和"3 点"是同一个时刻。
星期几也一样:"今天星期三"和"8 天后"也是星期三(因为 8 = 1 周 + 1 天)。
这种"相差整数个周期,就当作同一个"的关系,叫同余。
大白话
同余 = "在模 m 的世界里是同一个数"。
在模 7 的世界里,27 和 6 是同一个——因为它们都"落在转盘的同一格"(27 % 7 = 6,6 % 7 = 6)。
同余的运算法则(这几条很实用)
最常用的两条
① 加法和乘法可以直接对每个数取模
(x + y) % m = ((x % m) + (y % m)) % m
所以你算 (24 + 3) % 26 的时候,不用管 27 有多大,直接算就完了。
② 想"约掉"一个因数,必须先验证它与模数互素
若 a·x ≡ a·y (mod m) 且 (a, m) = 1,则 x ≡ y (mod m)
这条特别重要。举个反例看看不注意会怎样:
15 ≡ 9 (mod 6),两边都"约掉 3"
→ 5 ≡ 3 (mod 6)?
→ 5 − 3 = 2,不能被 6 整除,错了!
问题在于 gcd(3, 6) = 3 ≠ 1,3 和模数 6 不互素,所以不能约。
小心坑:模运算里没有"除法"
这是初学者最容易栽的地方。a·x ≡ b (mod m) 这种式子,两边不能"除以 a"。
正确做法是:两边乘以 a 的乘法逆元 a−1。
比如 5a ≡ 3 (mod 26),不能写成 a = 3/5,而要写成 a ≡ 5−1 × 3 (mod 26)。
至于怎么求逆元——就是下面第 ④ 条要讲的手艺。
自测:判断对错 —— "23 ≡ 5 (mod 9),两边都约掉 3,得 23 ≡ 5 (mod 3)"
对。 这属于另一条规则:x ≡ y (mod m),如果 d | m,则 x ≡ y (mod d)。
这里 m = 9,d = 3,而 3 确实整除 9,所以可以。
注意区别:
· 要改模数(比如从 mod 9 变成 mod 3)→ 只要 d 整除原模数就行
· 要约掉因数(比如把 15≡9 变成 5≡3)→ 必须这个因数与原模数互素
两条规则别混。做题时先问自己:"我是想改模数,还是想约因数?"
③ 素数:为什么密码学这么看重它
为什么需要它
注意加粗的那句:"而且这种分解是唯一的"。
这句话看着平淡,但它是 RSA 全部安全性的来源:
取两个大素数 p 和 q,乘起来得到 n = p × q
(这一步很快,小学生都会乘法)
反过来:给你 n,让你还原出 p 和 q
(这一步——难如登天)
这就是"因子分解难题":正着算容易,反着算几乎不可能。RSA 就架在这个不对称上。
大白话
把两袋沙子混在一起很容易,把混好的沙子分开就难了。
素数就是密码学里那袋"分不开的沙子"。而且数字越大,越分不开——所以 RSA 要求 n 至少 1024 比特(约 309 位十进制数)。
课件补充的小知识
- 素数有无穷多个:设 p1…pn 为所有素数,则 q = p1p2…pn + 1 必然包含一个"新"的素因子。
- 越大越稀:[1,100] 有 25 个;[1000,1100] 有 16 个;[100000,100100] 有 6 个;[10000000,10000100] 只有 2 个。
- 中国古代数学家提出"若 n | 2n−2,则 n 为素数"——但 n = 341 时不成立。这类"骗过检验的合数"就叫伪素数,专门骗检验的还有一类更狠的,叫卡迈克尔数(如 561 = 3×11×17)。
④ Euclid 算法与乘法逆元(重点中的重点)
为什么需要它
回忆前面遇到的两个麻烦:
- 仿射密码解密要用 a−1(a 的逆元)
- 同余式 5a ≡ 3 (mod 26) 要求 a,得两边乘 5−1
求逆元是密码学最常用的手艺——RSA 算私钥、欧拉定理降指数、坏种子公式,全都要用。而 Euclid 算法就是求逆元的标准工具。
大白话
求逆元,就是"找一个数,让它和原数相乘后,除以 n 余 1"。
课件那句"满足 ax + bn = 1 的 a 就是逆元"翻译过来是:
只要你能凑出"x 的多少倍 加 上 n 的多少倍 等于 1"这个式子,那个"x 的多少倍"里 x 前面的系数,就是答案。
Euclid 算法就是"凑这个式子"的工具。
动手做:Euclid 算法的两个阶段
算法分两步走,写在纸上是左右两列:
| 阶段 | 干什么 | 写在哪一列 |
| ① 辗转相除 | 大数除以小数、取余数,反复除到余数是 1 | 左列 |
| ② 回代 | 从"1 = …"倒着往回想,把 1 拆成原来两个数的组合 | 右列 |
完整示范:求 23−1 % 100(课件原例)
左列:辗转相除
100 − 4×23 = 8 (100 减去 4 个 23,剩 8)
23 − 2×8 = 7 (23 减去 2 个 8,剩 7)
8 − 1×7 = 1 (8 减去 1 个 7,剩 1)
7 − 7×1 = 0 (余数 0,停)
右列:回代——从"1 = 8 − 1×7"这一行出发,一路往上代:
1 = 8 − 1×7
= 8 − 1×(23 − 2×8) ← 把 7 换成它的来源
= 3×8 − 1×23
= 3×(100 − 4×23) − 1×23 ← 把 8 换成它的来源
= 3×100 − 13×23
读答案:式子 3×100 + (−13)×23 = 1 里,23 的系数是 −13。
所以 23−1 = −13。是负数,加 100 变正:−13 + 100 = 87。
验算:23 × 87 = 2001 = 20×100 + 1 ✓
再示范一个短的:求 5−1 % 26
左列:26 ÷ 5 = 5 余 1,一步就到 1!
26 − 5×5 = 1
5 − 5×1 = 0
右列:因为只除了一次,不用反复回代,直接把 1 整理成目标形式:
1 = 26 − 5×5 = (−5)×5 + 1×26
5 的系数是 −5,所以 5−1 = −5 = 21(加 26 变正)。
验算:5 × 21 = 105 = 4×26 + 1 ✓
难度取决于什么
对比上面两个例子:23−1 % 100 除了 4 步,所以要回代好几次;5−1 % 26 只除了 1 步,就不用回代。
所以手算时,如果发现"很快除到 1",就说明这题很轻松。
小心坑(三条)
- 必须先验证互素。 算之前先看 gcd(m, n) 是不是 1。不是就直接写"无解",别白费力气。
- 不能只算到 gcd 就停。 辗转相除本身只给最大公因子,回代才给逆元。
- 答案是负数要变正。 像上面 23−1 = −13 要写成 87。
自测:求 101−1 % 234
第一步:验互素 + 辗转相除
234 − 2×101 = 32
101 − 3×32 = 5
32 − 6×5 = 2
5 − 2×2 = 1 ← 到 1 了
2 − 2×1 = 0
最后非零余数是 1,所以 gcd(101, 234) = 1 ✓ 逆元存在。
第二步:回代
1 = 5 − 2×2
= 5 − 2×(32 − 6×5) = 13×5 − 2×32
= 13×(101 − 3×32) − 2×32 = 13×101 − 41×32
= 13×101 − 41×(234 − 2×101) = 95×101 − 41×234
第三步:读答案
101 的系数是 95,所以 101−1 % 234 = 95。
验算:101 × 95 = 9595 = 41 × 234 + 1 ✓
⑤ 群:一个"能自由做运算"的世界
生活场景
想象一个 6 格的转盘,格子上标着 0~5:
0
5 1
4 2
3
规则是:"每次走几步,走过头就绕回 0"。
试几个:从 4 走 3 步 → 5 → 0 → 1,答案是 1(因为 4+3 = 7,7 − 6 = 1)。
这个"6 格转盘的世界",就叫做 Z6,成员是 {0,1,2,3,4,5},运算是"模 6 加法"。
为什么需要它
因为密码学要反复做模运算,但不是随便什么集合配随便什么运算都能"讲道理"。
"群"就是规定了:一个集合上的运算,要满足哪几条规矩,才算是一个能自由使用的世界。
动手做:找 Z6 里每个元素的逆元
"逆元"的意思:找一个数,和它相加等于单位元(也就是 0)。
| 元素 | 它的逆元 | 验证 |
| 0 | 0 | 0 + 0 = 0 ✓ |
| 1 | 5 | 1 + 5 = 6 ≡ 0 ✓ |
| 2 | 4 | 2 + 4 = 6 ≡ 0 ✓ |
| 3 | 3 | 3 + 3 = 6 ≡ 0 ✓(自己抵消自己) |
| 4 | 2 | 4 + 2 = 6 ≡ 0 ✓ |
| 5 | 1 | 5 + 1 = 6 ≡ 0 ✓ |
六个元素全都有逆元 ✓
课件给的反例,很说明问题
课件说:Z* = Z\{0} 不是群。
为什么?"去掉 0 的所有整数"配上普通乘法:
- 有单位元 1 ✓
- 但 2 的逆元是 1/2 —— 不在整数里! ✗
所以它不满足"每个元素都有逆元"。
这个反例正好说明"互素"和"逆元"的关系——这也解释了你前面求逆元时,为什么必须先验证 gcd = 1。
自测:在 Z6(模 6 加法)里,元素 2 的逆元是几?为什么?
答:4。因为 2 + 4 = 6 ≡ 0(单位元)。
"逆元"的定义就是"和它运算后得到单位元"。加法里单位元是 0,所以找 2 + ? ≡ 0 (mod 6),答案就是 4。
顺便:这个逆元在密码学里叫"加法逆元",和我们前面讲的"乘法逆元"(a×t ≡ 1)是两回事,注意区分。
⑥ 剩余系:把整数"分箱子"
最重要的一个比喻
桌上有 6 个箱子,标着号码:
[ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]
现在把每个整数按"除以 6 的余数"丢进对应的箱子。
动手做:看看每个箱子里装了什么
箱子 0: … −12, −6, 0, 6, 12, 18, …
箱子 1: … −11, −5, 1, 7, 13, 19, …
箱子 2: … −10, −4, 2, 8, 14, 20, …
箱子 3: … −9, −3, 3, 9, 15, 21, …
箱子 4: … −8, −2, 4, 10, 16, 22, …
箱子 5: … −7, −1, 5, 11, 17, 23, …
每个箱子里的数,叫做一个剩余类("剩余"就是"余数"的意思——按余数分出来的类)。
同一个箱子里的数,在模 6 下是"同一个东西":0 ≡ 6 ≡ 12 ≡ −6 (mod 6)。
课件给了正式定义:模 m 的剩余类有 m 个(这里是 6 个)。
完全剩余系:每个箱子挑一个代表
动手做
每个箱子里有无穷多个数,太啰嗦。那就每个箱子只挑一个代表。
比如都挑"最小的非负数":
箱子 0 → 0 箱子 1 → 1 箱子 2 → 2
箱子 3 → 3 箱子 4 → 4 箱子 5 → 5
→ {0, 1, 2, 3, 4, 5}
这就是一个"完全剩余系"。"完全"的意思是:6 个箱子全都有代表,一个都没落下。
换个挑法也行。比如都挑"最小的正数":
箱子 0 → 6(因为 0 不是正数) 箱子 1 → 1 箱子 2 → 2 …
→ {6, 1, 2, 3, 4, 5}
这也是完全剩余系——只要"每个箱子恰好一个",挑谁都行。
课件给的 5 种完全剩余系(以 m=10 为例)
| 名称 | 内容 |
| 最小非负完全剩余系 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 |
| 最小正完全剩余系 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 |
| 最大非正完全剩余系 | −9, −8, −7, −6, −5, −4, −3, −2, −1, 0 |
| 最大负完全剩余系 | −10, −9, −8, −7, −6, −5, −4, −3, −2, −1 |
| 绝对值最小完全剩余系 | −5 … 0 … 4 或 −4 … 0 … 5 |
注意:模 10 时"最小正"是 1~10(不是 0~9),因为 10 和 0 在同一箱,挑"最小正数"时会挑到 10。
课件定理 1 说:"m 个整数是模 m 的完全剩余系 ⟺ 它们两两不同余。"翻译过来就是——只要 m 个数来自不同箱子,就必然每箱一个。
简化剩余系:只挑"互素"的箱子
为什么需要它
回忆上一节:Z6 有 6 个箱子,但只有 {1, 5} 两箱能做乘法(有逆元)。
那我们把"能安全做乘除的那部分箱子"单独拎出来——这就是简化剩余系。
动手做:$Z_8^*$ 里有几个箱子?
8 个箱子,编号 0~7,逐个查"和 8 互素吗":
| 箱子编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 与 8 互素? | ✗ | ✓ | ✗ | ✓ | ✗ | ✓ | ✗ | ✓ |
Z8* = {1, 3, 5, 7},共 4 个。
发现了吗——1、3、5、7 正好是 1~8 里的奇数。因为 8 的因子只有 2,所以"不是偶数"就跟它互素。
Zn 和 Zn* 的区别(最容易混)
| Zn | Zn* |
| 含义 | 完全剩余系 | 简化剩余系 |
| 包含 | 所有 n 个剩余类 | 只含与 n 互素的 |
| 含 0 吗 | 含 | 不含 |
| 元素个数 | n | φ(n) |
| 配加法 | 是群 ✓ | — |
| 配乘法 | 不是群 ✗ | 是群 ✓ |
| 用途 | 加法运算 | 乘除运算(RSA、DH 都靠它) |
那个星号的作用:把"能做乘法逆元的那部分"筛出来,让乘法也构成群——也就是能做除法。
自测:模 10 有几个箱子?其中哪些和 10 互素?Z10* 是哪些?
模 10 有 10 个箱子(0~9)。
10 = 2 × 5,所以"和 10 互素"就是"不是 2 的倍数、也不是 5 的倍数":
1 ✓ 2 ✗ 3 ✓ 4 ✗ 5 ✗ 6 ✗ 7 ✓ 8 ✗ 9 ✓
Z10* = {1, 3, 7, 9},共 4 个。(课件里也是这个例子)
验算一下它们都有乘法逆元:1×1 = 1 ✓;3×7 = 21 ≡ 1 ✓;9×9 = 81 ≡ 1 ✓ —— 7 和 3 互为逆元,9 自己和自己互为逆元。
⑦ 欧拉函数:就是"数个数"
为什么需要它
上一节说 Zn* 的元素个数是 φ(n)。那 φ(n) 怎么算?
如果 n 很大(比如 10000),难道要一个个数吗?不用,有公式。
先验证前两条规律
φ(7) = ?(7 是素数)
1~7 里与 7 互素的:1,2,3,4,5,6 —— 全都可以,因为 7 是素数,1~6 里没有 7 的因子。
所以 φ(7) = 6 = 7 − 1 ✓
φ(8) = ?(8 = 23)
1~8 里与 8 不互素的是偶数:2, 4, 6, 8,共 4 个。
所以 φ(8) = 8 − 4 = 4 ✓ 和上一节数出来的一致。
用公式验证:φ(23) = (2−1) × 23−1 = 1 × 4 = 4 ✓
这条公式为什么长这样?把 8 − 4 改写一下:
8 − 4 = 8 × (1/2) = 2³ − 2² = 2² × (2 − 1) = (p−1)·pe−1
就是"总数里减去一半(能被 p 整除的那些)"。
课件例题:φ(10000) 怎么算
直接数?不可能。用三步走:
第一步:分解质因数 10000 = 24 × 54
第二步:每项单独套公式
φ(2⁴) = (2−1) × 2³ = 1 × 8 = 8
φ(5⁴) = (5−1) × 5³ = 4 × 125 = 500
第三步:乘起来
φ(10000) = 8 × 500 = 4000
(课件写的是 8 × 4 × 125 = 4000,结果一样)
小心坑
pe−1 别漏掉。 比如 φ(8) = (2−1)×22 = 4,不是 1;φ(9) = (3−1)×31 = 6,不是 2。
只有一次幂(e = 1)时,pe−1 = p0 = 1,才"自动消失"——这就是素数规律 φ(p) = (p−1)×1 = p−1 的来历。
自测:算 φ(18)、φ(155)、φ(9)
φ(18):18 = 2 × 32
φ(18) = (2−1)×20 × (3−1)×31 = 1 × 1 × 2 × 3 = 6
验算:1~18 里与 18 互素的是 1,5,7,11,13,17,正好 6 个 ✓
φ(155):155 = 5 × 31
φ(155) = (5−1)×50 × (31−1)×310 = 4 × 1 × 30 = 120
φ(9):9 = 32
φ(9) = (3−1)×31 = 2 × 3 = 6
验算:1~9 里与 9 互素的是 1,2,4,5,7,8,正好 6 个 ✓
一条规律:分解时哪一种幂次都要单独套公式,不能漏。(18 里 2 是一次幂、3 是二次幂,两种都出现了。)
⑧ 欧拉定理:一直转圈,终会回到起点
先看一个现象
取 n = 9,x = 2。我们把 2 的幂一次次算出来,每次模 9:
| 算到 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 值 | 2 | 4 | 8 | 16 | 32 | 64 | 128 |
| 模 9 后 | 2 | 4 | 8 | 7 | 5 | 1 | 2 |
串起来看:2 → 4 → 8 → 7 → 5 → 1 → 2 → 4 → …
走了 6 步,回到 1!
发现一件很妙的事
刚才转圈经过的数字是:2, 4, 8, 7, 5, 1
排个序:1, 2, 4, 5, 7, 8
而 Z9* = {1, 2, 4, 5, 7, 8}
一模一样!
为什么?因为 x = 2 与 9 互素,所以"每乘一次 2"结果还在 Z9* 里,永远不会跑到外面去。
(反例:如果取 x = 3,因为 gcd(3,9) = 3 ≠ 1,那么 3 × 3 = 9 ≡ 0,跑到 0 去了,出圈了,永远回不来。)
它的用途:降指数
问题:算 345 mod 10。直接算?345 是个 22 位数。
用欧拉定理:
① 检查:gcd(3, 10) = 1 ✓
② 算 φ(10) = 4(因为 Z₁₀* = {1,3,7,9})
③ 所以 3⁴ ≡ 1 (mod 10),也就是"指数每加 4 就转一圈"
④ 把指数对 4 取模:45 ÷ 4 = 11 余 1,所以 45 ≡ 1 (mod 4)
⑤ 于是 3⁴⁵ = 3^(4×11+1) = (3⁴)¹¹ × 3¹ ≡ 1¹¹ × 3 = 3
一个 45 次方的题,变成了算 31。
小心坑
- 先看模数是不是素数。 是素数才能直接用费马小定理(用 p−1 当周期)。
- 底数先约简。 比如算 249197 mod 199,先把 249 约简成 50,数字小很多。
- 指数取模后余数为 0 时,结果是 1(因为 xφ(n) ≡ 1)。比如 348 mod 10:48 mod 4 = 0,答案是 1。
- "指数差 1"是个信号。 比如 249197 mod 199 里,197 = 198 − 1,这种题往往要变成"求逆元"。
自测:用欧拉定理算 3200 mod 7
① gcd(3, 7) = 1 ✓
② 7 是素数,φ(7) = 6
③ 所以 3⁶ ≡ 1 (mod 7)
④ 200 ÷ 6 = 33 余 2,所以 200 ≡ 2 (mod 6)
⑤ 3²⁰⁰ = (3⁶)³³ × 3² ≡ 1 × 3² = 9
⑥ 9 % 7 = 2
答:2
对比另外几个:
· 345 mod 10:φ(10) = 4,45 mod 4 = 1 → 31 = 3
· 2100 mod 13:φ(13) = 12,100 mod 12 = 4 → 24 = 16 ≡ 3
· 249197 mod 199:φ(199) = 198,197 = 198−1,所以 ≡ 249−1;249 ≡ 50,50−1 = 4 → 4
⑨ 阶与原根:更精确的"周期"
为什么需要它
欧拉定理说"转 φ(n) 步一定回到 1"。但实际上——可能早就回到了。
比如刚才 x=2、n=9,是走了 6 步才回到 1(正好是 φ(9))。但换个数可能更快。这个"实际走了几步"就叫阶。
动手做:求 n = 9 时每个元素的阶
| x | 自乘过程 | 阶(走了几步) |
| 1 | 1 | 1 |
| 2 | 2 → 4 → 8 → 7 → 5 → 1 | 6 |
| 4 | 4 → 7 → 1 | 3 |
| 5 | 5 → 7 → 8 → 4 → 2 → 1 | 6 |
| 7 | 7 → 4 → 1 | 3 |
| 8 | 8 → 1 | 2 |
而 φ(9) = 6。
看那一列阶:1, 6, 3, 6, 3, 2 —— 全都是 6 的约数!
(1|6 ✓, 2|6 ✓, 3|6 ✓, 6|6 ✓)
核心性质
阶一定整除 φ(n)。
课件原话:"若 g 是模 n 的原根,ordn(g) = φ(n);若 g 不是模 n 原根,则 ordn(g) | φ(n)。"
这也就顺便解释了欧拉定理为什么成立:既然每个元素的阶都整除 φ(n),那"走 φ(n) 步"必然是整数圈,当然回到 1。
怎么快速判断一个数是不是原根?
不用一个个试! 课件给了判定定理:
设 φ(n) 的素因子是 q1, q2, …, qk,则 g 是原根的充要条件是:
gφ(n)/qi ≢ 1 (mod n),对每个 i 都成立
大白话:只要"半路没提前回到 1",就是原根。
动手做:验证 3 是不是模 41 的原根
41 是素数,φ(41) = 40。把 40 分解:40 = 23 × 5,素因子是 2 和 5。
所以只需要验两个指数:40/2 = 20 和 40/5 = 8。
算 38 mod 41(逐步平方):
3² = 9
3⁴ = 9² = 81,81 − 41 = 40 ≡ −1 (mod 41)
3⁸ = (3⁴)² ≡ (−1)² = 1 (mod 41)
糟糕,38 ≡ 1!
按定理,只要有一个 gφ(n)/qi 等于 1,就不是原根。
结论:3 不是模 41 的原根。
(补充:因为 34 ≡ −1 ≠ 1、38 ≡ 1,所以 3 的阶正好是 8。而 8 | 40 ✓,符合"非原根的阶整除 φ(n)"。)
小心坑(两条)
- 除数用"素因子",不是"因子"。 40 的因子很多(2、4、5、8、10、20…),但定理只让你用素因子 2 和 5,也就是验 320 和 38。
- 有一个等于 1 就"否决"。 不是"全都等于 1 才否决",而是"只要一个等于 1,就不是原根"。
自测:n = 7,x = 3。3 的阶可能是 4 吗?
不可能。
因为阶必须整除 φ(7) = 6。而 4 不能整除 6(6 ÷ 4 = 1.5,不是整数)。
所以 3 的阶只能是 6 的约数:1、2、3 或 6。
实际算一下:31=3, 32=2, 33=6, 34=4, 35=5, 36=1 —— 所以 3 的阶是 6,而 6 = φ(7),所以 3 是模 7 的原根。
⑩ 快速指数算法:手算大指数的手法
为什么需要它
欧拉定理能降指数,但不是万能的。有时候指数还是很大(比如 380),那就需要一个"手算"的技巧。
核心思路:把指数写成二进制,利用"平方"来快速累积。
动手做:算 x5
| X | E | Y | 说明 |
| x | 5 | 1 | 初始 |
| x | 4 | x | E=5 是奇数:Y = x · 1 = x;E = 5−1 = 4 |
| x² | 2 | x | E=4 是偶数:X = x·x = x²;E = 4/2 = 2 |
| x⁴ | 1 | x | E=2 是偶数:X = x²·x² = x⁴;E = 2/2 = 1 |
| x⁴ | 0 | x⁵ | E=1 是奇数:Y = x⁴ · x = x⁵;E = 1−1 = 0 → 停 |
只做了 4 步(4 次乘法),而不是乘 5 次。
课件里的真实算例(带取模)
课件演示了算 380 mod 1000 和 3100 mod 1000:
3⁸⁰ mod 1000 3¹⁰⁰ mod 1000
3 80 1 3 100 1
9 40 1 9 50 1
81 20 1 81 25 1
561 10 1 561 12 81
721 5 1 721 6 81
721 4 721 841 3 81
841 2 721 281 2 121
281 1 721 281 1 121
→ 281 × 721 = 601 → 281 × 121 = 1
(每一步都取了模,所以数字都不大。)
自测:用三元组算 x6,Y 最后等于多少?
| X | E | Y | 说明 |
| x | 6 | 1 | 初始 |
| x² | 3 | 1 | E=6 偶:X = x²,E = 3 |
| x² | 2 | x² | E=3 奇:Y = x²·1 = x²,E = 2 |
| x⁴ | 1 | x² | E=2 偶:X = x⁴,E = 1 |
| x⁴ | 0 | x⁶ | E=1 奇:Y = x⁴·x² = x⁶,E = 0 → 停 |
Y = x6 ✓
验算:x6 = x4+2 = x4·x2 ✓ 正好是最后一步乘出来的。
⑪ 置换群与洗牌
为什么需要它
回忆换位密码:它不改变字母,只改变位置。这种"改位置"的操作,数学上就叫置换。
而"连着做几次置换会回到原样"这个问题,正好对应"洗几次牌能复原"。
不相交循环:把置换写 "圆" 起来
看这个置换:g = (1 2 3 4 5 6 7 / 4 3 2 5 7 6 1)
逐个跟一下:1 → 4 → 5 → 7 → 1(绕回来了!);2 → 3 → 2;6 → 6
所以它可以写成:g = (1 4 5 7)(2 3)(6)
这叫不相交循环分解 —— "不相交"指各个循环里的数字没有重复。
课件说:任何置换都能表示成不相交循环的乘积,而且不考虑次序时是唯一的。
关键公式(考试常考)
置换的阶 = 各循环长度的最小公倍数(lcm)
上面这个例子里:循环长度是 4、2、1,所以阶 = lcm(4,2,1) = 4。
意思是:把这个置换连做 4 次,所有元素都回到原位。
动手做:S5 里最大的阶是多少?
思路:把 5 拆成几段,让各段长度的 lcm 最大。
| 拆法 | lcm |
| 5 | 5 |
| 4+1 | 4 |
| 3+2 | 6 ← 最大 |
| 3+1+1 | 3 |
| 2+2+1 | 2 |
| 1+1+1+1+1 | 1 |
答:S5 的最大阶是 6(形如 (1 2 3)(4 5))。
为什么是 3+2 而不是别的? 因为 3 和 2 互素,lcm(3,2) = 3×2 = 6,乘积没浪费。换成 4+1,lcm 只有 4。
S10 呢? 同理试:最优是 5+3+2(正好用完 10 个数),lcm(5,3,2) = 30。
洗牌:换个场景用同一个概念
动手做:8 张牌(课件例)
2n = 8,所以模数 = 2n+1 = 9。洗牌规则:f(x) = (2x) mod 9。
洗一次的结果:
1 2 3 4 5 6 7 8 → 2 4 6 8 1 3 5 7
(自己验一个:5 号位的牌 f(5) = 10 mod 9 = 1,所以 5 号位 → 1 号位 ✓)
跟踪 1 号的轨迹:1 → 2 → 4 → 8 → 7 → 5 → 1
咦,走到 5 就回 1 了! 只串了 6 个位置(1,2,4,8,7,5),还剩 3 和 6 没串到。
再另起一头,从 3 开始:3 → 6 → 3(串了 2 个)
所以循环分解是 (1 2 4 8 7 5)(3 6),长度分别是 6 和 2。
洗牌次数 = lcm(6, 2) = 6 次 ✓ 和课件一致。
动手做:12 张牌(你之前问过的那道)
2n = 12,模数 = 13。规则:f(x) = (2x) mod 13。
从 1 开始跟踪,一路走:
1 → 2 → 4 → 8 → 3 → 6 → 12 → 11 → 9 → 5 → 10 → 7 → 回到 1
(中间用到的取模:16 mod 13 = 3;24 mod 13 = 11;22 mod 13 = 9;18 mod 13 = 5;20 mod 13 = 7;14 mod 13 = 1)
这一趟把 12 个位置全部串完了,所以只有一个循环:
(1 2 4 8 3 6 12 11 9 5 10 7)
洗牌次数 = 12。
两个容易懵的地方
① 模数是 2n+1,不是 2n。 12 张牌对应模 13,这一步错就全错。
② 中间会出现比 2n 大的数。 12 张牌的例子里出现了 16、24、22、18、20、14 —— 这些只是"乘 2 之后的中间量",取模之后必然回到 1~12,不是错误。
顺便:如果只有一个循环,洗牌次数就是这个循环的长度;如果有多个循环(像 8 张牌那样),就要取各循环长度的最小公倍数。
自测:14 张牌的循环分解是什么?洗几次复原?
2n = 14,模数 = 15。规则 f(x) = (2x) mod 15。
课件给了答案:(1 2 4 8)(3 6 12 9)(5 10)(7 14 13 11)
四段循环的长度分别是 4、4、2、4。
洗牌次数 = lcm(4, 4, 2, 4) = 4 次。
验证一段:从 1 开始:1 → 2 → 4 → 8 → 16 mod 15 = 1 ✓ 确实是长度 4 的循环。
再验一段:从 3 开始:3 → 6 → 12 → 24 mod 15 = 9 → 18 mod 15 = 3 ✓ 长度 4。
注意这里有 4 个循环,所以要取 lcm,不能只看某一个的长度。
1.4 节总结:这一节的六个"零件"
| 零件 | 一句话 | 后面用在哪 |
| 约简 | 取余数,负数要加模数变正 | 全书处处 |
| 同余 | 相差 m 的整数倍 = 同一个数 | 全书处处 |
| Euclid / 逆元 | 凑出 ax + bn = 1,a 就是逆元 | 仿射密码、RSA、坏种子 |
| 群 / 剩余系 | 分箱子;能自由做运算的世界 | Zp*、RSA、DH |
| 欧拉函数 / 定理 | 数个数;转 φ(n) 步回到 1 | 降指数、RSA 可解性 |
| 阶 / 原根 | 实际走几步;走遍全圈的叫原根 | 周期、LFSR、洗牌 |
这六个零件练熟,第 2 章就只是"换场景用零件"而已。