ctf crypto回顾总结

环境配置相关

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输出字符
MD512832
SHA-116040
SHA-25625664
SHA-2其它同理

MD5 加密结果字符串长度有两种:16 位与 32 位。默认使用32位。16 位实际上是从 32 位字符串中取中间的第 9 位到第 24 位的部分。

yafu指令

yafu-x64 “factor(@)” -batchfile p.txt

一些可能无助于解题的

使得an=e的最小正整数n称为a的阶。
求出循环群的阶的因子,就能求出子群的阶。
知道一个生成元,就能求出所有生成元(求该生成元的i次方,i为所有与总群阶互素的数)。
知道一个生成元,可以用构造法表示出子群的生成元,从而证明循环群的子群都是循环群。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值