Crypto
(話說題目做一半就當答案是什麼鬼)
Crypto-bacon
題目
flag{AAAABAAAAAAAABAABBBAABBABABAAABAABAAAABBAABAAABABBABAAAAAABAABAAAABBBABABAABAABA}
解答
簡單的培根密碼,略
Crypto-黃金分割RSA
題目
encryption
[1,28657,2,1,3,17711,5,8,13,21,46368,75025,34,55,89,610,377,144,233,1597,2584,4181,6765,10946,987]
output
publickey=[0x1d42aea2879f2e44dea5a13ae3465277b06749ce9059fd8b7b4b560cd861f99144d0775ffffffffffff,5]
c=421363015174981309103786520626603807427915973516427836319727073378790974986429057810159449046489151
解答
(本題爲2020 GACTF-da Vinci after rsa,然後河南天安杯又考一遍,這是第三遍)
首先使用yafu分解n,得到三個質因數
注意到e爲5,與其中的q = P17-1和r = P79-1均不互質,所以需要嘗試另外的解法。假設明文爲m,那麼m應該滿足
m^5 = c (mod p)
m^5 = c (mod q)
m^5 = c (mod r)
在線上賽的情況下,我們可以利用網站www.wolframalpha.com求解m模p、q、r的可能值,並利用中國剩餘定理求出m的所有可能值,進而判斷其中哪一個是flag。
p_roots = [7361]
q_roots = [2722510300825886, 6139772527803903, 6537111956662153, 8415400986072042, 9898464751509789]
r_roots = [180966415225632465120208272366108475667934082405238808958048294287011243645, 2816114411493328258682873357893989007684496552202823306045771363205185148674391, 1369135259891793292334345751773139388112378132927363770631732500241630990458667, 5570877862584063114417410584640901580756179707042774516590562822938385811269597, 8499052407588078002885931765166137308397074232361087682974448633946350539292222]
寫出解題代碼如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from Crypto.Util.number import *
import gmpy2
def GCRT(mi, ai):
assert (isinstance(mi, list) and isinstance(ai, list))
curm, cura = mi[0], ai[0]
for (m, a) in zip(mi[1:], ai[1:]):
d = gmpy2.gcd(curm, m)
c = a - cura
assert (c % d == 0) # 不成立則不存在解
K = c / d * gmpy2.invert(curm / d, m / d)
cura += curm * K
curm = curm * m / d
return (cura % curm, curm) # (解,最小公倍數)
n = 0x1d42aea2879f2e44dea5a13ae3465277b06749ce9059fd8b7b4b560cd861f99144d0775ffffffffffff
c = 421363015174981309103786520626603807427915973516427836319727073378790974986429057810159449046489151
p = 9749
q = 11237753507624591
r = n / p / q
e = 5
p_roots = [7361]
q_roots = [2722510300825886, 6139772527803903, 6537111956662153, 8415400986072042, 9898464751509789]
r_roots = [180966415225632465120208272366108475667934082405238808958048294287011243645, 2816114411493328258682873357893989007684496552202823306045771363205185148674391, 1369135259891793292334345751773139388112378132927363770631732500241630990458667, 5570877862584063114417410584640901580756179707042774516590562822938385811269597, 8499052407588078002885931765166137308397074232361087682974448633946350539292222]
m_list = []
for pp in p_roots:
for qq in q_roots:
for rr in r_roots:
res = GCRT([p, q, r], [pp, qq, rr])[0]
if pow(res, e, n) == c:
print long_to_bytes(res)
得到一個字符串flag{weadfa9987_adwd23123_454f},但是我們還有一個條件沒有使用,即一串數
[1,28657,2,1,3,17711,5,8,13,21,46368,75025,34,55,89,610,377,144,233,1597,2584,4181,6765,10946,987]
仔細觀察發現,這些數就是斐波那契數列的一個排列,且其總數爲25,恰好與flag{}內的字符數相同,因此 將字符按照同樣方式打亂後 可以得到真實flag。
(不要問我爲什麼原題需要按加密方式打亂而不是按照加密方式進行恢復,問GACTF出題人)
Crypto-Corrupted Keys
題目
ciphertext.txt
c = 0x6f9c3479883b414030032610a0831089ea2d5f6598d16f8b3415dbb7ff88e6214c7704dbaf1f0f0fe8243468b203b0c128933ab45f406109d234ab94457aa4ff81de3e0c1dda55b95344683e7cfef4e39dedd1203120af89e14702ac54a1a21adb500dadc67033deb2dcf844aa10c5b6425aca0a756ee5e5ce5b583de68d7dfa675b8142c4b175b347bd1c3b2d2cd32aa2e03356ecf4821704d7b7542a22d09ebb239e382fc5b72ea051b65596e41d228fb7b0f7acf5686d05b8d6807a26c1a1d92c8b116c6f27e2b21ded5f1f3b8f9a88e45ec7b14aee18e74454fefb1a482a9eafc9550d16f6683e2f7cbd0d9ce9a474f4db01e2f97d0d3d23fad566489e1e
private_key.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAANAL4ECQAIAAsACAUJBfAA0NIAADdwAIAGAAAAALoAAAAABw
PwBQAAAAnwAAcP4NAAE3QFAAcJMKwJbGANcAADCjDhC7DgAAANIAMAoAXgAAMQAA
AOAAIAYQIFEJAAgCYA8GwAHgAw1KcFhAAHzwAKAAAAA2BgMAYEBZIDcnAAEMMAAE
QcAAUVfQAD3VEABAsQAAAAAMAAegilfw0A8ALWABBAIKAAAAAIQAwApwkArfyDwG
ALAAAAQA9FBAoBqJAKAAgKUABzOQawlnoGAgUMYAoAAAACAocAHowABAAwUHAJCy
AgUFEA4AAAA0AIAOAOAAjwDADQDUeAAKIA7DAAIDAQABAoIBAAAAAAAAAAAADgAA
APvgAAEAAAAAAAAAAAAACgAAhAAAAABgAAAJAAAAAAAAAAAADAAAAA0AAAAA4A4A
AAAAAGAAAAAAAEAAAAAAAAAAAAAAAAAACwAAgAAAAAIAAAAA0AAAAAAAABAACBAA
AA8AAAAABgAAkAAAANAAAAAABgAAAAAAwAAIAAAAAADgAAAAAAAAAAADAACAAAAA
AAAAAAAAAACgAAAAAAAAAAAAAABgABAAAAAAsAACAAgAAAAAAAAAAABAAAAwcACQ
AAAOAN0AAAEHAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAQAAAAAAAAAAAAAA
AAAFAAACgYEAAAAAAAAQAAAAAAAAAAAAAFAAAAAAgAMAcACgAKAAAArAAAAAAAAA
AAAAAAAAAACQADAAAAAAAAAAAAAACwAAAACwAAgAAAAAAAANAAAAAAAAAAAAAAAg
AAAAAAAMAGAAAAAAAAAAsAAAAQACAAADAAAAAAAAAAAFAAAAAAAAAAACgYEAAACw
AKAAAAAA0AAAEAAHAAAAAAAAAAAPQAAAAAAAAAAAACAAAA4AALMAAAAABQAAoAAA
AAAAAAAAAABQAAAAAKAOAAACAAAAAAAAAAwIAAAAAA4AMAAAAAAAAGQAAGAAAAAA
AAADAACAAAAJAAAAUAUAAAAAAAAAAAAAAAAAAAACgYAFOW7M5UcyswjtKNXo783B
hDUPHTPG49nzxsU33eLi8pxZ6hFaFPaEO8NBkBHMqPI6lPn/wuisNQvWna5igQEA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAg0Fr7+S3oAktME9GMkvPYiqk1qeDYLZX
1mkrXy1v2aJ74q+J41UdYwKBgAAg8QsAAACgBQCQAAAAAAAAAgAAqQAABgDQAAAA
AAAAAAAAcAoAAAABAAwAAAAwBgAAsw0gCAAAAAAAAAgAAAAA8AAAAAAAAEDwAAAA
AACgAAAAAAAGAAAAAAAACVAAUAAAAADgAAAAAAAAkAAAgAAKAAAAAAALAAAAAADQ
5AAAAoGBAAAAAAAAoAUA0AwCDwAABADJAAAAAHAAAAAAAADgAAAAAAAAAAAAAI8A
AAYPAMAAADAAAAIAAAAACQAAgAAAoAcAAAAAAAAAAAAAAAAADAAAAAAA0AAAAAAZ
......
public_key.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjdPL4kqVHoXVu7GAWpZf
5y0NKjCDdwV4aWkRnGBLrlY+fI56P8hcFI3hn6Bfff4dkiE3Q18MeZMaxpbGI9cU
zjqjXhi7Dg8gJtLrORoNXt4PMa9dF+P/L3YRIFH5RhgSa69WwSHtg91KelhLV3zy
VqhtWf42lkNAaEJZJjcnaAFcPLM0QcBQUVfXfj3VGcpBsVdcPmMMMRepilf63I+b
LWcRJLKqqeVHx4SgxFp0nzrfyDw2croQFFSv9F5ApRqJMqwMh6VzZzOWa0lnq2gv
U8ZlohOU7C8oezHoymxDIyVHDZCyAgUFFU6ciic0AYA+QeCCj6nF/fLUeJEaI27D
8QIDAQAB
-----END PUBLIC KEY-----
解答
這道題本身設置了兩個考點:一是如何從殘缺的私鑰中提出信息,二是如何利用中間被挖去一段的dp來分解n。前者參見前兩年0ctf的一道題,後者參見今年11月遼寧祥雲杯。但是這道題私鑰直接給全了,導致使用openssl命令直接可以提出數據,第一個考點直接失效。應該如同上邊一樣給出。
開始解體。首先從公鑰中提出n和e
openssl rsa -in public_key.pem -pubin -modulus -text
但是僅有n、e、c是無法做題的,因爲n不能直接分解,所以我們還需要看看私鑰給我們留下了什麼。根據資料,我們得知RSA的私鑰通常以PKCS#1的模式進行存儲,簡單地說如下所示:
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
我們將現在已經被污染的私鑰base64解碼後,按照上邊的格式展開,得到如下內容:
308204a3020100
0282010100
(n)00d00be04090008000b0008050905f000d0d20000377000800600000000ba000000000703f00500000009f000070fe0d00013740500070930ac096c600d7000030a30e10bb0e000000d200300a005e000031000000e000200610205109000802600f06c001e0030d4a705840007cf000a00000003606030060405920372700010c30000441c0005157d0003dd5100040b1000000000c0007a08a57f0d00f002d600104020a000000008400c00a70900adfc83c0600b000000400f45040a01a8900a00080a5000733906b0967a0602050c600a000000020287001e8c000400305070090b2020505100e0000003400800e00e0008f00c00d00d478000a200ec300
0203
(e)010001
02820100
(d)00000000000000000e000000fbe00001000000000000000000000a00008400000000600000090000000000000000000c0000000d00000000e00e000000000060000000000040000000000000000000000000000b0000800000000200000000d00000000000001000081000000f0000000006000090000000d000000000060000000000c000080000000000e00000000000000000030000800000000000000000000000a0000000000000000000000060001000000000b0000200080000000000000000004000003070009000000e00dd000001070000000000000000000000000000000000007000000000000000000100000000000000000000000000050000
02818100
(p)0000000000100000000000000000000050000000008003007000a000a000000ac00000000000000000000000000000900030000000000000000000000b00000000b000080000000000000d00000000000000000000002000000000000c006000000000000000b000000100020000030000000000000000050000000000000000
02818100
(q)0000b000a000000000d0000010000700000000000000000f400000000000000000002000000e0000b300000000050000a0000000000000000000005000000000a00e000002000000000000000c08000000000e003000000000000064000060000000000000030000800000090000005005000000000000000000000000000000
028180
(dp)05396ecce54732b308ed28d5e8efcdc184350f1d33c6e3d9f3c6c537dde2e2f29c59ea115a14f6843bc3419011cca8f23a94f9ffc2e8ac350bd69dae6281010000000000000000000000000000000000000000000000000083416befe4b7a0092d304f46324bcf622aa4d6a78360b657d6692b5f2d6fd9a27be2af89e3551d63
028180
(dq)0020f10b000000a0050090000000000000020000a900000600d0000000000000000000700a00000001000c00000030060000b30d20080000000000000800000000f000000000000040f00000000000a00000000000060000000000000950005000000000e000000000000090000080000a00000000000b0000000000d0e40000
02818100
(coeff)0000000000a00500d00c020f00000400c90000000070000000000000e0000000000000000000008f0000060f00c000003000000200000000090000800000a007000000000000000000000000000c0000000000d00000000019(殘缺)0
可以看到,除了dp以外其他內容基本已經無法使用,dp只缺少其中的200位,因此可以嘗試使用coppersmith方法去解。由於coppersmith方法需要未知變量係數爲1,我們這裏嘗試推導一下:
我們知道
由於dp <= p-1,所以k<e,coppersmith方法通過遍歷1~e尋找k。現在假設k已知,那麼有
設
帶入原式得
這樣就構造出了係數爲1的同餘方程,可以使用coppersmith方法解題了。解題腳本如下:
from sage.all import *
from Crypto.Util.number import long_to_bytes
n = 0x8dd3cbe24a951e85d5bbb1805a965fe72d0d2a30837705786969119c604bae563e7c8e7a3fc85c148de19fa05f7dfe1d922137435f0c79931ac696c623d714ce3aa35e18bb0e0f2026d2eb391a0d5ede0f31af5d17e3ff2f76112051f94618126baf56c121ed83dd4a7a584b577cf256a86d59fe3696434068425926372768015c3cb33441c0505157d77e3dd519ca41b1575c3e630c3117a98a57fadc8f9b2d671124b2aaa9e547c784a0c45a749f3adfc83c3672ba101454aff45e40a51a8932ac0c87a5736733966b4967ab682f53c665a21394ec2f287b31e8ca6c432325470d90b2020505154e9c8a273401803e41e0828fa9c5fdf2d478911a236ec3f1
e = 0x10001
c = 0x6f9c3479883b414030032610a0831089ea2d5f6598d16f8b3415dbb7ff88e6214c7704dbaf1f0f0fe8243468b203b0c128933ab45f406109d234ab94457aa4ff81de3e0c1dda55b95344683e7cfef4e39dedd1203120af89e14702ac54a1a21adb500dadc67033deb2dcf844aa10c5b6425aca0a756ee5e5ce5b583de68d7dfa675b8142c4b175b347bd1c3b2d2cd32aa2e03356ecf4821704d7b7542a22d09ebb239e382fc5b72ea051b65596e41d228fb7b0f7acf5686d05b8d6807a26c1a1d92c8b116c6f27e2b21ded5f1f3b8f9a88e45ec7b14aee18e74454fefb1a482a9eafc9550d16f6683e2f7cbd0d9ce9a474f4db01e2f97d0d3d23fad566489e1e
s1 = 0x5396ecce54732b308ed28d5e8efcdc184350f1d33c6e3d9f3c6c537dde2e2f29c59ea115a14f6843bc3419011cca8f23a94f9ffc2e8ac350bd69dae628101
s2 = 0x83416befe4b7a0092d304f46324bcf622aa4d6a78360b657d6692b5f2d6fd9a27be2af89e3551d63
invE = 6072463480052222774484440188940687848932613396747764165170539263002801068323119971186434001223564329479603123138991852830747459341281298547464945329258352716978884436175214634055317578153997659440696241077620973159496044408967363538897046549076241323642446031523788310065048047069216086916780671758259593054935742629841385169465939611015008003189747360588471082992102089514769381157672791863436498000591096254007370955960943534440348460456861334522885919455014210659853726203177705141103038754371809891816964608801118434914236230355247754146610175756211785759304159230507837819605093784920450130362742047348692105621
invpow = 11164431502070747511500530021745510906685334856887665721011597986932079197780161963381238394880452910362678853293436976134225721195690734856639343780389327693213031686657799035703254166242337351340806493525437022390070372393198894731543660124496416698434549412871378260095086278600202820500790434411495555569397376528832359674292547896319750287821110784519344030510311252066880377461777551185303592001846300617993580196858607671355081397538545831351265009506760347727474227285668988817907365295771630760269871647111667154970535637451277321913843646237831493800721780046762737829851933071451680344164843758329746830379
def coppersmith(k):
F.<x> = PolynomialRing(Zmod(n))
f = (s1 << 520) * invpow + x + s2 * invpow + (k - 1) * invE * invpow # make monic
x0 = f.small_roots(X=2 ** 200, beta=0.44, epsilon=1/32)
return x0
for k in range(1, e):
print k
x0 = coppersmith(k)
if len(x0) != 0:
x = Integer(x0[0])
dp = (s1 << 520) + (x << 320) + s2
p = (e*dp - 1) // k + 1
if p != -1:
q = n // p
assert n == p * q
phi = (p-1)*(q-1)
d = inverse_mod(e,phi)
print d
print long_to_bytes(pow(c,d,n))
break
其中invE、invpow分別是計算好的invert(e, n)和invert(2 ** 320,n),最終求出k=1733。
Crypto-strange_GSW
題目
strange_GSW.sage
from Crypto.Util.number import *
from random import randrange
from hashlib import md5
from secret import FLAG
def GenerateG(_n, _q):
_len = int(round(log(_q, 2)))
_G = Matrix(ZZ, _len * _n, _n)
for i in range(_len):
for j in range(_n):
_G[j * _len + i, j] = 2 ** i
return _G
def BinaryExpansion(_C, _q):
expansion_C = Matrix(ZZ, _C.nrows(), _C.nrows())
log_q = _C.nrows() // _C.ncols()
for i in range(_C.nrows()):
for j in range(_C.ncols()):
bits = bin(_C[i, j] % _q)[2:].rjust(log_q, '0')[::-1]
for index in range(log_q):
expansion_C[i, j * log_q + index] = int(bits[index])
return expansion_C
def KeyGen(degree, _q, _B):
s = random_matrix(ZZ, degree, 1, x=_q // 4, y=4 * _q // 3)
return Matrix(s.list() + [-1]).transpose(), s
def BitEncrypt(plain_bit, _n, _q, _B, _G, _s):
_m = int(round(log(_q, 2)) * _n)
A = random_matrix(ZZ, _m, _n - 1, x=0, y=_q)
e = random_matrix(ZZ, _m, 1, x=0, y=_B)
_C = block_matrix([A, A * _s + e], ncols=2, subdivide=False) + plain_bit * _G
_C = BinaryExpansion(_C, _q)
return _C
def Encrypt(plain, _n, _q, _B, _G, _s):
plain_bits = bin(plain)[2:]
Cipher = []
for bit in plain_bits:
Cipher.append(BitEncrypt(int(bit), _n, _q, _B, _G, _s))
return Cipher
if __name__ == '__main__':
n = 11
q = 65537
B = q // 256
G = GenerateG(n, q)
decrypt_key, encrypt_key = KeyGen(n - 1, q, B)
flag = FLAG + md5(long_to_bytes(randrange(2 ** 127, 2 ** 128))).hexdigest().encode()[:8] + b'}'
_cipher = Encrypt(bytes_to_long(flag), n, q, B, G, encrypt_key)
str_cipher = ' '.join([str(i.list()) for i in _cipher])
with open('flag', 'wb') as f:
f.write(flag)
with open('cipher', 'w') as f:
f.write(str_cipher)
解答
(比賽好像沒人做出來,確實非常費勁)
題目看起來比較複雜,所以我們跟着程序走,看看具體流程是什麼樣的。首先看矩陣G
可知矩陣G是一個固定的矩陣。然後生成一組加密密鑰和解密密鑰看看
可以看到加密密鑰爲一個10 * 1的矩陣,其中每項的值在(q/4, q*4/3)之間,q = 65537。解密密鑰就比加密密鑰多一個-1。
最後看加密過程,首先將密文轉爲整數,再轉爲二進制,然後對於二進制表示的每一位(0或1)進行BitEncrypt加密操作,將這個0或者1加密成一個176 * 176的由0和1構造成的矩陣。加密的過程中參數生成隨機,所以相同明文加密結果不固定。加密生成的_C矩陣爲一個176 * 11的矩陣,再經過BinaryExpansion函數擴展到一個176 * 176的矩陣。這個擴展很簡單,就是把這些數每一個按照其二進制表示橫向展開成16位,然後倒序寫入,因此我們可以簡單將其收起得到_C。
def recoverMatrix(text):
text = text.replace(',','').replace(' ','').replace('[','')
Mdata = []
for i in range(0, len(text), 16):
Mdata.append(int(text[i:i+16][::-1],2))
_C = Matrix(ZZ, 176, 11, Mdata)
return _C
在得到_C之後,我們來嘗試求解key。_C的結構是矩陣A拼接一列A * _s + e的結果,其中e是0~256以內的隨機值,再根據明文該位置是0還是1來決定是否加上矩陣_G。考慮到大部分參數取值範圍在(q/4, q*4/3)之間,因此e的(0, 256)區間可以視爲小值,可以將本題當作一個格上的最近向量問題(CVP, Closet vector problem)進行求解。對明文首位進行求解時,針對結果可能爲1和0的兩種情況,求出的_C矩陣需要減去_G 或者 不發生變化。當存在解時,首位明文正確,且求解出的key就是加密所用key,後續每個明文字符直接用該key還原即可。完整解題代碼如下:
from sage.modules.free_module_integer import IntegerLattice
from Crypto.Util.number import long_to_bytes
def GenerateG(_n, _q):
_len = int(round(log(_q, 2)))
_G = Matrix(ZZ, _len * _n, _n)
for i in range(_len):
for j in range(_n):
_G[j * _len + i, j] = 2 ** i
return _G
def recoverMatrix(text):
text = text.replace(',','').replace(' ','').replace('[','')
Mdata = []
for i in range(0, len(text), 16):
Mdata.append(int(text[i:i+16][::-1],2))
_C = Matrix(ZZ, 176, 11, Mdata)
return _C
def CVP(lattice, target):
gram = lattice.gram_schmidt()[0]
t = target
for i in reversed(range(lattice.nrows())):
c = ((t * gram[i]) / (gram[i] * gram[i])).round()
t -= lattice[i] * c
return target - t
def BitDecrypt(_C, _G, _s, _q):
_C = _C * _G * _s
return 0 if abs(_C.list()[0] % _q - _q) < _q // 4 else 1
def Decrypt(Cipher, _G, _s, _q):
plain_bits = ''
for cipher in Cipher:
plain_bits += str(BitDecrypt(cipher, _G, _s, _q))
return int(plain_bits, 2)
if __name__ == '__main__':
n = 11
q = 65537
B = q // 256
G = GenerateG(n, q)
with open('cipher', 'r') as f:
cipher_str = f.read()
cipher_str = cipher_str.split(']')[:-1]
# Store Ciphertext
_cipher_str = [eval(i.strip()+']') for i in cipher_str]
_cipher_matrix = [Matrix(176, 176, i) for i in _cipher_str]
# Recover Matrix _C
_C = recoverMatrix(str(cipher_str[0]))
_C = _C - G
_A = _C[:176, :10]
res = _C[:176, 10:].list()
# Make a Matrix for CVP
M = Matrix(ZZ, 186, 176)
for i in range(176):
for j in range(10):
M[176 + j, i] = int(_A[i][j])
M[i, i] = 65537
lattice = IntegerLattice(M, lll_reduce=True)
target = vector(ZZ, res)
res = CVP(lattice.reduced_basis, target)
# Recover Key
R = IntegerModRing(65537)
M = Matrix(R, _A[:176])
key = M.solve_right(res)
key = [int(i) for i in key]
print key
# get flag
flag = long_to_bytes(Decrypt(_cipher_matrix, G, Matrix(key + [-1]).transpose(), q))
print(flag)
Crypto-random_fault
(我真不會國密。。。)