WEEK 3

NSSCTF Round#19 Basic

一.超级简单密码

题目:

from Crypto.Util.number import *
import gmpy2
from functools import reduce
from secret import flag

p = getPrime(1024)
i = 0
while True:
    r = p * 5 + i
    if isPrime(r):
        i = 0
        break
    else:
        i += 1
while True:
    q = p * 10 + i
    if isPrime(q):
        break
    else:
        i += 1

n = p * q * r
e = 65537
c = pow(bytes_to_long(flag.encode()), e, n)
print('c=' + str(c))
print('p3=' + str(pow(p, 3, n)))
print('q3=' + str(pow(q, 3, n)))
print('r3=' + str(pow(r, 3, n)))
# n = 44571911854174527304485400947383944661319242813524818888269963870884859557542264803774212076803157466539443358890313286282067621989609252352994203884813364011659788234277369629312571477760818634118449563652776213438461157699447304292906151410018017960605868035069246651843561595572415595568705784173761441087845248621463389786351743200696279604003824362262237505386409700329605140703782099240992158439201646344692107831931849079888757310523663310273856448713786678014221779214444879454790399990056124051739535141631564534546955444505648933134838799753362350266884682987713823886338789502396879543498267617432600351655901149380496067582237899323865338094444822339890783781705936546257971766978222763417870606459677496796373799679580683317833001077683871698246143179166277232084089913202832193540581401453311842960318036078745448783370048914350299341586452159634173821890439194014264891549345881324015485910286021846721593668473
# c = 11212699652154912414419576042130573737460880175860430868241856564678915039929479534373946033032215673944727767507831028500814261134142245577246925294110977629353584372842303558820509861245550773062016272543030477733653059813274587939179134498599049035104941393508776333632172797303569396612594631646093552388772109708942113683783815011735472088985078464550997064595366458370527490791625688389950370254858619018250060982532954113416688720602160768503752410505420577683484807166966007396618297253478916176712265476128018816694458551219452105277131141962052020824990732525958682439071443399050470856132519918853636638476540689226313542250551212688215822543717035669764276377536087788514506366740244284790716170847347643593400673746020474777085815046098314460862593936684624708574116108322520985637474375038848494466480630236867228454838428542365166285156741433845949358227546683144341695680712263215773807461091898003011630162481
# p3 = 891438237083490546089708018947678893226384856270496377765399277417697191150845296075484241536063149330788867177806265725641352439792185047059884077696267280233195764685547392586251429555216372682368991273055524268769223153988946085858123028200360359212117360701384933036871231911448311911374115683475228820531478240539549424647154342506853356292956506486091063660095505979187297020928573605860329881982122478494944846700224611808246427660214535971723459345029873385956677292979041143593821672034573140001092625650099257402018634684516092489263998517027205660003413512870074652126328536906790020794659204007921147300771594986038917179253827432120018857213350120695302091483756021206199805521083496979628811676116525321724267588515105188480380865374667274442027086789352802613365511142499668793725505110436809024171752137883546327359935102833441492430652019931999144063825010678766130335038975376834579129516127516820037383067
# q3 = 44571911854174527304485400947383944661319242813524818888269963870884859557542264803774212076803157466539443358890313286282067621989609252352994203884813364011659788234277369629312571477760818634118449563652776213438461157699447304292906151410018017960605868035069246651843561595572415595568705784173761440671033435053531971051698504592848580356684103015611323747688216493729331061402058160819388999663041629882482138465124920580049057123360829897432472221079140360215664537272316836767039948368780837985855835419681893347839311156887660438769948501100287062738217966360434291369179859862550767272985972263442512061098317471708987686120577904202391381040801620069987103931326500146536990700234262413595295698193570184681785854277656410199477649697026112650581343325348837547631237627207304757407395388155701341044939408589591213693329516396531103489233367665983149963665364824119870832353269655933102900004362236232825539480774
# r3 = 22285955927087263652242700473691972330659621406762409444134981935442429778771132401887106038401578733269721679445156643141033810994804626176497101942406682005829894117138684814656285738880409317059224781826388106719230578849723652146453075705009008980302934017534623325921780797786207797784352892086880720749202442492937918619992591614713131681306874944356693778359565004415437554407990089293135634916859631279984463829118336826115430997439527110961309956466956650522900331263720500751112297418506140413317489683875995326726992533904683800042127871963320754241310699432792081707870167598822650064976439270556418985242630368723264289700246406905189810458354474959276748887369363592834205660349184660073395182450526542246354364903399132116153732074081050985584216815493617906868615192465631416955706457835185743023758573279838341229835613609332206338401219168119635681832981552328638132500079074010106995297184587143613134093145

根据题目给的已知信息可知p3,q3,r3是开三次方再模,根据给出的代码,我们可以利用立方根运算来求解 p、q 和 r 的值,所以直接反着来

from Crypto.Util.number import is_square

p3 = pow(p, 3, n)
q3 = pow(q, 3, n)
r3 = pow(r, 3, n)

# 求解 p
p_candidates = []
for i in range(3):
    candidate = gmpy2.iroot(p3 + i * n, 3)
    if candidate[1]:
        p_candidates.append(candidate[0])

# 求解 q
q_candidates = []
for i in range(3):
    candidate = gmpy2.iroot(q3 + i * n, 3)
    if candidate[1]:
        q_candidates.append(candidate[0])

# 求解 r
r_candidates = []
for i in range(3):
    candidate = gmpy2.iroot(r3 + i * n, 3)
    if candidate[1]:
        r_candidates.append(candidate[0])

# 输出结果
print("p candidates:", p_candidates)
print("q candidates:", q_candidates)
print("r candidates:", r_candidates)

在这段代码中,我们先计算 p、q 和 r 的立方模 n 的结果,并使用 gmpy2.iroot() 函数来进行立方根运算,即可求出 p,q,r 的值

from Crypto.Util.number import *


p = 96241803526087516516438618680574139229212699224895199026126947479609515703069904259770933066463243844738712136916991719874179296797623802919752542053959297743706931240798992583332970879091497936378700193716012227086531507335444090574605921869576355704757589370608232721639204280020820678250787751406162350723
r = 481209017630437582582193093402870696146063496124475995130634737398047578515349521298854665332316219223693560684584958599370896483988119014598762710269796488718534656203994962916664854395457489681893500968580061135432657536677220452873029609347881778523787946853041163608196021400104103391253938757030811754131
e =  65537
c = 11212699652154912414419576042130573737460880175860430868241856564678915039929479534373946033032215673944727767507831028500814261134142245577246925294110977629353584372842303558820509861245550773062016272543030477733653059813274587939179134498599049035104941393508776333632172797303569396612594631646093552388772109708942113683783815011735472088985078464550997064595366458370527490791625688389950370254858619018250060982532954113416688720602160768503752410505420577683484807166966007396618297253478916176712265476128018816694458551219452105277131141962052020824990732525958682439071443399050470856132519918853636638476540689226313542250551212688215822543717035669764276377536087788514506366740244284790716170847347643593400673746020474777085815046098314460862593936684624708574116108322520985637474375038848494466480630236867228454838428542365166285156741433845949358227546683144341695680712263215773807461091898003011630162481
n = 44571911854174527304485400947383944661319242813524818888269963870884859557542264803774212076803157466539443358890313286282067621989609252352994203884813364011659788234277369629312571477760818634118449563652776213438461157699447304292906151410018017960605868035069246651843561595572415595568705784173761441087845248621463389786351743200696279604003824362262237505386409700329605140703782099240992158439201646344692107831931849079888757310523663310273856448713786678014221779214444879454790399990056124051739535141631564534546955444505648933134838799753362350266884682987713823886338789502396879543498267617432600351655901149380496067582237899323865338094444822339890783781705936546257971766978222763417870606459677496796373799679580683317833001077683871698246143179166277232084089913202832193540581401453311842960318036078745448783370048914350299341586452159634173821890439194014264891549345881324015485910286021846721593668473

n = p*r

#欧拉函数
phi_n=(p-1)*(r-1)
#求逆元
d=inverse(e,phi_n)
print(d)

m=c^d%n
m=pow(c,d,n)

print(long_to_bytes(m))

最后运行以上的RSA解密代码(欧拉函数变形)即可求出flag

二.简单密码

题目:

from random import randint
from secret import flag, enc_key

dir = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}"
assert len(flag) == 64
assert len(enc_key) == 64


def getGraph(row, column):
    graph = [['' for _ in range(row)] for _ in range(column)]
    for i in range(column):
        for j in range(row):
            graph[i][j] = dir[randint(0, 63)]
    return graph


def bestkasscnEncryption(str):
    binary = ''
    res = ''
    for c in str:
        binary += '0' + bin(ord(c))[2:] + ''
    while len(binary) % 6 != 0:
        binary += '0'
    for i in range(len(binary) // 6):
        res += dir[int(binary[i * 6:6 + i * 6], 2)]
    while len(res) % 3 != 0:
        res += '}'
    return res


encrypt = bestkasscnEncryption(enc_key)

graph1 = getGraph(len(encrypt), len(encrypt))
graph2 = getGraph(len(encrypt), len(encrypt))
for i in range(len(flag)):
    graph1[dir.index(encrypt[i])][i] = enc_key[i]
    graph2[i][dir.index(encrypt[i])] = flag[i]

for i in range(0, len(flag), 2):
    graph2[i][dir.index(encrypt[i])] = enc_key[i]
    graph1[dir.index(encrypt[i])][i] = flag[i]

with open('graph1.txt', 'w') as file:
    file.write("graph1:\n")
    for i in graph1:
        file.write(str(i))
        file.write(',')
        file.write('\n')
file.close()
with open('graph2.txt', 'w') as file:
    file.write("graph2:\n")
    for j in graph2:
        file.write(str(j))
        file.write(',')
        file.write('\n')
file.close()

with open('encrypt.txt', 'w') as file:
    file.write(encrypt)
file.close()

打开后发现文件夹中有 graph1.txt,encrypt.txt,以及被加密的graph2.txt

由于给的二维数组文本太多,故不进行展示

根据代码,我们可以推导出如下步骤来解密和获取密钥:

  1. 通过读取 “graph1.txt” 文件,我们可以得到 “graph1” 列表,其中包含了一些字符。
  2. 读取 “encrypt.txt” 文件,获取密文字符串。
  3. 根据密文字符串,使用函数 “bestkasscnEncryption” 进行解密,得到密钥。
def bestkasscnDecryption(ciphertext):
    dir = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}"
    binary = ''
    res = ''
    for c in ciphertext:
        binary += bin(dir.index(c))[2:].zfill(6)
    for i in range(0, len(binary) - 7, 8):
        res += chr(int(binary[i:i + 8], 2))
    return res

# 读取 encrypt.txt 文件
encrypt_file = open('encrypt.txt', 'r')
encrypt = encrypt_file.read()
encrypt_file.close()

key = bestkasscnDecryption(encrypt)

print("密钥:", key)

这个就解开了graph2.txt的密码:One_who_has_seen_the_ocean_thinks_nothing_of_mere_rivers_so_do_I

(我觉得写的的确很好)

接下来再根据题目中以下代码的提示,我们可以知道当 i取余2的时候,余数为0时,执行==graph1[dir.index(encrypt[i])][i] = flag[i]的循环,否则的话执行graph2[i][dir.index(encrypt[i])] = flag[i]==的循环

for i in range(len(flag)):
    graph1[dir.index(encrypt[i])][i] = enc_key[i]
    graph2[i][dir.index(encrypt[i])] = flag[i]

for i in range(0, len(flag), 2):
    graph2[i][dir.index(encrypt[i])] = enc_key[i]
    graph1[dir.index(encrypt[i])][i] = flag[i]

根据以上推断出的信息即可编写以下代码来获取flag了

from random import randint

dir = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}"
encrypt = "t25Lx3DOB19OyxnFC2vLBL90AgvFB2nLyw5FDgHPBMTZx25VDgHPBMDFB2zFBwvYzv9YAxzLCNnFC29Fzg9Fsq}"
//由于篇幅太多故省略了
graph1 = [[...]]
graph2 = [[...]]
flag = ''
for i in range(64):
    if i % 2 == 0:
        m = graph1[dir.index(encrypt[i])][i]
        flag += m
    else:
        m = graph2[i][dir.index(encrypt[i])]
        flag += m
        
print(flag)

WEEK 3

一.Rabin’s RSA

题目:

from Crypto.Util.number import *
from secret import flag
p = getPrime(64)
q = getPrime(64)
assert p % 4 == 3
assert q % 4 == 3

n = p * q

e = 2
m = bytes_to_long(flag)

c = pow(m,e,n)

print('n =', n)
print('c =', c)

# n = 201354090531918389422241515534761536573
# c = 20442989381348880630046435751193745753

根据题目提示Michael O. Rabin的加密方案可知此为Rabin RSA

首先把n放到网站进行分解,发现可以得到q,p,分别是:13934102561950901579 和 14450452739004884887,此时得到 p,q 之后,就可以套用 **Rabin **的模板进行解密

注:一定得推导一下具体过程

import gmpy2
from Crypto.Util.number import *

p= 13934102561950901579
q= 14450452739004884887
n= p*q
c= 20442989381348880630046435751193745753
e= 2

mp = pow(c, (p + 1) // 4, p)
mq = pow(c, (q + 1) // 4, q)
inv_p = gmpy2.invert(p, q)
inv_q = gmpy2.invert(q, p)

a = (inv_p * p * mq + inv_q * q * mp) % n
b = n - int(a)
c = (inv_p * p * mq - inv_q * q * mp) % n
d = n - int(c)

# 因为rabin 加密有四种结果,全部列出。

aa = [a, b, c, d]
for i in aa:
    print(long_to_bytes(int(i)))

二.NSSCTF Round#11 Basic ez_signin

(由于这道题也是用到了Rabin RSA,故也进行练习)

题目:

from Crypto.Util.number import *
from secret import flag

p = getPrime(512)
q = getPrime(512)
assert p > q
n = p*q
e = 65536
m = bytes_to_long(flag)
num1 = (pow(p,e,n)-pow(q,e,n)) % n
num2 = pow(p-q,e,n)
c = pow(m,e,n)

print("num1=",num1)
print("num2=",num2)
print("n=",n)
print("c=",c)

首先根据题目中给出的num1和num2的关系式,可以得出2pow(p,e,n),随后对这个式子进行p=gcd(num1+num2,n),q=n//p,即可获得p,q,式子如下:

from Crypto.Util.number import *
import gmpy2

num1= 134186458247304184975418956047750205959249518467116558944535042073046353646812210914711656218265319503240074967140027248278994209294869476247136854741631971975560846483033205230015783696055443897579440474585892990793595602095853960468928457703619205343030230201261058516219352855127626321847429189498666288452
num2= 142252615203395148320392930915384149783801592719030740337592034613073131106036364733480644482188684184951026866672011061092572389846929838149296357261088256882232316029199097203257003822750826537629358422813658558008420810100860520289261141533787464661186681371090873356089237613080052677646446751824502044253
n= 154128165952806886790805410291540694477027958542517309121222164274741570806324940112942356615458298064007096476638232940977238598879453357856259085001745763666030177657087772721079761302637352680091939676709372354103177660093164629417313468356185431895723026835950366030712541994019375251534778666996491342313
c= 9061020000447780498751583220055526057707259079063266050917693522289697419950637286020502996753375864826169562714946009146452528404466989211057548905704856329650955828939737304126685040898740775635547039660982064419976700425595503919207903099686497044429265908046033565745195837408532764433870408185128447965

tmp = num1+num2

p = gmpy2.gcd(n,tmp)
q = n // p

print(p)
print(q)

得到之后,原本的思路是得到p,q,n,c之后,就可以直接套用RSA解密脚本来进行解密,但解密过程中发生了报错,得知是 e与phi_n不互素 再看e=65536(偶数形式的)通过查阅资料可知当e为偶数时,可以将m看作整体,重新获得一个e来让e与phi_n互素 然后联想到Rabine=2,65536又正好是2的16次方,所以猜测这个题可能是多次解Rabin

from Crypto.Util.number import *
import gmpy2

num1= 134186458247304184975418956047750205959249518467116558944535042073046353646812210914711656218265319503240074967140027248278994209294869476247136854741631971975560846483033205230015783696055443897579440474585892990793595602095853960468928457703619205343030230201261058516219352855127626321847429189498666288452
num2= 142252615203395148320392930915384149783801592719030740337592034613073131106036364733480644482188684184951026866672011061092572389846929838149296357261088256882232316029199097203257003822750826537629358422813658558008420810100860520289261141533787464661186681371090873356089237613080052677646446751824502044253
n= 154128165952806886790805410291540694477027958542517309121222164274741570806324940112942356615458298064007096476638232940977238598879453357856259085001745763666030177657087772721079761302637352680091939676709372354103177660093164629417313468356185431895723026835950366030712541994019375251534778666996491342313
c= 9061020000447780498751583220055526057707259079063266050917693522289697419950637286020502996753375864826169562714946009146452528404466989211057548905704856329650955828939737304126685040898740775635547039660982064419976700425595503919207903099686497044429265908046033565745195837408532764433870408185128447965

tmp = num1+num2

p = gmpy2.gcd(n,tmp)
q = n // p

inv_p = gmpy2.invert(p, q)
inv_q = gmpy2.invert(q, p)

cs = [c]
for i in range(16):
    ps = []
    for c2 in cs:
        r = pow(c2, (p + 1) // 4, p)
        s = pow(c2, (q + 1) // 4, q)

        x = (r * inv_q * q + s * inv_p * p) % n
        y = (r * inv_q * q - s * inv_p * p) % n
        if x not in ps:
            ps.append(x)
        if n - x not in ps:
            ps.append(n - x)
        if y not in ps:
            ps.append(y)
        if n - y not in ps:
            ps.append(n - y)
    cs = ps

for m in cs:
    print(long_to_bytes(m))

DES学习

DES算法在1998年之后美国已经不再使用了。但它对于掌握分组密码的基本原理是有着重要的参考价值,它的结构和Fiscal密码的结构完全相同

Fiscal结构:将64bit明文分为两个32bit的L0和R0,将R0的值赋给L1,将R0和K1(密钥)进行一个轮换输出运算,再和L0进行一个异或,然后最后的值赋给R1,就可以根据一个图(或者公式)来理解
请添加图片描述
对于密钥长度以及它的变换要十分敏感

DES算法属于对称加密算法中的分组加密算法,算法分组长度64bit,密钥长度56bit,校验位8bit

对称密码体制其实就是单钥体制,单钥体制的加密方式有两种:一是明文消息按字符逐位的加密,称为流密码

​ 另一种是明文消息分组进行加密,称为分组密码

算法加密过程:它有三个阶段,首先是一个初始置换,将明文分组的64bit数据重新排列,然后是有相同功能的16轮变换,每一轮都有置换和代换的运算,第16轮交换变换的输出分为左右两半并交换次序,再经过一个逆初始置换产生64bit的密文,除初始置换和逆初始置换的话,DES的结构和Fiscal密码是完全相同的,简单的来讲,就是64bit的明文和56bit的密文经过一系列的运算,甚至64bit密码的过程

初始置换:初始置换表,它第一个数是58,就表示把第58个数放到第一位,第二个数是50,就表示把第50个数放到第一位(假如是123456——,则改变后就是58 50——)
请添加图片描述
轮函数:16轮的迭代,然后每一轮都是进行选择扩展运算(与初始置换相似,但这个是48位,初始置换是32位) 异或运算 S盒压缩处理(将48位数据分成8个6位数据,然后通过S盒压缩)左右交换 逆初始置换(与初始置换相似)
请添加图片描述

S盒压缩处理 举例 原始数据:1 0011 1

​ 头尾数据:11 十进制3(行数)

​ 中间数据: 0011 十进制3(列数)

​ 输出数据:2(0010)

密钥生成:置换选择1 左循环移位 置换选择2
在这里插入图片描述
在这里插入图片描述

举例:0001 0011

​ 0011 0101

​ 0101 0111

​ 0111 1001

​ 1001 1011

​ 1011 1101

​ 1101 1111

​ 1111 0001

64bit的拉进去置换,但是他这里只有7列,直接把第8列的数没有算进去,经过第一步的置换选择之后,就把64位变成了56位,就可以得到C0和D0,分别把C0和D0进行一个左移位

C0

1111000 0110011

0010101 0101111

D0

0101010 1011001

1001111 0001111

在这里插入图片描述

三.NSSCTF Round#19 Basic 3DES?

题目:

from Crypto.Util.number import *
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad
from secret import flag
from random import *

class DEStream():
    def __init__(self):
        self.table = '0123456789'
        self.key = ("".join([choice(self.table) for i in range(8)])).encode()
        self.seed = b"!NSSCTF!"
        self.des = DES.new(self.key,mode=DES.MODE_ECB)

    def next(self):
        self.seed = (self.des).encrypt(self.seed)
        return bytes_to_long(self.seed) & 1
    
    def enc(self,m):
        return (self.des).encrypt(m)

def combine(x1,x2,x3):
    return (x1&x2)^(x2&x3)^x3

des1 =  DEStream()
des2 =  DEStream()
des3 =  DEStream()

output = ""
for i in range(2000):
    output += str(combine(des1.next(),des2.next(),des3.next()))

cipher = des1.enc(pad(flag,8))
cipher = des2.enc(cipher)
cipher = des3.enc(cipher)

print("output =",output)
print("cipher =",cipher)


'''
output = 10110101110110001000111000110101000110111100110110011010000010001110010010101111000101101100101011110101111011000000001110011001011001110010010000001010111101011110011011000111011101010000111011011110010001101001110010111100101111110001000110000110111010100010111010000110000001011010111100100011011010001101001001100000000001100101111110110011000101101100110101010000010101100001010010000010010011001011110000010101111000001110010100110110111011001010111111100010100111000110110010101101000001011000101110101100000110110000101101010100101010000100110011011110100110111101101100011100011100101000000100000000101001100101001010101001000110011110110000111111001111101110110110100001010000100110010100010101000001010001111100000010100000101101111010010011010100011000100111001101100001011111001100111101100011001001000001000100011011010000001101110000001111011000111000011001010011100110110111111101010110101110110110000101110110000000101110000011110101001100010000101001010010000011110111100001101011001110100011101111011001010110111000010000111000010010111111111000111100110000100100100011111100101111011001000000011000001100110100011100110010100010001110011001011100101001101001100110001010000011001001101010010101001111110100010111000000101011000100100000010011111011001011010001101101101001100111010110101001001111000111000001000001010000001100101110110010111111001001011111111000100110011100011000111011010100001011010000011110101001110101010011111000100010011010000101011100110110101000100010010001011101010010100100001011010101000010000110001100100010000010111110011111000011000100011001110001100100111111101000001001101110000100101011010010011001100100100100010000101100111001100110000110010001001010011011011101011010100110001100001000111010000100100100010110111100000100111010101100000111010111011000100011010111110010010000110111111001101000011111000111100110111110011110101001100110111000111111011010011100011000010010011010100000100110010011111011110111011111011011110010110000110011110000
cipher = b'\xe3\t\x13\xe2\x8b\xd1\xdeql\x94F\xb5}\xb8d\xfa~\x06&~\x8f\xcb-&\xf3q/j\xd3\xbe\x1f\xef\x18\x84hn\x1c[t\x03\x10\xb8\x8e?\x89\x8b\x00\xc5\xb9`5E\xeaC\xe9,'
'''

提示:

三个DES

hint:
怎么爆破会好一点呢?
correlation attack
打印Geffe生成器的真值表进行观察
DES的密钥似乎并不是每一位都有用

先观察代码,根据以下代码可知,这个题目密钥由10个数字中随机的8个(0123456789)组成,执行流密钥的功能(self.key,但与之前讲过的LCG不同),对于它的每一次输出,都是将初始种子进行一次DES加密得到新的种子

   def __init__(self):
           self.table = '0123456789'
           self.key = ("".join([choice(self.table) for i in range(8)])).encode()
           self.seed = b"!NSSCTF!"
           self.des = DES.new(self.key,mode=DES.MODE_ECB)

再根据以下代码

  1. 使用DES算法加密self.seed属性,并将结果赋值给self.seed属性。每次调用next方法时,都会对当前的种子进行加密,并更新种子的值。
  2. return bytes_to_long(self.seed) & 1:将加密后的种子转换为一个整数,并与1进行按位与操作。返回结果为0或1,表示加密流的下一个元素。
  3. def enc(self, m)::定义了一个名为enc的方法,用于对给定的消息进行加密。
  4. return (self.des).encrypt(m):使用DES算法对消息m进行加密,并返回加密后的结果。
  5. def combine(x1, x2, x3)::定义了一个名为combine的函数,它接受三个参数x1x2x3
  6. return (x1 & x2) ^ (x2 & x3) ^ x3:对x1x2x3进行按位与、按位异或运算,并返回结果。这段代码的作用是将这三个值进行组合生成一个新的值
    def next(self):
           self.seed = (self.des).encrypt(self.seed)
           return bytes_to_long(self.seed) & 1
    
    def enc(self,m):
           return (self.des).encrypt(m)

def combine(x1,x2,x3):
       return (x1&x2)^(x2&x3)^x3

接着再根据异或运算,可以获得以下8种情况:

x1 = 0,x2 = 0,x3 = 0,combine = 0
x1 = 0,x2 = 0,x3 = 1,combine = 1
x1 = 0,x2 = 1,x3 = 0,combine = 0
x1 = 0,x2 = 1,x3 = 1,combine = 0
x1 = 1,x2 = 0,x3 = 0,combine = 0
x1 = 1,x2 = 0,x3 = 1,combine = 1
x1 = 1,x2 = 1,x3 = 0,combine = 1
x1 = 1,x2 = 1,x3 = 1,combine = 1

最后题目只给出长度2000的二进制序列,所以只能通过这2000个值求3个key

for i in range(2000):
    output += str(combine(des1.next(),des2.next(),des3.next()))

cipher = des1.enc(pad(flag,8))
cipher = des2.enc(cipher)
cipher = des3.enc(cipher)

以上为代码的分析,接下来再看提示:打印Geffe生成器的真值表进行观察

通过查阅资料可知:

(x1&x2)^(x2&x3)^x3

即为Geffe生成器,刚刚列出的8种情况也是其的真值表

至于怎么爆破会好一点呢?这一个提示没有明白,在查看出题人的wp之后,才知道需要爆破X1和X2,因为combine的结果有75%的概率和X1一样,有50%的概率和X2的概率一样,有75%的概率和X3一样,而X1,X3分别是KEY1,KEY3对初始seed加密使用的密钥,这说明我们可以通过爆破的手段求得KEY1,KEY3,判断方式是观察其输出是否与2000位给定的序列有75%的相似度,由于题目的DES密钥均为8位数字组成,所以需要爆破的可能组合共有10^8 种,复杂度依然比较高。但是需要注意到,DES的八字节密钥实际参与加密的只有56位,因为每个第8bit实际上本来是作为校验位的,在产生轮密钥之前就会被去掉,因此实际需要爆破的仅有5^8种可能。

对于DES密钥,每个字节有8位,但是最高位(第8位)是奇偶校验位,不参与实际的加密运算。因此,每个字节实际上只有7位有效位。由于每个有效位可以是0或1,所以每个字节有2^7 = 128种可能性。

由于DES密钥由8个字节组成,因此总共有8个字节 * 128种可能性 = 1024种可能性。这解释了为什么是5^ 8(即1024种可能性)而不是10^8种可能性

DES的密钥似乎并不是每一位都有用根据这个提示,再结合上面的DES学习可知,并不是8位数字都会用到,在密钥生成中只用到了7位数字

correlation attack则是一篇论文,旨在对于流密码的快速攻击

以下代码就是依据以上提示给出的爆破X1和X2的过程(由于正确密钥的输出应该和output有75%左右的相似度,因此200个比特基本上已经很难再有误判的可能性,并不需要把给出的2000bit全部用上,所以就需要4分钟就可以爆破出来DES1、DES3的密钥了)(也是经过一次次爆破尝试发现的确200bit就是最优解)

from Crypto.Util.number import *
from Crypto.Cipher import DES
from tqdm import *
from itertools import product

output = "10110101110110001000111000110101000110111100110110011010000010001110010010101111000101101100101011110101111011000000001110011001011001110010010000001010111101011110011011000111011101010000111011011110010001101001110010111100101111110001000110000110111010100010111010000110000001011010111100100011011010001101001001100000000001100101111110110011000101101100110101010000010101100001010010000010010011001011110000010101111000001110010100110110111011001010111111100010100111000110110010101101000001011000101110101100000110110000101101010100101010000100110011011110100110111101101100011100011100101000000100000000101001100101001010101001000110011110110000111111001111101110110110100001010000100110010100010101000001010001111100000010100000101101111010010011010100011000100111001101100001011111001100111101100011001001000001000100011011010000001101110000001111011000111000011001010011100110110111111101010110101110110110000101110110000000101110000011110101001100010000101001010010000011110111100001101011001110100011101111011001010110111000010000111000010010111111111000111100110000100100100011111100101111011001000000011000001100110100011100110010100010001110011001011100101001101001100110001010000011001001101010010101001111110100010111000000101011000100100000010011111011001011010001101101101001100111010110101001001111000111000001000001010000001100101110110010111111001001011111111000100110011100011000111011010100001011010000011110101001110101010011111000100010011010000101011100110110101000100010010001011101010010100100001011010101000010000110001100100010000010111110011111000011000100011001110001100100111111101000001001101110000100101011010010011001100100100100010000101100111001100110000110010001001010011011011101011010100110001100001000111010000100100100010110111100000100111010101100000111010111011000100011010111110010010000110111111001101000011111000111100110111110011110101001100110111000111111011010011100011000010010011010100000100110010011111011110111011111011011110010110000110011110000"
cipher = b'\xe3\t\x13\xe2\x8b\xd1\xdeql\x94F\xb5}\xb8d\xfa~\x06&~\x8f\xcb-&\xf3q/j\xd3\xbe\x1f\xef\x18\x84hn\x1c[t\x03\x10\xb8\x8e?\x89\x8b\x00\xc5\xb9`5E\xeaC\xe9,'

def combine(x1,x2,x3):
    return (x1&x2)^(x2&x3)^x3

def compare(str1,str2):               #计算相似度
    assert len(str1) == len(str2)
    num = 0
    for i in range(len(str1)):
        if str1[i] == str2[i]:
            num += 1
    if (num / len(str1)) > 0.75:
        return True
    else:
        return False
    
table = "0123456789"
prefix = []
for i in table:
    temp = bin(ord(i) // 2)[2:].zfill(7)
    if temp not in prefix:
        prefix.append(temp)
# 把这些字符2进制形式的高7位存入,遍历table中的字符,并根据每个字符的ASCII值生成一个二进制前缀。如果该前缀不在prefix列表中,将其添加到列表中。最终,prefix列表将包含table中每个字符的二进制前缀,且没有重复项

key_may = []
for i in tqdm(product(prefix,repeat = 8)):      #爆破key1和key3,长度为八个字节
    temp = "".join(j + "0" for j in list(i))
    key = long_to_bytes(int(temp,2))
    # 开始爆
    des1 = DES.new(key,mode=DES.MODE_ECB)
    seed = b"!NSSCTF!"
    output1 = ""
    for j in range(200):
        seed = des1.encrypt(seed)
        out = str(bytes_to_long(seed) & 1)
        output1 += out
    if compare(output1,output[:200]):
        key_may.append(key)

# 大概4分钟
print(key_may)
# [b'06660408', b'84822042']

有了正确的DES1、DES3的密钥,又有种子”!NSSCTF!”,就有了他们对应的两百位输出X1、X3,那么爆破x2就只需要看combine之后的结果是否与output完全相等即可。同时,由于密钥错误但依然连续50位均相等的概率已经非常低,所以只取前五十位参与爆破可以节省时间

key_may = [b'06660408', b'84822042']
# 可能的密钥只有2个,只需要来回试一下key1,key3即可
key1 = key_may[1]                   
key3 = key_may[0]
des1 = DES.new(key1,mode=DES.MODE_ECB)
des3 = DES.new(key3,mode=DES.MODE_ECB)

# 接下来爆key2
# 大概1分20秒
for i in tqdm(product(prefix,repeat = 8)):
    temp = "".join(j + "0" for j in list(i))
    key = long_to_bytes(int(temp,2))
    # 该循环遍历了所有可能的长度为8的二进制前缀组合,并将每个组合扩展为长度为16的字符串。然后,通过将扩展后的字符串转换为整数,进一步将其转换为字节串,可能用作密钥。在循环中使用tqdm库显示了循环的进度条,使得可以跟踪循环的执行进度
    des2 = DES.new(key,mode=DES.MODE_ECB)
    
    seed1 = seed2 = seed3 = b"!NSSCTF!"
    judge = True
    for j in range(50):
        seed1 = des1.encrypt(seed1)
        seed2 = des2.encrypt(seed2)
        seed3 = des3.encrypt(seed3)
        out1 = bytes_to_long(seed1) & 1
        out2 = bytes_to_long(seed2) & 1
        out3 = bytes_to_long(seed3) & 1
        temp = combine(out1,out2,out3)
        if str(temp) != output[j]:
            judge = False
            break
    
    if judge:
        flag = des3.decrypt(cipher)
        flag = des2.decrypt(flag)
        flag = des1.decrypt(flag)
        
        print(flag)
        print(f"key2 = {key}")

        break

四.小明的密码题

题目:

from Crypto.Util.number import *
from secret import *
flag_part = flag_content + '#' + secret_token
p = getPrime(512)
q = getPrime(512)

m = bytes_to_long(flag_part.encode())

e = 5
n = p*q

c = pow(m,e,n)

print('n =', n)
print('c =', c)
print('flag_part =', flag_part)
print()
print('--- hint begin ---')
print('flag = "flag{" + flag_part + "}"')
print('type of secret_token is', type(secret_token))
print('length of secret_token is', len(secret_token))

# n = 131889193322687215946601811511407251196213571687093913054335139712633125177496800529685285401802802683116451016274353008428347997732857844896393358010946452397522017632024075459908859131965234835870443110233375074265933004741459359128684375786221535003839961829770182916778717973782408036072622166388614214899
# c = 11188201757361363141578235564807411583085091933389381887827791551369738717117549969067660372214366275040055647621817803877495473068767571465521881010707873686036336475554105314475193676388608812872218943728455841652208711802376453034141883236142677345880594246879967378770573385522326039206400578260353074379
# flag_part = sm4ll_r00ts_is_brilliant#◼️◼️◼️◼️◼️◼️◼️◼️
# 
# --- hint begin ---
# flag = "flag{" + flag_part + "}"
# type of secret_token is <class 'str'>
# length of secret_token is 8

提示:

小明正在完成课后作业, 但是屏幕的某一块地方被墨水涂黑了(我们不妨假设小明无法擦去墨水, 并且该电脑是一个真空中的球形电脑)

根据提示:sm4ll_r00ts_is_brilliant,可知这个为高位泄露,使用Coppersmith 攻击,然后依据length of secret_token is 8可知,这个缺失了8位,所以左移64位(8的2次方)

通过学习**Coppersmith 攻击 **参考文章:Coppersmith 攻击 (ruanx.net)

(这里公式中的3是指e)
在这里插入图片描述

然后通过SageMath运行代码

import gmpy2
from Crypto.Util.number import *
n = 131889193322687215946601811511407251196213571687093913054335139712633125177496800529685285401802802683116451016274353008428347997732857844896393358010946452397522017632024075459908859131965234835870443110233375074265933004741459359128684375786221535003839961829770182916778717973782408036072622166388614214899
c = 11188201757361363141578235564807411583085091933389381887827791551369738717117549969067660372214366275040055647621817803877495473068767571465521881010707873686036336475554105314475193676388608812872218943728455841652208711802376453034141883236142677345880594246879967378770573385522326039206400578260353074379
e = 5

flag = b"sm4ll_r00ts_is_brilliant#"
m1 = bytes_to_long(flag)
m1 = m1 << 64

R.<x> = PolynomialRing(Zmod(n))
f = (m1 + x)^e - c
#表示解密方程 (m1 + x)^e - c = 0
f = f.monic()
#多项式 f 调整为首项系数为 1(monic)
root = f.small_roots(X = 2^64)
#2^64是指2的移位次数 通过指定 X = 2^64,它告诉函数只寻找小于等于 2^64 的根

print(root)
m = m1 + root[0]
flag = b"flag{" + long_to_bytes(int(m)) + b"}"
print(flag)
  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值