文章目录
环境配置相关
pycryptodomex囊括加密、签名、摘要、随机数生成。
sympy是符号计算(与数值计算相对)。
gmpy2是python的gmp,可用于求逆、大整数开根号、(扩展)欧几里得、模幂。
pip install pycryptodomex
pip install sympy
pip install mod
clion中使用openssl,需配置cmakelist:
set(OPENSSL_INC_DIR D:\\OpenSSL-Win64\\include)
set(OPENSSL_LINK_DIR D:\\OpenSSL-Win64\\lib)
include_directories(${OPENSSL_INC_DIR})
link_directories(${OPENSSL_LINK_DIR})
target_link_libraries(contest D:/OpenSSL-Win64/lib/libssl.lib D:/OpenSSL-Win64/lib/libcrypto.lib)
crypto用到的数据形式转换
字符串和bytes
利用str.encode(‘utf-8’)和bytes.decode(‘utf-8’)。
bytes.fromhex()也是将字符串转为bytes,但str.encode是逐字符的,而bytes.fromhex是以两个字符为单位的,且按16进制来理解字符串。输入的字符串一般是str()或者hex()后的数字。
数和bytes
long2bytes和bytes2long用于转换long人眼见到和实际存储的两种形态,大端下第一个byte是高位,通常用于文件读写。
base系列
不同的base使用不同数量的ascii字符来表示128个ascii字符(最高位b7用于校验,所以其实只有128个),以此来化不可见为可见,传输图片什么的。等号仅用于填充,不用于编码。
原本1个字符对应8bit,在base编码下,一个字符对应的bit更少,所以字符总数也会增多。
将原本的字符串写为bit表示,不同的base对其做不同的划分。
base64以24bit(24为6和8的最小公倍数)为一组进行编码,最后一组只有可能为24bit、16bit、8bit,分别对应没有=、一个=、两个=。base32以40bit(40为5和8的最小公倍数)为一组进行编码,最后一组只有可能为40bit、32bit、24bit、16bit、8bit,分别对应没有=、一个=、三个=、四个=、六个=。填充方式是先填0bit,然后填=。以base32最后一组为24bit为例,需要在最后一组补1个0bit,得到5组,即5x5=25bit,这距离40bit还有15bit,需要三个5bit的=。官方文档https://www.rfc-editor.org/rfc/pdfrfc/rfc4648.txt.pdf。
至于做题的时候,要点则是要熟悉各种base都用什么来编码,以及正确长度。base64包含大写字母(A-Z),小写字母(a-z),数字(0-9)以及+/(另一种标准是减号和下划线,可确保文件名和URL不违规);Base32中只有大写字母(A-Z)和数字234567;Base16中只有数字(0-9)和字母(ABCDEF)。base64编码后字符串长度肯定为4n,base32为8n,base16为2n。
base类的码表是可以换的,由此可以产生关于base的变形题。python字符串有一个translate方法,可以轻松解决字符双射替换。这里附上标准base64码表:
table1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
table2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234{}789+/'
'somestr'.translate(str.maketrans(table2, table1))
命题的时候可以对码表做一个简单的异或加密,能掩盖一下。
base58
base58与上面的base系列有较大的区别。
编码集不同,Base58 的编码集在 Base64 的字符集的基础上去掉了比较容易混淆的0(零)、O(大写字母 O)、I(大写字母i)、l(小写字母L),还有俩符号。
Base64 采用直接切割 bit 的方法(8->6),而 Base58 采用大数进制转换,效率更低,使用场景更少。
- ABD
- 65 66 68
- (65 x 256 + 66) x 256 + 68 = 4276804
- 4276804 = 21x(58x58x58) + 53x(58x58) + 20x58 + 0
- nVm1
从这个例子可以看出,base58将字符串视作一个大数,然后转为58进制。
算法特征上,就是需要传入字符串长度,代码中会涉及bigint。
中国剩余定理CRT
CRT最明显的作用是解一次同余式组。注意在mod N意义下解的唯一性。
CRT也可以用于问题的拆解,N有时很大,如果N能分解,想解决N下的同余问题,可以先求这个同余式组左边的a1、a2等。
扩展欧几里得
主要作用是求模逆。
AES相关
AES是块加密,感觉主要考的就是工作模式了。
工作模式共五种。
ECB各块独立。
CBC上一块密文会与当前块明文异或,也因此不能并行。
ECB和CBC要补成16字节的倍数,而以下三种模式密文长度等于明文长度。
CFB和OFB非常类似,都用到了移位寄存器。
CTR可以视为ECB的改进,差别在于AES加密的不是明文,而是计数器。可以认为是双方额外共享了一段强明文。
https://blog.csdn.net/jackone12347/article/details/122238274
U2FsdGVkX1 是AES或DES开头。
AES解题总结帖,感觉适合进阶。
https://blog.csdn.net/qq_51999772/article/details/124301715
仿射加密
a和b可以被视为密钥。仿射变换只加密英文。a应当与26互素,否则多个明文会映射到同一个密文,导致解密不唯一。
解题经验上来说,如果给了两个密钥,或者两个等长明密文序列但没给密钥,都有可能是仿射。
RSA、大数分解相关
低解密指数d攻击,代表者为维纳攻击。解题经验,e很大就可以试试。
低加密指数e攻击,代表者为hastad攻击。解题经验,e=3。
共模攻击,解题经验,n,e1,e2,c1,c2。
这位哥的RSA,有东西的。
https://blog.csdn.net/vhkjhwbs/article/details/101160822
pollard p-1分解smooth素数
https://ctftime.org/writeup/32914
已知ed,可以分解n。此处g为随机数,k=ed-1。
def divide_pq(e, d, n):
k = e*d - 1
while True:
g = random.randint(2, n-1)
t = k
while True:
if t % 2 != 0:
break
t = t // 2
x = pow(g, t, n)
if x > 1 and gmpy2.gcd(x-1, n) > 1:
p = gmpy2.gcd(x-1, n)
return p
ECC相关
不光是理论,sageMath的安装和使用也算是一个门槛。
ECC最常用的两个套路,大步小步法,Pohlig-Hellman。还有MOV攻击。
大步小步法应该是没有方法时的方法。Pohlig-Hellman适合素数域曲线中p-1可以分解为小素数乘积的情况(也就是smooth prime)。
这里放一个Pohlig-Hellman的模板。order()方法可以对点、曲线、数域调用。E.gens()可以输出生成元。E.points()可以输出所有点(三个数的意思分别是x,y,是否为无穷远)。E.random_point()可以随机取点。
M = 321926610273616650525824108536700279212350866647530622269302809271682737763257560088641
A = 31030867082543847272019450082250517880295729931851175197103022808416739451723625980005
B = 142049469222136951075696377209080640921370274261432456129295338173419592979392060338227
P = (61621803131408319635029766825719907867842946540104700557009765261727264962796229094568,90378017679706887143409145158264522897855153307452350004018644435133535912241818885072)
Q = (310437823810844905448242773133403586337899801305811045192960893487704024383375218773605, 304988667194749192663091131350248651649623530747853134775926969946385963577679155080700)
F = FiniteField(M)
E = EllipticCurve(F,[A,B])
P = E.point(P)
Q = E.point(Q)
factors, exponents = zip(*factor(P.order()))
primes = [factors[i] ^ exponents[i] for i in range(len(factors))][:-2]
dlogs = []
for fac in primes:
t = int(P.order()) // int(fac)
dlog = discrete_log(t*Q,t*P,operation="+")
dlogs += [dlog]
print("factor: "+str(fac)+", Discrete Log: "+str(dlog)) #calculates discrete logarithm for each prime order
l = crt(dlogs,primes)
print(l)
其它离散对数api:
#求解私钥,自动选择bsgs或Pohlig Hellman算法
discrete_log(K,G,operation='+')
#求解私钥,Pollard rho算法
discrete_log_rho(K,G,operation='+')
#求解私钥,Pollard Lambda算法,能够确定所求值在某一小范围时效率较高
discrete_log_lambda(K,G,(1500000,2000000),operation='+')
其它常见攻击:
超奇异椭圆曲线(阶整除p^k-1,k不小于2),MOV攻击(sample:Trick,cmcc-200421;keygreed,VolgaCTF 2020)
多组不同的(c,a,b,n)的四元组,对同一明文加密,低指数广播攻击
已知两个明文的差,相关密文攻击(sample:relatedmessage,dasctf-2020-12)
格相关
格可以用于攻击背包密码。
格可以用于coppersmith攻击。(coppersmith适用于RSA各种参数泄露(部分明文泄露,质数部分位泄露,等等)。
jsfuck
新建一个html文件,把这段代码放进去。然后把jsfuck的内容放进去,有多余的字符也没关系(网上的在线解码并不好用,不允许多余的字符)。然后用浏览器打开html即可看见解码结果。
<script>
function deEquation(str) {
for(let i = 0; i <= 1; i++) {
str = str.replace(/l\[(\D*?)](\+l|-l|==)/g, (m, a, b) => 'l[' + eval(a) + ']' + b);
}
str = str.replace(/==(\D*?)&&/g, (m, a) => '==' + eval(a) + '&&');
return str;
}
js="jsf**k"//将代码放入其中
res=deEquation(js);
document.write(res);
</script>
不常用hash盘点
较老的,RIPEMD,HAVAL,TIGER;
较新的,whirlpool。
SM3也算是吧。
不常用对称加密盘点
idea,international data encryption algorithm,也是一种对称加密算法。
blowfish、twofish。
safer+、safer++。
misty1、Camellia。
以上六个可以理解为AES当年的竞争者及后续发展。
SM4也算是。
签名算法盘点
RSA,ELgamal,DSA,椭圆曲线ECDSA,SM2。加密与解密一书中都有介绍。
CRC
循环冗余校验,最常用的校验。家族内有众多版本,区别在于多项式不同。校验位是数据位经多项式运算后的结果。实现时,判断最高位(最高位为符号位,所以if里可能是判断正负,也有可能是按位与运算)是左移还是异或、左移,每次异或的是同一个poly。也有打表实现的,根据输入的不同查crc表获得不同的结果。
chacha20
一种流密码,以字节为单位进行加密。初始矩阵中有常数、协商初始密钥、随机数、计数器。由初始矩阵生成密钥矩阵。
与poly1305消息认证码配合后作为谷歌标准。
tea、xtea、xxtea
首先是python2的xxtea,测试过可以用。
# coding=utf-8
import struct
_DELTA = 0x9E3779B9
def str_ljust(s, m, ch):
if m > len(s):
return s + ch * (m - len(s))
return s
def _long2str(v, w):
n = (len(v) - 1) << 2
if w:
m = v[-1]
if (m < n - 3) or (m > n): return ''
n = m
s = struct.pack('<%iL' % len(v), *v)
return s[0:n] if w else s
def _str2long(s, w):
n = len(s)
m = (4 - (n & 3) & 3) + n
s = s.ljust(m, "\0")
tup = struct.unpack('<%iL' % (m >> 2), s)
v = list(tup)
if w: v.append(n)
return v
def encrypt(str, key):
if str == '': return str
v = _str2long(str, True)
# k = _str2long(key.ljust(16, "\0"), False)
k = _str2long(str_ljust(key, 16, '\0'), False)
n = len(v) - 1
z = v[n]
y = v[0]
sum = 0
q = 6 + 52 // (n + 1)
while q > 0:
sum = (sum + _DELTA) & 0xffffffff
e = sum >> 2 & 3
for p in range(n):
y = v[p + 1]
v[p] = (v[p] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
z = v[p]
y = v[0]
v[n] = (v[n] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[n & 3 ^ e] ^ z))) & 0xffffffff
z = v[n]
q -= 1
return _long2str(v, False)
def decrypt(str, key):
if str == '': return str
v = _str2long(str, False)
# k = _str2long(key.ljust(16, "\0"), False)
k = _str2long(str_ljust(key, 16, '\0'), False)
n = len(v) - 1
z = v[n]
y = v[0]
q = 6 + 52 // (n + 1)
sum = (q * _DELTA) & 0xffffffff
while (sum != 0):
e = sum >> 2 & 3
for p in range(n, 0, -1):
z = v[p - 1]
v[p] = (v[p] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
y = v[p]
z = v[n]
v[0] = (v[0] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[0 & 3 ^ e] ^ z))) & 0xffffffff
y = v[0]
sum = (sum - _DELTA) & 0xffffffff
return _long2str(v, True)
if __name__ == "__main__":
key = '16bytelongstring'
enc = encrypt('Hello XXTEA!', key)
# print(enc)
dec = decrypt(enc, key)
print(dec)
key = 'flag' + '\x00' * 12
# 把od的字节复制出来,加上\x。
dec = decrypt('\xbc\xa5\xce\x40\xf4\xb2\xb2\xe7\xa9\x12\x9d\x12\xae\x10\xc8\x5b\x3d\xd7\x06\x1d\xdc\x70\xf8\xdc', key)
print dec
然后是未经测试的tea。
import struct
def Hex2Bytes(hexstr:str):
strBytes = hexstr.strip()
pkt = bytes.fromhex(strBytes)
return pkt
def Bytes2Hex(bin_: bytes):
return ''.join(['%02X ' % b for b in bin_])
class QQ_TEA():
"""QQ TEA 加解密, 64比特明码, 128比特密钥
这是一个确认线程安全的独立加密模块,使用时必须要有一个全局变量secret_key,要求大于等于16位
"""
def xor(self,a, b):
op = 0xffffffff
a1,a2 = struct.unpack(b'>LL', a[0:8])
b1,b2 = struct.unpack(b'>LL', b[0:8])
return struct.pack(b'>LL', ( a1 ^ b1) & op, ( a2 ^ b2) & op)
def code(self,v, k):
n=16
op = 0xffffffff
delta = 0x9e3779b9
k = struct.unpack(b'>LLLL', k[0:16])
y, z = struct.unpack(b'>LL', v[0:8])
s = 0
for i in range(n):
s += delta
y += (op &(z<<4))+ k[0] ^ z+ s ^ (op&(z>>5)) + k[1]
y &= op
z += (op &(y<<4))+ k[2] ^ y+ s ^ (op&(y>>5)) + k[3]
z &= op
r = struct.pack(b'>LL',y,z)
return r
def decipher(self,v, k):
n = 16
op = 0xffffffff
y, z = struct.unpack(b'>LL', v[0:8])
a, b, c, d = struct.unpack(b'>LLLL', k[0:16])
delta = 0x9E3779B9
s = (delta << 4)&op
for i in range(n):
z -= ((y<<4)+c) ^ (y+s) ^ ((y>>5) + d)
z &= op
y -= ((z<<4)+a) ^ (z+s) ^ ((z>>5) + b)
y &= op
s -= delta
s &= op
return struct.pack(b'>LL', y, z)
def encrypt(self,v):
END_CHAR = b'\0'
FILL_N_OR = 0xF8
vl = len(v)
filln = (8-(vl+2))%8 + 2
fills = b''
for i in range(filln):
fills = fills + bytes([220])
v = ( bytes([(filln -2)|FILL_N_OR])
+ fills
+ v
+ END_CHAR * 7)
tr = b'\0'*8
to = b'\0'*8
r = b''
o = b'\0' * 8
for i in range(0, len(v), 8):
o = self.xor(v[i:i+8], tr)
tr = self.xor( self.code(o, secret_key), to)
to = o
r += tr
return r
def decrypt(self,v):
l = len(v)
prePlain = self.decipher(v, secret_key)
pos = (prePlain[0] & 0x07) +2
r = prePlain
preCrypt = v[0:8]
for i in range(8, l, 8):
x = self.xor(self.decipher(self.xor(v[i:i+8], prePlain),secret_key ), preCrypt)
prePlain = self.xor(x, preCrypt)
preCrypt = v[i:i+8]
r += x
if r[-7:] != b'\0'*7:
return None
return r[pos+1:-7]
if __name__ == '__main__':
global secret_key
secret_key = Hex2Bytes('11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11')
print(Bytes2Hex(secret_key))
QQ = QQ_TEA()
plaintext = '哈哈哈hhh,'
plaintext = bytes(plaintext,encoding = "utf-8")
enc = QQ.encrypt(plaintext)
print(Bytes2Hex(enc))
dec = QQ.decrypt(enc)
print(Bytes2Hex(dec))
找到了在线的tea和xtea。
http://www.atoolbox.net/Tool.php?Id=861
http://tool.chacuo.net/cryptxtea
解题经验
打过的比赛不多,不过已经见到好几次与服务端交互的题目了。这种题一般是提供加解密或者签名的服务,但对服务做出一些限制。比如说,加密/签名之前,或者解密/验签之后,它会先看看明文内容是什么,再决定要不要将信息返回给用户,或者执行用户的命令。有的则是限制服务使用次数。
加密机类的题目,也可以考虑类似时间盲注的timing attack。
如果是黑名单的限制,那可以考虑用多次请求来推测非法请求的结果。比如说aes的字节翻转攻击。
如果是白名单的限制,还没想到怎么办。
附录
常用哈希算法输出长度
算法 | 输出长度bit | 输出字符 |
---|---|---|
MD5 | 128 | 32 |
SHA-1 | 160 | 40 |
SHA-256 | 256 | 64 |
SHA-2其它同理 |
MD5 加密结果字符串长度有两种:16 位与 32 位。默认使用32位。16 位实际上是从 32 位字符串中取中间的第 9 位到第 24 位的部分。
yafu指令
yafu-x64 “factor(@)” -batchfile p.txt
一些可能无助于解题的
使得an=e的最小正整数n称为a的阶。
求出循环群的阶的因子,就能求出子群的阶。
知道一个生成元,就能求出所有生成元(求该生成元的i次方,i为所有与总群阶互素的数)。
知道一个生成元,可以用构造法表示出子群的生成元,从而证明循环群的子群都是循环群。