BUUCTF_Crypto题目题解记录1

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编码前的值
总之,参与解密运算的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值