简介:配套清华大学杨波《现代密码学(第4版)》教材的习题与参考答案以Word文档形式整理,适合高校信息安全、密码学课程学习者对照教材复习与备考。内容覆盖仿射变换加密解密、多表代换密码、线性反馈移位寄存器等经典章节,涵盖课后练习常见题型;每题均给出完整推导过程和计算结果,包含明文/密文数字序列、模逆元求解及矩阵运算等关键步骤,便于读者逐题核对并理解分组加解密的完整逻辑。文档按章节组织,答案集中在一个docx文件内,压缩包整体约1.61MB,可离线阅读、检索或打印,轻量便于备考使用。目前已有5676人学习下载,适合正在学习现代密码学或准备考试的学生作为课后练习与自查参考,能有效帮助验证解题思路、提高密码学运算熟练度。
1. 这份现代密码学习题答案资源,解决的是“会看不会算”的最后一公里
现代密码学这门课,最容易卡住的不是概念,而是手算。仿射变换的模逆、LFSR 的反馈系数、DES 的补性质、RSA 的模指数,每一步都有数值细节,差一个数字结果就全乱。这份《现代密码学(第 4 版)习题+参考答案》资源,覆盖了从古典密码到分组密码再到数论基础的常见题型,适合正在备考或赶课程作业的读者,也适合想拿标准答案反推计算过程的从业者。拿到它,你能快速定位一道题的标准解法,但更重要的是看清每一步数字是怎么来的。
2. 从仿射变换到多表代换:手工密码计算的两种基本功
2.1 仿射变换:先会求模逆元,再谈加解密
仿射变换的加密形式是 E(m) ≡ a·m + b (mod 26),其中 a 和 b 是密钥,m 是明文字母对应的数字(A=0,B=1,…,Z=25)。为什么必须是 mod 26?因为英文字母只有 26 个。关键约束是 gcd(a, 26) = 1,否则当两个不同明文映射到同一密文时,解密就会出现二义性,这个密码体制直接失效。
资源里第一个例子就是 a=11, b=23 的仿射变换,明文是“THE NATIONAL SECURITY AGENCY”。加密时先做数字映射:T→19,H→7,E→4,然后逐个代入 E(m) ≡ 11m+23 (mod 26)。比如 T 是 19,11×19+23=209+23=232,232 mod 26 = 24,即 Y。整段明文加密后得到“YWPKXYHVKXONPTJCHYBXLPKTB”。
解密变换要先求 11 在模 26 下的逆元。最简单的方法是穷举 1~25,找 i 使 11×i ≡ 1 (mod 26),算到 i=19 时 11×19=209,209 mod 26 = 1,所以 11⁻¹ = 19。然后解密式 D(c) ≡ 19×(c−23) (mod 26),把密文逐字母代回去,就能还原出原始明文。注意解密时 c 也要先映射成数字,减完 23 后取模,遇到负值要加 26 调整成 0~25。
这道题的价值不只是算一遍,而是暴露了一个高频踩坑点:求逆元之前必须先确认 a 与 26 互素。如果 a=13,gcd(13,26)=13,整个仿射变换就不是一一映射,根本没法解密。我在实际调密码算法时,也会先把密钥合法性检查写进校验逻辑,避免后续白跑。
2.2 多表代换:矩阵分组的维度陷阱
多表代换是仿射变换的矩阵版本:Cᵢ ≡ A·Mᵢ + B (mod 26)。这里 Mᵢ 是第 i 组明文向量,A 是 n×n 矩阵,B 是 n 维向量,Cᵢ 是第 i 组密文向量。分组长度由 A 的维度决定,比如 A 是 2×2 矩阵,就把明文每 2 个字符一组;A 是 3×3,就每 3 个一组。分组后如果末尾不足,一般用约定字符补齐,但题目里明文长度通常刚好匹配。
解密时要求 Mᵢ ≡ A⁻¹·(Cᵢ−B) (mod 26)。这里有两个难点:一是矩阵求逆,先求 A 的行列式 det(A),再找 det(A) 在模 26 下的逆元,最后用伴随矩阵除以这个逆元;二是矩阵乘法顺序,如果题目约定明文是列向量,就必须按 A·M 算,不能写成 M·A,否则答案完全不对。
资源里有一个典型例子:已知明文前两个字符“if”加密后是“ed”,求仿射变换的 a、b。这里虽然是仿射,但思路和多表代换一致:把已知明密文对代入加密式,列出同余方程组。明文 i=8,e=4,所以 4 ≡ a×8+b (mod 26);明文 f=5,d=3,所以 3 ≡ a×5+b (mod 26)。两式相减得 1 ≡ 3a (mod 26),求出 a=9,再代回得 b=10。这种利用已知明密文对反推密钥的做法,是破译古典密码的基本功,也是后面理解线性密码分析的雏形。
多表代换的另一种常见考点是已知明密文求矩阵 A。资源里给出明文“dont”被加密为“elni”,且 B 是零矩阵。把字母转数字:d=3, o=14, n=13, t=19,对应密文 e=4, l=11, n=13, i=8。设 A=(a b; c d),按列向量分组,明文前两个一组 (3,14)ᵀ,密文前两个 (4,11)ᵀ,明文后两个 (13,19)ᵀ,密文后两个 (13,8)ᵀ。列出方程:
a×3 + b×14 ≡ 4 (mod 26)
c×3 + d×14 ≡ 11 (mod 26)
a×13 + b×19 ≡ 13 (mod 26)
c×13 + d×19 ≡ 8 (mod 26)
解这个四元一次同余方程组,就能得到 A 的四个元素。解的时候要注意模 26 的除法本质是乘逆元,只有当系数与 26 互素时才能直接约简。这一题的关键不在计算量,而在建立方程时不要搞混明密文对的对应顺序。
3. 流密码与 LFSR:密钥流序列的周期与破译边界
3.1 线性反馈移位寄存器的特征多项式与 m 序列
线性反馈移位寄存器(LFSR)是流密码里最经典的密钥流生成器。n 级 LFSR 的特征多项式决定了反馈连接方式,给定初始状态后,输出序列完全由这个多项式确定。资源里第 2 章第 1 题专门考察 3 级 LFSR 的全部 4 种线性反馈函数,并对比它们的输出序列和周期。
3 级 LFSR 的特征多项式的一般形式是 f(x) = c₀ + c₁x + c₂x² + c₃x³,其中 c₀=1,c₁、c₂、c₃ 取 0 或 1,共 4 种组合。初始状态设为非零向量 (1,0,0) 之类。输出序列的周期和多项式的性质有关:如果特征多项式是不可约的,但不一定是本原多项式,则序列周期可能是 2³−1 的某个真因子;只有当它是本原多项式时,输出才是 m 序列,周期达到最大值 2³−1=7。资源里给出的结论很明确:某些反馈函数下序列周期只有 3 甚至 1,只有特定多项式才能产生周期 7 的 m 序列。
这里想强调一个判断流程:先算反馈函数生成的序列,看是否重复;再用多项式的不可约性和本原性解释周期的差异。实际工程中,设计 LFSR 第一步就是检查特征多项式是否为本原多项式,否则密钥流周期太短,很容易被攻击。
资源里还有一个关于 m 序列游程性质的证明题:在 GF(2) 上的 n 级 m 序列中,长为 i 的游程有 2^(n−i−1) 个,且 0 和 1 游程各半。基于这个性质,可以计算形如 (S_j, S_{j+1}) = (1,1) 的比特对数量。推导思路是统计所有长度大于等于 2 的 1 游程内部相邻 1 对的贡献,最后求和得到 S = 2^(n−2) − n + 1。这道题如果直接死记公式容易忘,建议自己拿 n=3 的 m 序列(比如 0010111)手推一遍,理解游程的计数方法。
3.2 已知明密文对时,破译 LFSR 的两种路径
流密码的安全性建立在密钥流不可预测上,但 LFSR 是线性的,只要知道足够长的密钥流,就可以破解。资源里给出了一个具体例子:密文串 1010110110,明文串 0100010001,两者异或得到密钥流 1110100111。已知密钥流由 3 级 LFSR 产生,目标是求出反馈系数。
n 级 LFSR 的递推关系是 a_{k+n} = c₁·a_{k+n−1} ⊕ c₂·a_{k+n−2} ⊕ … ⊕ c_n·a_k。取密钥流前 6 比特,可以列出 3 个线性方程。密钥流前 6 位是 1,1,1,0,1,0,设状态分别为 (a₁,a₂,a₃)=(1,1,1),(a₂,a₃,a₄)=(1,1,0),等等。代入递推式得到:
a₄ = c₁·a₃ ⊕ c₂·a₂ ⊕ c₃·a₁ → 0 = c₁·1 ⊕ c₂·1 ⊕ c₃·1
a₅ = c₁·a₄ ⊕ c₂·a₃ ⊕ c₃·a₂ → 1 = c₁·0 ⊕ c₂·1 ⊕ c₃·1
a₆ = c₁·a₅ ⊕ c₂·a₄ ⊕ c₃·a₃ → 0 = c₁·1 ⊕ c₂·0 ⊕ c₃·1
在 GF(2) 上解这个方程组,得到唯一的反馈系数组合。得到递推关系后,可以继续生成后续密钥流验证是否与已知全部 10 位一致。这一步验证不能省,因为如果密钥流长度不足,可能有多组系数都满足前几项,只有回代才能排除伪解。
资源里还有一个更刁钻的问题:如果敌手只知道长为 2n−2 的明密文对,而不是 2n,怎么破译?答案是枚举未知的 2 个比特的所有可能。比如 n=3,已知 4 比特明密文,密钥流差 2 位,就把这 2 位分别取 00、01、10、11,补全成 6 比特密钥流,再解 LFSR 系数。这样一共会有 4 种候选系数,但每种都会生成更长的密钥流,其中符合 m 序列游程性质的那个就是正确结果。当然结果可能不唯一,这时需要更多上下文或统计特性来筛选。这个思路在真实密码分析里很实用:数据不足时,先枚举未知位,再用冗余条件筛。
4. 分组密码与标准算法:DES、IDEA、SM4 的课内证明与实现
4.1 DES 的补性质如何帮你把穷搜索密钥空间减半
DES 的补性质描述起来很简单:如果 Y = DES_K(X),那么 Y' = DES_{K'}(X'),其中 X'、K'、Y' 分别是 X、K、Y 的逐比特取补。也就是说,明文取补、密钥取补后,密文也会取补。这个性质能直接帮助穷搜索攻击减少一半工作量。
证明的思路要抓住轮函数。DES 每轮做的是 Lᵢ = Rᵢ₋₁,Rᵢ = Lᵢ₋₁ ⊕ f(Rᵢ₋₁, Kᵢ)。轮函数 f 的第一步是把 R 扩展成 48 位,再与 48 位子密钥异或。当 R 和 K 都取补时,扩展后的 R' 与 K' 异或结果等于 (扩展R)⊕(取补)⊕(K取补) = 扩展R ⊕ K,也就是说异或后的值完全不变。因此 S 盒的输入不变,输出也不变;经过 P 置换后,f(R',K') = f(R,K)。再看左右迭代:R'ᵢ = L'ᵢ₋₁ ⊕ f(R'ᵢ₋₁, K'ᵢ) = Lᵢ₋₁补 ⊕ f(Rᵢ₋₁,Kᵢ) = (Lᵢ₋₁ ⊕ f(Rᵢ₋₁,Kᵢ))补 = Rᵢ补。每一轮都保持“输入补、输出补”的规律,最终密文自然是原密文的补。
利用这个性质,攻击者给定时明文 X,如果测出 Y = DES_K(X),那就无需再测 K' 对应的密文,因为 DES_{K'}(X') 必然等于 Y'。这样原本要在 2^56 个密钥里逐个搜索,现在只需要搜索 2^55 个:先搜一半密钥 K,另一半 K' 的结果自动补出来。资源里这道证明题经常出现在笔试中,重点是把轮函数中异或抵消这一步写清楚,不能只背结论。
资源里还让证明 DES 解密是加密的逆。标准做法是定义 T(L,R) = (R,L) 为交换函数,F_k(L,R) = (L⊕f(R,k), R)。因为 T 自逆,F_k 也自逆,所以 F_k∘T 的逆是 F_k∘T 的某种组合。把 16 轮迭代展开后,加密是 IP⁻¹ ∘ F_k16 ∘ T ∘ F_k15 ∘ T ∘ … ∘ F_k1 ∘ IP,解密则是 IP⁻¹ ∘ F_k1 ∘ T ∘ F_k2 ∘ T ∘ … ∘ F_k16 ∘ IP。两者复合抵消,说明解密过程只是把子密钥顺序反过来,轮结构完全相同。这也是 DES 硬件实现能复用同一套电路的底层原因。
4.2 分组模式与错误传播:CBC 和 CFB 的边界值
分组密码的工作模式也是常考内容。资源里专门比较了 ECB 和 CBC 对错误传播的不同表现。ECB 模式中,一个密文分组错误只影响对应的一个明文分组,因为每个分组独立解密。CBC 模式中,C₁ 错误会影响 P₁ 和 P₂,因为 P₂ = D(C₂) ⊕ C₁,而 D(C₂) 正确,但异或的 C₁ 错了,所以 P₂ 也错。P₂ 之后的分组 P₃、P₄ 等不再依赖 C₁,所以完全不受影响。这种“自恢复”特性使 CBC 在传输错误后能自动回到正确状态。
如果错误发生在加密前的明文分组 P₁,情况完全不同:P₁ 的错误会进入 C₁,而 C₁ 又作为下一个分组的异或输入,这样 C₂、C₃ 等所有后续密文分组都会改变。但对接收者解密后,只有 P₁ 本身是错的,其余明文分组都能正确恢复,因为解密时错误的只是密文链,而每个分组的 D(Cᵢ) 本身是对的,异或回去后除了 P₁ 都还原了。这个结论在面试里常被问,记住“CBC 密文错误传播长度是 2,明文错误影响所有密文但接收者只损失一个明文分组”就够了。
CFB 模式则是另一种反馈结构。资源里问:8 比特 CFB 中,密文字符出现 1 比特错误,能传播多远?答案是连续 9 组出错。原因是错误比特进入反馈移位寄存器后,要经过 9 次移位才完全移出,所以后续 9 个解密输出的字符(或分组)都会受影响。这个数字是 n+1,其中 n 是 CFB 反馈单位长度。如果你看到的题是 64 比特 CFB,那就是 65 个分组受影响,别数错。
4.3 IDEA 模乘与 SM4 对合性:笔试常考的推演点
IDEA 的一个经典设计问题是:为什么模乘用 2^16+1,而模加用 2^16?答案要从运算的代数结构看。模乘需要非零元都有逆元,构成乘法群,这就要求模数必须是素数。2^16+1 = 65537 恰好是素数,所以选它。但 2^16 不是素数,如果模乘也用它,就无法保证每个非零元素可逆。另一方面,IDEA 内部同时使用加法、乘法和异或,三种运算混合时不能都在同一个模下,否则分配律和结合律会带来结构上的脆弱性。模加选 2^16,模乘选 2^16+1,刻意让两个群分离,破坏可能的代数攻击路径。
资源里那道高效模乘推导题,本质上是给 16 位乘法器实现模 65537 乘法提供了一套公式。设 ab = q(2^n+1) + r,则 q 的范围在 0 到 2^n−3 之间,且 q+r < 2^n+1。于是 (ab) div 2^n 和 (ab) mod 2^n 都可以用 q 和 r 表达。最终结论:r = (ab mod 2^n) − (ab div 2^n),如果结果小于 0,再加 2^n+1。这个技巧在硬件实现里可以省掉一个除法器,只用普通乘法和移位就能算出模乘结果。
SM4 的对合性是国产分组密码的一个特点。所谓对合,就是加密过程和解密过程基本一样,只要把轮密钥的使用顺序反过来。证明的核心是轮函数 F 自逆。SM4 的轮函数包含非线性变换 τ 和线性变换 L,整体记为 T,T 本身是自逆的。于是连续执行两次相同轮函数,输入会被还原。再加上轮间的数据交换 E 也是自逆的,整个加密轮函数 G∘E 就是自逆的。因此 32 轮加密后,解密只需把 rk₀, rk₁, …, rk₃₁ 倒过来用一遍,轮函数代码完全复用。资源里的证明用了复合函数展开,建议你按“轮函数 Fᵢ = Gᵢ∘E,Gᵢ 自逆,E 自逆”的框架理解,比死记数学符号更牢。
5. 避坑与常见问题:手算步骤里的那些翻车点
5.1 模逆元求错,整道题白算
现象:仿射变换解密结果乱码,或者算到一半出现负数,最后和答案对不上。
原因:最常见的两个原因,一是没有先检查 gcd(a, 26),选了 a=2、a=13 这类与 26 不互素的密钥,导致加密本身不可逆;二是求逆元时用了错误的欧几里得算法,比如把 26 和 11 的辗转相除过程抄错,或者忘记最后取正余数。
解决:先算 gcd(a, 26),不为 1 直接换密钥。求逆元用扩展欧几里得,算完后立刻做乘法验证:a×inv mod 26 必须等于 1。例如 11×19=209≡1 (mod 26),所以 11⁻¹=19。这个验证实际操作只要三秒,但能挡掉一半以上的低级错误。
5.2 多表代换矩阵乘法的行列约定不一致
现象:同一道题,两个人按答案推出来的矩阵 A 不一样,但都能解出正确明文。
原因:矩阵乘法不满足交换律,如果一个人用列向量 C=A·M,另一个人用行向量 C=M·A,得到的 A 互为转置。题目如果没有明确说明,默认按列向量处理,但很多教材的习题排版会把向量写成行,导致抄公式时方向搞反。
解决:做题前先约定字母映射和向量形式。拿到已知明密文对时,用“明文第一个数乘以 A 的第一行”还是“乘以 A 的第一列”,结果完全不同。最稳妥的办法是先把明文分组写成列向量,再代入 C≡A·M+B,算出的密文如果和题目一致,说明约定正确;不一致就转置 A 再试一次。
5.3 LFSR 反馈系数顺序搞反
现象:用代码实现 LFSR 时,生成的序列和答案序列不是同一个,但周期却一样。
原因:特征多项式 f(x)=c₀+c₁x+…+c_nx^n 的系数,从低次到高次排列时,对应反馈连接是从最后一级抽头往前接。很多初学者把 c₁ 当成最高位抽头,结果状态移位方向反了,生成的序列整体错位。
解决:先写递推式 a_{k+n}=c₁·a_{k+n−1}+c₂·a_{k+n−2}+…+c_n·a_k,再按这个式子去映射抽头位置。每解完一组系数,用前 2n 个密钥流回代验证,如果第 n+1 位起对不上,说明系数顺序肯定错了。回代验证只需要纸笔,但能救回半个小时的调试时间。
5.4 DES 补性质证明中对“取补”的定义模糊
现象:证明时写了一大堆,最后卡在“为什么轮函数输出取补了”这点上。
原因:取补后,扩展后的数据和子密钥都翻转,异或后确实回到原值,但很多人忽略这一步骤之间的中间变量,直接写“f(R',K')=f(R,K)'”,这在数学上不严谨,也会让自己绕晕。
解决:按三步走。第一步,写清 E(R') = E(R)⊕全1,K'=K⊕全1,所以 E(R')⊕K' = E(R)⊕K。第二步,S 盒输入不变则输出不变,P 置换后也不变,所以 f(R',K') = f(R,K)。第三步,再看左半部分的更新,Rᵢ' = Lᵢ₋₁' ⊕ f = Lᵢ₋₁' ⊕ f = (Lᵢ₋₁⊕f)⊕全1 = Rᵢ⊕全1,即输出取补。每一轮都保持这个规律,整体才能下结论。
5.5 只看答案不验算,换个数就懵
现象:平时对着答案能看懂,考试时把明文换一句、把密钥换个数字,立刻不知道怎么下手。
原因:答案只是某道具体题的数值结果,没有沉淀成计算流程。仿射变换、LFSR 破译、DES 补性质证明,每一步都是固定的操作序列,只背答案等于没学。
解决:每做完一道题,把中间量(比如字母数字映射表、每轮状态)单独列出来,对照答案的每一步数字。如果中间量一致,最后结果就不太可能错。更重要的是,用随机数据自己出题再算一遍,比如随便选一个与 26 互素的 a,加密一段字母,再解密回来,自己验证整个过程。下面这章就给你一套可复用的验证脚本思路。
6. 验证与进阶:把答案当成校验集,而不是背诵稿
6.1 手算与脚本验证的配合
我拿到任何一份密码学习题答案,第一反应不是从头看,而是挑一道中等难度的题,先用手算一遍,再用脚本把结果打印出来对比。这里给你一个仿射变换的验证小工具,它能把字母映射、加密、解密全过程摊开看:
def egcd(a, b): if b == 0: return a, 1, 0 g, x, y = egcd(b, a % b) return g, y, x - (a // b) * y def inv_mod(a, m): g, x, _ = egcd(a, m) if g != 1: raise ValueError(f"{a} 与 {m} 不互素,无法求逆元") return x % m def affine_encrypt(text, a, b): inv = inv_mod(a, 26) print(f"a 的逆元: {inv}") result = [] for ch in text.upper().replace(" ", ""): if not ch.isalpha(): continue p = ord(ch) - ord('A') c = (a * p + b) % 26 result.append(chr(c + ord('A'))) print(f"{ch}({p}) -> {chr(c + ord('A'))}({c})") return ''.join(result) def affine_decrypt(cipher, a, b): inv = inv_mod(a, 26) result = [] for ch in cipher.upper().replace(" ", ""): if not ch.isalpha(): continue c = ord(ch) - ord('A') p = (inv * (c - b)) % 26 result.append(chr(p + ord('A'))) print(f"{ch}({c}) -> {chr(p + ord('A'))}({p})") return ''.join(result) plain = "THENATIONALSECURITYAGENCY" a, b = 11, 23 cipher = affine_encrypt(plain, a, b) print("密文:", cipher) print("解密:", affine_decrypt(cipher, a, b))这个脚本的核心逻辑分三块:扩展欧几里得求逆元、加密映射、解密映射。你可以把资源里那道题的数据套进去,比如明文和 a=11、b=23,看看输出的密文是不是“YWPKXYHVKXONPTJCHYBXLPKTB”。脚本里每一轮都打印了字母对应的数字和结果,这样如果你手算到某一步对不上,马上能定位是映射表错了还是乘法取模错了。
参数上要注意两点:一是 a 必须满足 gcd(a, 26)=1,否则 inv_mod 会直接抛异常,这比手算时最后才发现解密失败要高效得多;二是空格和标点会被过滤,只保留大写字母,这是为了和教材习题的“忽略空格”规则保持一致。如果你想验证多表代换,可以把脚本里的单个字符换成小矩阵运算,思路完全一样:先求矩阵行列式的逆元,再算伴随矩阵。
从那以后,我每次做密码学习题都强制走一遍“先手算、再脚本验算、最后对照答案”的流程。手算练的是步骤熟悉度,脚本验算抓低级错误,答案只用来确认最终方向。这个方法让我在换数据、换参数的题目上再也没翻过车。希望帮到你。
本文还有配套的精品资源,点击获取