前言
第一次接觸加密算法,通過Crytopals來學習。
參考文章:
Set 1:Basics
1.第一題
從題中可以瞭解,需要將文章中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()
結果爲
2.第二題
由題可知,這題是考察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;
3.第三題
在第三題裏,只給了一個經過異或的密文,而我們知道是由單個字符所異或,這裏起初想用暴力破解。
字符頻率是一個很好的指標,我沒有考慮到字符頻率的問題(不在目前知識範圍內)而忽略了這個提示,查閱百度發現,字符頻率就是日常生活該字符的使用頻率。如果爲正常的英文文本,那麼它的字符頻率應該儘可能的大。所以,只要能計算出哪個字符異或後字符頻率打,這就是這道題的答案。
比如這裏有兩個解密後的字符串:
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。
參考文章: