c++用rsa加密一段文字_CTF中的RSA

【参考文献1和文献4中总结的比较好,部分内容直接照搬,感谢】

  1. RSA简介

完全理解RSA需要对初等数论理解比较多。在非对称加密中有过简单的介绍,这里总结一下:

  1. 选择两个大素数p和q,计算出模数N = p * q
  2. 计算φ = (p−1) * (q−1) 即N的欧拉函数,然后选择一个e (1<e<φ),且e和φ互质
  3. 取e的模反数为d,计算方法: e * d ≡ 1 (mod φ)
  4. 对明文m进行加密:c = pow(m, e, N),得到的c即为密文
  5. 对密文c进行解密,m = pow(c, d, N),得到的m即为明文

整理一下得到我们需要认识和记住的参数

  • p 和 q :大整数N的两个因子(factor)
  • N:大整数N,我们称之为模数(modulus
  • e 和 d:互为模反数的两个指数(exponent)
  • c 和 m:分别是密文和明文,这里一般指的是一个十进制的数

然后我们一般称

  • (N,e):公钥
  • (N,d):私钥

2. CTF中的RSA

CTF中的RSA题目一般是将flag进行加密,然后把密文(即c)和其他一些你解题需要的信息一起给你,你需要克服重重难关,去解密密文c,得到flag(即m),一般有下列题型

公钥加密文

这是CTF中最常见最基础的题型,出题人会给你一个公钥文件(通常是以.pem或.pub结尾的文件)和密文(通常叫做flag.enc之类的),你需要分析公钥,提取出(N,e),通过各种攻击手段恢复私钥,然后去解密密文得到flag。

文本文档

对于第一种题型,耿直点的出题人直接给你一个txt文本文档,里面直接写出了(N,e,c)所对应的十进制数值,然后你直接拿去用就行了。当然也不都是给出(N,e,c)的值,有时还会给出其他一些参数,这时就需要思考,这题具体考察的什么攻击方法

pcap文件

有时出题人会给你一个流量包,你需要用wireshark等工具分析,然后根据流量包的通信信息,分析题目考察的攻击方法,你可以提取出所有你解题需要用到的参数,然后进行解密

本地脚本分析

题目会给你一个脚本和一段密文,一般为python编写,你需要逆向文件流程,分析脚本的加密过程,写出对应的解密脚本进行解密

远程脚本利用

这种题型一般难度较大。题目会给你一个运行在远程服务器上的python脚本和服务器地址,你需要分析脚本存在的漏洞,确定攻击算法,然后编写脚本与服务器交互,得到flag


3. 例题

基本上来说,RSA的题目都是围绕着c,m,e,d,n,p,q这几个参数展开的,但是题目一般不会直接给这种样子的参数,而是通过别的方式给出,这里就需要使用一些工具或者自己手工将这些参数提取出来。

pem文件针对此类文件可以直接使用openssl提取:

openssl   rsautl -encrypt -in FLAG -inkey public.pem -pubin -out flag.enc
openssl   rsa -pubin -text -modulus -in warmup -in public.pem

pcap文件:针对此类文件可以使用wireshark follow一下。这种问题一般都是写了一个交互的crypto系统,所以可能产生多轮交互。

PPC模式:这种模式是上述pcap文件的交互版,会给一个端口进行一些crypto的交互,参数会在交互中给出。

a. 最简单的RSA:

就是直接给出来了所需要的参数,直接使用扩展欧几里得算法计算出私钥,然后使用私钥解密:

p = 3487583947589437589237958723892346254777
q = 8767867843568934765983476584376578389
e = 65537
d = modinv(e, (p-1)*(q-1))
print d

b. 稍微复杂一点的譬如:http://ctf5.shiyanbar.com/crypto/RSAROLL.txt

{920139713,19}
 
704796792
752211152
274704164
18414022
368270835
483295235
263072905
459788476
483295235
459788476
663551792
475206804
459788476
428313374
475206804
459788476
425392137
704796792
458265677
341524652
483295235
534149509
425392137
428313374
425392137
341524652
458265677
263072905
483295235
828509797
341524652
425392137
475206804
428313374
483295235
475206804
459788476
306220148

解题思路是什么呢?

首先使用RSA-tool,输入n和e,点击Factor N(分解),得到p,q

v2-66a583141011181dc92b448f07bf4fb0_b.jpg

2. 再使用Calc.D,获得d

v2-307a520fef2d978199ecef875a3ba140_b.jpg

使用Big Integer Calculator;在Y、Z输入d和n。在X输入密文

v2-51ab6294180615030e99a1e9eb4f3c66_b.jpg

接下来就是对照ASCII码,翻译出明文:

上面解出的102对应的就是小写字母f。全部解完之后就是:

flag{13212je2ue28fy71w8u87y31r78eu1e2}

c. 直接分解

大数分解问题是困难的,但是可以通过计算机进行暴力分解。1999年,名为Cray的超级计算机用了5个月时间分解了512bit的n。2009年,一群研究人员成功分解了768bit的n。2010年,又提出了一些针对1024bit的n的分解的途径,但是没有正面分解成功。通常意义上来说,一般认为2048bit以上的n是安全的。现在一般的公钥证书都是4096bit的证书。

如果n比较小,那么可以通过工具进行直接n分解,从而得到私钥。如果n的大小小于256bit,那么我们通过本地工具即可爆破成功。例如采用windows平台的RSATool2v17,可以在几分钟内完成256bit的n的分解。

如果n在768bit或者更高,可以尝试使用一些在线的n分解网站,这些网站会存储一些已经分解成功的n,比如:http://factordb.com

通过在此类网站上查询n,如果可以分解或者之前分解成功过,那么可以直接得到p和q。然后利用前述方法求解得到密文。

识别:

此类问题一般是分值较小的题目,提取出n之后可以发现n的长度小于等于512bit,可以直接取分解n。如果大于512bit,建议在使用每个题目都用后面所说的方法去解题。

例题:

比如在某次竞赛中,发现:

n=87924348264132406875276140514499937145050893665602592992418171647042491658461

利用factordb分解:

v2-ed6b392886f79b5a5075f1455ce43672_b.png

d. 利用公约数

利用公约数

介绍:

如果在两次公钥的加密过程中使用的

具有相同的素因子,那么可以利用欧几里得算法直接将
分解。

通过欧几里得算法可以直接求出

的最大公约数p:

gcd(

,
)=p

可以得出:

=

=

直接分解成功。而欧几里得算法的时间复杂度为:O(log n)。这个时间复杂度即便是4096 bit也是秒破级别。

def gcd(a, b):
   if a < b:
     a, b = b, a
   while b != 0:
     temp = a % b
     a = b
     b = temp
   return a

识别此类题目,通常会发现题目给了若干个n,均不相同,并且都是2048bit,4096bit级别,无法正面硬杠,并且明文都没什么联系,e也一般取65537。 识别:

例题:

在一个题目中,你拿到了两个n,e都为65537,两个n分别为:

n1=905101396540408448287008786482145553515900869604295302196563108909579534883095438312732385327252896772931104
517960540769359266568331166058120488657114632772028845587492728112812111732357969120479239991310662754327445703
617245581480571566829370560367538687822094772218691411299045272217436371363029768515966932895152089193840345279
765068584952365819194741142906882973405374518046075860428305134433964142981937311236521173921616042049416707199
6438506850526168389386850499796102003625404245645796271690310748804327
n2=132259483961796038160620464187172147926685124136250915699975243642439959919610188941500592078240938374204513
752405503100502093989645063185189916201425759266237804115322572307019858216294257220306087220355706904741712592
381539470953103035228319716646660675426490344616217256562348690055012934239751847019297291700772802514362161672
930585600300890061402243754256795711817872069827124772614325795379812780557553445737670769517933120624802750045
64657590263719816033564139497109942073701755011873153205366238585665743

通过直接分解,上factordb都分解失败。通过尝试发现:

print gcd(n1,n2)

打印出:

1564859779720039565508870182569324208117555667917997801104862601098933699462849007879184203051278194180664616470
6695595753708683848203689301045600745388721992132362038223371869272758791395902487311486223628804714393104892281
47093224418374555428793546002109

则此致即为两个n共有的素因子p,然后进一步求出q,求解完毕。

e. Fermat方法与Pollard rho方法

介绍:

针对大整数的分解有很多种算法,性能上各有优异,有Fermat方法,Pollard rho方法,试除法,以及椭圆曲线法,连分数法,二次筛选法,数域分析法等等。其中一些方法应用在RSA的攻击上也有奇效。

在p,q的取值差异过大,或者p,q的取值过于相近的时候,Format方法与Pollard rho方法都可以很快将n分解成功。

此类分解方法有一个开源项目yafu将其自动化实现了,不论n的大小,只要p和q存在相差过大或者过近时,都可以通过yafu很快地分解成功。

识别:

在直接分解n无望,不能利用公约数分解n之后,都应该使用yafu去试一下。

这里讨论下Fermat方法。

Fermat的因子分解方法利用了这样一个事实,即任何奇数都可以表示为两个平方之间的差。 当因子接近于数字的平方根时,费马可以非常快速地起作用。 但是如果因子不是接近数字的平方根,则方法将非常缓慢。

首先简单看一下,为什么任何奇数都可以表示为两个平方之间的差。就是要证明:

因为这里,我们是想要对一个大数进行因子分解,所以

肯定可以写成两个因子的乘积。【如果是素数也无妨,因为可以写成
】假设写成:

这样的话,实际上就是可以求出来

。也即,

举例来看:

Fermat方法的目的就是通过找到

,来计算得出

我们可以从

的平方根的上整开始算起,计算
,如果能够得到一个平方,那么就找出来了相应的a和b。

举例来看:

对于91而言,取平方根的上整是10;

-91 = 9;刚好是平方数,所以

对于63而言,取平方根的上整是8;8的平方是64,所以

上面的两个例子都是分解之后的因子很接近大数的平方根,所以很快可以算出来。

再看一个例子:51

对于51而言,平方根上整为8。然后开始计算,8,9的平方减51都得不到平方数,一直到10。可以得到

如果继续做下去,还可以得到

此时,计算得出的a和b,相差仅为1,可以停止。

对于像5555389669094450920099599的大数,费马方法一次计算也行。

f. 低加密指数攻击

在RSA中e也称为加密指数。由于e是可以随意选取的,选取小一点的e可以缩短加密时间,但是选取不当的话,就会造成安全问题。

e=3时的小明文攻击

介绍:

当e=3时,如果明文过小,导致明文的三次方仍然小于n,那么通过直接对密文三次开方,即可得到明文。

关键代码如下:此题通过不断给明文+n开三次方即可求得:

i=0
   while 1:
   if(gmpy.root(c+i*N, 3)[1]==1):
     print gmpy.root(c+i*N, 3)
     break
   i=i+1

g. 共模攻击

共模攻击

介绍:

如果在RSA的使用中使用了相同的模n对相同的明文m进行了加密,那么就可以在不分解n的情况下还原出明文m的值。

即:

此时不需要分解n,不需要求解私钥,如果两个加密指数互素,就可以通过共模攻击在两个密文和公钥被嗅探到的情况下还原出明文m的值。

过程如下,首先两个加密指数互质,则:

即存在

使得:

又因为:

通过代入化简可以得出:

明文解出。

识别:

非常简单,若干次加密,每次n都一样,明文根据题意也一样即可。

如果已知:n1,n2,c1,c2,e1,e2,并且其中n1=n2的话:

s = egcd(e1, e2)
 s1 = s[1]
 s2 = s[2]
   print s
 n=n1
   if s1<0:
     s1 = - s1
     c1 = modinv(c1, n)
   elif s2<0:
     s2 = - s2
     c2 = modinv(c2, n)
 m=(pow(c1,s1,n)*pow(c2,s2,n)) % n

接下来讨论下Wiener's attack。

Dan Boneh于1999年发表了《二十年来针对RSA Cryptosystem的攻击》一文。文中有针对RSA的大量应用攻击。 其中一种是维纳(Wiener),它采用连续分数近似法(在某些条件下)有效地破坏RSA。

Continued fraction 连分数

v2-73bce034d5a21969fb9fca2e2ef1b8a9_b.jpg

连分数展开 continued fraction expansion:

[a0,a1,a2,...,an]

渐近分数 convergents:

v2-ddf711173f90eef166c36a77a6b1229b_b.jpg

例子:

v2-b061382736776b830983258bdf23fe53_b.jpg

以下算法使用分母n和分母d计算有理数的连续分数展开。

def cf_expansion(n, d):
    e = []

    q = n // d
    r = n % d
    e.append(q)

    while r != 0:
        n, d = d, r
        q = n // d
        r = n % d
        e.append(q)

    return e

我们知道

是一个无理数。 很酷的事情是,我们可以使用其连续的分数展开来形成
的简洁有理逼近。

v2-111cd67db054799bed43e9aa2926d47c_b.jpg

这些有理数

称为连分数的收敛。随着
的增加,
逐渐接近

下面的算法计算连分数的收敛

def convergents(e):
    n = [] # Nominators
    d = [] # Denominators

    for i in range(len(e)):
        if i == 0:
            ni = e[i]
            di = 1
        elif i == 1:
            ni = e[i]*e[i-1] + 1
            di = e[i]
        else: # i > 1
            ni = e[i]*n[i-1] + n[i-2]
            di = e[i]*d[i-1] + d[i-2]

        n.append(ni)
        d.append(di)
        yield (ni, di)

使用

来分解

v2-572242cce82fe687aa211c6575afee8b_b.jpg

这样的话,如果

已知,那么就可以直接解方程求出
了。问题是我们只知道
,而不知道
。为了求出
,要对
进行猜测。

因为

,所以以下等式成立:

v2-970578caeb9cc57d0311cec645997767_b.jpg

因此,

的有理近似。(因为
很小)。如果攻击者知道了

,那么就能够在它的收敛中找到

所以现在的问题是,能不能从

从找到

这就是Wiener定理。

如果:

v2-e2bce426f0024811150b39e876adfe5d_b.jpg

已知(e,N),由于

v2-be0ea82dbc006dbbe847cdca91a3eb71_b.jpg

, 攻击者可以从

的连分数的渐近分数(convergent)中找到正确的
,从而获得密钥d。

举例:

已知(N,e)=(90581,17993)

由Wiener's theorem可知,可以从

的连分数的渐近分数(convergent)中找到正确的
从而获得密钥d,求得
的连分数的渐近分数(convergent):

v2-8b2c10436793f075c0049b47a1b5f3c9_b.jpg

v2-1c2d1b6f01717a7ac63a0a80d509b0f4_b.png

循环k,d的值,求解方程:

v2-b0dda571f3250c7ed6901770c3866dcf_b.png

v2-ce6fd501d3ef9168b7bf275a6380bdbd_b.png

如果方程获得两个有效解x1,x2,则为p,q

本例中,当k=1,d=5时:

φ(n)=89964,

方程为:

v2-35b79ab043684d7bb54256a1600b3c22_b.jpg

x1=379, x2=293。

代码如下,首先生成一对弱密钥:

import gmpy2, random
from gmpy2 import isqrt, c_div
# Adapted from Hack.lu 2014 CTF

urandom = random.SystemRandom()

def get_prime(size):
    while True:
        r = urandom.getrandbits(size)
        if gmpy2.is_prime(r): # Miller-rabin
            return r

def test_key(N, e, d):
    msg = (N - 123) >> 7
    c = pow(msg, e, N)
    return pow(c, d, N) ==  msg

def create_keypair(size):
    while True:
        p = get_prime(size // 2)
        q = get_prime(size // 2)
        if q < p < 2*q:
            break

    N = p * q
    phi_N = (p - 1) * (q - 1)

    # Recall that: d < (N^(0.25))/3
    max_d = c_div(isqrt(isqrt(N)), 3)
    max_d_bits = max_d.bit_length() - 1

    while True:
        d = urandom.getrandbits(max_d_bits)
        try:
            e = int(gmpy2.invert(d, phi_N))
        except ZeroDivisionError:
            continue
        if (e * d) % phi_N == 1:
            break
    assert test_key(N, e, d)

    return  N, e, d, p, q

然后使用wiener定理进行攻击:

#!/usr/bin/python3
import cf, sys, hashlib
import vulnerable_key as vk
from sympy import *

def sha1(n):
    h = hashlib.sha1()
    h.update(str(n).encode('utf-8'))
    return h.hexdigest()

if __name__ == '__main__':
    N, e, d, p, q = vk.create_keypair(1024)
    print('[+] Generated an RSA keypair with a short private exponent.')
    print('[+] For brevity, keypair components are crypto. hashed:')
    print('[+] ++ SHA1(e):    ', sha1(e))
    print('[+] -- SHA1(d):    ', sha1(d))
    print('[+] ++ SHA1(N):    ', sha1(N))
    print('[+] -- SHA1(p):    ', sha1(p))
    print('[+] -- SHA1(q):    ', sha1(q))
    print('[+] -- SHA1(phiN): ', sha1((p - 1)*(q - 1)))
    print('[+] ------------------')

    cf_expansion = cf.get_cf_expansion(e, N)
    convergents = cf.get_convergents(cf_expansion)
    print('[+] Found the continued fractions expansion convergents of e/N.')

    print('[+] Iterating over convergents; '
            'Testing correctness through factorization.')
    print('[+] ...')
    for pk, pd in convergents: # pk - possible k, pd - possible d
        if pk == 0:
            continue;

        possible_phi = (e*pd - 1)//pk

        p = Symbol('p', integer=True)
        roots = solve(p**2 + (possible_phi - N - 1)*p + N, p)

        if len(roots) == 2:
            pp, pq = roots # pp - possible p, pq - possible q
            if pp*pq == N:
                print('[+] Factored N! :) derived keypair components:')
                print('[+] ++ SHA1(e):    ', sha1(e))
                print('[+] ++ SHA1(d):    ', sha1(pd))
                print('[+] ++ SHA1(N):    ', sha1(N))
                print('[+] ++ SHA1(p):    ', sha1(pp))
                print('[+] ++ SHA1(q):    ', sha1(pq))
                print('[+] ++ SHA1(phiN): ', sha1(possible_phi))
                sys.exit(0)

    print('[-] Wiener's Attack failed; Could not factor N')
    sys.exit(1)

以上代码参考自文献10.

参考:

  1. https://www.freebuf.com/articles/others-articles/161475.html
  2. https://blog.csdn.net/Zhou_ZiZi/article/details/84987851#1.%E6%89%93%E5%BC%80RSA-tool
  3. https://en.wikipedia.org/wiki/Wiener%27s_Attack
  4. https://www.anquanke.com/post/id/84632
  5. https://www.jianshu.com/p/dda528239554
  6. https://sagi.io/2016/04/crypto-classics-wieners-rsa-attack/
  7. https://www.iacr.org/archive/pkc2004/29470001/29470001.pdf
  8. https://blog.csdn.net/Zhou_ZiZi/article/details/84987851#1.%E6%89%93%E5%BC%80RSA-tool
  9. https://trans4mind.com/personal_development/mathematics/numberTheory/divisibilityFermat.htm
  10. https://sagi.io/2016/04/crypto-classics-wieners-rsa-attack/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值