前言
前文分享了我對 Base64 的觀點,並提到正規 Base64 的主要用途在數據傳送,它的強項不在資料的保密,用 ASCII 字元呈現二進位資料反而在傳播的過程中更清澈、透明,而擴展了適用的範疇。雖然經過 Base64 編碼後的資料量大約多了 33%,但在小量資料的處理上它提供了簡潔的解決方案,像是:
- 在 MIME 標準下 Email 傳送時可把圖片、音效等資料直接嵌入信件本文,形成附檔。
這是一封郵件的原始檔,可看到附檔用的就是 Base64 字串。 - 在資料庫的文字欄位裡面,可以用文字型態存取圖像資料,像是會員頭像、身分頭銜標章圖示等。
我的德州 Poker 玩家頭像圖片就是存在 MySQL 的文字欄位。 - 經 Base64 編碼的資料用純文字就可以編輯、複製、傳遞、散發...,雖然要使用實際資料時還是要用程式碼解決,但相對於 Binary 格式的資料而言,平面文字仍算是輕量級的,尤其它用的演算法是十分簡單的。
什麼是 Base64 的索引字串
第一篇貼文說到 Base64 是用 64 進制的表示法,它用 64 個不同的符號分別代表數據的 0-63,就如同我們在 16 進位下用 0123456789ABCDEF 表示 0-15 一樣。這個 Base64 的索引字串就是:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
數一下這個字串剛好 64 個字元,它們排列的順序是 RFC 文件規範的公定標準(http://tools.ietf.org/html/rfc1421),不同於一般之處是它的 0-9 不是放在最前面,不過這不重要,只要編碼和解碼用的都是一樣的順序,就一定能忠實的解回原來的數據。
讓 Base64 可用來加密資料
如果刻意不用標準的索引字串,改用自行安排字元順序的字串為索引會發生什麼事呢?結果會是:
- 這個 Base64 系統還是會正常工作,也能編碼出一大串看起像是正常 Base64 編出來的字元集合。
- 但用標準 Base64 解碼無法解出資料內容,只有再度使用編碼時相同順序的索引字串才能正確解回。
- 換句話說資料有了隱密性,Base64 編碼有了附加價值。
因此如果我們把那 64 個字元重新排列組合產生新字串,並用這個新字串來做索引,經過 Base64 編碼程序產生的 Base64 字串就可以稱得上是加過密了。因為外人並不知道索引字串及其排列順序為何。要解出正確內容非得解碼的一端也用相同的索引字串才行,而描述這個字串順序的資訊就叫做 key 了,如何把這個字串順序(key)傳遞到解碼端,就是個技巧了。
- 把索引字串直接寫成常數,固定在編/解碼程序裡就沒有傳遞 key 的問題了。你我都知道這不是個好主意,因為沒有了 key 的彈性。
- 編碼時把索引字串附加到編碼輸出的字串中,解碼時先取出這個 key,然後再用來解開其餘的密文。這也不是很妥當,太明目張膽了很容易被洞悉夾帶的方式,而且編碼長度至少多了 64 Bytes,也會形成負擔。
- 永遠使用互動式介面處理,每次解碼時都彈出對話框要求輸入。這也不妥,因為多數情形下解碼是在安靜模式下進行的,而且長度 64 字元的 key 也不方便記在腦袋裡。
- 解碼函式使用 Optional 引數來傳入 key,若發現 key 是空值就用標準的索引字串處理,若 key 有值就用所描述的自訂字串處理。這有個好處就是當編碼不用 key(不上鎖)時它和標準的 Base64 完全相容,若編碼時加了 key 就是客製化的 Base64 了,一魚可以兩吃。
下面這段程式碼可以根據輸入任意長度的字串做為 key,然後再用 key 來弄亂那 64 個字元的順序:
'-------------------------------------------------------------- '函數功能:解碼字串至 Binary 陣列 '-------------------------------------------------------------- Function Ku64_To_Array(ByVal s As String, Optional ByVal key As String = "") As Array Dim kuKey As String = Base64_索引字串 '---若 key 有值則產生一組非標準的字元順序 --- If key <> "" Then kuKey = CreateKey64(key) '..... '.....<略>..... '..... End Function Function CreateKey64(ByVal key As String) As String Dim 基因 = key.GetHashCode '---以 key 的雜湊碼為「亂數種子」把字串弄亂--- Dim X = Rnd(-1) : Randomize(基因) '---用 Rnd(-1) 維持後續亂數的一致性--- Dim 前段字串 As String = Base64_索引字串, 後段字串 As String = "", tmp As Char For i = 1 To 32 '---隨機抽出32個字母排在最前面, 剩餘的排在後面--- tmp = Mid(前段字串, Int(Rnd() * Len(前段字串) + 1), 1) 後段字串 &= tmp : 前段字串 = Replace(前段字串, tmp, "") Next Return 後段字串 & 前段字串 End Function
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Debug.Print(ku.CreateKey64("$/?..0")) Debug.Print(ku.CreateKey64("john")) Debug.Print(ku.CreateKey64("哈利波特")) Debug.Print(ku.CreateKey64("123456")) Debug.Print(ku.CreateKey64("abc")) Debug.Print(ku.CreateKey64("VB.net Hello World...")) End Sub
不同的 key 確實可以產生不同的 Base64 索引字串:
7VoCGqmuntih+HXZ4RJPUQpDjFAkaL3dBEIKMNOSTWYbcefglrsvwxyz0125689/
s7NUMc0X2dKQYnAW1eILk8SV+RbCa4vpBDEFGHJOPTZfghijlmoqrtuwxyz3569/
BjcRgNqifISuThkmZPyJwCEvHd/0YXbMADFGKLOQUVWaelnoprstxz123456789+
bci5EBYoPAWR/4eD0J6fZlxzmMXV3ajOCFGHIKLNQSTUdghknpqrstuvwy12789+
Qfy6sK1hlZekr4WTADdc7/RY2x8iC+nFBEGHIJLMNOPSUVXabgjmopqtuvwz0359
7Lj4v/gBWVGnDQ9yceH8ZaMfCkRbAmx1EFIJKNOPSTUXYdhilopqrstuwz02356+
光是弄亂索引字串還是不夠
如果 key 的操作邏輯和傳遞方式已經解決了,在編碼時已經可以根據提供的 key 值得到一個混亂的索引字串,並且解碼時也可以用相同的 key 解回原狀了,此時這個機制已經初步可用了。但還不算專業,因為:
- 它用的原理還是 256 進位 → 64 進位的數底轉換,這個演算法太陽春了。
- 相同的 key 會產生唯一的編碼結果,如果把原始字串逐步增加一個字元(或刪減最後的字元)再觀察編碼結果,而分析比對的話,花了功夫是可能找到這個索引字串的,它的安全性不夠。
所以除了混亂索引字串之外還是要建立自己的演算法,這才算完整。
基因突變
如同生物演化的進程,由一個單獨的基因啟動事先規畫好的程序,就可以用極其輕小的 key 做為種子,再進一步根據這個基因 key 所蘊藏的演化法則去運作,例如產生那 64 字元的索引字串,不同的基因會演化出不同的結果,所以如果基因可以突變,達成每次編碼都能以新的索引變化出新的物種,即便是相同的原始資料(明文)也能在每次編碼之後產生完全不同的結果(密文),就能達到混淆的目的。
下回貼文
本文暫打住,下回要分享的是:
- 重寫 Base64 Encode/Decode 函式:因為 VB.net 的 Base64 是封裝在類別裡面的,它固定使用標準的 Base64 索引字串,我們要能置換掉它最好是完全掌控每一個環節。
- 建立自己的演算法:針對位元組陣列 Byte() 做位元運算,從而輸出一個改頭換面的(就像上面貼圖的內容)。
- 基因突變的做法:讓我們的 Base64 除了可以有 key 來加密外,還可以做到即便是相同的原始字串,即便是使用相同的 key 做加密也能讓每次輸出的密文不同。