BUUCTF刷题Crypto篇
文章目录
前言
在无脑做了几道密码学题型后迟来的总结记录。以后还是要边做边记啊喂!
一、[NCTF2019]childRSA
考点:费马小定理
题目给出了一个Python代码文件内容如下:
from random import choice
from Crypto.Util.number import isPrime, sieve_base as primes
from flag import flag
def getPrime(bits):
while True:
n = 2
while n.bit_length() < bits:
n *= choice(primes)
if isPrime(n + 1):
return n + 1
e = 0x10001
m = int.from_bytes(flag.encode(), 'big')
p, q = [getPrime(2048) for _ in range(2)]
n = p * q
c = pow(m, e, n)
# n = 32849718197337581823002243717057659218502519004386996660885100592872201948834155543125924395614928962750579667346279456710633774501407292473006312537723894221717638059058796679686953564471994009285384798450493756900459225040360430847240975678450171551048783818642467506711424027848778367427338647282428667393241157151675410661015044633282064056800913282016363415202171926089293431012379261585078566301060173689328363696699811123592090204578098276704877408688525618732848817623879899628629300385790344366046641825507767709276622692835393219811283244303899850483748651722336996164724553364097066493953127153066970594638491950199605713033004684970381605908909693802373826516622872100822213645899846325022476318425889580091613323747640467299866189070780620292627043349618839126919699862580579994887507733838561768581933029077488033326056066378869170169389819542928899483936705521710423905128732013121538495096959944889076705471928490092476616709838980562233255542325528398956185421193665359897664110835645928646616337700617883946369110702443135980068553511927115723157704586595844927607636003501038871748639417378062348085980873502535098755568810971926925447913858894180171498580131088992227637341857123607600275137768132347158657063692388249513
# c = 26308018356739853895382240109968894175166731283702927002165268998773708335216338997058314157717147131083296551313334042509806229853341488461087009955203854253313827608275460592785607739091992591431080342664081962030557042784864074533380701014585315663218783130162376176094773010478159362434331787279303302718098735574605469803801873109982473258207444342330633191849040553550708886593340770753064322410889048135425025715982196600650740987076486540674090923181664281515197679745907830107684777248532278645343716263686014941081417914622724906314960249945105011301731247324601620886782967217339340393853616450077105125391982689986178342417223392217085276465471102737594719932347242482670320801063191869471318313514407997326350065187904154229557706351355052446027159972546737213451422978211055778164578782156428466626894026103053360431281644645515155471301826844754338802352846095293421718249819728205538534652212984831283642472071669494851823123552827380737798609829706225744376667082534026874483482483127491533474306552210039386256062116345785870668331513725792053302188276682550672663353937781055621860101624242216671635824311412793495965628876036344731733142759495348248970313655381407241457118743532311394697763283681852908564387282605279108
1.先记录代码里不会的知识点
1)random模块中choice函数可以从序列(列表,元组或字符串)中获取一个随机元素并返回。
2)Crypto.Util.number中的sieve_base是由前10000的素数组成的列表,顺便看了一下里面最大的数是104729
3)涉及到明文m的这一行:
m = int.from_bytes(flag.encode(), ‘big’)
虽然做这道题对此不必深究,但还是去查了一下这个int.from_bytes函数
int.from_bytes(bytes, byteorder, *, signed=False)
bytes就是byte类型的参数,形如b’aa’
byteorder:可以选’big’和’little’,其中big代表正常顺序,little代表逆序。
signed:选True、Flase表示是否要区分二进制的正负数含义。
自己试验了一下以’aa’为bytes参数,得到函数的返回值是24929,而’aa’是前后两个97,如果用二进制形式连写两个97就是0110000101100001,再转换成十进制就是24929,如此也就清晰了这个函数所做的变换。
分析代码可知,p和q都是由getPrime函数生成的素数,生成过程是在前10000个素数中随机选择若干个素数相乘直至得到的乘积n满足长度不小于2048bit,再判断n+1是否是素数,如果n+1是素数就得到了满足条件的p或q,函数返回n+1的值,否则重新开始计算,直至得到满足长度不小于2048bit的n,再判断n+1是否为素数。
总之,p和q都满足其可以转化为若干素数的乘积+1,而且都是素数。
经过看网上的wp后,得知这道题的解题关键是使用费马小定理,那么……
2.什么是费马小定理
若b为一个素数,则对于任意与b互质的整数a,有a^(b-1) mod b = 1
这里a只要不是b的倍数就可以满足与b互质,这里的^代表乘方运算。
也就是说,a^(b-1)-1是b的整数倍
拓展结论:
a^(k*(b-1))-1是b的整数倍,k是任意一个正整数
3.解题思路
根据p和q的生成方式,可知(p-1)和(q-1)都是由前10000个素数中的若干个素数相乘得到的(个人觉得这里应该要默认在生成并计算素数乘积的过程中,choice函数没有出现随机生成了相同的素数去做乘积的情况
如果把前10000个素数的乘积记为∏,则∏肯定为(p-1)和(q-1)的倍数,令∏ = k*(p-1)
由费马小定理,a^(k*(b-1))-1是b的倍数,那么有2^∏-1是p的倍数
由于n=p*q,显然n也是p的倍数
可以联想到去计算gcd(2^∏-1, n) = p,得到p,但是直接计算2^∏的计算量会很大,所以再进一步优化:
2^∏ mod p = 1,即2^∏ = 1 + k1*p
而2^∏ % n = 2^∏ - k2*n = 2^∏ - k2*p*q
两边同时去 mod p,有2^∏ % n mod p = 2^∏ mod p (因为k2*p*q是p的倍数所以mod p 的结果是0)
而右边的2^∏ mod p = 1,所以同样有2^∏ % n mod p = 1 ,即2^∏ % n - 1 也是p的整数倍
因此只需要计算gcd((2^∏ % n)-1, n)即可,模幂计算会比直接幂计算快很多
这里的%和mod都是取模,只是做下区分
解题代码如下:
import gmpy2
import binascii
from Crypto.Util.number import isPrime, sieve_base as primes
e = 0x10001
n = 32849718197337581823002243717057659218502519004386996660885100592872201948834155543125924395614928962750579667346279456710633774501407292473006312537723894221717638059058796679686953564471994009285384798450493756900459225040360430847240975678450171551048783818642467506711424027848778367427338647282428667393241157151675410661015044633282064056800913282016363415202171926089293431012379261585078566301060173689328363696699811123592090204578098276704877408688525618732848817623879899628629300385790344366046641825507767709276622692835393219811283244303899850483748651722336996164724553364097066493953127153066970594638491950199605713033004684970381605908909693802373826516622872100822213645899846325022476318425889580091613323747640467299866189070780620292627043349618839126919699862580579994887507733838561768581933029077488033326056066378869170169389819542928899483936705521710423905128732013121538495096959944889076705471928490092476616709838980562233255542325528398956185421193665359897664110835645928646616337700617883946369110702443135980068553511927115723157704586595844927607636003501038871748639417378062348085980873502535098755568810971926925447913858894180171498580131088992227637341857123607600275137768132347158657063692388249513
c = 26308018356739853895382240109968894175166731283702927002165268998773708335216338997058314157717147131083296551313334042509806229853341488461087009955203854253313827608275460592785607739091992591431080342664081962030557042784864074533380701014585315663218783130162376176094773010478159362434331787279303302718098735574605469803801873109982473258207444342330633191849040553550708886593340770753064322410889048135425025715982196600650740987076486540674090923181664281515197679745907830107684777248532278645343716263686014941081417914622724906314960249945105011301731247324601620886782967217339340393853616450077105125391982689986178342417223392217085276465471102737594719932347242482670320801063191869471318313514407997326350065187904154229557706351355052446027159972546737213451422978211055778164578782156428466626894026103053360431281644645515155471301826844754338802352846095293421718249819728205538534652212984831283642472071669494851823123552827380737798609829706225744376667082534026874483482483127491533474306552210039386256062116345785870668331513725792053302188276682550672663353937781055621860101624242216671635824311412793495965628876036344731733142759495348248970313655381407241457118743532311394697763283681852908564387282605279108
#primes是前10000个素数的列表sieve_base的别名
prd = 1
#计算prd = ∏ primes 即前10000个素数相乘的结果
for i in primes:
prd *= i
#p为(2^prd mod n -1)和n的公约数
p = gmpy2.gcd(gmpy2.powmod(2,prd,n)-1,n)
q = n // p
d = gmpy2.invert(e,(p-1)*(q-1)) #计算私钥d
m = gmpy2.powmod(c, d, n) #解密
flag = bytes.fromhex(hex(m)[2:]) #去掉16进制的0x两位再转换成byte
print(flag)
参考博客:
https://blog.csdn.net/xiao_han_a/article/details/118670716
(感谢写了这篇文章的大佬,超棒!)
二、[HDCTF2019]bbbbbbrsa
1.列出题目已知条件
打开题目压缩包,给出了两个文件:
enc是文本文件,给出了n、e、c,c看起来是逆序的base64编码结果。
p = 177077389675257695042507998165006460849
n = 37421829509887796274897162249367329400988647145613325367337968063341372726061
c = ==gMzYDNzIjMxUTNyIzNzIjMyYTM4MDM0gTMwEjNzgTM2UTN4cjNwIjN2QzM5ADMwIDNyMTO4UzM2cTM5kDN2MTOyUTO5YDM0czM3MjM
py文件是p、q、e的生成原理。
from base64 import b64encode as b32encode
from gmpy2 import invert,gcd,iroot
from Crypto.Util.number import *
from binascii import a2b_hex,b2a_hex
import random
flag = "******************************"
nbit = 128
p = getPrime(nbit)
q = getPrime(nbit)
n = p*q
print p
print n
phi = (p-1)*(q-1)
e = random.randint(50000,70000)
while True:
if gcd(e,phi) == 1:
break;
else:
e -= 1;
c = pow(int(b2a_hex(flag),16),e,n)
print b32encode(str(c))[::-1]
# 2373740699529364991763589324200093466206785561836101840381622237225512234632
把这个题给出的文本文件和py代码一起看就有个很迷惑又很坏的地方,就是
py文件中第一句是from base64 import b64encode as b32encode
把base64编码的函数弄了个base32的别名是什么鬼???
根据对文本文件enc里c的猜测,把“c==gMzYDNzIjMxUTNyIzNzIjMyYTM4MDM0gTMwEjNzgTM2UTN4cjNwIjN2QzM5ADMwIDNyMTO4UzM2cTM5kDN2MTOyUTO5YDM0czM3MjM”逆序后,再按base64解码,得到的结果刚好是在py文件中最后注释的那一串数字……所以是要让人浪费时间白白解码一遍c吗?还要迷惑人哪来的base32?(我想的可真多
直接在终端里敲了:
总之,参与解密运算的c就是:2373740699529364991763589324200093466206785561836101840381622237225512234632
这个值。
又已知了n和p,q直接n除以p即可得到。
2.利用循环和RSA常规解密方法求解e
在题目给出的py文件中可知e的由来是:50000到70000之间取一个随机整数,判断这个数是否满足和phi(n)互质,如果是就输出e并结束循环,不是就将当前数-1后继续判断。
现在,n、p、q、phi(n)都是已知或直接可以通过计算求出的值。关键就是求e,进而求出私钥d就可以解密了,只要按常规rsa求解的步骤来就行了,在这个过程中借助循环找到满足条件的e,这里先假设那个满足条件的e仍处于50000-70000之间。
由于实际运行程序时发现在50000-70000的范围中能找到好几个满足和phi(n)互质的e,进而得到不同的d和明文m,所以筛选一下含有“flag”关键字的结果输出。 phi(n)和代码中写的phi是一个东西哈!
(这里试一试才知道不筛选flag的话会输出好多的结果,不止是好几个,我根本看不见正确的flag,所以直接筛选输出flag
解题代码如下:
from gmpy2 import invert,gcd,iroot
from Crypto.Util.number import *
p = 177077389675257695042507998165006460849
n = 37421829509887796274897162249367329400988647145613325367337968063341372726061
q=n//p
phi = (p-1)*(q-1)
c=2373740699529364991763589324200093466206785561836101840381622237225512234632
for e in range(50000,70000):
if gcd(e,phi) == 1:
d=invert(e,phi)
m=pow(c,d,n)
flag=str(long_to_bytes(m))
if 'flag' in flag:
print(flag)
上面代码的输出结果是 b’flag{rs4_1s_s1mpl3!#}’
三、[NCTF2019]Keyboard
题目给的是一个txt文件,内容很短,经观察发现出现的全都是电脑键盘第一行字母,而且没有字母q和p,只有中间的8个字母,题目又叫keyboard,而且九键键盘实际上也只有八个键上有字母,所以再联想到九键键盘!
直接说答案怎么找:当前字母在26键的第一行中是第几个字母,就对应去看九键的第几个键,当前字母出现几次,就去找对应键位上的第几个字母。可以直接找,也可以写脚本。因为对我来说写脚本动脑也挺慢的(哭
写脚本的思路:将这一串字母按空格分组转换成list,找到list中每组的字母在26键中的位置序号是几,并确定每组字母的长度,然后再去九键中找到该“位置序号”的键上的第“长度”个字母。
cipher="ooo yyy ii w uuu ee uuuu yyy uuuu y w uuu i i rr w i i rr rrr uuuu rrr uuuu t ii uuuu i w u rrr ee www ee yyy eee www w tt ee"
base=" qwertyuiop"
a=[" "," ","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
l=cipher.split(" ")
#print(l) #l是按空格分割得到的list
for part in l:
s=base.index(part[0]) #在26键第一行中是第几个
count=len(part)
print(a[s][count-1],end="")
注意:代表26键和九键的字符串和列表,分别空出了第一个位置和前两个位置
程序输出:youaresosmartthatthisisjustapieceofcake
你们平时在手机上用九键还是26键呢?
总结
你们有没有发现,这三道题,难度在递减,但第三题很考脑洞!
之后要坚持继续归纳总结密码学题目之路~(flag已立的感觉呢emmm