2014年9月1日 星期一

[VB.net][VB6][VBA]客製化 Base64 編碼/解碼 的技巧(一)數底的轉換

http://www.dotblogs.com.tw/ku3/archive/2011/08/21/33702.aspx

先認識 Base64維基百科

Base64 不算是「加密」和「解密」的技術,它其實是「數底轉換」的一種情境,把數據用 64 進制來表達就是 Base64 編碼的內涵。
在數字系統中我們是用「視覺符號」來圖象化數學上抽象的「」,所用的邏輯基礎是「進位原則」,而顯現的結果則是連續排列的符號,並且用這組「連續排列的符號」做為傳達「量」的方式。
寫一個多用途的數底轉換函式,用實例說明一下。

若有一個計物量是「三萬二千七百六十七」用各種不同條件做數底轉換看看會是什麼情形:

Imports System.Text
Public Class Form1
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Demo(32767, "0,1,2,3,4,5,6,7,8,9")
        Demo(32767, "◎,○,●,←,→,?,┼,※,€,《")
        Demo(32767, "0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F")
        Demo(32767, "0,1")
        Demo(32767, "0,1,2,3,4,5,6,7")
        Demo(32767, "0,1,2,3,4")
        Demo(32767, "○,●,↑,↓")
    End Sub
    Sub Demo(ByVal v As Long, ByVal s As String)
        Dim 進位底數 = Split(s, ",").Length
        Dim 圖像 As String = 數值_to_視覺圖像(v, s)
        Dim 數值 As Long = 視覺圖像_to_數值(圖像, s)
        Debug.WriteLine(String.Format("十進制的 {0} 用[{1}]符號以 '{2} 進制' 呈現的圖像是:", v, s, 進位底數) & 圖像)
        Debug.WriteLine(String.Format("用[{0}]符號組成的 {1} 進數 '{2}' 代表了十進制的 ", s, 進位底數, 圖像) & v)
        Debug.WriteLine("")
    End Sub
    '---將數值符號串轉換為 10 進數---
    Function 視覺圖像_to_數值(ByVal 符號 As String, ByVal 使用的符號 As String) As Long
        Dim 符號集合 As List(Of String) = Split(使用的符號, ",").ToList
        Dim 數字陣列 = Split(符號, " ")
        Dim 進位底數 = 符號集合.Count
        Dim 位元數 As Integer = UBound(數字陣列)
        Dim tmp As Long = 0
        For i = 0 To 位元數
            tmp += 符號集合.IndexOf(數字陣列(位元數 - i)) * (進位底數 ^ i)
        Next
        Return tmp
    End Function
    '---將 10 進數轉為用指定進位方式的符號串表示---
    Function 數值_to_視覺圖像(ByVal 數值 As Long, ByVal 使用的符號 As String) As String
        Dim 符號陣列 As List(Of String) = Split(使用的符號, ",").ToList
        Dim 進位底數 = 符號陣列.Count
        Dim tmp As String = ""
        Do
            tmp = 符號陣列(數值 Mod 進位底數) & " " & tmp
            數值 = 數值 \ 進位底數
        Loop While 數值 > 0
        Return Trim(tmp)
    End Function
End Class

輸出結果:

十進制的 32767 用[0,1,2,3,4,5,6,7,8,9]符號以 '10 進制' 呈現的圖像是:3 2 7 6 7
用[0,1,2,3,4,5,6,7,8,9]符號組成的 10 進數 '3 2 7 6 7' 代表了十進制的 
32767
十進制的 32767 用[◎,○,●,←,→,?,┼,※,€,《]符號以 '10 進制' 呈現的圖像是:← ● ※ ┼ ※
用[◎,○,●,←,→,?,┼,※,€,《]符號組成的 10 進數 '← ● ※ ┼ ※' 代表了十進制的 32767
十進制的 32767 用[0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F]符號以 '16 進制' 呈現的圖像是:7 F F F
用[0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F]符號組成的 16 進數 '7 F F F' 代表了十進制的 
32767
十進制的 32767 用[0,1]符號以 '2 進制' 呈現的圖像是:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
用[0,1]符號組成的 2 進數 '1 1 1 1 1 1 1 1 1 1 1 1 1 1 1' 代表了十進制的 
32767
十進制的 32767 用[0,1,2,3,4,5,6,7]符號以 '8 進制' 呈現的圖像是:7 7 7 7 7
用[0,1,2,3,4,5,6,7]符號組成的 8 進數 '7 7 7 7 7' 代表了十進制的 
32767
十進制的 32767 用[0,1,2,3,4]符號以 '5 進制' 呈現的圖像是:2 0 2 2 0 3 2
用[0,1,2,3,4]符號組成的 5 進數 '2 0 2 2 0 3 2' 代表了十進制的 
32767
十進制的 32767 用[○,●,↑,↓]符號以 '4 進制' 呈現的圖像是:● ↓ ↓ ↓ ↓ ↓ ↓ ↓
用[○,●,↑,↓]符號組成的 4 進數 '● ↓ ↓ ↓ ↓ ↓ ↓ ↓' 代表了十進制的 32767
從上面例子可看出:

  1. 同一個數據 32767 用不同的進位制來表示,會有不同的外觀。
  2. 採用的進位底數越大,輸出的位元長度越小。
  3. 符號不一定要用印度阿拉伯數字,用 ◎§←→※ 也是可以,因為它們也是符號。
  4. 數底轉換是可逆的,因此用來做「編碼/解碼」是可行的。

那麼 Base64 是什麼呢?這回我們以字串為例加以說明。

  1. 把字串編碼為數字,放到 Byte() 陣列。
  2. Byte 陣列可以視為 256 進位表示法,每個 Byte 都是一個符號。(它用了 00、01、.........、FF 共 256 個不同的符號)
  3. 用 A-Z、a-z、0-9 及 +/ 共64個符號建立 64 進位系統。
  4. 依序取出陣列的3個字節轉換為 64 進位制的4個字節
  5. 來源陣列長度若不為3的倍數就補 0。
  6. 目的字串長度若不為4的倍數就補 =。

實作看一下:

把「大家好」三個字用 Base64 編碼和解碼。
仍用剛才的函式,只是叫用前先處理一下:
        '---建立所使用的符號集---
        Dim B1 = "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,"
        Dim B2 = "0,1,2,3,4,5,6,7,8,9,+,/"
        Dim strBase64 = B1 & B1.ToLower & B2
        '---把 '大家好' 編碼為 Uncode 數據---
        Dim code As New UnicodeEncoding
        Dim src() = code.GetBytes("大家好")
        '---用 256 進位制(16進制的二個符號一組)來表示--- 
        Dim b = New StringBuilder
        For i = 0 To src.Length - 1
            b.Append(String.Format("{0:x2}", src(i)))
        Next
        Demo("&h" & b.ToString, strBase64)
輸出結果:

十進制的 43266265021785 用[A,B,…(略)…7,8,9,+,/]符號以 '64 進制' 呈現的圖像是:J 1 m 2 W 3 1 Z
用[A,B,…(略)…7,8,9,+,/]符號組成的 64 進數 'J 1 m 2 W 3 1 Z' 代表了十進制的 
43266265021785
用工具驗證一下:
image
也可以用 System.Convert 類別的ToBase64String() 同樣可以得到 ‘J1m2W31Z’的結果。
心得結語:

  1. 經過 Base64 編碼後的字串看起來的確有「加密」效果,但實則不然,因為那 64 個符號排列的順序是公開的(見 RFC1421http://tools.ietf.org/html/rfc1421)。
  2. Base64 編碼後的長度是可以明確估算的 = ((Bytes.Lenth-1)\3+1)*4
  3. 如果改變了符號集的字元順序再用同樣手法進行 base64 編碼,那就有加密效果了。
  4. 如要真正的加密編碼,光是改變進位制是不夠的,還要:
    1. 利用亂數 Key 和原始 Byte() 陣列的每個 Byte 做位元運算產生新值;解碼時再反向操作得到原始數據。
    2. 要有可以一對多的性質,即便是完全相同的原始資料,也要能每次都編碼出不同的結果,增加破解的難度。
    3. 要有混淆功能,即便只是更動了一篇文章中的一個字元,再編碼的結果也要有全新的風貌。
  5. 本文暫打住,後續再把這些功能加上去。