仿射密码原理
讲解
在下面这道例题中,会通过题目直接告诉你仿射密码是怎么用的
- 首先会通过分析题目给出的源码去知道,这道题目采用了仿射密码的加密方式
- 然后通过已知的部分明文和全部密文去推出仿射密码的解题关键,元素a和b
- 最后用a和b直接调用仿射密码的解密公式
加密解密公式
加密
E(x)=(ax+b)(modm)
- m表示编码系统的字母个数
- x表示明文按照某种编码得到的数字
- a和b就是仿射密码的核心
解密
D(x)=a^-1(x−b)(mod m)
- a^-1 是a的乘法逆元
例题
题目代码:
import sys
key = '****CENSORED***************' #表明有一个密钥
flag = 'TWCTF{*******CENSORED********}' #这是我们需要获取的flag
# 下面整个都是在告诉我们,这道题目在加密,我们需要通过分析代码,去判断他采用了什么加密方法,需要怎么去解密
#如果长度是奇数 则表示密钥错误
if len(key) % 2 == 1:
print("Key Length Error")
sys.exit(1)
#在密码是偶数的情况下 去加密
n = len(key) / 2
encrypted = ''
#对flag中的每一个字母进行操作,相当于对flag的每一个明文进行加密
for c in flag:
c = ord(c) #取该字母的ascii码值
for a, b in zip(key[0:n], key[n:2*n]):
#相当于进行n次这样的循环
#不要把zip想象成压缩,仅仅只是一个简单的合并打包过程
#在key[0:n]取第一个给到a 在key[n:2*n]取也取第一个给到b
c = (ord(a) * c + ord(b)) % 251 #把c进行加密
#解释一下为什么要模251
#因为加密对应要放到整数的范围中,ascii码表0~255 但是选一个质数更安全,既要大且要质数 所以2
#最终选择了251
#解释一下为什么能确定这是一个仿射密码
#因为c1 = a1*c + b1
# c2 = a2 * c1 + b2
# = (a1 * a2)*c + a2*b1 + b2
# 即为 k*c + d 的形式,并且有一个打包集合key始终保持不变 故判定为仿射密码
encrypted += '%02x' % c
#这里'%02x'的意思是指去创建一个2位宽的16进制空字符串
# %c的意思不是对c取模 ,而是把c转换后放入到%02x的位置中去
print (encrypted)
# "805eed80cbbccb94c36413275780ec94a857dfec8da8ca94a8c313a8ccf9"
构建解密脚本思路解析
- 涉及语句内容具体详解在后面的解密代码中
-
首先需要把上面加密最靠近输出层的那个加密给破解,即
encrypted += '%02x' % c
所以要先把密文两个两个的读取转换成16进制,即
encrypted = [int(data[i:i + 2], 16) for i in range(0, len(data), 2)]
-
然后根据下列语句推断出是仿射密码:
c = (ord(a) * c + ord(b)) % 251
所以确定目标,求解密中的a和b:
plaindelta = ord(flag[1]) - ord(flag[0]) #提取部分已知明文
cipherdalte = encrypted[1] - encrypted[0] #提取已知密文
a = gmpy2.invert(plaindelta, 251) * cipherdalte % 251 #数据全部放在0~251之间
b = (encrypted[0] - a * ord(flag[0])) % 251 #数据全部放在0~251之间
-
根据a和b,带入到仿射密码的解密公式中,即
a_inv = gmpy2.invert(a, 251) result += chr((c - b) * a_inv % 251) #c是在encrypted列表中的每一个字符密文
解密代码
import gmpy2
#key = '****CENSORED***************' #密钥 censored 被遮盖
flag = 'TWCTF{*******CENSORED********}' #部分明文
data = "805eed80cbbccb94c36413275780ec94a857dfec8da8ca94a8c313a8ccf9" #密文待解密
encrypted = [int(data[i:i + 2], 16) for i in range(0, len(data), 2)]
#对密文进行操作
# range(0,len(cip),2):生成一个从 0 开始、以 2 为步长、不超过 data 长度的整数序列。这个序列中的每一个整数 i,都是 data 中两个字符的起始位置。
# data[i:i+2]:对于这个序列中的每一个整数 i,取 data 中从 i 开始、长度为 2 的子串。这样就可以把 data 中的所有字符两两分组。
# int(data[i:i+2],16):对于这个子串,使用 int 函数将其转换为一个 16 进制的整数。
# for i in range(0,len(data),2):将上述操作应用到 range 函数生成的整数序列中的每一个整数 i,得到一个整数列表。
# 最终的结果就是 encrypted 列表,它包含了 data 中所有字符两两分组后转换成的 16 进制整数。例如,如果 data 是字符串 "805eed80cbbccb94c36413275780ec94a857dfec8da8ca94a8c313a8ccf9",则 encrypted 是一个整数列表,它的值为 [128, 94, 237, 128, 203, 188, 185, 76, 195, 100, 19, 39, 87, 128, 236, 148, 168, 87, 223, 236, 141, 168, 202, 148, 168, 195, 19, 168, 204, 249]。
#去计算密钥key
plaindelta = ord(flag[1]) - ord(flag[0]) #提取部分已知明文
cipherdalte = encrypted[1] - encrypted[0] #提取已知密文
#原来的加密公式
#y1 = (a * x1 + b) (mod 26)
#y2 = (a * x2 + b) (mod 26)
#故有
#y1 - y2 = a*(x1-x2)(mod 251)
#想要求a
#即为
#plaindelta = x1-x2
#cipherdalta = y1-y2
#cipherdalte = a * plaindelta(mod 251) !!取模只是让最后的结果在0~251范围之间!!
#计算公式中的a,引入初等数论里面的内容
#ci = a * p % 251
#ci / p = a % 251
#在初等数论中 除相当于乘逆元
#故 ci * invert(p,251)= a (% 251)
#所以%251只是一个对于整体结果的一个限制,在公式推到的时候可以忽略存在
a = gmpy2.invert(plaindelta, 251) * cipherdalte % 251 #数据全部放在0~251之间
#通过a计算b
b = (encrypted[0] - a * ord(flag[0])) % 251 #数据全部放在0~251之间
a_inv = gmpy2.invert(a, 251)
result = ""
for c in encrypted:
result += chr((c - b) * a_inv % 251)
print(result)
我是哈皮,祝您每天嗨皮,我们下期再见!