Crypto刷题记录(二)

  1. 2022赣育杯–Wilson
    1. 考点
  • 一步到喂
    1. 考点
  • baby_reverse
  • easy_reverse
  • baby_math
    1. 考点
  • 2022赣育杯–Wilson

    题目:

    from os import urandom
    from gmpy2 import next_prime
    from Crypto.Util.number import getPrime, bytes_to_long
    
    p = getPrime(512)
    q = next_prime(p)
    f = open('flag.txt', 'rb')
    flag = bytes_to_long(f.read() + urandom(80))
    f.close()
    
    N = 1
    a = p * q
    for i in range(1, p):
        N = (N * i) % a
    e = 65537
    m = N * flag % a
    c = pow(m, e, a)
    f = open('Encode.txt', 'w')
    f.write(f'a = {a}\n')
    f.write(f'c = {c}\n')
    f.close()
    '''
    a = 156853895847604116708242664263151514811095704969640303272039451331791888050995073274981545693518063639560286348739938318495685137088495867703518198511200409009953879436648706837731243061114851474801565873584183542649886358523850682697732574913523360866915083642887238043256280849100274825940626065115676325169
    c = 3459715117165130065996389169943285249501133832272446001239391765859259811270526185228996906338576254353123756173289118671028939933226544773197852424767051933844004667155191851195814295922794480300237399956789038592856532530692732011427288405114650955620859282144504446058845961744702163836107847961388150810
    '''
    

    先想办法求出m来。因为p、q相近,所以对a开方得到p(或q)的近似值:$p_{near}$($p<p_{near}<q$),对$p_{near}逐步求nextprime$,直至$amodp_{near}==0$:

    a = 156853895847604116708242664263151514811095704969640303272039451331791888050995073274981545693518063639560286348739938318495685137088495867703518198511200409009953879436648706837731243061114851474801565873584183542649886358523850682697732574913523360866915083642887238043256280849100274825940626065115676325169
    c = 3459715117165130065996389169943285249501133832272446001239391765859259811270526185228996906338576254353123756173289118671028939933226544773197852424767051933844004667155191851195814295922794480300237399956789038592856532530692732011427288405114650955620859282144504446058845961744702163836107847961388150810
    
    p_near = iroot(a,2)[0]
    while True:
        q = next_prime(p_near)
        if a%q==0:
            print(q)
            break
    p = a // q
    

    根据Wilson定理($(p-1)! \equiv -1 \space mod \space p$),再结合题目的条件有:

    $m = (p-1)!flag = -flag\space mod\space p $

    $\Rightarrow flag = -m \space mod \space p$

    再由:

    $m \equiv (p-1)!flag \space mod \space (pq)$

    $两侧同乘上p(p+1)…q \Rightarrow$

    $mp(p+1)…q \equiv (q-1)!flag \space mod \space (pq)$

    $再用Wilson定理 \Rightarrow$

    $m^{‘} \equiv -flag \space mod \space q \Rightarrow$

    $flag \equiv -m^{‘} \space mod \space q$

    对比常规RSA里的$m=c^{e}modn$,可以看作已知两组c和n,利用中国剩余定理求m:

    def broadcast_attack(data):
        def extended_gcd(a,b):
            x,y = 0,1
            lastx,lasty = 1,0
            while b:
                a,(q,b) = b,divmod(a,b)
                x,lastx = lastx-q*x,x
                y,lasty = lasty-q*y,y
            return (lastx,lasty,a)
        def chinese_remaindor_theorem(items):
            N = 1
            for a,n in items:
                N *= n
            result = 0
            for a,n in items:
                m = N//n
                r,s,d = extended_gcd(n,m)
                if d != 1:
                    N = N//n
                    continue
                result += a*s*m
            return result%N ,N
        x, n = chinese_remaindor_theorem(data)
        return x
    m = pow(c,d,a)
    mp = -m % p
    mq = -m % q
    for i in range(p,q):
        mq = mq * i % q
    
    data = (mp,p),(mq,q)
    flag = broadcast_attack(data)
    
    print(long_to_bytes(flag))
    
    考点

    Wilson定理,剩余定理应用

    一步到喂

    from Crypto.Util.number import *
    from secret import flag,x,y
    p=getPrime(512)
    q=getPrime(512)
    n=p*q
    e=x
    assert 1293023064232431070902426583269468463*pow(x,2)==105279230912868770223946474836383391725923*pow(y,2)+1293023064232431070902426583269468463
    m=bytes_to_long(flag)
    c=pow(m,e,n)
    print(p,q)
    print(c)
    #9850212100620338486478343799784951721581757936621772924971218807300819701941457605399099898870264241518769370682330612103782092302148525012450902351701339
    #10749429992014823019923966246896511618886613763258781706004694804949547801668777655988055847885755337127548775758133804022361510427909703124161450470578543
    #66847321508502017574023222490247591875197038421108556106531660421662626233521063441647157067220450229816184622038471812597874582613385516838920632450015292570673816423432903604941781889308906893966588610214614726388822851471695742453496232748358301888465563812947038856742838097152549971517159475947566599664
    

    来自杭师大校赛。

    分析断言处,记为:
    $$
    num_1 * x^2 = num_2 * y^2 + num1
    \两边同除num_1,并记num_2/num_1为D
    \ \Rightarrow x^2-D*y^2 = 1
    $$
    通过了解,是Pell方程,还未成涉猎过,比赛中大致看了下,一顿搜才找到了个脚本(问就是菜没本事自己写😥),解出e来,之后便是基础RSA了。

    def pell_equation(D):
        a0 = int(D**0.5)
        a1 = int(2*a0/(D-a0**2))
        seq,P,Q = [a0,a1],[a0,a1*a0+1],[1,a1]
        m,d = a0,D-a0**2
        while seq[-1] != 2*a0:
            m = d*seq[-1]-m
            d = (D-m**2)//d
            seq.append(int((a0+m)/d))
            P.append(seq[-1]*P[-1]+P[-2])
            Q.append(seq[-1]*Q[-1]+Q[-2])
        if (len(seq)-1) % 2 == 0:
            return (P[-2],Q[-2])
        else:
            return (P[-2]**2+Q[-2]**2*D,2*P[-2]*Q[-2])
    
    res = pell_equation(81421)#D的值为81421
    print(res)
    
    考点

    Pell方程(有待进一步深入学习。。。)

    baby_reverse

    # python3
    from badmonkey import flag
    
    message = bytes_to_long(flag)
    
    enc = message ^ message>>23
    
    print("enc = ",enc)
    # enc = 696190889020604856890193198853561201348825165171326836202150445122902404137656377702109518236445
    

    从enc = message ^ (message>>23)入手,注意运算顺序。

    分析可知:$message$前23位与$enc$的前23位相同,$message$的23-46位与本身的前23位异或得到$enc$的23-46位。通过不断往低位推最终可以得到完整的$message$

    enc = ...
    tmp = enc
    for i in range(enc.bit_length()//23):
        tmp = enc ^ tmp >> 23
    print(long_to_bytes(tmp))
    

    easy_reverse

    from Crypto.Util.number import *
    from badmonkey import flag
    m = bytes_to_long(flag)
    
    BITS = m.bit_length()
    
    a,b,c,d = 15,27,19,23
    mask1 =  3698415493855604199601451107484163420720646551661606649093806737041154400431942060340531696401
    mask2 =  3460949362284136053271491912952156811286686375965162899233783178191434976143378804364416056910
    
    
    def encrypt(y):
        y = y ^ y >> a
        y = y ^ y << b & mask1
        y = y ^ y << c & mask2
        y = y ^ y >> d
        return y&((2<<BITS)-1)
    
    enc = encrypt(m)
    print("enc = ",enc)
    # enc = 1050202800288756594888207862919284086044293654918425636192291797436341243157293011540594049798
    

    思路与上一题大致相同,y&((2<<BITS)-1)的作用是对2^bits取模。

    a,b,c,d = 15,27,19,23
    mask1 =  ...
    mask2 =  ...
    
    BITS = mask1.bit_length()
    enc = ...
    
    def inverse_right(res, shift, BITS):
        tmp = res
        for i in range(BITS // shift):
            tmp = res ^ tmp >> shift
        return tmp
    
    def inverse_left_mask(res, shift, mask, BITS):
        tmp = res
        for i in range(BITS // shift):
            tmp = res ^ tmp << shift & mask
        return tmp
    
    def encrypt(y):
        y = y ^ y >> a
        y = y ^ y << b & mask1
        y = y ^ y << c & mask2
        y = y ^ y >> d
        return y&((2<<BITS)-1)
    
    def recover(y):
        y = inverse_right(y,d,BITS)
        y = inverse_left_mask(y,c,mask2,BITS)
        y = inverse_left_mask(y,b,mask1,BITS)
        y = inverse_right(y,a,BITS)
        return y&((2<<BITS)-1)
    
    flag = recover(enc)
    print(long_to_bytes(flag))
    

    baby_math

    # baby math
    from math import factorial
    from badmonkey import flag
    
    p =  6754809195983416889371518438404230464318771258362527973287334033265667200119099459184180646680169474847824019197256829826149222241090098845978570412659797
    
    def my_wilson(x,p):
        return factorial(x)%p
    
    res = my_wilson(p-2333,p)
    mid = md5(str(res).encode()).hexdigest()
    assert  mid == "a3fe2bd4fb7c2f3642d57d67967c649b"
    
    print("Spirit{{{}}}".format(res%(2**100)))
    

    有下面的关系:

    $res = (p-2333)! \space mod \space p$

    关键在求res,我们由威尔逊定理:$(p-1)! \equiv -1 mod p$

    $\Rightarrow (p-2)!(p-1) \equiv (p-1)mod p$

    $\Rightarrow(p-2)! \equiv 1mod p$

    $\Rightarrow (p-2)(p-3)…(p-2332)res \equiv 1 mod p$

    依次对每部分求逆即可:

    p =  ...
    
    res = 1
    for i in range(2,2333):
        res = res*inverse((p-i),p)
        res %= p
    mid = md5(str(res).encode()).hexdigest()
    assert mid == "a3fe2bd4fb7c2f3642d57d67967c649b"
    print("Spirit{{{}}}".format(res%(2**100)))
    
    考点

    威尔逊定理

    参考:JLU校赛 | badmonkey’s blog


    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1666739907@qq.com
    github