会写这篇文章主要是因为前段时间出了一道一次一密的密码学题目,虽然技术含量很低,但还是把那次搜集的一次一密的学习总结一下。
介绍
一次一密有绝对安全的说法,是因为每次加密时密钥都要变化。每次的密钥变化使密文之间没有统计学规律。但是每次加密都变换密钥使得密钥的传递和分发变得困难。
密钥生成可以使用随机密码字母集来生成密钥,密钥分发可以使用RSA、ECC来进行交换密钥。不过说起来信道传输始终是问题,而且非对称加密本来速度就很慢,如果每次发送密文前先用非对称加密发送一次密钥就会非常慢了,所以一次一密一般用于高度机密的低带宽信道。
加密方法
一次一密的加密算法就是异或加密。将明文分组,与密钥按位异或即可。
我这里出的题目是用同一个密钥去加密多条明文,当密文条数较多时就很容易被攻击,例如Many Time Pad。
# -*- coding: utf-8 -*-
import string
import collections
import sets
def strxor(a, b): # xor two strings (trims the longer input)
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
def OPT_create(key,crypto):
ciphertext=strxor(key,crypto)
return ciphertext
key='afctf{OPT_1s_Int3rest1ng}'
c1='Dear Friend, This time I u'
c2='nderstood my mistake and u'
c3='sed One time pad encryptio'
c4='n scheme, I heard that it '
c5='is the only encryption met'
c6='hod that is mathematically'
c7=' proven to be not cracked '
c8='ever if the key is kept se'
c9='cure, Let Me know if you a'
c10='gree with me to use this e'
c11='ncryption scheme always.'
ciphers = [c1, c2, c3, c4, c5, c6, c7, c8,c9,c10,c11]
for x in ciphers:
k=OPT_create(key,x).encode('hex')
print k
#print k.decode('hex')
得到11条密文
25030206463d3d393131555f7f1d061d4052111a19544e2e5d
0f020606150f203f307f5c0a7f24070747130e16545000035d
1203075429152a7020365c167f390f1013170b1006481e1314
0f4610170e1e2235787f7853372c0f065752111b15454e0e09
081543000e1e6f3f3a3348533a270d064a02111a1b5f4e0a18
0909075412132e247436425332281a1c561f04071d520f0b11
4116111b101e2170203011113a69001b475206011552050219
041006064612297020375453342c17545a01451811411a470e
021311114a5b0335207f7c167f22001b44520c15544801125d
06140611460c26243c7f5c167f3d015446010053005907145d
0f05110d160f263f3a7f4210372c03111313090415481d49
Many Time Pad攻击
这个攻击的原理是c1⊕c2 = m1⊕m2,而通过m1⊕m2可以分析出m1和m2,因此m1与m2不再安全。
代码搬运自GitHub。
#!/usr/bin/python
## OTP - Recovering the private key from a set of messages that were encrypted w/ the same private key (Many time pad attack) - crypto100-many_time_secret @ alexctf 2017
# Original code by jwomers: https://github.com/Jwomers/many-time-pad-attack/blob/master/attack.py)
import string
import collections
import sets, sys
# 11 unknown ciphertexts (in hex format), all encrpyted with the same key
c1='25030206463d3d393131555f7f1d061d4052111a19544e2e5d'
c2='0f020606150f203f307f5c0a7f24070747130e16545000035d'
c3='1203075429152a7020365c167f390f1013170b1006481e1314'
c4='0f4610170e1e2235787f7853372c0f065752111b15454e0e09'
c5='081543000e1e6f3f3a3348533a270d064a02111a1b5f4e0a18'
c6='0909075412132e247436425332281a1c561f04071d520f0b11'
c7='4116111b101e2170203011113a69001b475206011552050219'
c8='041006064612297020375453342c17545a01451811411a470e'
c9='021311114a5b0335207f7c167f22001b44520c15544801125d'
c10='06140611460c26243c7f5c167f3d015446010053005907145d'
c11='0f05110d160f263f3a7f4210372c03111313090415481d49'
ciphers = [c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11]
# The target ciphertext we want to crack
#target_cipher = "0529242a631234122d2b36697f13272c207f2021283a6b0c7908"
# XORs two string
def strxor(a, b): # xor two strings (trims the longer input)
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
def target_fix(target_cipher):
# To store the final key
final_key = [None]*150
# To store the positions we know are broken
known_key_positions = set()
# For each ciphertext
for current_index, ciphertext in enumerate(ciphers):
counter = collections.Counter()
# for each other ciphertext
for index, ciphertext2 in enumerate(ciphers):
if current_index != index: # don't xor a ciphertext with itself
for indexOfChar, char in enumerate(strxor(ciphertext.decode('hex'), ciphertext2.decode('hex'))): # Xor the two ciphertexts
# If a character in the xored result is a alphanumeric character, it means there was probably a space character in one of the plaintexts (we don't know which one)
if char in string.printable and char.isalpha(): counter[indexOfChar] += 1 # Increment the counter at this index
knownSpaceIndexes = []
# Loop through all positions where a space character was possible in the current_index cipher
for ind, val in counter.items():
# If a space was found at least 7 times at this index out of the 9 possible XORS, then the space character was likely from the current_index cipher!
if val >= 7: knownSpaceIndexes.append(ind)
#print knownSpaceIndexes # Shows all the positions where we now know the key!
# Now Xor the current_index with spaces, and at the knownSpaceIndexes positions we get the key back!
xor_with_spaces = strxor(ciphertext.decode('hex'),' '*150)
for index in knownSpaceIndexes:
# Store the key's value at the correct position
final_key[index] = xor_with_spaces[index].encode('hex')
# Record that we known the key at this position
known_key_positions.add(index)
# Construct a hex key from the currently known key, adding in '00' hex chars where we do not know (to make a complete hex string)
final_key_hex = ''.join([val if val is not None else '00' for val in final_key])
# Xor the currently known key with the target cipher
output = strxor(target_cipher.decode('hex'),final_key_hex.decode('hex'))
print "Fix this sentence:"
print ''.join([char if index in known_key_positions else '*' for index, char in enumerate(output)])+"\n"
# WAIT.. MANUAL STEP HERE
# This output are printing a * if that character is not known yet
# fix the missing characters like this: "Let*M**k*ow if *o{*a" = "cure, Let Me know if you a"
# if is too hard, change the target_cipher to another one and try again
# and we have our key to fix the entire text!
#sys.exit(0) #comment and continue if u got a good key
target_plaintext = "cure, Let Me know if you a"
print "Fixed:"
print target_plaintext+"\n"
key = strxor(target_cipher.decode('hex'),target_plaintext)
print "Decrypted msg:"
for cipher in ciphers:
print strxor(cipher.decode('hex'),key)
print "\nPrivate key recovered: "+key+"\n"
for i in ciphers:
target_fix(i)