【Python 資安筆記】編碼(Encoding)
【Python 資安筆記】編碼(Encoding)
Cover : https://www.publicdomainpictures.net/en/view-image.php?image=563833&picture=hacking
感謝你點進本篇筆記!該系列筆記主要紀錄學習資安的過程,以及我個人的一些簡單白話解釋,另外也涉及到在學校中上課所學的資安技巧及知識。
若本篇文章有誤,麻煩各位告訴我,這樣才能好讓我進步!謝謝~
自網站 CryptoHack 進行學習:https://cryptohack.org/
ASCII 字元編碼
Python 之間的轉換可以用 chr() 以及 ord() 兩個函式做到。
以下是對於 chr() 以及 ord() 兩函式的解釋:
chr()接受十進位或十六進位的數字,回傳值為傳入參數所對應的 ASCII 字元,如傳入參數97,就回傳輸出'a'這個字元。ord()與chr()相反,它接受一個字元當作參數傳入,回傳對應的 ASCII 數值,或是 Unicode 數值。
以下程式碼就做了兩個函數之間的轉換關係:
1 | flag = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100] |
Output:
1 | Hello World |
Hexadecimal ASCII Code
先把 ASCII 字元轉成相對應的十進位數字,如 'a' 轉成 97。
再把這個十進位數字 97 轉成十六進位數字,即可形成一個十六進位字串。
這邊舉 ‘Hello World’ 當例子:
- H (72) → 48 (十六進制)
- e (101) → 65 (十六進制)
- l (108) → 6C (十六進制)
- l (108) → 6C (十六進制)
- o (111) → 6F (十六進制)
- 空格 (32) → 20 (十六進制)
- W (87) → 57 (十六進制)
- o (111) → 6F (十六進制)
- r (114) → 72 (十六進制)
- l (108) → 6C (十六進制)
- d (100) → 64 (十六進制)
最後把這些十六進位數字拼起來,就會得到這樣的十六進位字串:48656C6C6F20576F726C64
在 Python 中,可以利用 bytes.fromhex() 函式把 hex(十六進位)轉換成 bytes 資料型態,即將這串十六進位數字編碼了。
與其相對的方法即為 .hex() 方法,再把 bytes 轉回去 hex。
以下是對 bytes.fromhex() 函式以及 .hex() 方法的解釋:
bytes.fromhex()接受一個字串當作參數(這字串當然就是十六進位字串了),然後會回傳一個 bytes 型態的值,如b'Hello World'裡面的 b 就表示將字串轉成 bytes。.hex()是 bytes 物件的方法,主要把 bytes 轉成純十六進位字串(不含 0x)。
以下 Python 程式碼實作出兩個函式的轉換關係:
1 | # Original hex string |
Base64
Base64 也是一種常見的編碼系統。
Base64 是將二進位資料編碼成可顯示 ASCII 字元的方法,以 64 個字元組成的 ASCII 字串來表示二進位資料,其中 4 個字元編碼為 3 個 Bytes。
在 Python 中,有個函式是 base64.b64encode(),可以對 Base64 做編碼。
在此之前需要引入 base64 函式庫 import base64。
另外這個函式傳入的參數要是 bytes,輸出的東西也會是 bytes,所以要事先將字串轉換成 bytes,這邊就用方法 encode() 去轉換成 bytes。
以下是個 Python 程式碼範例:
1 | import base64 |
Output:
1 | b'SGVsbG8gV29ybGQ=' |
要轉成 ASCII 字串的話,可以使用 decode('ascii') 轉換。
1 | import base64 |
Output:
1 | SGVsbG8gV29ybGQ= |
字串轉數字
將文字訊息轉換成數字,這樣有益於在密碼系統中做數學運算。如 RSA 密碼系統是基於數學運算的,只能處理數字,但我們要加密的訊息通常是由字元組成的文字,因而需要一個標準方法將文字轉換成數字。
最常見的轉換方法:
- 取得 ASCII 數值:將每個字元轉換成對應的 ASCII 數值。
- 轉成十六進位:將每個數值表示為十六進位格式。
- 串接:將所有十六進位數字連接在一起。
- 當作一個大數字:將整串十六進位當作一個大的數字來處理。
我們拿 “HELLO” 這個字串來舉例:
ASCII 字元值:
- H → 72
- E → 69
- L → 76
- L → 76
- O → 79
把這些數值轉換成十六進位來表示:[0x48, 0x45, 0x4c, 0x4c, 0x4f]。
串接後的十六進位變這樣:0x48454c4c4f
轉成十進位大數:310400273487。
若要將十進位大數轉換回去原本的文字訊息,可能稍嫌麻煩,但是沒關係,Python 有個函式庫叫做 PyCryptodome,我們輸入 pip install pycryptodome 即可安裝。
那這個函式庫幫我們實現了兩個方法:bytes_to_long()、long_to_bytes()。
如其名,前者是 bytes 轉大數,後者為大數轉 bytes。
在使用函式前可以用以下的程式碼引入函式庫:from Crypto.Util.number import *。
以下是 Python 範例程式碼,展示兩個方法之間的轉換關係:
1 | from Crypto.Util.number import * |
Output:
1 | b'Python' |
以下程式碼展示了如何將字串轉成大數:
1 | from Crypto.Util.number import * |
Output:
1 | 129003106218635378415641025119450797906483319236477 |
XOR
原本的 OR 運算之真值表是這樣的:
| Input 1 | Input 2 | Output |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 1 | 1 |
XOR 運算只有當單一個 1 在輸入端時才會 Output 1,亦即當兩個輸入都是 1 的時候會輸出 0,而非 1:
| Input 1 | Input 2 | Output |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 1 | 0 |
在大多數程式語言當中,表示 XOR 會用 ^ 符號表示,另外其數學符號是 ⊕,一個圓裡面有十字。
那這在編碼上有什麼應用呢?如可以將一個字串轉成 ASCII 數值,之後對每個字元的 ASCII 數值做 XOR 運算,即可得到另外一個字串。
以下是一個簡單的範例,要從字串 "Ehaab" 中對其做 XOR 13 的運算,看會得到什麼字串:
1 | flag = "Ehaab" |
Output:
1 | Hello |
XOR 四大特性
- 交換律(Commutative):
- 表示 XOR 的運算順序不重要。
A ⊕ B = B ⊕ A
- 結合律(Associative):
- 同交換律,XOR 運算順序不重要,也不用去擔心有括號這件事。
A ⊕ (B ⊕ C) = (A ⊕ B) ⊕ C
- 恆等式(Identity):`
- 表示對 0 做 XOR 還是等同他自己(A)。
- A ⊕ 0 = A`
- 自反律(Self-Inverse):
- 表示對自己 XOR 會回傳 0。
A ⊕ A = 0
在 CryptoHack 有個題目:
1 | KEY1 = a6c8b6733c9b22de7bc0253266a3867df55acde8635e19c73313 |
利用 XOR 的四大特性去推測出 FLAG。
範例程式碼:
1 | KEY1 = 0xa6c8b6733c9b22de7bc0253266a3867df55acde8635e19c73313 |
上述程式碼的部分,要求 KEY2,那就在題目給的 KEY2 ^ KEY1 中再 XOR 一次 KEY1,讓 KEY1 消掉,即可獲得 KEY2。KEY3、FLAG 以此類推去做。
暴力搜尋 XOR
一樣是 CryptoHack 的題目,題目給你一個十六進位字串,然後去找 Flag:
1 | 73626960647f6b206821204f21254f7d694f7624662065622127234f726927756d |
這邊先用 bytes.fromhex() 把他轉成 bytes 看看:
會發現出現一堆亂碼:
然後題目沒有給你任何提示說,是用哪一個 bytes 去做 XOR 運算的,所以這邊我們可以嘗試用暴力破解看看,因為完整的 ASCII 256 個字元,直接暴力下去就對了。
範例程式碼:
1 | flag = "73626960647f6b206821204f21254f7d694f7624662065622127234f726927756d" |
由於它官方的 flag 是由 crypto{ 開頭,以及 } 結尾,所以這邊可以用到字串方法 .startswith() 跟 .endswith() 判斷字串開頭即結尾。
若找到就直接輸出該字串,不用看到一大堆亂碼了。
更複雜的 XOR
接下來這個題目用剛剛單一個 bytes 去暴力破解是不行的,然後請看題目:
1 | 0e0b213f26041e480b26217f27342e175d0e070a3c5b103e2526217f27342e175d0e077e263451150104 |
若用剛剛單一 bytes 去暴力破解,會發現完全找不出有關 crypto{} 的資訊,所以唯一可能就是這個 XOR 運算用到多 bytes。
然後 CryptoHack 裡面有提示:”Remember the flag format and how it might help you in this challenge!”
這邊我們試著把 flag 跟 ‘crypto{‘ 做 XOR 看看(用 pwntools):
pwntools installation in Windows:https://blog.pcat.cc/%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95/pwn/pwntools
1 | from pwn import * |
Output:
1 | b'myXORke+y_Q\x0bHOMe$~seG8bGURN\x04DFWg)a|\x1dTM!an\x7f' |
可以發現出現了 myXORke+y 這個字串,如果我們去掉 + 號,讓 myXORkey 跟 flag 做 XOR 看看。
1 | from pwn import * |
Output:
1 | b'myXORke+y_Q\x0bHOMe$~seG8bGURN\x04DFWg)a|\x1dTM!an\x7f' |
居然,這就是答案了!
解法來自:https://cryptohack.org/challenges/xorkey1/solutions/
這是一名叫 oushanmu 的 user 的解法。
然後我們在 CryptoHack 中的 Introduction to CryptoHack 環節就完成啦。
總整理
ASCII 字元編碼
chr():數字 → 字元 (如:chr(97) → ‘a’)ord():字元 → 數字 (如:ord(‘a’) → 97)
十六進位轉換
bytes.fromhex():十六進位字串 → bytes.hex():bytes → 十六進位字串- 範例:”Hello” → ASCII → 十六進位 → “48656C6C6F”
須注意這邊 .hex() 是方法,而 hex() 是函式,兩者是不同的,後者是轉換成 hex 字串。
Base64 編碼
使用 base64 方法前須引入 import base64
base64.b64encode():bytes → Base64 字串decode('ascii'):bytes → 字串- 用於二進位資料的 ASCII 表示。
大數轉換 (PyCryptodome)
使用下面這些方法前須引入 from Crypto.Util.number import *
bytes_to_long():bytes → 大整數long_to_bytes():大整數 → bytes- 用於 RSA 等密碼學應用。
XOR 運算
四大特性:
- 交換律(Commutative):
- 表示 XOR 的運算順序不重要。
A ⊕ B = B ⊕ A
- 結合律(Associative):
- 同交換律,XOR 運算順序不重要,也不用去擔心有括號這件事。
A ⊕ (B ⊕ C) = (A ⊕ B) ⊕ C
- 恆等式(Identity):`
- 表示對 0 做 XOR 還是等同他自己(A)。
- A ⊕ 0 = A`
- 自反律(Self-Inverse):
- 表示對自己 XOR 會回傳 0。
A ⊕ A = 0
破解方法:
- 暴力破解:嘗試 0~255 ASCII Code 去推導出所有的可能 key。
- 已知明文攻擊:利用已知格式推導 key。
實用工具
- pwntools:
xor()函數方便於 XOR 運算。 startswith()/endswith():檢查字串開頭結尾。
Reference
CryptoHack – A free, fun platform for learning cryptography
Day 5 - 編碼 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
base64 —- Base16、Base32、Base64、Base85 資料編碼 — Python 3.13.7 說明文件





