现代密码学(Cryptography)经典习题及解法(一)

习题一:Many Time Pad

题目链接:https://www.coursera.org/learn/crypto/home/week/1

Question:

Let us see what goes wrong when a stream cipher key is used more than once. Below are eleven hex-encoded ciphertexts that are the result of encrypting eleven plaintexts with a stream cipher, all with the same stream cipher key. Your goal is to decrypt the last ciphertext, and submit the secret message within it as solution.

Hint: XOR the ciphertexts together, and consider what happens when a space is XORed with a character in [a-zA-Z].
ciphertext #1:

315c4eeaa8b5f8aaf9174145bf43e1784b8fa00dc71d885a804e5ee9fa40b16349c146fb778cdf2d3aff021dfff5b403b510d0d0455468aeb98622b137dae857553ccd8883a7bc37520e06e515d22c954eba5025b8cc57ee59418ce7dc6bc41556bdb36bbca3e8774301fbcaa3b83b220809560987815f65286764703de0f3d524400a19b159610b11ef3e

ciphertext #2:

234c02ecbbfbafa3ed18510abd11fa724fcda2018a1a8342cf064bbde548b12b07df44ba7191d9606ef4081ffde5ad46a5069d9f7f543bedb9c861bf29c7e205132eda9382b0bc2c5c4b45f919cf3a9f1cb74151f6d551f4480c82b2cb24cc5b028aa76eb7b4ab24171ab3cdadb8356f

ciphertext #3:

32510ba9a7b2bba9b8005d43a304b5714cc0bb0c8a34884dd91304b8ad40b62b07df44ba6e9d8a2368e51d04e0e7b207b70b9b8261112bacb6c866a232dfe257527dc29398f5f3251a0d47e503c66e935de81230b59b7afb5f41afa8d661cb

ciphertext #4:

32510ba9aab2a8a4fd06414fb517b5605cc0aa0dc91a8908c2064ba8ad5ea06a029056f47a8ad3306ef5021eafe1ac01a81197847a5c68a1b78769a37bc8f4575432c198ccb4ef63590256e305cd3a9544ee4160ead45aef520489e7da7d835402bca670bda8eb775200b8dabbba246b130f040d8ec6447e2c767f3d30ed81ea2e4c1404e1315a1010e7229be6636aaa

ciphertext #5:

3f561ba9adb4b6ebec54424ba317b564418fac0dd35f8c08d31a1fe9e24fe56808c213f17c81d9607cee021dafe1e001b21ade877a5e68bea88d61b93ac5ee0d562e8e9582f5ef375f0a4ae20ed86e935de81230b59b73fb4302cd95d770c65b40aaa065f2a5e33a5a0bb5dcaba43722130f042f8ec85b7c2070

ciphertext #6:

32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd2061bbde24eb76a19d84aba34d8de287be84d07e7e9a30ee714979c7e1123a8bd9822a33ecaf512472e8e8f8db3f9635c1949e640c621854eba0d79eccf52ff111284b4cc61d11902aebc66f2b2e436434eacc0aba938220b084800c2ca4e693522643573b2c4ce35050b0cf774201f0fe52ac9f26d71b6cf61a711cc229f77ace7aa88a2f19983122b11be87a59c355d25f8e4

ciphertext #7:

32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd90f1fa6ea5ba47b01c909ba7696cf606ef40c04afe1ac0aa8148dd066592ded9f8774b529c7ea125d298e8883f5e9305f4b44f915cb2bd05af51373fd9b4af511039fa2d96f83414aaaf261bda2e97b170fb5cce2a53e675c154c0d9681596934777e2275b381ce2e40582afe67650b13e72287ff2270abcf73bb028932836fbdecfecee0a3b894473c1bbeb6b4913a536ce4f9b13f1efff71ea313c8661dd9a4ce

ciphertext #8:

315c4eeaa8b5f8bffd11155ea506b56041c6a00c8a08854dd21a4bbde54ce56801d943ba708b8a3574f40c00fff9e00fa1439fd0654327a3bfc860b92f89ee04132ecb9298f5fd2d5e4b45e40ecc3b9d59e9417df7c95bba410e9aa2ca24c5474da2f276baa3ac325918b2daada43d6712150441c2e04f6565517f317da9d3

ciphertext #9:

271946f9bbb2aeadec111841a81abc300ecaa01bd8069d5cc91005e9fe4aad6e04d513e96d99de2569bc5e50eeeca709b50a8a987f4264edb6896fb537d0a716132ddc938fb0f836480e06ed0fcd6e9759f40462f9cf57f4564186a2c1778f1543efa270bda5e933421cbe88a4a52222190f471e9bd15f652b653b7071aec59a2705081ffe72651d08f822c9ed6d76e48b63ab15d0208573a7eef027

ciphertext #10:

466d06ece998b7a2fb1d464fed2ced7641ddaa3cc31c9941cf110abbf409ed39598005b3399ccfafb61d0315fca0a314be138a9f32503bedac8067f03adbf3575c3b8edc9ba7f537530541ab0f9f3cd04ff50d66f1d559ba520e89a2cb2a83

target ciphertext (decrypt this one): 

32510ba9babebbbefd001547a810e67149caee11d945cd7fc81a05e9f85aac650e9052ba6a8cd8257bf14d13e6f0a803b54fde9e77472dbff89d71b57bddef121336cb85ccb8f3315f4b52e301d16e9f52f904

For completeness, here is the python script used to generate the ciphertexts.(it doesn't matter if you can't read this)

import sys
 
MSGS = ( ---  11 secret messages  --- )
 
def strxor(a, b):     # xor two strings of different lengths
    if len(a) > len(b):
        return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)])
    else:
        return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])])
 
def random(size=16):
    return open("/dev/urandom").read(size)
 
def encrypt(key, msg):
    c = strxor(key, msg)
    print
    print c.encode('hex')
    return c
 
def main():
    key = random(1024)
    ciphertexts = [encrypt(key, msg) for msg in MSGS]

Answer:

由提示,空格space的ASCII码二进制形式为0010 0000,大写字母A~Z的ASCII码二进制形式为01000001~01011010,小写字母a~z的ASCII码二进制形式为 01100001~01111010。

因此可以用space和字母做xor操作,对字母进行大小写切换,而两个字母做xor操作,结果将不在字母范围内。

又由于将两个密文做xor操作相当于将两个密文对应的明文做xor操作,如果结果中某个位置出现字母,则说明这两个明文的其中一个在该位置可能为空格。

对11个密文分别做两两xor操作,然后通过结果判断不同明文中可能存在空格的位置,然后将对应位置上的密文和space做xor操作,就可得到对应位置的密钥信息,当获取足够多的密钥信息后,即可对目标密文进行解密。

附代码:

import sys  
  
ciphertexts=[  
"315c4eeaa8b5f8aaf9174145bf43e1784b8fa00dc71d885a804e5ee9fa40b16349c146fb778cdf2d3aff021dfff5b403b510d0d0455468aeb98622b137dae857553ccd8883a7bc37520e06e515d22c954eba5025b8cc57ee59418ce7dc6bc41556bdb36bbca3e8774301fbcaa3b83b220809560987815f65286764703de0f3d524400a19b159610b11ef3e",  
"234c02ecbbfbafa3ed18510abd11fa724fcda2018a1a8342cf064bbde548b12b07df44ba7191d9606ef4081ffde5ad46a5069d9f7f543bedb9c861bf29c7e205132eda9382b0bc2c5c4b45f919cf3a9f1cb74151f6d551f4480c82b2cb24cc5b028aa76eb7b4ab24171ab3cdadb8356f",  
"32510ba9a7b2bba9b8005d43a304b5714cc0bb0c8a34884dd91304b8ad40b62b07df44ba6e9d8a2368e51d04e0e7b207b70b9b8261112bacb6c866a232dfe257527dc29398f5f3251a0d47e503c66e935de81230b59b7afb5f41afa8d661cb",  
"32510ba9aab2a8a4fd06414fb517b5605cc0aa0dc91a8908c2064ba8ad5ea06a029056f47a8ad3306ef5021eafe1ac01a81197847a5c68a1b78769a37bc8f4575432c198ccb4ef63590256e305cd3a9544ee4160ead45aef520489e7da7d835402bca670bda8eb775200b8dabbba246b130f040d8ec6447e2c767f3d30ed81ea2e4c1404e1315a1010e7229be6636aaa",  
"3f561ba9adb4b6ebec54424ba317b564418fac0dd35f8c08d31a1fe9e24fe56808c213f17c81d9607cee021dafe1e001b21ade877a5e68bea88d61b93ac5ee0d562e8e9582f5ef375f0a4ae20ed86e935de81230b59b73fb4302cd95d770c65b40aaa065f2a5e33a5a0bb5dcaba43722130f042f8ec85b7c2070",  
"32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd2061bbde24eb76a19d84aba34d8de287be84d07e7e9a30ee714979c7e1123a8bd9822a33ecaf512472e8e8f8db3f9635c1949e640c621854eba0d79eccf52ff111284b4cc61d11902aebc66f2b2e436434eacc0aba938220b084800c2ca4e693522643573b2c4ce35050b0cf774201f0fe52ac9f26d71b6cf61a711cc229f77ace7aa88a2f19983122b11be87a59c355d25f8e4",  
"32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd90f1fa6ea5ba47b01c909ba7696cf606ef40c04afe1ac0aa8148dd066592ded9f8774b529c7ea125d298e8883f5e9305f4b44f915cb2bd05af51373fd9b4af511039fa2d96f83414aaaf261bda2e97b170fb5cce2a53e675c154c0d9681596934777e2275b381ce2e40582afe67650b13e72287ff2270abcf73bb028932836fbdecfecee0a3b894473c1bbeb6b4913a536ce4f9b13f1efff71ea313c8661dd9a4ce",  
"315c4eeaa8b5f8bffd11155ea506b56041c6a00c8a08854dd21a4bbde54ce56801d943ba708b8a3574f40c00fff9e00fa1439fd0654327a3bfc860b92f89ee04132ecb9298f5fd2d5e4b45e40ecc3b9d59e9417df7c95bba410e9aa2ca24c5474da2f276baa3ac325918b2daada43d6712150441c2e04f6565517f317da9d3",  
"271946f9bbb2aeadec111841a81abc300ecaa01bd8069d5cc91005e9fe4aad6e04d513e96d99de2569bc5e50eeeca709b50a8a987f4264edb6896fb537d0a716132ddc938fb0f836480e06ed0fcd6e9759f40462f9cf57f4564186a2c1778f1543efa270bda5e933421cbe88a4a52222190f471e9bd15f652b653b7071aec59a2705081ffe72651d08f822c9ed6d76e48b63ab15d0208573a7eef027",  
"466d06ece998b7a2fb1d464fed2ced7641ddaa3cc31c9941cf110abbf409ed39598005b3399ccfafb61d0315fca0a314be138a9f32503bedac8067f03adbf3575c3b8edc9ba7f537530541ab0f9f3cd04ff50d66f1d559ba520e89a2cb2a83",  
"32510ba9babebbbefd001547a810e67149caee11d945cd7fc81a05e9f85aac650e9052ba6a8cd8257bf14d13e6f0a803b54fde9e77472dbff89d71b57bddef121336cb85ccb8f3315f4b52e301d16e9f52f904"  
]  
 

NUM_CIPHER=len(ciphertexts)
THRESHOLD_VALUE=6
 
 
def strxor(a, b): 
    """xor two strings of different lengths"""  
    if len(a) > len(b):  
        return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)])  
    else:  
        return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])])   
 
 
def letter_position(s): 
    """ Return the position of letters in the given string """  
    position=[]
    for idx in range(len(s)):  
        if (s[idx]>='A' and s[idx]<='Z') or (s[idx]>='a' and s[idx]<='z'):  
            position.append(idx)  
    return position  
  
def find_space(cipher):
    """Find the position of space"""
    space_position={}
    space_possible={}  
    for cipher_idx_1 in range(NUM_CIPHER):  
        space_xor=[]  
        for cipher_idx_2 in range(NUM_CIPHER):   
            plain_xor=strxor(cipher[cipher_idx_1].decode('hex'),cipher[cipher_idx_2].decode('hex'))
            if cipher_idx_2!=cipher_idx_1:  
                space_xor.append(letter_position(plain_xor)) # record the possible value of space 
        space_possible[cipher_idx_1]=space_xor
    for cipher_idx_1 in range(NUM_CIPHER):  
        spa=[]  
        for position in range(100):  
            count=0 
            for cipher_idx_2 in range(NUM_CIPHER-1):  
                if position in space_possible[cipher_idx_1][cipher_idx_2]:  
                    count+=1  
            if count>THRESHOLD_VALUE: # if possible position value appear more than THRESHOLD_VALUE times,
                                      # we consider it as a space position
                spa.append(position)
        space_position[cipher_idx_1]=spa;  
    return space_position
 
 
def calculate_key(cipher):
    key=[0]*100
    space=find_space(cipher)  
    for cipher_idx_1 in range(NUM_CIPHER):  
        for position in range(len(space[cipher_idx_1])):  
            idx=space[cipher_idx_1][position]*2; # ciphertext is hex-encoded, so its scale times two
            key[space[cipher_idx_1][position]]=ord((ciphertexts[cipher_idx_1][idx]+ciphertexts[cipher_idx_1][idx+1]).decode('hex'))^ord(' '); #derive key  
    key_str="";  
    for k in key:  
        key_str+=chr(k);
    return key_str   
  
result="";
key=calculate_key(ciphertexts)
for i in range(11):
    for letter in strxor(ciphertexts[i].decode('hex'),key): # decrypt the target cipher
        if (letter>='a' and letter<='z') or (letter>='A' and letter<='Z'):  
            result+=letter;  
        elif letter==' ':  
            result+=letter;  
        else:  
            result+='0';
    print result;
    print '\n'
    result=''

习题二:Vigenere-like cipher

Question:

Write a program that allows you to "crack" ciphertexts generated using a Vigenere-like cipher, where byte-wise XOR is used instead of addition modulo 26.

Specifically, the ciphertext

F96DE8C227A259C87EE1DA2AED57C93FE5DA36ED4EC87EF2C63AAE5B9A7EFFD673BE4ACF7BE8923CAB1ECE7AF2DA3DA44FCF7AE29235A24C963FF0DF3CA3599A70E5DA36BF1ECE77F8DC34BE129A6CF4D126BF5B9A7CFEDF3EB850D37CF0C63AA2509A76FF9227A55B9A6FE3D720A850D97AB1DD35ED5FCE6BF0D138A84CC931B1F121B44ECE70F6C032BD56C33FF9D320ED5CDF7AFF9226BE5BDE3FF7DD21ED56CF71F5C036A94D963FF8D473A351CE3FE5DA3CB84DDB71F5C17FED51DC3FE8D732BF4D963FF3C727ED4AC87EF5DB27A451D47EFD9230BF47CA6BFEC12ABE4ADF72E29224A84CDF3FF5D720A459D47AF59232A35A9A7AE7D33FB85FCE7AF5923AA31EDB3FF7D33ABF52C33FF0D673A551D93FFCD33DA35BC831B1F43CBF1EDF67F0DF23A15B963FE5DA36ED68D378F4DC36BF5B9A7AFFD121B44ECE76FEDC73BE5DD27AFCD773BA5FC93FE5DA3CB859D26BB1C63CED5CDF3FE2D730B84CDF3FF7DD21ED5ADF7CF0D636BE1EDB79E5D721ED57CE3FE6D320ED57D469F4DC27A85A963FF3C727ED49DF3FFFDD24ED55D470E69E73AC50DE3FE5DA3ABE1EDF67F4C030A44DDF3FF5D73EA250C96BE3D327A84D963FE5DA32B91ED36BB1D132A31ED87AB1D021A255DF71B1C436BF479A7AF0C13AA14794

was generated by encrypting English-language text using the following C program:

#include <stdio.h>
#define KEY_LENGTH 2 // Can be anything from 1 to 13

main(){
  unsigned char ch;
  FILE *fpIn, *fpOut;
  int i;
  unsigned char key[KEY_LENGTH] = {0x00, 0x00};
  /* of course, I did not use the all-0s key to encrypt */

  fpIn = fopen("ptext.txt", "r");
  fpOut = fopen("ctext.txt", "w");

  i=0;
  while (fscanf(fpIn, "%c", &ch) != EOF) {
    /* avoid encrypting newline characters */  
    /* In a "real-world" implementation of the Vigenere cipher, 
       every ASCII character in the plaintext would be encrypted.
       However, I want to avoid encrypting newlines here because 
       it makes recovering the plaintext slightly more difficult... */
    /* ...and my goal is not to create "production-quality" code =) */
    if (ch!='\n') {
      fprintf(fpOut, "%02X", ch ^ key[i % KEY_LENGTH]); // ^ is logical XOR    
      i++;
      }
    }
 
  fclose(fpIn);
  fclose(fpOut);
  return;
} 

(Of course, when encrypting I used a random key length and chose each byte of the key at random.) The plaintext contains upper- and lower-case letters, punctuation, and spaces, but no numbers.

Recovered the original plaintext

Answer:

假设加密者使用的密钥为vigenerekey,长度为keylen,密文为字符串arr

那么字符串subarr0=arr[0]+arr[keylen]+arr[keylen*2]……是被vigenerekey[0]解密的内容

依次类推,subarr1=arr[1]+arr[1+keylen]+arr[1+keylen*2]……是被vigenerekey[1]解密的内容

subarr2=arr[2]+arr[2+keylen]+arr[2+keylen*2]……是被vigenerekey[2]解密的内容

……

按照这种分割方法,可以把密文arr分割成keylen份,每份都被密钥vigenerekey的同一位解密

比如:

密钥为WXYZ,密文为abcdefghijklmnop

那么解密过程为

abcdefghijklmnop
WXYZWXYZWXYZWXYZ

subarr0=aeim

subarr1=bfjn

 

以此类推

对每个vigenerekey[index],穷举vigenerekey[index]的值(范围是0x00~0xFF),并与对应的subarr里的内容解码,找到能将该subarr所有内容解为可见字符的所有可能值

附代码:

def findindexkey(subarr):#该函数可以找出将密文subarr解密成可见字符的所有可能值
    visiable_chars=[]#可见字符
    for x in range(32,126):
        visiable_chars.append(chr(x))
    #print(vi)
    test_keys=[]#用于测试密钥
    ans_keys=[]#用于结果的返回
    for x in range(0x00,0xFF):# 枚举密钥里所有的值
        test_keys.append(x)
        ans_keys.append(x)
    for i in test_keys:#对于0x00~0xFF里的每一个数i和subarr里的每个值s异或
        for s in subarr:
            if chr(s^i) not in visiable_chars:#用i解密s,如果解密后明文不是可见字符,说明i不是密钥
                ans_keys.remove(i)#去掉ans_keys里测试失败的密钥
                break
    #print(ans_keys)
    return ans_keys
 
strmi='F96DE8C227A259C87EE1DA2AED57C93FE5DA36ED4EC87EF2C63AAE5B9A7EFFD673BE4ACF7BE8923C\
AB1ECE7AF2DA3DA44FCF7AE29235A24C963FF0DF3CA3599A70E5DA36BF1ECE77F8DC34BE129A6CF4D126BF\
5B9A7CFEDF3EB850D37CF0C63AA2509A76FF9227A55B9A6FE3D720A850D97AB1DD35ED5FCE6BF0D138A84C\
C931B1F121B44ECE70F6C032BD56C33FF9D320ED5CDF7AFF9226BE5BDE3FF7DD21ED56CF71F5C036A94D96\
3FF8D473A351CE3FE5DA3CB84DDB71F5C17FED51DC3FE8D732BF4D963FF3C727ED4AC87EF5DB27A451D47E\
FD9230BF47CA6BFEC12ABE4ADF72E29224A84CDF3FF5D720A459D47AF59232A35A9A7AE7D33FB85FCE7AF5\
923AA31EDB3FF7D33ABF52C33FF0D673A551D93FFCD33DA35BC831B1F43CBF1EDF67F0DF23A15B963FE5DA\
36ED68D378F4DC36BF5B9A7AFFD121B44ECE76FEDC73BE5DD27AFCD773BA5FC93FE5DA3CB859D26BB1C63C\
ED5CDF3FE2D730B84CDF3FF7DD21ED5ADF7CF0D636BE1EDB79E5D721ED57CE3FE6D320ED57D469F4DC27A8\
5A963FF3C727ED49DF3FFFDD24ED55D470E69E73AC50DE3FE5DA3ABE1EDF67F4C030A44DDF3FF5D73EA250\
C96BE3D327A84D963FE5DA32B91ED36BB1D132A31ED87AB1D021A255DF71B1C436BF479A7AF0C13AA14794'
arr=[]#密文,每个元素为字符的ascii码
for x in range(0,len(strmi),2):
    arr.append(int(strmi[x:2+x],16))
 
 
for keylen in range(1,14):#枚举密钥的长度1~14
    for index in range(0,keylen):#对密钥里的第index个进行测试
        subarr=arr[index::keylen]#每隔keylen长度提取密文的内容,提取出来的内容都被密文的第index个加密
        ans_keys=findindexkey(subarr)#找出密钥中第index个的可能的值
        print('keylen=',keylen,'index=',index,'keys=',ans_keys)
        if ans_keys:#如果密钥第index个有可能存在,尝试用密钥的index个去解密文
            ch=[]
            for x in ans_keys:
                ch.append(chr(x^subarr[0]))
            print(ch)

运行结果:

可以看出,keylen=7时有结果

不妨缩小一下范围,假设明文只有字母、数字、空格、逗号和句号,再进行穷举

附代码:

import string
def findindexkey2(subarr):#再造一个函数筛选密钥
    test_chars=string.ascii_letters+string.digits+','+'.'+' '#将检查的字符改为英文+数字+逗号+句号+空格
    #print(test_chars)
    test_keys=[]#用于测试密钥
    ans_keys=[]#用于结果的返回
    for x in range(0x00,0xFF):# 枚举密钥里所有的值
        test_keys.append(x)
        ans_keys.append(x)
    for i in test_keys:#对于0x00~0xFF里的每一个数i和substr里的每个值s异或
        for s in subarr:
            if chr(s^i) not in test_chars:#用i解密s,如果解密后不是英文、数字、逗号、句号、空格,说明i不是密钥
                ans_keys.remove(i)#去掉ans_keys里测试失败的密钥
                break
    #print(ans_keys)
    return ans_keys
 
vigenerekeys=[]#维基尼尔密码的密钥
for index in range(0,7):#已经知道密钥长度是7
    subarr=arr[index::7]
    vigenerekeys.append(findindexkey2(subarr))
print(vigenerekeys)

运行结果:

[[186], [31], [145], [178], [83], [205], [62]]

结果已经是唯一的了,它就是密钥,我们再使用这个密钥去解密密文

附代码:

ming=''
for i in range(0,len(arr)):
    ming=ming+chr(arr[i]^vigenerekeys[i%7][0])
print(ming)

解密结果:

Cryptography is the practice and study of techniques for, among other things, secure communication in the presence of attackers. Cryptography has been used for hundreds, if not thousands, of years, but traditional cryptosystems were designed and evaluated in a fairly ad hoc manner. For example, the Vigenere encryption scheme was thought to be secure for decades after it was invented, but we now know, and this exercise demonstrates, that it can be broken very easily.

注:此解法为穷举,算是非预期解法,关于字母频率分析解法后面会发布,请关注

附完整代码:

def findindexkey(subarr):#该函数可以找出将密文subarr解密成可见字符的所有可能值
    visiable_chars=[]#可见字符
    for x in range(32,126):
        visiable_chars.append(chr(x))
    #print(vi)
    test_keys=[]#用于测试密钥
    ans_keys=[]#用于结果的返回
    for x in range(0x00,0xFF):# 枚举密钥里所有的值
        test_keys.append(x)
        ans_keys.append(x)
    for i in test_keys:#对于0x00~0xFF里的每一个数i和subarr里的每个值s异或
        for s in subarr:
            if chr(s^i) not in visiable_chars:#用i解密s,如果解密后明文不是可见字符,说明i不是密钥
                ans_keys.remove(i)#去掉ans_keys里测试失败的密钥
                break
    #print(ans_keys)
    return ans_keys
 
strmi='F96DE8C227A259C87EE1DA2AED57C93FE5DA36ED4EC87EF2C63AAE5B9A7EFFD673BE4ACF7BE8923C\
AB1ECE7AF2DA3DA44FCF7AE29235A24C963FF0DF3CA3599A70E5DA36BF1ECE77F8DC34BE129A6CF4D126BF\
5B9A7CFEDF3EB850D37CF0C63AA2509A76FF9227A55B9A6FE3D720A850D97AB1DD35ED5FCE6BF0D138A84C\
C931B1F121B44ECE70F6C032BD56C33FF9D320ED5CDF7AFF9226BE5BDE3FF7DD21ED56CF71F5C036A94D96\
3FF8D473A351CE3FE5DA3CB84DDB71F5C17FED51DC3FE8D732BF4D963FF3C727ED4AC87EF5DB27A451D47E\
FD9230BF47CA6BFEC12ABE4ADF72E29224A84CDF3FF5D720A459D47AF59232A35A9A7AE7D33FB85FCE7AF5\
923AA31EDB3FF7D33ABF52C33FF0D673A551D93FFCD33DA35BC831B1F43CBF1EDF67F0DF23A15B963FE5DA\
36ED68D378F4DC36BF5B9A7AFFD121B44ECE76FEDC73BE5DD27AFCD773BA5FC93FE5DA3CB859D26BB1C63C\
ED5CDF3FE2D730B84CDF3FF7DD21ED5ADF7CF0D636BE1EDB79E5D721ED57CE3FE6D320ED57D469F4DC27A8\
5A963FF3C727ED49DF3FFFDD24ED55D470E69E73AC50DE3FE5DA3ABE1EDF67F4C030A44DDF3FF5D73EA250\
C96BE3D327A84D963FE5DA32B91ED36BB1D132A31ED87AB1D021A255DF71B1C436BF479A7AF0C13AA14794'
arr=[]#密文,每个元素为字符的ascii码
for x in range(0,len(strmi),2):
    arr.append(int(strmi[x:2+x],16))
 
 
for keylen in range(1,14):#枚举密钥的长度1~14
    for index in range(0,keylen):#对密钥里的第index个进行测试
        subarr=arr[index::keylen]#每隔keylen长度提取密文的内容,提取出来的内容都被密文的第index个加密
        ans_keys=findindexkey(subarr)#找出密钥中第index个的可能的值
        print('keylen=',keylen,'index=',index,'keys=',ans_keys)
        if ans_keys:#如果密钥第index个有可能存在,尝试用密钥的index个去解密文
            ch=[]
            for x in ans_keys:
                ch.append(chr(x^subarr[0]))
            print(ch)
#运行到这里,观察输出可以发现,密钥长度为7时有解
print('###############')
import string
def findindexkey2(subarr):#再造一个函数筛选密钥
    test_chars=string.ascii_letters+string.digits+','+'.'+' '#将检查的字符改为英文+数字+逗号+句号+空格
    #print(test_chars)
    test_keys=[]#用于测试密钥
    ans_keys=[]#用于结果的返回
    for x in range(0x00,0xFF):# 枚举密钥里所有的值
        test_keys.append(x)
        ans_keys.append(x)
    for i in test_keys:#对于0x00~0xFF里的每一个数i和substr里的每个值s异或
        for s in subarr:
            if chr(s^i) not in test_chars:#用i解密s,如果解密后不是英文、数字、逗号、句号、空格,说明i不是密钥
                ans_keys.remove(i)#去掉ans_keys里测试失败的密钥
                break
    #print(ans_keys)
    return ans_keys
 
vigenerekeys=[]#维基尼尔密码的密钥
for index in range(0,7):#已经知道密钥长度是7
    subarr=arr[index::7]
    vigenerekeys.append(findindexkey2(subarr))
print(vigenerekeys)#输出的是[[186], [31], [145], [178], [83], [205], [62]].
 
 
print("#########")
ming=''
for i in range(0,len(arr)):
    ming=ming+chr(arr[i]^vigenerekeys[i%7][0])
print(ming)

习题三:Cryptopals Set 1

题目链接:http://www.cryptopals.com/sets/1

Question:

(1) Convert hex to base64

(2) Fixed XOR

(3) Single-byte XOR cipher

(4) Detect single-character XOR

(5) Implement repeating-key XOR

(6) Break repeating-key XOR

Answer:

(1)

import base64
str1="49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d".decode("hex")
m = base64.b64encode(str1)
print m

(2)

import base64
import re
str1="1c0111001f010100061a024b53535009181c".decode('hex')
str2="686974207468652062756c6c277320657965".decode('hex')
str3=[]
for i in range(0,len(str1)):
    str3+=[chr(ord(str1[i])^ord(str2[i]))]
str3="".join(str3)
print str3.encode('hex')

(3)

import re
str="1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736"
score=0
for i in range(0,129):
    tmp=[]
    for j in re.findall(".{2}",str):
        tmp += chr(i^int(j,16))
    tmpstr = "".join(tmp)
    num=0
    for j in range(0,len(tmpstr)):
       if tmpstr[j]>='a'and tmpstr[j]<='z':
           num+=1
    if num>score:
       score=num
       ansstr=tmpstr
       key=chr(i)
print key
print ansstr

(4)

import re
wenben=[]
for i in open("ex4.txt","r").readlines():
    wenben+=[i.replace("\n","")] 
score=0
for k in wenben:
    for i in range(0,129):
        tmp=[]
        for j in re.findall(".{2}",k):
            tmp += chr(i^int(j,16))
        tmpstr = "".join(tmp)
        num=0
        num=len(re.findall(r'[a-zA-Z ]',tmpstr))
        if num>score:
            score=num
            ansstr=tmpstr
            c=k
            key=chr(i)
print c
print key
print ansstr

(5)

import re
str1="Burning 'em, if you ain't quick and nimble I go crazy when I hear a cymbal"
str2="ICE"*200
str3=[]
for i in range(0,len(str1)):
    str3 +=[(chr(ord(str1[i])^ord(str2[i])))]
print "".join(str3).encode('hex')

(6)

import re
import base64
with open("challenge6.txt","r") as fp:
    wenben=[base64.b64decode(i) for i in fp.readlines()]
wenben="".join(wenben)
def english_test(sentence):
    score = 0
    freqs = {
        'a': 0.0651738, 'b': 0.0124248, 'c': 0.0217339,
        'd': 0.0349835, 'e': 0.1041442, 'f': 0.0197881,
        'g': 0.0158610, 'h': 0.0492888, 'i': 0.0558094,
        'j': 0.0009033, 'k': 0.0050529, 'l': 0.0331490,
        'm': 0.0202124, 'n': 0.0564513, 'o': 0.0596302,
        'p': 0.0137645, 'q': 0.0008606, 'r': 0.0497563,
        's': 0.0515760, 't': 0.0729357, 'u': 0.0225134,
        'v': 0.0082903, 'w': 0.0171272, 'x': 0.0013692,
        'y': 0.0145984, 'z': 0.0007836, ' ': 0.1918182}
    for x in sentence.lower():
        if x in freqs:
            score += freqs[x]
    return score 
def hanming(x,y):
    num=0
    for i in range(0,len(x)):
        t=ord(x[i])^ord(y[i])
        while t:
           if t&1 : num+=1
           t>>=1
    return num
def thechar(st1):
    score = 0
    for i in range(0, 255):
        tmp = []
        for j in range(0,len(st1)):  
            tmp += chr(i ^ int(st1[j],16))
        tmpstr = "".join(tmp)

        num=english_test(tmpstr)
        if num > score:
            score = num  
            key = chr(i)
    return key

ans = []
for i in range(1,41):
    str1=[]
    str2=[]
    str3=[]
    str4=[]
    for j in range(0,i): str1+=[wenben[j]]
    for j in range(i,2*i): str2+=[wenben[j]]
    for j in range(2*i,3*i): str3+=[wenben[j]]
    for j in range(3*i,4*i): str4+=[wenben[j]]
    str1="".join(str1)
    str2="".join(str2)
    str3="".join(str3)
    str4="".join(str4)
    x1=float(hanming(str1,str2))/i
    x2=float(hanming(str2,str3))/i
    x3=float(hanming(str3,str4))/i
    x4=float(hanming(str1,str4))/i
    x5=float(hanming(str1,str3))/i
    x6=float(hanming(str2,str4))/i
    aa=(x1+x2+x3+x4+x5+x6)/6
    ans+=[(i,aa)]
ans.sort(lambda x,y:cmp(x[1],y[1]))
for i in range(len(ans)):
    print ans[i][0],ans[i][1]

wenben=wenben.encode('hex')
block=[re.findall(r'(.{2})',z)  for z in re.findall(r'(.{58})',wenben)]

keyy = []
for i in range(0,29):
    tmp=[]
    for j in range(0,len(block)):
        tmp+=[block[j][i]]
    keyy+=[thechar(tmp)]
keyy="".join(keyy)

print keyy
keyy=keyy*10000

wenben=wenben.decode('hex')
an=[]
for i in range(0,len(wenben)):
    an+=[chr(ord(wenben[i])^ord(keyy[i]))]
an="".join(an)
print an

习题四:MTC3 Cracking SHA1-Hashed Passwords

题目链接:https://www.mysterytwisterc3.org/en/challenges/level-2/cracking-sha1-hashed-passwords

Question:

The Secure Hash Algorithmus 1 has been standardized by the National Institute of Standards and Technology in 1995 and is besides MD5 the most commonly used hash algorithm in practice. An example for its usage is password-based authentification. In that case, the server does not store the user password in plain text but instead the SHA1 hash value of it. Once the user enters his password and after its received at the server, its hash value is computed and compared to the value stored on the server in order to verify its correctness.

A vulnerability of a surveillance system’s webserver leaked the SHA1 hash value of the password of the administrator account. The password’s hash value is

67ae1a64661ac8b4494666f58c4822408dd0a3e4

Furthermore, the keyboard of the login terminal shows clear signs of the entered password since after a successful login navigation in the software is only done via arrow keys. What is the password?

Remark: Note the German keyboard layout!

Answer:

根据键盘上的按键分布,可以看出右边的数字键只有2486,很有可能是当做上下左右的功能,然后对剩余的按照每个按键出现1次进行暴力,可得出解。

由于需要在10s之内求解,而python遍历全部密钥空间需要15s,而根据遍历的原理,我们只要选出使最外层的循环正确的字母,就可以缩短一半的时间,因此若求解时间不符合要求,将最外层的求解顺序变换一下即可。

附代码:

import re
from Crypto.Hash import SHA
import hashlib
import itertools
import datetime
starttime = datetime.datetime.now()
hash1="67ae1a64661ac8b4494666f58c4822408dd0a3e4"
str1="QqWw%58(=0Ii*+nN"
str2=[['Q', 'q'],[ 'W', 'w'],[ '%', '5'], ['8', '('],[ '=', '0'], ['I', 'i'], ['*', '+'], ['n', 'N']]
def sha_encrypt(str):
    sha = hashlib.sha1(str)
    encrypts = sha.hexdigest()
    return encrypts
st3="0"*8
str4=""
str3=list(st3)
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
                                       exit(0)

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值