【密码学】密码学实验报告

这篇博客详细记录了密码学实验的过程,包括Many Time Pad、维吉尼亚密码的破解,以及两次实验中的各种密码攻击策略。在第三次实验中,作者深入探讨了RSA算法的共模攻击、低加密指数攻击、公因数攻击和费马分解法,成功破解了多个信息片段,并结合已知明文推测出完整的明文,展示了密码学攻击的实践应用。

密码学实验报告

第一次实验

任务1:破解一个Many Time Pad加密

思路:可以看注释~

(运行结果也一并放到代码里了,Shell部分就是)

__reference = 'https://github.com/Morrandir/Crypto001_Week1'

from string import ascii_letters
TARGET = '32510ba9babebbbefd001547a810e67149caee11d945cd7fc81a05e9f85aac650e9052ba6a8cd8257bf14d13e6f0a803b54fde9e77472dbff89d71b57bddef121336cb85ccb8f3315f4b52e301d16e9f52f904'
#使用TARGET与每条之前的MSGS进行异或
#原理:c(a)^c(b) == m(a) ^ m(b) ^ k ^ k == m(a) ^ m(b)
#以及空格和ascii中大小写字母异或的结果为反转其大小写
potential_str = ' ' + ascii_letters  #有意义的字符
MSGS = (
    '315c4eeaa8b5f8aaf9174145bf43e1784b8fa00dc71d885a804e5ee9fa40b16349c146fb778cdf2d3aff021dfff5b403b510d0d0455468aeb98622b137dae857553ccd8883a7bc37520e06e515d22c954eba5025b8cc57ee59418ce7dc6bc41556bdb36bbca3e8774301fbcaa3b83b220809560987815f65286764703de0f3d524400a19b159610b11ef3e',
    '234c02ecbbfbafa3ed18510abd11fa724fcda2018a1a8342cf064bbde548b12b07df44ba7191d9606ef4081ffde5ad46a5069d9f7f543bedb9c861bf29c7e205132eda9382b0bc2c5c4b45f919cf3a9f1cb74151f6d551f4480c82b2cb24cc5b028aa76eb7b4ab24171ab3cdadb8356f',
    '32510ba9a7b2bba9b8005d43a304b5714cc0bb0c8a34884dd91304b8ad40b62b07df44ba6e9d8a2368e51d04e0e7b207b70b9b8261112bacb6c866a232dfe257527dc29398f5f3251a0d47e503c66e935de81230b59b7afb5f41afa8d661cb',
    '32510ba9aab2a8a4fd06414fb517b5605cc0aa0dc91a8908c2064ba8ad5ea06a029056f47a8ad3306ef5021eafe1ac01a81197847a5c68a1b78769a37bc8f4575432c198ccb4ef63590256e305cd3a9544ee4160ead45aef520489e7da7d835402bca670bda8eb775200b8dabbba246b130f040d8ec6447e2c767f3d30ed81ea2e4c1404e1315a1010e7229be6636aaa',
    '3f561ba9adb4b6ebec54424ba317b564418fac0dd35f8c08d31a1fe9e24fe56808c213f17c81d9607cee021dafe1e001b21ade877a5e68bea88d61b93ac5ee0d562e8e9582f5ef375f0a4ae20ed86e935de81230b59b73fb4302cd95d770c65b40aaa065f2a5e33a5a0bb5dcaba43722130f042f8ec85b7c2070',
    '32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd2061bbde24eb76a19d84aba34d8de287be84d07e7e9a30ee714979c7e1123a8bd9822a33ecaf512472e8e8f8db3f9635c1949e640c621854eba0d79eccf52ff111284b4cc61d11902aebc66f2b2e436434eacc0aba938220b084800c2ca4e693522643573b2c4ce35050b0cf774201f0fe52ac9f26d71b6cf61a711cc229f77ace7aa88a2f19983122b11be87a59c355d25f8e4',
    '32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd90f1fa6ea5ba47b01c909ba7696cf606ef40c04afe1ac0aa8148dd066592ded9f8774b529c7ea125d298e8883f5e9305f4b44f915cb2bd05af51373fd9b4af511039fa2d96f83414aaaf261bda2e97b170fb5cce2a53e675c154c0d9681596934777e2275b381ce2e40582afe67650b13e72287ff2270abcf73bb028932836fbdecfecee0a3b894473c1bbeb6b4913a536ce4f9b13f1efff71ea313c8661dd9a4ce',
    '315c4eeaa8b5f8bffd11155ea506b56041c6a00c8a08854dd21a4bbde54ce56801d943ba708b8a3574f40c00fff9e00fa1439fd0654327a3bfc860b92f89ee04132ecb9298f5fd2d5e4b45e40ecc3b9d59e9417df7c95bba410e9aa2ca24c5474da2f276baa3ac325918b2daada43d6712150441c2e04f6565517f317da9d3',
    '271946f9bbb2aeadec111841a81abc300ecaa01bd8069d5cc91005e9fe4aad6e04d513e96d99de2569bc5e50eeeca709b50a8a987f4264edb6896fb537d0a716132ddc938fb0f836480e06ed0fcd6e9759f40462f9cf57f4564186a2c1778f1543efa270bda5e933421cbe88a4a52222190f471e9bd15f652b653b7071aec59a2705081ffe72651d08f822c9ed6d76e48b63ab15d0208573a7eef027',
    '466d06ece998b7a2fb1d464fed2ced7641ddaa3cc31c9941cf110abbf409ed39598005b3399ccfafb61d0315fca0a314be138a9f32503bedac8067f03adbf3575c3b8edc9ba7f537530541ab0f9f3cd04ff50d66f1d559ba520e89a2cb2a83'
)

CRACK = 'The secret message is: When using a stream cipher, never use the key more than once'
#根据异或结果,我们可以推断出,结果大概为:
#The secuet message is Whtn usinw a stream cipher never use the key more than once
#因为密钥为一串有意义的字符,我们可以推断出,其真正含义大概为:
#The secret message is When using a stream cipher never use the key more than once


def strxor(a, b):     # xor two strings of different lengths,因为源代码给的是py2的,这里修改了下改成了py3的
    if len(a) > len(b):
        return "".join([chr(x ^ y) for (x, y) in zip(a[:len(b)], b)])
    else:
        return "".join([chr(ord(x) ^ y) for (x, y) in zip(a, b[:len(a)])])



for MSG in MSGS:
    result = strxor(bytes.fromhex(MSG), bytes.fromhex(TARGET))
    for j in range(0, len(bytes.fromhex(TARGET))):
        if result[j] in potential_str:
            print(result[j],end='')
        else:
            print('*',end='')
    print('')

key = strxor(CRACK, bytes.fromhex(TARGET))

for MSG in MSGS:
    result = strxor(key, bytes.fromhex(MSG))
    print(result)
keyint = ''
for i in range(0,len(key)):
    keyint+=str(ord(key[i]))
Eighth = strxor(key,bytes.fromhex(MSGS[7]))
print(f"key={keyint}\n8th message={Eighth}")
'''
shell:
**EC**C***T**S***EN**XE*HT******GQ*A****A*O********N**E*A*S*L**EF***O*O**ET***B**CT
***E*E****DM******L*S*N***NT***N*O*****E**E****E*IC****RAU**R*******N*O*******T*NNE
********E*H***S***U*SqE****QU**N*O****R***P******DE**V**NU**I**EAK**TM**EF*********
**********T***S***D***Dw**NAU******N******O*I*****I***E*O******EG******R*I****T***E
*******U*TW***S**EB***Aw******I**RAK***E**O*I*H**U****E*P***A***E*E*NM***A*********
***R*E***TT**S****SI*******T*****H***T**********R*I**V**E*S*E***T*E*A**R*R**A*O**C*
***R*E***TT**S****SI*******O*****Y*****E**A*I*****SN***Rg***R***N*E*OM********EO***
**EC**C*******S***N*SMH***NT**I**I****R***A***H***AN****GU**TT******TM********U***E
*HMP**********ZAG*N**CP**********EAS*****M*C*****ET***IRN***L*H*****C****ET********
t**ES*****S*E*****D**YT****R*SA*W*W*S*****N**P****T*E**RT**EA**EO*EYW****N*H*NRO***
We can factor the number 15 with quantum computers. We can also factor the number 1
Euler would probably enjoy that now his theorem becomes a corner stone of crypto -
The nice thing about Keeyloq is now we cryptographers can drive a lot of fancy cars
The ciphertext produced by a weak encryption algorithm looks as good as ciphertext
You don't want to buy a set of car keys from a guy who specializes in stealing cars
There are two types of cryptography - that which will keep secrets safe from your l
There are two types of cyptography: one that allows the Government to use brute for
We can see the point where the chip is unhappy if a wrong bit is sent and consumes
A (private-key)  encryption scheme states 3 algorithms, namely a procedure for gene
 The Concise OxfordDictionary (2006) deï¬nes crypto as the art of  writing o r sol
key=1025711013720121921620415211653422059914916461752061201701272374016012710720114141197111051765115425248170642615610911214312819210219999254240184972205216232220891169135119519317425223621315667581073813996191782406015497
8th message=We can see the point where the chip is unhappy if a wrong bit is sent and consumes
'''

任务2:破解维吉尼亚密码,和任务3思路很相似

思路:写在代码注释里~(还注释掉了一部分另一种思路的做法)

import string
import re
cipherlist = []
testkeys = []
for i in range(0x00,0xff):
    testkeys.append(i)
#加入所有可能密钥
potentialchars = test_chars = string.ascii_letters + string.digits + ',' + '.' + ' '
re__ = '[A-Za-z0-9,. ]'
def findindexkey(arr):
    global testchars
    global testkeys
    anskeys = list(testkeys)#不要使用anskeys =testkeys,python复习了属于是
    for i in testkeys:
        for s in arr:
            if chr(s ^ i) not in potentialchars:
                anskeys.remove(i)
                break
            #if re.search(re__,str(chr(s ^ i))):
            #    pass
            #else:
            #    anskeys.remove(i)
            #    break
            #使用正则表达式的实现方法
    return anskeys
#找出所有可能密钥,通过删除实现


text = 'F96DE8C227A259C87EE1DA2AED57C93FE5DA36ED4EC87EF2C63AAE5B9A7EFFD673BE4ACF7BE8923CAB1ECE7AF2DA3DA44FCF7AE29235A24C963FF0DF3CA3599A70E5DA36BF1ECE77F8DC34BE129A6CF4D126BF5B9A7CFEDF3EB850D37CF0C63AA2509A76FF9227A55B9A6FE3D720A850D97AB1DD35ED5FCE6BF0D138A84CC931B1F121B44ECE70F6C032BD56C33FF9D320ED5CDF7AFF9226BE5BDE3FF7DD21ED56CF71F5C036A94D963FF8D473A351CE3FE5DA3CB84DDB71F5C17FED51DC3FE8D732BF4D963FF3C727ED4AC87EF5DB27A451D47EFD9230BF47CA6BFEC12ABE4ADF72E29224A84CDF3FF5D720A459D47AF59232A35A9A7AE7D33FB85FCE7AF5923AA31EDB3FF7D33ABF52C33FF0D673A551D93FFCD33DA35BC831B1F43CBF1EDF67F0DF23A15B963FE5DA36ED68D378F4DC36BF5B9A7AFFD121B44ECE76FEDC73BE5DD27AFCD773BA5FC93FE5DA3CB859D26BB1C63CED5CDF3FE2D730B84CDF3FF7DD21ED5ADF7CF0D636BE1EDB79E5D721ED57CE3FE6D320ED57D469F4DC27A85A963FF3C727ED49DF3FFFDD24ED55D470E69E73AC50DE3FE5DA3ABE1EDF67F4C030A44DDF3FF5D73EA250C96BE3D327A84D963FE5DA32B91ED36BB1D132A31ED87AB1D021A255DF71B1C436BF479A7AF0C13AA14794'
for i in range(0,len(text),2):
    cipherlist.append(int(text[i:i+2],16))

for keylenth in range(1,12):
    for index in range(0,keylenth):
        modedlist = cipherlist[index::keylenth]
        anskeys = findindexkey(modedlist)
        #print(f"anskeys={anskeys},index is {index},and at this time the len is {keylenth}")
#此时发现只有7的时候有anskeys结果出现,结果为
'''
anskeys=[186],index is 0,and at this time the len is 7
anskeys=[31],index is 1,and at this time the len is 7
anskeys=[145],index is 2,and at this time the len is 7
anskeys=[178],index is 3,and at this time the len is 7
anskeys=[83],index is 4,and at this time the len is 7
anskeys=[205],index is 5,and at this time the len is 7
anskeys=[62],index is 6,and at this time the len is 7
'''
key = [186,31,145,178,83,205,62]
origin = ''
for i in range(0,len(cipherlist)):
    origin = origin + chr(cipherlist[i] ^ key[i%7])
print(origin)

运行结果:

在这里插入图片描述

任务3:给出一串用维吉尼亚密码加密后的密文,要求使用重合指数法获得明文。

http://www.cryptopals.com/sets/1

思路:先求密钥长度,看密钥可能的长度是多少,然后分片分成每个段的[0],[1]…的位置,依次进行试验,使得每个子段进行维吉尼亚解密后的结果处于正常英文的ASCII序号列表里,更好点的还可以通过进行字母出现频率判断来进一步缩小答案范围,最终范围只有一个。


import base64
import binascii

cipherbase64 = b'HUIfTQsPAh9PE048GmllH0kcDk4TAQsHThsBFkU2AB4BSWQgVB0\dQzNTTmVS\
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'
cipher = base64.b64decode(cipherbase64)
cipher64 = binascii.b2a_hex(base64.b64decode(cipherbase64))
cipherlist = []
for i in range(0,len(cipher64),2):
    cipherlist.append(int(cipher64[i:i+2],16))
#print(cipherlist)
#cipherlist是一个list,里面每项为字符ASCII的int值
dict1 = {}
def HammingDistance(list1,list2):
    string = ''
    for i in range(0,len(list1)):
        a = bin(list1[i]^list2[i])
        string+=a[2:]
    return string.count('1')

def hamdistenceaverage(mlist):
  count = 0
  for j in mlist:
      for k in mlist:
        if j != k:
          count += HammingDistance(j,k) / i
  return count
  

for i in range(2,41):
    firstm = cipherlist[0:i]
    secondm = cipherlist[i:2*i]
    thirdm = cipherlist[2*i:3*i]
    fouthm = cipherlist[3*i:4*i]
    mlist = [firstm,secondm,thirdm,fouthm]
    count = hamdistenceaverage(mlist)
    dict1[f'{i}'] = count
#此部分用于找KEYSIZE

print(dict1)
list1= sorted(dict1.items(),key=lambda x:x[1])
print(list1)

OptionalKEYSIZE = [29,5,2,24]
'''
[('29', 32.96551724137932), ('5', 34.8), ('2', 36.0), ('24', 36.25), ('7', 36.85714285714286), ('6', 37.0), ('19', 37.1578947368421), ('20', 37.2), 
('3', 37.333333333333336), ('8', 37.5), ('28', 37.85714285714286), ('30', 38.13333333333333), ('34', 38.1764705882353), ('9', 38.22222222222222), ('39', 38.35897435897436), ('10', 38.4), ('17', 38.47058823529411), ('16', 38.5), ('40', 38.6), ('18', 39.0), ('26', 39.0), ('37', 39.027027027027025), ('32', 39.125), ('23', 39.130434782608695), ('38', 39.21052631578947), ('15', 39.33333333333333), ('33', 39.45454545454546), ('21', 39.61904761904762), ('35', 39.65714285714286), ('31', 39.74193548387096), ('25', 39.76), ('13', 39.84615384615385), ('4', 40.0), ('14', 40.142857142857146), ('27', 40.148148148148145), ('22', 40.54545454545455), ('36', 40.611111111111114), ('11', 41.09090909090909), ('12', 41.5)]
'''

def singlekey_xor(cipher):
    '''get the most probable key byte
    cipher: encrypted by the same byte of key(bytes)
    return: the byte of key(bytes)'''
    result = -1
    for i in range(256):
        tempmsg = bytes([i ^ x for x in cipher])
        #tempresult = score(tempmsg.decode('latin1'))
        tempresult = ChiSquared(tempmsg.decode('latin1') , 1)
        if tempresult > result:#比较如果score更大则几率更大
            result = tempresult
            key = bytes([i])
    return key
def break_key_reuse_xor(msg):#显示所有可用的keysize对应密文,词典形式排序
    '''msg: bytes
    return a dic {keysize : key},keysize is int, key is bytes'''
    key_size = OptionalKEYSIZE
    key = []
    for i in key_size:
        tempkey = b''
        for j in range(i):
            tempkey += singlekey_xor(msg[j::i])
        key.append(tempkey)
    return key
  
def ChiSquared(msg , mod):#重要!使用这个函数进行score排序,不然返回的将是一大堆列表,都可以
    '''Chi-squared Statistic
    msg: str
    mod: 1 is simply plus, 0 is multiple'''
    standard_letter_frequency = {  # From https://en.wikipedia.org/wiki/Letter_frequency.
    'e': 0.12702,'t': 0.09056,'a': 0.08167,'o': 0.07507,'i': 0.06966,'n': 0.06749,
    's': 0.06327,'h': 0.06094,'r': 0.05987,'d': 0.04253,'l': 0.04025,'c': 0.02782,
    'u': 0.02758,'m': 0.02406,'w': 0.02360,'f': 0.02228,'g': 0.02015,'y': 0.01974,
    'p': 0.01929,'b': 0.01492,'v': 0.00978,'k': 0.00772,'j': 0.00153,'x': 0.00150,
    'q': 0.00095,'z': 0.00074,' ': 0.25000}
    score = 0
    msg = msg.lower()
    if mod:
        for i in msg:
            if i in standard_letter_frequency:
                score += standard_letter_frequency[i]
    else:
        msg = msg.lower()
        for i in standard_letter_frequency:
            temp = standard_letter_frequency[i] * len(msg)
            score += (msg.count(i)- temp)**2 / temp
    return score

#均以bytes类型,防止串类型做不出来
XOR = lambda s1 , s2 : bytes([x1 ^ x2 for x1 , x2 in zip(s1 , s2)])
key = break_key_reuse_xor(cipher)
print(key)#可以看出key[0]有意义其余无意义
key = key[0]*len(cipher)
print(XOR(key , cipher))

运行结果:

在这里插入图片描述

任务4:用户输入密码所使用的按键,告诉你密码的长度和密码SHA1之后的值,让你求密码的值。

MTC3 Cracking SHA1- HashedPasswords
https://www.mysterytwisterc3.org/en/challenges/level-2/cracking-sha1-hashed-passwords

思想:就是暴力枚举,看哪个算出来SHA1和目标SHA1一样就行了

#coding:utf-8
import hashlib
import itertools
import datetime

hash1="67ae1a64661ac8b4494666f58c4822408dd0a3e4"
str1="QqWw%58(=0Ii*+nN"
str2=[['Q', 'q'],[ 'W', 'w'],[ '%', '5'], ['8', '('],[ '=', '0'], ['I', 'i'], ['*', '+'], ['n', 'N']]
def sha_encrypt(str):
    hash = hashlib.sha1()
    hash.update(str.encode('utf-8'))
    encrypts = hash.hexdigest()
    return encrypts
st3="0"*8
str4=""
str3=list(st3)
starttime = datetime.datetime.now()
for a in range(0,2):
    str3[0]=str2[0][a]
    for b in range(0,2):
        str3[1]=str2[1][b]
        for c in range(0,2):
            str3[2]=str2[2][c]
            for d in range(0,2):
                str3[3] = str2[3][d]
                for e in range(0,2):
                    str3[4] = str2[4][e]
                    for f in range(0,2):
                        str3[5] = str2[5][f]
                        for g in range(0,2):
                            str3[6] = str2[6][g]
                            for h in range(0,2):
                                str3[7] = str2[7][h]
                                newS="".join(str3)
                                for i in itertools.permutations(newS, 8):
                                    str4 = sha_encrypt("".join(i))
                                    if str4==hash1:
                                        print(("".join(i)))
                                        endtime = datetime.datetime.now()
                                        print(((endtime - starttime).seconds))

运行结果:

在这里插入图片描述

第二次实验

任务1:根据错误的Padding获得信息

PA2 option
AES decrypt a challenge cipher text generated using AES in CBC-mode with PKCS#5 padding.
connect(('128.8.130.16', 49101)))
思路:CBC 模式是一种分组链接模式,目的是为了使原本独立的分组密码加密过程形成迭代,使每次加密的结果影响到下一次加密。初始化向量 IV 与第一组明文(分组 1)异或后,再经过运算得到的结果作为新的 IV,用于下一分组(分组 2),不断迭代下去。

而在实际运用中,明文的长度并不会刚好为 16 字节的倍数,这时,分组的最后一块的长度必然小于 16 字节。这时,就需要使用填充(padding)。题目给的是 PKCS #7 填充方式,规则是需要填充 n 个字节,那么这 n 个填充位就填上 n 的 16 进制数。如 缺少 1 位(1 个 Padding), 填充位为: 0x01 缺少 5 位(5 个 Padding), 填充位为: 0x05 0x05 0x05 0x05 0x05

当我们提交 IV+密文到服务端时,服务端便会按照字符串的第一组为 IV,之后为密文进行解密。如此题中: IV: 9F0B13944841A832B2421B9EAF6D9836 C_Block1:813EC9D944A5C8347A7CA69AA34D8DC0 C_Block2:DF70E343C4000A2AE35874CE75E64C31

解密后,若最后的 Padding 不正确(值和数量不一致),则解密程序往往会抛出异常(Padding Error)。在这题里就是返回 0。而利用此回显,我们就可以判断出 Paddig 是否正确
在这里插入图片描述
可以看到,padding 为 0x17,却只填充了一位,那么这种情况下,服务端就会返回 0。 接下来要利用选择密文攻击的思想,不断调整,修正 IV。来对 IValue 进行猜测。不断地调整 IV 的值,目的是解密后,最后一个字节的值为正确的 Padding。第一次进行穷举时,是 0×01。因为 IValue 是固定的(未知),所以从 0×00~0xFF 之间,只可能有一个 IV 的值与 IValue 的最后一个字节进行 XOR 后,结果是 0×01。 在 CBC 模式下进行解密时,第一组密文还原成明文时需要先用密钥解密得到一个中间结果(记为 IVALUE),然后将 IVALUE 与 IV 进行异或得到明文 而我们通过穷举 IV,利用服务端检测 padding 是否正确的返回值来判断 IV 是否正确,一旦正确,就立即可以通过 IV 异或 padding 来求得 IVALUE 值。
参考资料:https://www.tr0y.wang/2017/10/06/Crypto1/

友情提示:下面这个是python2,因为python3对于某些字符的识别和python2差别比较大,python3进行识别总是报错,最终无奈选择了python2

# -*- coding: cp936 -*-
from oracle import *
from Crypto.Util import strxor
import re

C = '9F0B13944841A832B2421B9EAF6D9836813EC9D944A5C8347A7CA69AA34D8DC0DF70E343C4000A2AE35874CE75E64C31'
BLOCK = 2
div = len(C) / (BLOCK + 1)
C = re.findall('.{' + str(div) + '}', C)

Oracle_Connect()
M = []
IVALUE = []
for b in range(BLOCK): #对 2 组密文分别求解
    print '[*] Detecting Block',b+1
    IV = C[b]
    Ivalue = []
    iv = '00000000000000000000000000000000' #初始化 iv
    iv = re.findall('.{2}', iv)[::-1]
    padding = 1

    for l in range(16):
        print "  [+] Detecting IVALUE's last", l + 1 , 'block'
        for ll in range(l):
            iv[ll] = hex(int(Ivalue[ll], 16) ^ padding)[2:].zfill(2) #更新 iv

        for n in range(256): #遍历 0x00-0xFF
            iv[l] = hex(n)[2:].zfill(2)
            data = ''.join(iv[::-1]) + C[b + 1]
            
            ctext = [(int(data[i:i + 2], 16)) for i in range(0, len(data), 2)]
            rc = Oracle_Send(ctext, 2)
            
            if str(rc) == '1': #Padding 正确时, 记录 Ivalue, 结束爆破
                Ivalue += [hex(n ^ padding)[2:].zfill(2)]
                break

        print '    [-]', ''.join(iv[::-1])
        print '    [-]', ''.join(Ivalue[::-1])
        
        padding += 1

    Ivalue = ''.join(Ivalue[::-1])
    IVALUE += [Ivalue]

    #IV 与 Ivalue 异或求密文
    m = re.findall('[0-9a-f]+', str(hex(int(IV, 16) ^ int(''.join(Ivalue), 16))))[1].decode('hex')
    M += [m]

    print '[#] Detecting Block', b + 1 ,'-- Done!'
    print '[#]', 'The IValue' + str(b + 1), 'is:', Ivalue
    print '[#]', 'The M' + str(b + 1) , 'is:', m
    print '-' * 50
    
Oracle_Disconnect()

print '[!] The Intermediary Value is:', ''.join(IVALUE)
print '[!] The M is:', ''.join(M)


运行结果:

在这里插入图片描述

任务2:Crptopals Sets2

Question 2 cryptopals
http://www.cryptopals.com/sets/2

  1. Implement PKCS#7 padding

  2. Implement CBC mode

  3. An ECB/CBC detection oracle4. Byte-at-a-time ECB decryption
    (Simple)

  4. ECB cut-and-paste

  5. Byte-at-a-time ECB decryption
    (Harder)

  6. PKCS#7 padding validation8. CBC bit flipping attacks
    上传6,7,8的代码截图

6.破解ECB,通过在秘密前面加上特定数量的字节,将秘密的第一个未知字节恰好“顶”到一块的最后一个字节,的到一个加密串。另一个则根据已经求出的秘密前缀将暴力猜测的字节“顶”到同样的位置,保证本字节之前的字节和上一个串是一样的。这样通过比较本块的加密结果是否一样就可以暴出这一字节。

from base64 import b64decode
from typing_extensions import ParamSpecKwargs
from S2C10 import aes_ecb_encrypt
from S2C09 import pkcs7_unpad
from random import randint
from Crypto import Random
from S1C08 import count_aes_ecb_repetitions
from S2C12 import find_block_length, ECBOracle


class HarderECBOracle(ECBOracle):

    def __init__(self, secret_padding):
        super(HarderECBOracle, self).__init__(secret_padding)
        self._random_prefix = Random.new().read(randint(0, 255))

    def encrypt(self, data):
        """Encrypts with AES-128-ECB mode, after prepending a fixed (randomly-generated) string
        and appending a fixed (given) string to every plaintext.
        使用AES-128-ECB模式进行加密
        """
        return aes_ecb_encrypt(self._random_prefix + data + self._secret_padding, self._key)


def get_next_byte(prefix_length, block_length, curr_decrypted_message, encryption_oracle):
    """Finds the next byte of the mysterious message that the oracle
    is appending to our plaintext.
    寻找下一块密文并且将预言加到明文上
    """

    # Compute the number of characters that we want to use as input to have the first unknown
    # character of the mysterious message at the end of a block
    length_to_use = (block_length - prefix_length - (1 + len(curr_decrypted_message))) % block_length
    my_input = b'A' * length_to_use

    # Compute the number of bytes that we will take from the fake and from the real ciphertexts
    # to compare them. We will ignore everything is beyond the byte we are trying to discover.
    cracking_length = prefix_length + length_to_use + len(curr_decrypted_message) + 1

    # Compute the real ciphertext that the oracle would output with my input
    real_ciphertext = encryption_oracle.encrypt(my_input)

    # For each possible character
    for i in range(256):

        # Compute our fake ciphertext, trying to obtain the same as the real ciphertext
        fake_ciphertext = encryption_oracle.encrypt(my_input + curr_decrypted_message + bytes([i]))

        # If we found a character that, used in our fake input, let us obtain the same ciphertext
        if fake_ciphertext[:cracking_length] == real_ciphertext[:cracking_length]:

            # Return that character as the next byte of the message
            return bytes([i])

    # If there was no match (most likely due to padding), return empty byte
    return b''


def has_equal_block(ciphertext, block_length):
    """Checks if the given ciphertext contains two consecutive equal blocks
    查看给予的密文有没有两个相同的块,这里是一串一串一位一位的算"""
    for i in range(0, len(ciphertext) - 1, block_length):
        if ciphertext[i:i+block_length] == ciphertext[i+block_length:i+2*block_length]:
            return True

    return False


def find_prefix_length(encryption_oracle, block_length):
    """Finds the length of the randomly generated prefix that the encryption oracle
    adds to every plaintext before encrypting. First, the block where the prefix ends
    is searched; then the precise index where the prefix ends is searched.
    """

    # To find the index of the block where the prefix ends, we use the oracle to encrypt
    # an empty message and a 1 character message. Then we use them to find it as explained below.
    ciphertext1 = encryption_oracle.encrypt(b'')
    ciphertext2 = encryption_oracle.encrypt(b'a')

    # The first block where the two ciphertexts differ will be the block where the
    # prefix (which was the same for both the inputs) ended.
    prefix_length = 0
    for i in range(0, len(ciphertext2), block_length):
        if ciphertext1[i:i+block_length] != ciphertext2[i:i+block_length]:
            prefix_length = i
            break

    # Now, to find the precise index where the prefix ended, we will encrypt identical bytes,
    # in a number equal to two block_lengths, and we will increase this amount by an incremental
    # offset to see when those bytes will be shifted to be autonomous blocks (thus encrypted the same way)
    for i in range(block_length):
        fake_input = bytes([0] * (2 * block_length + i))
        ciphertext = encryption_oracle.encrypt(fake_input)

        # If the bytes have shifted enough, we can compute the precise index where the prefix ends
        # inside its last block, which is going to be equal to block_length - i
        if has_equal_block(ciphertext, block_length):
            return prefix_length + block_length - i if i != 0 else prefix_length

    raise Exception('The oracle is not using ECB')


def byte_at_a_time_ecb_decryption_harder(encryption_oracle):
    """Performs the byte-at-a-time ECB decryption attack to discover the secret padding used by the oracle."""

    # Find the block length
    block_length = find_block_length(encryption_oracle)

    # To detect if the oracle encrypts with ECB mode, we can encrypt a big enough (more
    # than three block sizes) plaintext of identical bytes. If the ciphertext presents
    # repeated blocks then we can deduct that it is very likely using ECB.
    ciphertext = encryption_oracle.encrypt(bytes([0] * 64))
    assert count_aes_ecb_repetitions(ciphertext) > 0

    # The number of bytes that we have to decrypt by breaking the encryption oracle
    # will be equal to the length of the ciphertext when we encrypt an empty message
    # subtracted by the length of the prefix (which we have to find).
    prefix_length = find_prefix_length(encryption_oracle, block_length)
    mysterious_text_length = len(encryption_oracle.encrypt(b'')) - prefix_length

    # At this point we have all the information that we need to crack the ECB
    # encryption oracle byte by byte.
    secret_padding = b''
    for i in range(mysterious_text_length):
        secret_padding += get_next_byte(prefix_length, block_length, secret_padding, encryption_oracle)

    # Return the complete padding as bytes
    return secret_padding


def main():
    """Approach:
    1) Find the block_length and the encryption mode (as in S2C12)
    2) Find the prefix length
    3) Decrypt byte-by-byte the mysterious message (similar to S2C12)
    方法:我们通过控制AAAAA*n字符串的长度,慢慢更改A的长度,使得密文中每个字符慢慢暴露出来在另一个块
    中,这样就能一次一个字符的进行爆破,大大降低了计算量
    """
    secret_padding = b64decode("Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkgaGF"
                               "pciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdXN0IH"
                               "RvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUgYnkK")
    oracle = HarderECBOracle(secret_padding)
    discovered_secret_padding = byte_at_a_time_ecb_decryption_harder(oracle)

    # Check if the attack works correctly
    print(secret_padding)
    print(discovered_secret_padding)
    print(pkcs7_unpad(discovered_secret_padding))
    assert pkcs7_unpad(discovered_secret_padding) == secret_padding


if __name__ == '__main__':
    main()

在这里插入图片描述

7.一个简单的Padding Check,这个只是一个中间步骤,其实这个题的目的是要后面用这个Padding Check的函数要调用的

def check_padding(s: bytes):
	padding = s[-s[-1]:]
	for i in padding:
		if not i == len(padding):
			return False
	return True
 
print(check_padding(b'ICE ICE BABY\x04\x04\x04\x04'))
print(check_padding(b'ICE ICE BABY\x05\x05\x05\x05'))
print(check_padding(b'ICE ICE BABY\x01\x02\x03\x04'))
print(check_padding(b'ICE ICE BABY'))

在这里插入图片描述

8.这是CBC的比特翻转攻击,我们可以通过添加很长的data构造出一个纯没有意义的块,然后异或这一块的某个密文字节,这样下一块的明文字节在解密时就会被篡改

from S2C10 import aes_cbc_encrypt, aes_cbc_decrypt
from Crypto import Random
from Crypto.Cipher import AES


class Oracle:

    def __init__(self):
        self._key = Random.new().read(AES.key_size[0])
        self._iv = Random.new().read(AES.block_size)
        self._prefix = "comment1=cooking%20MCs;userdata="
        self._suffix = ";comment2=%20like%20a%20pound%20of%20bacon"

    def encrypt(self, data):
        """Adds the prefix and the suffix specified in the challenge and encrypts the data with AES-128-CBC"""
        data = data.replace(';', '').replace('=', '')  # Remove special characters to avoid injection
        plaintext = (self._prefix + data + self._suffix).encode()
        return aes_cbc_encrypt(plaintext, self._key, self._iv)

    def decrypt_and_check_admin(self, ciphertext):
        """Decrypts the string and returns whether the characters ";admin=true;" are in the string"""
        data = aes_cbc_decrypt(ciphertext, self._key, self._iv)
        return b';admin=true;' in data


def find_block_length(encryption_oracle):
    """Returns the length of a block for the block cipher used by the encryption_oracle.
    To find the length of a block, we encrypt increasingly longer plaintexts until the size of the
    output ciphertext increases too. When this happens, we can then easily compute the length of a block
    as the difference between this new length of the ciphertext and its initial one.
    """
    my_text = ''
    ciphertext = encryption_oracle(my_text)
    initial_len = len(ciphertext)
    new_len = initial_len

    while new_len == initial_len:
        my_text += 'A'
        ciphertext = encryption_oracle(my_text)
        new_len = len(ciphertext)

    return new_len - initial_len


def find_prefix_length(encryption_oracle, block_length):
    """Returns the length of the prefix that the encryption oracle prepends to every plaintext."""

    # Encrypt two different ciphertexts
    ciphertext_a = encryption_oracle('A')
    ciphertext_b = encryption_oracle('B')

    # Find their common length
    common_len = 0
    while ciphertext_a[common_len] == ciphertext_b[common_len]:
        common_len += 1

    # Make sure that the common length is multiple of the block length
    common_len = int(common_len / block_length) * block_length

    # Try to add an increasing number of common bytes to the plaintext till they until
    # the two ciphertexts will have one extra identical block
    for i in range(1, block_length + 1):
        ciphertext_a = encryption_oracle('A' * i + 'X')
        ciphertext_b = encryption_oracle('A' * i + 'Y')

        # If there is one more identical block, it will mean that by adding i bytes
        # we made the common input (including prefix) to the same length multiple of
        # a block size. Then we can easily get the length of the prefix.
        if ciphertext_a[common_len:common_len + block_length] == ciphertext_b[common_len:common_len + block_length]:
            return common_len + (block_length - i)


def cbc_bit_flip(encryption_oracle):
    """Performs a CBC bit flipping attack to accomplish admin privileges in the decrypted data."""

    # Get the length of a block and the length of the prefix
    block_length = find_block_length(encryption_oracle.encrypt)
    prefix_length = find_prefix_length(encryption_oracle.encrypt, block_length)

    # Compute the number of bytes to add to the prefix to make its length a multiple of block_length
    additional_prefix_bytes = (block_length - (prefix_length % block_length)) % block_length
    total_prefix_length = prefix_length + additional_prefix_bytes

    # Compute the number of bytes to add to the plaintext to make its length a multiple of block length
    plaintext = "?admin?true"
    #此处输入?admin?true绕过plaintext对于=和?的过滤
    additional_plaintext_bytes = (block_length - (len(plaintext) % block_length)) % block_length

    # Make the plaintext long one block_length and encrypt it
    final_plaintext = additional_plaintext_bytes * '?' + plaintext
    ciphertext = encryption_oracle.encrypt(additional_prefix_bytes * '?' + final_plaintext)

    # Because XORing a byte with itself produces zero, we can produce the byte that we want
    # by changing the bytes of the block before the plaintext
    semicolon = ciphertext[total_prefix_length - 11] ^ ord('?') ^ ord(';')
    equals = ciphertext[total_prefix_length - 5] ^ ord('?') ^ ord('=')
    # 因为plainchar就是?,? ^ plainchar ^ ; = ;就把?变成;了
    #同理把?变成;

    # Put the pieces of our forged ciphertext together to generate the full ciphertext
    forced_ciphertext = ciphertext[:total_prefix_length - 11] + bytes([semicolon]) + \
                        ciphertext[total_prefix_length - 10: total_prefix_length - 5] + \
                        bytes([equals]) + ciphertext[total_prefix_length - 4:]

    return forced_ciphertext

def main():
    encryption_oracle = Oracle()
    forced_ciphertext = cbc_bit_flip(encryption_oracle)

    # Check if the ciphertext was forced properly
    assert encryption_oracle.decrypt_and_check_admin(forced_ciphertext)


if __name__ == '__main__':
    main()

第三次实验

大作业

尝试第一届全国高校密码学挑战赛,目的为练习RSA算法常见漏洞的利用。

Alice 正在使用一个使用RSA加密的通信程序。她发了21条信息,但其中有些是相同的,还有些参数不是很合适。现在已经拦截了所有21条信息,告诉你密文的格式是各64bit的n、e、c拼接,明文的格式是64bit的标识符、32bit的序列号、若干个0和64bit的明文,总共521个bit。要求你尽可能多地破解Alice的信息。

  1. 共模攻击
    如果一对消息的模数和明文相同,而指数互质,则可以找到有x,y使得
    在这里插入图片描述
    ,则有
    在这里插入图片描述
    如果两个用户的公钥分别为e1和e2,而且两者互质。铭文消息为m,密文分别为:
    在这里插入图片描述
    则当攻击者截获c1和c2后,就可以恢复出明文,使用扩展欧几里得算法求出 的两个整数r和s,由此可得:
    在这里插入图片描述

对应的代码如下:

import gmpy2
import binascii
import math

# 欧几里得算法
def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)


if __name__ == "__main__":
    N = []
    C = []
    E = []
    for i in range(21):
        with open("./RSA大礼包/frame_set/Frame" + str(i), "r") as f:
            tmp = f.read()
            N.append(tmp[0:256])
            E.append(tmp[256:512])
            C.append(tmp[512:768])
    # 使用公共模数攻击的方法还原出Frame0和Frame4
    # Frame0: My secre
    # Frame4: My secre
    i1 = 0
    i2 = 0
    for i in range(21):
        for j in range(i + 1, 21):
            if N[i] == N[j]:
                print("寻找到公共模数:" + str((N[i], N[j])))
                i1, i2 = i, j
    e1 = int(E[i1], 16)
    e2 = int(E[i2], 16)
    n = int(N[i1], 16)
    c1 = int(C[i1], 16)
    c2 = int(C[i2], 16)
    s = egcd(e1, e2)
    s1 = s[1]
    s2 = s[2]
    # 求模反元素
    if s1 < 0:
        s1 = -s1
        c1 = gmpy2.invert(c1, n)
    elif s2 < 0:
        s2 = -s2
        c2 = gmpy2.invert(c2, n)

    m = pow(c1, s1, n) * pow(c2, s2, n) % n

    print(m)
    print(binascii.a2b_hex(hex(m)[2:]))
    plaintext0_and_4 = binascii.a2b_hex(hex(m)[2:])
    print(plaintext0_and_4)

根据实验发现此攻击针对Frame0和Frame4有效。
得到的结果为:
Frame0: My secre
Frame4: My secre

2.低加密指数攻击
攻击条件:e特别小,比如e为3
攻击原理:如果若干个消息明文和指数相同,指数和明文个数相同,而且模数互质,则有方程组
在这里插入图片描述
成立。可以利用中国剩余定理解出m^e<n1·n2·…·ne,从而可以直接解出明文。
对应的代码如下:

import gmpy2
import math
import binascii

# 欧几里得算法
def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def CRT(items):
    N = 1
    for a, n in items:
        N *= n
        result = 0
    for a, n in items:
        m = N // n
        d, r, s = egcd(n, m)
        if d != 1:
            N = N // n
            continue
        result += a * s * m
    return result % N, N

# 低加密指数e == 3

def Be_3():
    Brange = [7, 11, 15]
    for i in range(3):
        c = int(C[Brange[i]], 16)
        n = int(NN[Brange[i]], 16)
        print("This is frame" + str(i))
        for j in range(20):
            plain = gmpy2.iroot(gmpy2.mpz(c + j * n), 3)
            print("This is test" + str(j))
            print(binascii.a2b_hex(hex(plain[0])[2:]))

def low_e_3():
    ss = [
        {"c": int(C[7], 16), "n": int(NN[7], 16)},
        {"c": int(C[11], 16), "n": int(NN[11], 16)},
        {"c": int(C[15], 16), "n": int(NN[15], 16)},
    ]
    data = []
    for session in ss:
        data = data + [(session["c"], session["n"])]
    x, y = CRT(data)
    #开三次方根
    plaintext7_11_15 = gmpy2.iroot(gmpy2.mpz(x), 3)
    return binascii.a2b_hex(hex(plaintext7_11_15[0])[2:])

def low_e_5():
    ss = [
        {"c": int(C[3], 16), "n": int(NN[3], 16)},
        {"c": int(C[8], 16), "n": int(NN[8], 16)},
        {"c": int(C[12], 16), "n": int(NN[12], 16)},
        {"c": int(C[16], 16), "n": int(NN[16], 16)},
        {"c": int(C[20], 16), "n": int(NN[20], 16)},
    ]
    data = []
    for session in ss:
        data = data + [(session["c"], session["n"])]
    x, y = CRT(data)
    # 直接开五次方根
    plaintext3_8_12_16_20 = gmpy2.iroot(gmpy2.mpz(x), 5)
    return binascii.a2b_hex(hex(plaintext3_8_12_16_20[0])[2:])

if __name__ == "__main__":
    NN = []
    C = []
    E = []
    for i in range(21):
        with open("./RSA大礼包/frame_set/Frame" + str(i), "r") as f:
            tmp = f.read()
            NN.append(tmp[0:256])
            E.append(tmp[256:512])
            C.append(tmp[512:768])
    plaintext3_8_12_16_20 = low_e_5()
    print(plaintext3_8_12_16_20)
    plaintext7_11_15 = low_e_3()
    print(plaintext7_11_15)  # low_e_3的是无法破解的

经过分析可以发现:
Frame7, Frame11, Frame15和Frame3, Frame8, Frame12, Frame16, Frame20这两组数分别符合低加密指数攻击的条件。
Frame7、11、15可以直接从上述方式攻击,发现Frame7、11、15并不是由相同明文加密得到的。
对于Frame3、Frame8、Frame2、Frame16、Frame20,由上面的经验,只需要三块就可以获得对应的密文。
但是运行时会发现,7/11/15块是不能使用该方法进行破解的。
但是对于3/8/12/16/20,是可以使用该方法进行破解的。

3.公因数攻击
如果两个不同的模数n1=p·q1,n2=p·q2,其中q相同,那么可以直接求出p=gcd(n1,n2)。毕竟,求gcd的时间复杂度是远小于大数分解的,导致可以分解出模数的因子,进而求得密钥。
Frame1和Frame18由大于1的最大公因数。所以我们可以通过除法运算求的N1和N18的另外一个因数,即求出素数p和q,破译明文。
代码如下:

import gmpy2
import binascii
import math

if __name__ == "__main__":
    N = []
    C = []
    E = []
    for i in range(21):
        with open("./RSA大礼包/frame_set/Frame" + str(i), "r") as f:
            tmp = f.read()
            N.append(tmp[0:256])
            E.append(tmp[256:512])
            C.append(tmp[512:768])
    plaintext = []
    index = []
    for i in range(21):
        for j in range(i + 1, 21):
            if int(N[i], 16) == int(N[j], 16):
                continue
            prime = gmpy2.gcd(int(N[i], 16), int(N[j], 16))
            if prime != 1:
                print((N[i], N[j]))
                print((i, j))
                index.append(i)
                index.append(j)
                Pframe = prime
    Qframe1 = int(N[index[0]], 16) // Pframe  # 通过碰撞因数求得p、q
    Qframe18 = int(N[index[1]], 16) // Pframe
    print(Pframe)
    print(Qframe1, Qframe18)

    φframe1 = (Pframe - 1) * (Qframe1 - 1)
    φframe18 = (Pframe - 1) * (Qframe18 - 1)

    d_of_frame1 = gmpy2.invert(int(E[index[0]], 16), φframe1)
    d_of_frame18 = gmpy2.invert(int(E[index[1]], 16), φframe18)

    Pframe1 = gmpy2.powmod(
        int(C[index[0]], 16), d_of_frame1, int(N[index[0]], 16)
    )
    Pframe18 = gmpy2.powmod(
        int(C[index[1]], 16), d_of_frame18, int(N[index[1]], 16)
    )

    PLAIN1 = binascii.a2b_hex(hex(Pframe1)[2:])
    PLAIN18 = binascii.a2b_hex(hex(Pframe18)[2:])

    plaintext.append(PLAIN1)
    plaintext.append(PLAIN18)

    print(plaintext)

可以得到结果:
Frame1: .Imagin
Frame18: m A to B

4.费马分解法
要求:|p-q|较小
首先:
在这里插入图片描述
费马分解法基于:若N=pq,其中p<=q而且都是奇数,然后令x=(1/2)(p+q),y=(1/2)(q-p),则可以找到N=x2-y2=(x+y)(x-y)或者y2=x2-N。
设N=pq,其中Δ=p-q则当Δ<N1/4,费马分解法就可以有效地分解N。
爆破后可以发现之后只有Frame10中的模数在短时间内可以使用此方法进行分解。
代码如下:

import gmpy2
import binascii
import math
import time

def pq(n):
    B = math.factorial(2 ** 14)
    u = 0
    v = 0
    i = 0
    u0 = gmpy2.iroot(n, 2)[0] + 1
    timeStart = time.time()
    while i <= (B - 1):
        u = (u0 + i) * (u0 + i) - n
        if gmpy2.is_square(u):
            v = gmpy2.isqrt(u)
            break
        i = i + 1
        timeEnd = time.time()
        if timeEnd-timeStart>1:
            return 0 #超时
    p = u0 + i + v
    return p

def fermat_resolve():
    ilist = []
    for i in range(10, 16):
        timeStart = time.time()
        N = int(NN[i], 16)
        p = pq(N)
        timeEnd = time.time()
        print(f"i={i},p={p},time={timeEnd-timeStart}")
        if p != 0:
            ilist.append(i)
    print(f"Fermat攻击尝试结束。{ilist}是可以进行攻击的。")

if __name__ == "__main__":
    NN = []
    C = []
    E = []
    for i in range(21):
        with open("./RSA大礼包/frame_set/Frame" + str(i), "r") as f:
            tmp = f.read()
            NN.append(tmp[0:256])
            E.append(tmp[256:512])
            C.append(tmp[512:768])
    fermat_resolve()

    #底下的代码是根据上面解出的结果再写的
    p = 9686924917554805418937638872796017160525664579857640590160320300805115443578184985934338583303180178582009591634321755204008394655858254980766008932978699
    n = int(NN[10], 16)
    c = int(C[10], 16)
    e = int(E[10], 16)
    q = n // p
    φframe10 = (p - 1) * (q - 1)
    d = gmpy2.invert(e, φframe10)
    m = gmpy2.powmod(c, d, n)
    Plain = binascii.a2b_hex(hex(m)[2:])
    print(Plain)

5.Pollard p-1分解法
原理:RSA 加密算法中,素数 p 和 q 应该满足:p 1 和 q 1 至少有一个素因子大于 1020,否则,要分解的整数 N 有一个素因数 p ,且(p-1)有小的素因数时就可以 利用 Pollardp1 分解法有效分解模数 N ,从而得到 p 。
选取一个数B,使得k=B!,如果B足够大,那么就有(p-1)|k,则由于2^(p-1) ≡ 1 (mod p)可知p|(2^k - 1),又有p|n,所以p|a,a ≡ 2^k - 1 mod n。将次数与n取gcd即可分解模数。
根据以上原理构造Pollardp1 分解函数:
分解:

import math
import gmpy2
import binascii

def pollardPMinus1(n):
    B = 2 ** 20
    a = 2
    for i in range(2*2, (B + 1)*2 , 2):
        a = pow(a, i, n)
        d = gmpy2.gcd(a - 1, n)
        if (d >= 2) and (d <= (n - 1)):
            q = n // d
            n = q * d
    return d

if __name__ == "__main__":
    NN = []
    C = []
    E = []
    for i in range(21):
        with open("./RSA大礼包/frame_set/Frame" + str(i), "r") as f:
            tmp = f.read()
            NN.append(tmp[0:256])
            E.append(tmp[256:512])
            C.append(tmp[512:768])
    index_list = [2, 6, 19]
    plainTextList = []
    for i in range(3):
        N = int(NN[index_list[i]], 16)
        c = int(C[index_list[i]], 16)
        e = int(E[index_list[i]], 16)
        p = pollardPMinus1(N)
        print("p of " + str(index_list[i]) + " is : " + str(p))
        q = N // p
        φofFrame = (p - 1) * (q - 1)
        d = gmpy2.invert(e, φofFrame)
        m = gmpy2.powmod(c, d, N)
        plainTextList.append(binascii.a2b_hex(hex(m)[2:]))
    print(plainTextList)
    print("Pollard finished!")

可以发现Frame2、6、19可以使用该方法进行分解。
处理结果如下:
Frame2: That is
Frame6: “Logic
Frame19: instein.
现在已经破解的部分:
在这里插入图片描述
根据以上得信息可以大概得到明文为My secret is a f ---------------- instein. That is "Logic will get --------- m A to B. Imagin -------

其中,m A to B这样的句式并不多见,考虑到to,我们可以猜测为from A to B。

通过搜集资料,可以找到爱因斯坦的一句名言Logic will get you from A to B. Imagination will take you everywhere. 这句名言很明显符合要求。

通过进行加密验证和暴力破解遍历,验证成功。我们可以得到5、9、12、13、14、15块得内容。考虑到instein.这样的单词很为少见,同时爱因斯坦名字叫做"Albert Einstein"自然可以考虑4的明文为"Albert E",通过验证也成功。

现在已知的明文为:My secret is a f --------- Albert Einstein. That is "Logic will get you from A to B. Imagination will take you everywhere."

根据已知的明文,我们可以大胆猜测,这个f可能是 “famous” 或者 “from”,如果是"famous"则符合语境的短语有:“famous quotes”, “famous saying”, “famous nots"等,通过继续用加密密钥进行猜测明文攻击,验证了分块3为"amous sa”,所以4很明显为"ying"

My secret is a famous saying --- Albert Einstein. That is "Logic will get you from A to B. Imagination will take you everywhere."

根据这样的一个语法结构和剩余位置只有4个(还必须包括两个空格),可以继续猜测剩下的单词为of,进行加密验证,猜想正确。

所以全部明文为My secret is a famous saying of Albert Einstein. That is "Logic will get you from A to B. Imagination will take you everywhere."

这样一想,还真是"Imagination will take you everywhere."啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值