本文章主要是详细的讲解如何合理选择工具或者python的三方库来解决常见的CTF中密码学相关问题,针对偏门或者高深的密码学知识点,作者表示也无能为力!
RSA
现在 RSA 在 CTF 比赛中一般作为简单题甚至签到题出现!如果你还不了解 RSA,那机会来了,赶紧跟着来学一学怎么解 RSA 这纸老虎!
- 分解大整数
RSA 中常用的运算有三个:分解大整数,求逆元以及模幂运算。一般分解大整数有三个常用的工具 factordb
,msieve
以及 yafu
,如果这三个都不能分解大整数,那就要不要强行分解了,多半是其它思路。
在线 factordb
msieve和yafu的 网盘链接
使用方法:
factordb离线,命令行 factordb xxx
msieve.exe -q xxx
yafu-x64.exe factor(xxx)
yafu-x64.exe "factor(@)" -batchfile xxx.txt
- libnum与gmpy2
建议安装在:windows-python2
当我们分解大整数之后,我们需要用到的 python2 的库有两个 libnum
与 gmpy2
,这两个库建议在 windows 下安装,安装过程简单许多
链接: 百度网盘 密码: inbd
我建议用 libnum 转换整数与字符串,gmpy2 用来作核心的运算,libnum 常用的几个函数有
# Encoding=UTF-8
from libnum import *
#
s2n(str) # 字符串转整数
n2s(n) # 整数转字符串,任意进制数也能直接转,它会先把任意进制数转成16进制数
s2b(str) # 字符串转2进制位串
b2s(bin) # 2进制位串转字符串
复制代码
gmpy2常用的几个函数有
# Encoding=UTF-8
import gmpy2
#
gmpy2.mpz(x) # 初始化一个大整数x
gmpy2.mpfr(x) # 初始化一个高精度浮点数x
C = gmpy2.powmod(M,e,n) # 幂取模,结果是 C = (M^e) mod n
d = gmpy2.invert(e,n) # 求逆元,de = 1 mod n
gmpy2.is_prime(n) # 判断n是不是素数
gmpy2.gcd(a,b) # 欧几里得算法
gmpy2.gcdext(a,b) # 扩展欧几里得算法
gmpy2.iroot(x,n) # x开n次根
复制代码
- 神器sage
懒人的话建议在线使用
sage 非常适合用来做大数运算、矩阵运算、数论以及解方程,所以说 sage 用来解 RSA 中的大数方程再适合不过了。我这里有个 sage中文文档,这东西要研究透彻还是挺难的!下面会列出一些解方程基本用法
# encoding=utf-8
# 解方程
var('x y')
solve([x+y==10,x*y==21],[x,y])
# 其它
inverse_mod(e,fn) # sage求逆元
pow(m,e,n) # sage模幂运算
复制代码
在线sage运算
- RSA解题思路
1)如果遇到题目给了 pem 或者 key 后缀结尾的文件,就需要用 Kali 自带的 OpenSSL 从公钥文件中提取出 e 和 n。命令如下:openssl rsa -pubin -text -modulus -in key.pem
2)当我们有 n,e,c 时,我们就要尝试分解 n,因为分解 n 能得到 fn,从而求得 e 的逆元 d,根据 d 再对密文 c 求模幂,最终得到明文 m
3)当 n 不能使用常规手段分解时,就需要考虑其它的思路了,这里是大佬总结的思路传送门。然后我也总结了一些经典且常用的 RSA 套路模不互素
-这是最简单的情况了,对两个模数N1,N2求最大公约数,然后愉快的分解模数共模攻击
-给了两段密文c,这两段密文是用同一个明文m以及模数n和各自的e加密得到的。意思就是 e不同,n和m相同,这种情况能用共模攻击直接解出明文m低加密指数
-e就是加密指数,当e很小时,c = m ^ e mod n,当 m^e < n 时,直接对n开e次根就得到了m,或者当 c = m^e + kn,此时的 k也非常小,可以爆破k的值低解密指数
-d就是解密指数,当d非常小时,即e很大,此时可以用 rsa-wiener-attack 攻击,rsa-wiener-attack的源码在Github选择密文攻击
-我们可以给定一个密文,然后系统返回给我们对应的明文
假如原始密文 C = M ^ e mod n,我们选择一个 X (X与n互素),再计算一个密文为 C2 = C * (X ^ e) mod n,将他发送给解密系统,系统返回我们一个明文 M2 = C2 ^ d mod n,此时表达式为:M2 = C2 ^ d = (C * (X ^ e)) ^ d = (C ^ d) * X = ((M ^ e) ^ d) * X = M * X mod n,注意 d * e = 1。此时求 M 就相对容易了,M = M2 * (X ^ -1) mod n
RSA LSB Oracle Attack
-我们发送任意的密文,系统可以返回给我们该密文对应明文的奇偶性
攻击原理:因为 C = M ^ e mod n,当我们发送 C * (2 ^ e) 时,相当于系统返回给我们 2M mod n 的奇偶性,且我们知道 2M ^ e 为偶数,n 为奇数。当返回为奇数时,说明 2M - kn 为奇数,那么 k 必定为奇数,又 2M < 2n,k < 2,所以 k = 1,那么 n/2 <= M < n;当返回偶数时,说明 2M < n,即 0 <= M < n/2。那么下一次我们可以发送 C * (4 ^ e),就能得到 n/4 <= M < n/2 或者 0 <= M < n/4,最后可以把 M 的值缩小到一个很小的区间
这是 Oracle 攻击解题模板:
from pwn import *
e = xxx
n = xxx
c = xxx
lb=0
ub=n
k=1
while True:
sh=remote('ip',port)
sh.recvuntil('tip infomation!')
tmp=pow(2,k,n)
my_c=(c*pow(tmp,e,n))%n
my_c=hex(my_c)[2:].strip('L')
sh.sendline(my_c)
data=sh.recvline()[:-1]
sh.close()
if(data=='even')
ub=(lb+ub)/2
elif(data=='odd'):
lb=(lb+ub)/2
else:
break
k+=1
print lb
复制代码
关于 RSA 的攻击原理可能我写得并不是很清楚,因为我主要是讲脚本库的选择以及如何使用库函数,如果大家遇到攻击原理方面的问题可以参见CTF-WIKI,或者想了解其它的 RSA 攻击技巧也参见师傅们的CTF-WIKI
Crypto算法库
建议安装在:windows-python3
pycrypto 这个库已经好几年没有更新了,而pycryptodome
正是这个库的替代库,并且也经常更新 issue。这个库就建议安装在 windows 下的 python3 里,目前也只实现了对 python3 的支持,直接 pip 就能安装好!这个库的文档
常见对称密码在 Crypto.Cipher 库下,主要有:
DES 3DES AES RC4 Salsa20
非对称密码在 Crypto.PublicKey 库下,主要有:RSA ECC DSA
哈希密码在 Crypto.Hash 库下,常用的有:MD5 SHA-1 SHA-128 SHA-256
数字签名在 Crypto.Signature 库下
随机数在 Crypto.Random 库下
实用小工具就是 Crypto.Util 库
- 手把手教你使用Crypto
首先要知道 Crypto 库加解密用的都是字节流,python3 中的字节串用 b'xxx' 表示,python2 中没有字节串这东西!字符串与字节串相互转换:
字符串转字节
bytes('str',encoding='utf8')
'str'.encode(encoding='utf8')
字节转字符串
str(b'byte',encoding='utf8')
b'str'.decode(encoding='utf8')
先教你写个简单的 AES 加密,AES 的 key 长度为128bit,192bit 或者 256bit,可以自己写 pad() 函数填充密钥到指定的位数,也可以用 Util 中的 pad() 函数填充!首先导入包from Crypto.Cipher import AES
,然后初始化一个key = b'this_is_a_key'
,再实例化一个 aes 的对象aes = AES.new(key,AES.MODE_ECB)
,再调用加密函数就行text_enc = aes.encrypt(b'helloworld')
,注意ECB 模式下的 key 和 text 需要 pad!Demo:
from Crypto.Cipher import AES
import base64
key = bytes('this_is_a_key'.ljust(16,' '),encoding='utf8')
aes = AES.new(key,AES.MODE_ECB)
# encrypt
plain_text = bytes('this_is_a_plain'.ljust(16,' '),encoding='utf8')
text_enc = aes.encrypt(plain_text)
text_enc_b64 = base64.b64encode(text_enc)
print(text_enc_b64.decode(encoding='utf8'))
# decrypt
msg_enc = base64.b64decode(text_enc_b64)
msg = aes.decrypt(msg_enc)
print(msg.decode(encoding='utf8'))
复制代码
再来个经典的 DES 算法,思路一样,先初始化 key,用 key 实例化 des 对象选择填充模式,就可调用加解密函数了!注意 key 的长度,Demo:
from Crypto.Cipher import DES
import base64
key = bytes('test_key'.ljust(8,' '),encoding='utf8')
des = DES.new(key,DES.MODE_ECB)
# encrypt
plain_text = bytes('this_is_a_plain'.ljust(16,' '),encoding='utf8')
text_enc = des.encrypt(plain_text)
text_enc_b64 = base64.b64encode(text_enc)
print(text_enc_b64.decode(encoding='utf8'))
# decrypt
msg_enc = base64.b64decode(text_enc_b64)
msg = des.decrypt(msg_enc)
print(msg.decode(encoding='utf8'))
复制代码
讲讲 Crypto 的非对称密码,Crypto 的 PublicKey 主要用来生成 RSA/ECC 所需的公私钥对,或者读取密钥文件!我们先来生成一个密钥文件
from Crypto.PublicKey import RSA
rsa = RSA.generate(2048) # 返回的是密钥对象
public_pem = rsa.publickey().exportKey('PEM') # 生成公钥字节流
private_pem = rsa.exportKey('PEM') # 生成私钥字节流
f = open('public.pem','wb')
f.write(public_pem) # 将字节流写入文件
f.close()
f = open('private.pem','wb')
f.write(private_pem) # 将字节流写入文件
f.close()
#
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArreg3IX19DbszqSdBKhR
9cm495XAk9PBQJwHiwjKv6S1Tk5h7xL9/fPZIITy1M1k8LwuoSJPac/zcK6rYgMb
DT9tmVLbi6CdWNl5agvUE2WgsB/eifEcfnZ9KiT9xTrpmj5BJql9H+znseA1AzlP
iTukrH1frD3SzZIVnq/pBly3QbsT13UdUhbmIgeqTo8wL9V0Sj+sMFOIZY+xHscK
IeDOv4/JIxw0q2TMTsE3HRgAX9CXvk6u9zJCH3EEzl0w9EQr8TT7ql3GJg2hJ9SD
biebjImLuUii7Nv20qLOpIJ8qR6O531kmQ1gykiSfqj6AHqxkufxTHklCsHj9B8F
8QIDAQAB
-----END PUBLIC KEY-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEArreg3IX19DbszqSdBKhR9cm495XAk9PBQJwHiwjKv6S1Tk5h
7xL9/fPZIITy1M1k8LwuoSJPac/zcK6rYgMbDT9tmVLbi6CdWNl5agvUE2WgsB/e
ifEcfnZ9KiT9xTrpmj5BJql9H+znseA1AzlPiTukrH1frD3SzZIVnq/pBly3QbsT
13UdUhbmIgeqTo8wL9V0Sj+sMFOIZY+xHscKIeDOv4/JIxw0q2TMTsE3HRgAX9CX
vk6u9zJCH3EEzl0w9EQr8TT7ql3GJg2hJ9SDbiebjImLuUii7Nv20qLOpIJ8qR6O
531kmQ1gykiSfqj6AHqxkufxTHklCsHj9B8F8QIDAQABAoI...
-----END RSA PRIVATE KEY-----
复制代码
再用生成的 RSA 密钥文件来加解密字符串,这里会涉及读取 pem 文件,这个操作在实际中经常会用到!读取公钥文件,导入 Key,使用 key 初始化 rsa 对象,然后加密字符串,解密的话过程相同,只是读取的是私钥文件
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
def rsa_encrypt(plain):
with open('public.pem','rb') as f:
data = f.read()
key = RSA.importKey(data)
rsa = PKCS1_v1_5.new(key)
cipher = rsa.encrypt(plain)
return base64.b64encode(cipher)
def rsa_decrypt(cipher):
with open('private.pem','rb') as f:
data = f.read()
key = RSA.importKey(data)
rsa = PKCS1_v1_5.new(key)
plain = rsa.decrypt(base64.b64decode(cipher),'ERROR') # 'ERROR'必需
return plain
if __name__ == '__main__':
plain_text = b'This_is_a_test_string!'
cipher = rsa_encrypt(plain_text)
print(cipher)
plain = rsa_decrypt(cipher)
print(plain)
复制代码
注意:RSA 有两种填充方式,一种是 PKCS1_v1_5,另一种是 PKCS1_OAEP,具体区别可以百度看看,我就不啰嗦了!
再来学习学习 Hash 算法吧,之前我一直用的 hashlib
,使用很简单,初始化后调用 update() 就行了,其实 Crypto 的 Hash 库用法也类似,先上个例子:
from Crypto.Hash import SHA1,MD5
sha1 = SHA1.new()
sha1.update(b'sha1_test')
print(sha1.digest()) # 返回字节串
print(sha1.hexdigest()) # 返回16进制字符串
md5 = MD5.new()
md5.update(b'md5_test')
print(md5.hexdigest())
复制代码
最后来看看数字签名,随机数和常用小工具
,这个我没有自己写 Demo,只有贴官方 API 的例子了。首先是数字签名,这个和非对称加密相反,发送方用私钥签名,验证方用公钥验证,Example:
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
# 签名
message = 'To be signed'
key = RSA.import_key(open('private_key.der').read())
h = SHA256.new(message)
signature = pkcs1_15.new(key).sign(h)
# 验证
key = RSA.import_key(open('public_key.der').read())
h = SHA.new(message)
try:
pkcs1_15.new(key).verify(h, signature):
print "The signature is valid."
except (ValueError, TypeError):
print "The signature is not valid."
复制代码
生成随机数的几个用法,第一个函数挺常用的:
import Crypto.Random
import Crypto.Random.random
print(Crypto.Random.get_random_bytes(4)) # 得到n字节的随机字节串
print(Crypto.Random.random.randrange(1,10,1)) # x到y之间的整数,可以给定step
print(Crypto.Random.random.randint(1,10)) # x到y之间的整数
print(Crypto.Random.random.getrandbits(16)) # 返回一个最大为N bit的随机整数
复制代码
Util 中有很多简化操作的函数,但是太多了,我列举几个十分常用的函数:
from Crypto.Util.number import *
from Crypto.Util.Padding import *
# 按照规定的几种类型 pad,自定义 pad可以用 ljust()或者 zfill()
str1 = b'helloworld'
pad_str1 = pad(str1,16,'pkcs7') # 填充类型默认为'pkcs7',还有'iso7816'和'x923'
print(unpad(pad_str1,16))
# number
print(GCD(11,143)) # 最大公约数
print(bytes_to_long(b'hello')) # 字节转整数
print(long_to_bytes(0x41424344)) # 整数转字节
print(getPrime(16)) # 返回一个最大为 N bit 的随机素数
print(getStrongPrime(512)) # 返回强素数
print(inverse(10,5)) # 求逆元
print(isPrime(1227)) # 判断是不是素数
复制代码
矩阵运算
虽然矩阵之类的运算可以用 sage很快很方便,但我还是强烈推荐用 numpy,上手快而且库函数容易记!这是 numpy官方文档,看不懂英文的,这里还有 numpy中文文档
- 安装方法
建议安装在:windows-python2/python3
这个 numpy
和常用的 scipy
在 windows 的 python3 下直接 pip 就能安装好,python2 的话安装 .whl 会快一些也基本不会出错,对应的 whl 包可以在这里查询。如果你用 python2.7,我这里还有帮你打包好的安装文件,下面我列举一些常用的函数,再放个Demo供大家参考
import numpy as np
np.array([[1,2,3],[4,5,6]]) # 定义一个二维数组
np.mat([[1,2,3],[4,5,6]]) # 两行三列矩阵
np.mat(list) # 列表或者数组转 matrix(矩阵)
np.tolist(matrix) # 与上面相反
np.shape(array) # 求矩阵或者数组array的维度
array.reshape(m,n) # 数组或矩阵重塑为m行n列
np.eye(m,n) # 创建m行n列单位矩阵
np.zeros([m,n],dtype) # 创建初始化为0的矩阵
# .transpose()转置矩阵 .inv()逆矩阵
# .T转置矩阵,.I逆矩阵
复制代码
要注意: numpy 的数组和 python 的列表是有区别的,比如:列表 list 只有一维。然后 numpy 的数组和矩阵也有区别!比如:矩阵有逆矩阵,数组是没有逆的!!
# This is a demo
import numpy as np
# 先创建一个长度为12的列表,,再重塑为4行3列的矩阵
list1 = [0,1,2,3,4,5,6,7,8,9,0,1]
list1_to_mat = np.mat(list1) # 列表先转成矩阵
mat1 = list1_to_mat.reshape(4,3) # 重塑
print(mat1)
# 求上面矩阵的转置矩阵和逆矩阵
mat_transpose = mat1.T
mat_inv = mat1.I
# 再定义一个3行4列的数组转成矩阵,和上面矩阵相乘
array1 = np.array([[1,2,3,4],[4,5,6,7],[3,2,1,0]])
mat2 = np.mat(array1)
print(mat2)
print(mat2*mat1) # 或者你可以用 np.dot()以及 np.multiply()
复制代码
求解方程
- numpy
只能解点线性方程组!求解速度比较快 需要构造系数矩阵,然后用矩阵思维去解方程组 我觉得太复杂了,如果你数学菜就不建议使用了!
- sympy
建议安装在:windows-python3
windows 下 python3 直接 pip,python2 不太好装,我装的时候老是出错就没去纠结它了,建议装两个版本的 python 哪个能用用哪个。sympy 能解线性方程组和简单的非线性方程组,也算是比较好用了,个人认为逊色于 sage 和 z3
from sympy import *
x = symbols('x')
y = symbols('y')
res = solve([x+y-3,x-y-1],[x,y])[0]
print(res)
复制代码
- sage
sage 既能解线性方程组,又能解非线性方程组,堪称解方程界的神器,但是表达式不支持位运算,比如:与或非,取余以及异或。出现位运算的方程就只能用 z3 创建约束求解!写了个 sage 解方程的 demo,可以在线sage求解
var('x y')
solve([x**3+y**2+666==142335262,x**2-y==269086,x+y==1834],[x,y])
#
[[x == 520, y == 1314]]
复制代码
- z3
建议安装在:linux-python2/python3
它叫约束求解器,用来解任何方程都没有问题!但是 windows 不太好装,所以我基本上是在 linux 上跑,python2和python3都支持!使用的思路非常简单,先创建你所需类型的符号变量,再初始化一个约束器,添加约束,最后判断约束是否有解以及求解变量,下面列举常用的函数
# 符号变量类型
Int('x')
Real('x')
Bool('x')
BitVec('x',N) # N bit的符号变量,用于位操作
BitVecVal(num,N) # N bit的数据 num
# 初始化约束器
solver = Solver()
# 添加约束
solver.add(x+y==10,x-y==0)
# 求解约束
solver.check()
ans = solver.mode()
# 初始化多个符号变量
x = [Int('x%d' % i) for i in range(n)]
# 取结果中某个变量的值
value = ans[x].as_long()
复制代码
上面的基本操作一般来说是够用的,如果你需要更加高级的用法,戳这里 z3-solver文档
数据类型转换
先讲讲 python 中的一些简单的变量类型转换,注意:python3 比 python2 多了个字节串类型
# python2 和 python3 通用:
int(‘123456’,10) # 转换为指定进制的整数
hex(123456) # 整数转换为16进制串,转换后类型为字符串
bin(123) # 整数转换为2进制串
oct(123) # 整数转换为8进制串
# python2 专用
'abcd'.encode('hex') # 字符串转换为16进制串,对应字符的ascii码
'61626364'.decode('hex') # ascii码转换为对应的字符串
# python3 字节串专用
字符串转字节
bytes('str',encoding='utf8')
'str'.encode(encoding='utf8')
字节转字符串
str(b'byte',encoding='utf8')
b'str'.decode(encoding='utf8')
复制代码
再来了解下 python 中的C语言数据类型转换,numpy 和 ctypes 推荐用 ctypes
# numpy
import numpy as np
a = np.int32(0xffffffff) # 会报错,超范围了
b = np.uint32(0xffffffff)
print a,b
# ctypes
from ctypes import *
a = c_uint32(0xfffffff).value
b = c_int32(0xffffffff).value # 显示为 -1
print a,b
复制代码
再谈谈 struct
库,输入的几个字符可以被认作一个 WORD 或者 DWORD 甚至 QWORD 的类型进行运算,struct 库就是帮我们把几个字符打包成一个整数,或者将一个整数解包成几个字符,还能定义大小端模式!
# > 大端模式;< 小端模式;默认小端模式
常用的几个格式字符:
b char 1
B uchar 1
h short 2
H ushort 2
i int 4
I uint 4
l long 4
L ulong 4
q longlong 8
Q ulonglong 8
f float 4
d double 8
# Example:
from struct import *
a = 0x41424344
b = 0x6162
c = 0x66
print pack(',a,b,c)
print unpack(','DCBAbaf')[0]
复制代码
最后讲讲这两个库 binascii 和 libnum
,其实组合使用上述函数,就能实现任何数据类型间的转换了,介绍这两个东西感觉有点多余!但是这两个库懒人必备,能够快速转换字符与 ascii 码!解古典密码的时候非常有用!
# Encoding=UTF-8
from libnum import *
s2n(str) # 字符串转整数
n2s(n) # 整数转字符串,任意进制数也能直接转,它会先把任意进制数转成16进制数
s2b(str) # 字符串转2进制位串
b2s(bin) # 2进制位串转字符串
#
import binascii
binascii.hexlify(str) # 字符串转16进制串,比hex()强
binascii.unhexlify(hex_str) # 16进制串转字符串
复制代码
注意:binascii 与 python2 的 encode('hex')/decode('hex')效果一模一样,所以常用在 python3 中,因为 python3 没有 ecnode('hex') 函数