手把手教你攻克CTF密碼學

本文章主要是詳細的講解如何合理選擇工具或者python的三方庫來解決常見的CTF中密碼學相關問題,針對偏門或者高深的密碼學知識點,作者表示也無能爲力!

RSA

現在 RSA 在 CTF 比賽中一般作爲簡單題甚至簽到題出現!如果你還不瞭解 RSA,那機會來了,趕緊跟着來學一學怎麼解 RSA 這紙老虎!

分解大整數

RSA 中常用的運算有三個:分解大整數,求逆元以及模冪運算。一般分解大整數有三個常用的工具 factordbmsieve 以及 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 的庫有兩個 libnumgmpy2,這兩個庫建議在 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 攻擊,源碼在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('<IHb',a,b,c)
print unpack('<IHb','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') 函數

END

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章