前言
上一篇了解了一下差分分析,这次我们结合一道CTF题目聊一聊线性分析
同属于选择明文的差分分析不同,线性分析属于已知明文攻击方法,它通过寻找明文和密文之间的一个“有效”的线性逼近表达式,将分组密码与随机置换区分开来,并再此基础上进行密钥恢复攻击。
在正式介绍线性分析之前,我们还是要介绍一下相关的基础概念,参考《分组密码的攻击方法与实例分析》一书
基础概念
首先还是给出和差分分析一样的一个迭代分组密码的加密流程
迭代分组密码的加密流程
内积
线性掩码
线性逼近表达式
线性壳
迭代分组密码的一条 i 轮线性壳是指一对掩码(β0,βi),其中β0 是输入掩码,βi 是输出掩码。
线性特征
线性壳的线性概率
线性特征的线性概率
线性区分器
先给出一个命题,对{0,1}^n 上的随机置换R,任意给定掩码α,β,α ≠ 0,β ≠ 0, 则 LP(α,β ) = 0, 即 偏差 ε(α,β) = 0
如果我们找到了一条 r-1 轮线性逼近表达式 (α,β),其线性概率 LP(α,β) ≠ 0,即偏差 ε(α,β) ≠ 0。则利用该线性逼近表达式可以将 r-1 轮的加密算法与随即置换区分开来,利用该线性区分器就可以对分组密码进行密钥恢复攻击。假设攻击 r 轮加密算法,为获得第 r 轮的轮密钥的攻击步骤如下。
步骤1
寻找一个 r – 1轮的线性逼近表达式 (α,β) ,设其偏差为ε(α,β),使得 |ε(α,β)|较大。
步骤2
根据区分器的输出,攻击者确定要恢复的第 r 轮轮密钥 k_r(或者其部分比特):设攻击的密钥比特长度为 l,对每个可能的候选密钥gk_i, 0 ≤ i ≤ 2^l -1,设置相应的 2^l 个计数器λ_i,并初始化。
步骤3
均匀随机地选取明文 X,在同一个未知密钥 k 下加密,(一般是让拥有密钥地服务端帮你加密)获得相应地密文 Z, 这里选择明文地数目为 m ≈ c ·(1/ε ^2),c 为某个常数。
步骤4
对一个密文Z,我们用自己猜测地第 r 轮轮密钥 gk_i(或其部分比特)对其进行第 r 轮解密得到Y_{r-1},然后我们计算线性逼近表达式 α x · X ⊕ β · Y_{r-1} 是否为0,若成立,则给相应计数器λ_i 加 1
步骤5
将 2^l 个计数器中|λ/m – 1/2| 最大地指所对应地密钥 gk_i(或其部分比特)作为攻击获得地正确密钥值。
Remark 针对步骤1中,我们如何去寻找一个高概率地 r -1 轮线性逼近表达式呢?例如针对一个S盒,我们可以选择穷举所有的输入、并获得相应的输出,然后穷举输入掩码、输出掩码,去获取这个S盒的相关线性特性。
下面就根据一道CTF中出现的赛题来解释分析上述过程。
实例-[NPUCTF2020]EzSPN
task.py
import os
from binascii import hexlify, unhexlify
import Crypto.Random.random as random
from secret import flag
SZ = 8
coef = [239, 163, 147, 71, 163, 75, 219, 73]
sbox = list(range(256))
random.shuffle(sbox)
sboxi = []
for i in range(256):
sboxi.append(sbox.index(i))
def doxor(l1,l2):
return [x[0]^x[1] for x in zip(l1,l2)]
def trans(blk):
res = []
for k in range(0, SZ, 8):
bits = [bin(x)[2:].rjust(8,'0') for x in blk[k:k+8]]
for i in range(8):
res.append(int(''.join([x[(i+1) % 8] for x in bits]),2))
res[k:k+8] = [(coef[i] * res[k+i]) % 256 for i in range(8)]
return res
def encrypt_block(pt, ks):
cur = doxor(pt, ks[:SZ])
cur = [sbox[x] for x in cur]
cur = trans(cur)
cur = [sboxi[x] for x in cur]
cur = doxor(cur, ks[SZ:])
return cur
def encrypt(pt, k):
x = 0 if len(pt)%SZ==0 else (SZ-len(pt)%SZ)
pt += [x]*x
ct = ''
for i in range(0, len(pt