第一次接触加密算法,通过Crytopals来学习。
参考文章:
从题中可以了解,需要将文章中16进制转换为base64编码,那么首先了解一下base64。
Base64:是网络上最常见的用于传输8Bit字节码的编码方式之一,是一种基于64个可打印字符来表示二进制数据的方法。
(1) Base64是网络上最常见的用于传输8Bit字节码的可读性编码算法之一。
(2) 可读性编码算法不是为了保护数据的安全性,而是为了可读性。
(3) 可读性编码不改变信息内容,只改变信息内容的表现形式
在一些网络传送渠道,有时候有些的字节字符不能被支持.比如图片的二进制流的每个字节不可能全部是可见字符,这种情况下传送不了.而Base64的机制就能很好解决这种问题,它不改变原来的协议,在原来的基础做一种扩展,基于64个可打印字符来表示二进制.这样不会改变原来的图片,同理还有邮件等需要加密的文件。
首先,了解下base64是如何编码的:
首先我们规定一个字符:abc。
1.将字符通过ASCII码编码。
2.将编好的ASCII码变为二进制(8位),并以6位为一组,分成四组。
3.将这四组的高位各补两个0,转为十进制数。
4.查表,得到对应的字符,就是对应的Base64转化的字符。
如果最后剩下两个输入数据,在编码结果后加1个“=”;如果最后剩下一个输入数据,编码结果后加2个“=”;如果没有剩下任何数据,就什么都不要加。或分成6位一组后,最后一组没有到6位时需要填充一个=或者两个=。
代码实现(python):
from enum import Enum
b64_encoding_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk" \
"lmnopqrstuvwxyz0123456789+/"
class Status(Enum):
START_NEX = 0
TAKE_2 = 1
TAKE_4 = 2
def hex_to_base64(hexdata):
b64data = ""
sixbits = 0
status = Status.START_NEX
for hexchar in hexdata:
dec = int(hexchar, 16)
if status == Status.START_NEX:
sixbits = dec
status = Status.TAKE_2
elif status == Status.TAKE_2:
sixbits = (sixbits << 2) | (dec >> 2)
b64data += b64_encoding_table[sixbits]
sixbits = (dec & 0x3)
status = Status.TAKE_4
elif status == Status.TAKE_4:
sixbits = (sixbits << 4) | dec
b64data += b64_encoding_table[sixbits]
status = Status.START_NEX
if status == Status.TAKE_2:
sixbits <<= 2
b64data += b64_encoding_table[sixbits]
b64data += "="
elif status == Status.TAKE_4:
sixbits <<= 4
b64data += b64_encoding_table[sixbits]
b64data += "=="
return b64data
def main():
print(hex_to_base64("49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d"))
if __name__ == '__main__':
main()
由题可知,这题是考察XOR异或。
需要对两个16进制代码异或,生成下面的代码。
使用python实现:
def hex_xor (hex1, hex2):
dec1 = int(hex1, 16)
dec2 = int(hex2, 16)
xor = dec1 ^ dec2
return hex(xor)
def main():
a = hex_xor("1c0111001f010100061a024b53535009181c",
"686974207468652062756c6c277320657965")
print(a)
if __name__ == '__main__':
main()
结果为
但是,结果中含有[0x]这个前缀,查阅资料发现,只要在return处后面加[2:] 就可以解决。
return hex(xor)[2:]
结果与题目中所要结果相同。
异或:异或运算符”∧”,它的规则是若参加运算的两个二进位同号,则结果为0(假);异号则为1(真)。即 0∧0=0,0∧1=1, 1^0=1,1∧1=0。
运算 | 说明 |
---|---|
0^1=1, 0^0=0 | 0异或任何数,其结果=任何数 |
1^0=1, 1^1=0 | 1异或任何数,其结果=任何数取反 |
x^x=0 | 任何数异或自己,等于把自己置0 |
异或的运算:先转化为二进制,再对照位来进行运算,相同为0,不同为1。
通过按位异或运算,可以实现两个值的交换,而不必使用临时变量。例如交换两个整数a=3,b=4的值,可通过下列语句实现:
a=a∧b;
b=b∧a;
a=a∧b;
在第三题里,只给了一个经过异或的密文,而我们知道是由单个字符所异或,这里起初想用暴力破解。
字符频率是一个很好的指标,我没有考虑到字符频率的问题(不在目前知识范围内)而忽略了这个提示,查阅百度发现,字符频率就是日常生活该字符的使用频率。如果为正常的英文文本,那么它的字符频率应该尽可能的大。所以,只要能计算出哪个字符异或后字符频率打,这就是这道题的答案。
比如这里有两个解密后的字符串:
I am a student.
sd evbgac rgbq.
通过计算
第一个:0.04+0.19+0.08+0.02+0.19+0.08+0.19+0.06+0.09+0.02+0.04+0.12+0.06+0.09=1.27
同理第二个:
0.82
由此可知。第一个为正确结果
def get_score(input_bytes):
score = 0
for byte in input_bytes:
score += CHARACTER_FREQ.get(chr(byte).lower(), 0)
return score
对字符串中每个字符与key进行异或
def singlechar_xor(input_bytes, key_value):
output = b''
for char in input_bytes:
output += bytes([char ^ key_value])
return output
用每个可能的字节对密码异或解密,计算得出的明文分数。所用的key就是题目要找出来的key
def singlechar_xor_brute_force(ciphertext):
candidates = []
for key_candidate in range(256):
plaintext_candidate = singlechar_xor(ciphertext, key_candidate)
candidates_score = get_score(plaintext_candidate)
result = {
'key': key_candidate,
'score': candidates_score,
'plaintext': plaintext_candidate,
}
candidates.append(result)
return sorted(candidates, key=lambda c: c['score'], reverse=True)[0]
打印明文和key
def pretty_print_result(result):
print(result['plaintext'].decode().rstrip(),
"\tKey:", chr(result['key']))
最后的结果为
由此可知加密前的明文和key。
参考文章: