2014年9月1日 星期一

cocos2dx 使用 crypto++

http://blog.sina.com.cn/s/blog_62b2318d0101h095.html


在ios上使用加密解密,crypto++是一个好选择,配置过程如下:

我在github上找到了CryptoPP-for-iOS,地址:https://github.com/3ign0n/CryptoPP-for-iOS,下载后发现工程里没有crypto++,需要执行其中的external/scripts/build-cryptopp.sh脚本,脚本会下载http://www.cryptopp.com/cryptopp561.zip,这个是crypto++代码,但是在天朝,下载会失败,原因不言而喻,想办法下载吧!

cryptopp561.zip下载后,我没有编译,将其中的代码复制到工程中,
将其中的validat1.cpp,validat2.cpp,validat3.cpp,bench.cpp,bench2.cpp删除,
然后将-DCRYPTOPP_DISABLE_ASM -DCRYPTOPP_DISABLE_SSE2两个宏定义加到工程中,原因是crypto++内嵌了很多汇编,这个我们不需要。
开始编译,过程中提示cocos2dx的zip_support/ioapi.h中存在错误,原因是其包含了zlib.h,而crypto++中存在同名文件,
ioapi.h中是 #include "zlib.h"
改成 #include <zlib.h>  
编译通过。

在android工程上编译流程大同小异,在jni/Android.mk 中增加
CPPFLAGS += -DCRYPTOPP_DISABLE_ASM -DCRYPTOPP_DISABLE_SSE2
在jni/Application.mk 中增加
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
编译通过。

写了个使用RC2算法编解码的程序测试一下:
cRC2Test.h

#include "rc2.h"
using namespace CryptoPP;

//RC2加密解密类
class cRC2Test
{
public:
    //设置key
    static void setKey(const unsigned char* pKey);
    
    //加密
    static std::string encode(const std::string& rStr);
    //解密
    static std::string decode(const std::string& rStr);
    
private:
    //加密RC2
    static void encodeBlock(unsigned char* pData, unsigned char* pOut);
    //解密RC2
    static void decodeBlock(unsigned char* pData, unsigned char* pOut);
    
private:
    //RC2 key
    static unsigned char sm_cKey[RC2::DEFAULT_KEYLENGTH];
    
    //RC2加密类
    static RC2Encryption sm_rcEncr;
    //RC2解密类
    static RC2Decryption sm_rcDecr;
    
};

cRC2Test.cpp


#include <vector>
#include <string>
using namespace std;

//静态变量定义
unsigned char cRC2Test::sm_cKey[RC2::DEFAULT_KEYLENGTH] = {0,};
RC2Encryption cRC2Test::sm_rcEncr;
RC2Decryption cRC2Test::sm_rcDecr;

void cRC2Test::setKey(const unsigned char* pKey)
{
    memset(sm_cKey, 0, sizeof(sm_cKey));
    
    //保存key
    memcpy(sm_cKey, pKey, RC2::DEFAULT_KEYLENGTH);
    
    //对加密器与解密器设置key
    sm_rcEncr.SetKey(sm_cKey, RC2::DEFAULT_KEYLENGTH);
    sm_rcDecr.SetKey(sm_cKey, RC2::DEFAULT_KEYLENGTH);
}

std::string cRC2Test::encode(const std::string& rStr)
{
    size_t strIndex = 0;
    size_t strLen = rStr.size();
    
    //保存加密后的数据
    vector<unsigned char> vBuffer;
    //保存加密后的临时数据块
    unsigned char rcBlock[RC2::BLOCKSIZE];
    
    unsigned char *ptr = (unsigned char*)rStr.c_str();
    
    //遍历字符串
    while(strIndex < strLen) {
        memset(rcBlock, 0RC2::BLOCKSIZE);
        //加密一块数据
        encodeBlock(&ptr[strIndex], rcBlock);
        
        //保存加密结果
        vBuffer.insert(vBuffer.end(), rcBlock, rcBlock+RC2::BLOCKSIZE);
        
        strIndex += RC2::BLOCKSIZE;
    }
    
    return string(vBuffer.begin(), vBuffer.end());
}

std::string cRC2Test::decode(const std::string& rStr)
{
    size_t strIndex = 0;
    size_t strLen = rStr.size();
    
    //保存解密后的数据
    vector<unsigned char> vBuffer;
    //保存解密后的临时数据块
    unsigned char rcOut[32];
    
    unsigned char *ptr = (unsigned char*)rStr.c_str();
    
    //遍历字符串
    while(strIndex < strLen) {
        memset(rcOut, 0sizeof(rcOut));
        //解密一块数据
        decodeBlock(&ptr[strIndex], rcOut);
        
        //保存解密结果
        vBuffer.insert(vBuffer.end(), rcOut, rcOut+strlen((char*)rcOut));
        
        strIndex += RC2::BLOCKSIZE;
    }
    
    return string(vBuffer.begin(), vBuffer.end());
}

void cRC2Test::encodeBlock(unsigned char* pData, unsigned char* pOut)
{
    sm_rcEncr.ProcessBlock(pData, pOut);
}

void cRC2Test::decodeBlock(unsigned char* pData, unsigned char* pOut)
{
    sm_rcDecr.ProcessBlock(pData, pOut);
}

测试代码:
main.cpp


unsigned char pkey[RC2::DEFAULT_KEYLENGTH];
string testData = "1234567890测试";
    
cRC2Test::setKey(pkey);
string sEncode = cRC2Test::encode(testData);
cout<<cRC2Test::decode(sEncode);


在ios与android上测试通过。

[VB.net][VB6][VBA]客製化 Base64 編碼/解碼 的技巧(三)建立自己的演算法 - 對資料加解密

http://www.dotblogs.com.tw/ku3/archive/2011/08/25/34131.aspx

前言

前文說明了 Base64 的原理、索引字串的關鍵性角色、用固定種子亂數為基因演化出一整串的亂數集合,現在再試著實作一個包含前述手法的客製化資料加解密系統。

程式功能需求及基本做法


參考比較標準 Base64 的編碼及輸出格式後,決定只在文字輸出時採用 Base64 字串的 64 個字元,其他的都調整一下:
  1. 程式可對任何形式的資料進行加密、解密。例如執行檔、影音檔,或程式中以 Binary 序列化的物件等等。
    • 支援字串操作的輸入輸出的介面。
    • 程式碼如下:
          '---以 Unicode 編碼為 Ku64Ux 字串---
          Function Ku64_Ux_Encode(ByVal 字串 As String, Optional ByVal 金鑰 As String = "", Optional ByVal l As Integer = 76) As String
              Dim 編碼方式 As New UnicodeEncoding
              Dim src() = 陣列加密(編碼方式.GetBytes(字串))          '---把來源字串轉為 Unicode byte() 陣列再加密
              Dim 結果 = 字串寬度修飾(Array_To_ku64(src, 金鑰), l)   '---進行加密運算後以固定寬度輸出編碼後的字串
              Return 結果                                            '---進行加密運算後以固定寬度輸出編碼後的字串
          End Function
      
          '---解碼 Ku64Ux 為 Unicode 字串---
          Public Function Ku64_Ux_Decode(ByVal 字串 As String, Optional ByVal 金鑰 As String = "") As String
              Ku64_Ux_Decode = ""
              If 字串 = "" Then Exit Function
              Dim tmp() As Byte = 陣列解密(Ku64_To_Array(字串, 金鑰))
              If tmp Is Nothing Then MsgBox(解碼錯誤訊息 & IIf(金鑰 = "", "", ",或金鑰不正確") & "。") : Return ""
              Dim code As New UnicodeEncoding
              Return code.GetChars(tmp)
          End Function
      
      
    • 核心動作都在二進位的 Byte() 陣列中進行
    • 程式碼如下: 
          '--------------------------------------------------------------
          ' 用自己的演算法對 Binary() 陣列加密
          '--------------------------------------------------------------
          Function 陣列加密(ByVal 原始陣列() As Byte) As Byte()
              Dim 亂數基因 As Double = 基礎亂數產生器.NextDouble                 '---先產生一個 Double 型別的亂數基因
              Dim 基因陣列() As Byte = BitConverter.GetBytes(亂數基因)           '---轉 Double 為 Byte 陣列
              Dim 亂數陣列 = 由基因演化出陣列(亂數基因)                          '---建立一組亂數
              Dim tmp(UBound(原始陣列) + 基因陣列.Length + 16) As Byte           '---準備輸出用的陣列空間
              基因陣列.CopyTo(tmp, 0)                                            '--- Copy 基因到輸出陣列
              For i = 0 To UBound(原始陣列)                                      '---依序和亂數陣列做運算
                  tmp(i + 基因陣列.Length) = 原始陣列(i) Xor 亂數陣列(i Mod 亂數陣列.Length)
              Next
              Dim MD5陣列 = MD5_Binary(tmp)                                      '---附加 MD5 驗證碼到輸出陣列--- 
              MD5陣列.CopyTo(tmp, UBound(tmp) - 15)
              Return tmp
          End Function
          '--------------------------------------------------------------
          ' 用自己的演算法解密 Binary() 陣列
          '--------------------------------------------------------------
          Function 陣列解密(ByVal 密文陣列() As Byte) As Byte()
              陣列解密 = Nothing
              Try
                  Dim 亂數基因 As Double = BitConverter.ToDouble(密文陣列, 0)        '---從陣列最前取出基因
                  Dim 亂數陣列 = 由基因演化出陣列(亂數基因)                          '---解回亂數陣列
                  Dim 編碼時的驗證值(15) As Byte                                     '---建立陣列存放 MD5 CheckSum 
                  If 密文陣列.Length >= 16 Then
                      Array.Copy(密文陣列, 密文陣列.Length - 16, 編碼時的驗證值, 0, 16)  '---從密文陣列取出夾帶的 MD5 驗證值
                      Array.Clear(密文陣列, 密文陣列.Length - 16, 16)                '---清空密文最後 16 個元素
                      Dim 當下資料驗證值() = MD5_Binary(密文陣列)                    '---重新計算 MD5 驗證值
                      If MD5_String(編碼時的驗證值) <> MD5_String(當下資料驗證值) Then Exit Function '---比對若不正確就傳回 Nothing
                      Dim tmp(密文陣列.Length - 1 - 8 - 16) As Byte                  '---修正為原始陣列長度(扣減亂數基因和驗證區的長度)
                      For i = 0 To UBound(tmp)
                          tmp(i) = 密文陣列(i + 8) Xor 亂數陣列(i Mod 亂數陣列.Length)
                      Next
                      Return tmp
                  End If
              Catch ex As Exception
              End Try
          End Function
       
  2. 以字串輸出時,所使用的字元和 Base64 同為 A-Za-z0-9 及 +/。並可設定輸出寬度(Default=76)。
    • 仍使用數底轉換為基礎。
    • 寫一個字串切割函式處理固定寬度,程式碼如下: 
          Function 字串寬度修飾(ByVal ret As String, ByVal Width As Integer)
              '---以固定寬度輸出編碼結果---
              Dim tmp As String = ""
              While Len(ret) > Width
                  tmp = tmp & Left(ret, Width) & vbCrLf
                  ret = Mid(ret, Width + 1)
              End While
              If Len(ret) > 0 Then
                  tmp = tmp & ret
              End If
              Return tmp
          End Function
       
  3. 編碼時可選擇性使用金鑰上鎖,金鑰可為任何長度的字串。
    • 需重新撰寫 Base64 的編解碼函式,數底轉換自己來做,程式碼如下:
          '--------------------------------------------------------------
          '函數功能:編碼 Byte() 陣列為 Ku64 字串
          '--------------------------------------------------------------
          Function Array_To_ku64(ByVal 原始陣列() As Byte, ByVal 金鑰 As String) As String
              Dim 索引字串 As String = Base64_索引字串
              If 金鑰 <> "" Then 索引字串 = 重建索引字串(金鑰) '---若使用金鑰則產生一組非標準的字元順序
              Dim 位置, 餘數, skip As Integer
              餘數 = (UBound(原始陣列) + 1) Mod 3
              If 餘數 Then                                            '---修正來源陣列長度為3的倍數,不足則補0
                  skip = 3 - 餘數                                     '---計算需補幾個0
                  ReDim Preserve 原始陣列(UBound(原始陣列) + skip)    '---重新配置原始陣列大小(保留原資料)
              End If
              Dim out As New StringBuilder                            '---準備建立輸出字串
              Dim Data8bit = 0, Data6bit = 0
              位置 = UBound(原始陣列)
              For i = 0 To 位置 Step 3                                '---從來源陣列一次轉3個 Bytes(8*3=24 To 6*4=24)
                  Dim Value = 0                                       '---預存值為0
                  For j = 0 To 2                                      '---從來源陣列的 i 位置取3個 8bit(0-255)
                      Data8bit = 原始陣列(i + j)                      '---取出第 i 組第 j 個 8bit 資料
                      Value = 256 * Value + Data8bit                  '---求值
                  Next
                  For j = 0 To 3                                      '---轉為4個 6bit(0-63)---
                      Data6bit = Value \ (64 ^ (3 - j)) Mod 64        '---求值
                      out.Append(索引字串.Substring(Data6bit, 1))     '---對照索引字串轉換為 ASCII 字元
                  Next
              Next
              Return out.ToString.Substring(0, out.Length - skip) & StrDup(skip, "=")
          End Function
          '--------------------------------------------------------------
          '函數功能:解碼字串至 Byte 陣列
          '--------------------------------------------------------------
          Function Ku64_To_Array(ByVal 字串 As String, Optional ByVal 金鑰 As String = "") As Array
              Dim kuKey As String = Base64_索引字串
              If 金鑰 <> "" Then kuKey = 重建索引字串(金鑰) '---若使用金鑰則產生一組非標準的字元順序
              Dim src = Replace(Replace(Replace(字串, vbCrLf, ""), vbTab, ""), " ", "")  '---忽略斷行字元
              Dim 餘數 = Len(src) Mod 4
              If 餘數 Then                                 '---修正字串長度為4的倍數(補 '=')
                  src &= StrDup(4 - 餘數, "=")
              End If
              Dim 字元數 = Len(src)
              Try
                  Dim 輸出陣列(字元數 / 4 * 3 - 1) As Byte
                  Dim id As Integer = 0, skip As Integer = 0
                  Dim Data8bit = 0, Data6bit = 0
                  For i = 1 To 字元數 Step 4               '---處理來源陣列
                      Dim Value = 0                        '---預存值為0
                      For j = 0 To 3                       '---從來源陣列一次取4個 Data6bit(6*4=24)為 Token
                          Dim Ch = Mid(src, i + j, 1)
                          If Ch = "=" Then
                              skip = skip + 1
                              Data6bit = 0
                          Else
                              Data6bit = InStr(kuKey, Ch) - 1
                          End If
                          Value = 64 * Value + Data6bit    '---求 Token 值
                      Next
                      For j = 0 To 2                       '---轉為3個 Data8bit 值(8*3=24)
                          Data8bit = Value \ (256 ^ (2 - j)) Mod 256
                          輸出陣列(id + j) = Data8bit
                      Next
                      id = id + 3                          '---輸出陣列處埋位置指標加3
                  Next
                  ReDim Preserve 輸出陣列(UBound(輸出陣列) - skip)
                  Return 輸出陣列
              Catch ex As Exception
                  Return Nothing
              End Try
          End Function
      
      
    • 寫一個經由金鑰(key)來產生非正規字元順序的索引字串。
          Function 重建索引字串(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
      
      
  4. 有混淆功能,於相同編碼條件之下每次都輸出不同密文,且確定都能解密還原。
    • 每次加密編碼時都重建亂數種子,據以產生不特定數量的亂數集合元素,合併到輸出密文內再於解密時引用。
    • 程式碼已附在前面「用自己的演算法對 Binary() 陣列加密」、「用自己的演算法解密 Binary() 陣列

       
  5. 有密文自我驗證能力,密文產生後若曾被篡改,解密程序會拒絕解密並送出提示訊息。
    • 加密後陣列用 MD5 採值並附在輸出內容,解密時隔離掉 MD5 區塊後再採一次 MD5 值與先前比對,若不符合立即停止解碼傳回 nNothing。
          '---計算 MD5---  
          Public Function MD5_Binary(ByVal src() As Byte) As Byte()
              Dim kumd5 As New MD5CryptoServiceProvider()
              Dim data As Byte() = kumd5.ComputeHash(src)
              Return data
          End Function
          Public Function MD5_String(ByVal src() As Byte) As String
              Dim kumd5 As New MD5CryptoServiceProvider()
              Dim sBuilder As New StringBuilder()
              Dim data As Byte() = kumd5.ComputeHash(src)
              For i = 0 To data.Length - 1
                  sBuilder.Append(UCase(data(i).ToString("x2")))
              Next i
              Return sBuilder.ToString()
          End Function
          Public Function MD5(ByVal src As String) As String
              Dim kumd5 As New MD5CryptoServiceProvider()
              Dim sBuilder As New StringBuilder()
              Dim data As Byte() = kumd5.ComputeHash(Encoding.Default.GetBytes(src))
              Dim i As Integer
              For i = 0 To data.Length - 1
                  sBuilder.Append(UCase(data(i).ToString("x2")))
              Next i
              Return sBuilder.ToString()
          End Function

       
  6. 要能防逆向工程(這部分的程式碼都在前面了)。
    • 加密原則採位元組運算,將來源資料逐一和亂數陣列相對應之位元組做 XOR 運算,週而復始直到來源資料用盡。
    • 亂數陣列採動態長度,每次編碼使用的亂數量都不一致,較難反向運算破解。
    • 密文並不包含亂數集合實際內容,亂數全由基因自行在解碼程序中演化、展開。
    • 解密加上延時程序,防暴力式破解。 

比較一下


  • 用簡單的原始字串(A - AAAAA)以標準 Base64 和自製的 ku64 各輸出兩次,比較一下輸出的字串就可以推估一下破解的難易度。 
    1. Base64 :

      • 標準 Base64 編碼輸出 
        --------------------------------------
        A→Base64 編碼→QQA=
        A→Base64 編碼→QQA=
        AA→Base64 編碼→QQBBAA==
        AA→Base64 編碼→QQBBAA==
        AAA→Base64 編碼→QQBBAEEA
        AAA→Base64 編碼→QQBBAEEA
        AAAA→Base64 編碼→QQBBAEEAQQA=
        AAAA→Base64 編碼→QQBBAEEAQQA=
        AAAAA→Base64 編碼→QQBBAEEAQQBBAA==
        AAAAA→Base64 編碼→QQBBAEEAQQBBAA==
    2. ku64:
      • 自製 ku64 編碼輸出
        --------------------------------------
        A→ku64 編碼→3u20GO922j8CbLUMjbxqKffigA736cTqqac=
        A→ku64 編碼→nPB7zE347T8BVvS0g6sw5YhNVrUpqH5nlco=
        AA→ku64 編碼→0QQgV2gC0D9+PXKPojBohp4VaulhWr2UlyQ4zQ==
        AA→ku64 編碼→33qwbm896D9rQEeFbA4rXkhWMML2/DeSa4sbdA==
        AAA→ku64 編碼→Jfm9i5L87j8ekIYjA8KijLP0NAbh9SAWO7OSnCTX
        AAA→ku64 編碼→+lFty/yo5j9/Mdx3++9fdigeoH3e/rMWA1YLaj5K
        AAAA→ku64 編碼→d3EuQrs4xz9q9Nqm9GYrD5qIlc6HyCs4VUJ2gl8XwYM=
        AAAA→ku64 編碼→7FMjMfapwT9q8fBAF0Hn9WUgKOT0b1cbOY5R7NbfRHE=
        AAAAA→ku64 編碼→gIbyMUBD6T8QW7rJwWtuRVPoBsmXMFjpm0Gh8+I0T67+yA==
        AAAAA→ku64 編碼→ORujlpyN4T99YP8SthVEVHaJl+Z1ACr9r5wcWplubuvs4g== 

         
  • 再直接對 Byte() 陣列做加密看看: 
  1. 用 ku64 加密Binary 陣列並觀察內在的變化:
  2. 結果5次加密後 Byte() 陣列內容都不同,但都可正確解密:
Private Sub Button4_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button4.Click
     '---建立一個供測試用的 Byte() 陣列-
     Dim b() As Byte = {&H11, &H22, &H33, &H44, &H55, &H66, &H77, &H88, &H99}
     '---連續做5次加密、解密--
     For n = 1 To 5
         Dim a() As Byte = ku.陣列加密(b) : show陣列(a) : Debug.Write(" → ")
         Dim c() As Byte = ku.陣列解密(a) : show陣列(c) : Debug.Write(vbNewLine)
     Next

End Sub

Sub show陣列(ByVal b() As Byte)
     For Each i In b
         Debug.Write(i.ToString("x2") & " ")
     Next
End Sub
  • 05 a5 38 45 82 52 cc 3f 43 fa c5 3a 22 2f 3e 52 3e c7 1c 73 3d cb 26 41 b6 28 50 05 62 79 28 8d 51  → 11 22 33 44 55 66 77 88 99
  • eb 98 2b 5f 75 cc d5 3f 4e c3 d0 da 35 df f5 1d a4 0d 47 0b 6d 01 62 cd 3b 2f f2 a9 15 74 d4 2e f4  → 11 22 33 44 55 66 77 88 99
  • fc 57 22 f1 fd 2b e1 3f 53 44 f5 e1 6f 3c 78 17 96 f1 23 e7 f1 2b d1 d7 7d 6c 5f d8 ef 0d 20 8b 50  → 11 22 33 44 55 66 77 88 99
  • e9 21 2c 49 f4 10 e6 3f 55 6b 04 49 8f 96 94 d2 ae ee 5e e9 43 ab b5 8b 35 ac 0b e8 6a 32 a0 e0 a2  → 11 22 33 44 55 66 77 88 99
  • 7e 27 ac 3b bf 13 d6 3f 5c ab 41 21 f9 83 b6 05 a2 73 f5 03 9e 5c b2 d6 b3 4a 27 4b 2b 31 5a 84 b0  → 11 22 33 44 55 66 77 88 99    
測試程式畫面:
    • image image

結語

  1. 有時候也不要太過於妄自菲薄,一些高知名度的專業級加密演算法、驗證演算法、或者是排序方法其實也都是人寫出來的。
  2. 把寫程當做是一種 藝術 看待,用自己的見解和想法加上擁有的技術能力實作一下,讓生活多采多姿也是不錯的。
  3. 有興趣的朋友不妨幫我看看有哪些缺失或漏洞,如果還有閒情逸緻的話。
  4. 下回有空再談一下「產品序號機制」「產品網路啟用」「自動偵測新版本」「產品自動下載更新」等等技巧。

[VB.net][VB6][VBA]客製化 Base64 編碼/解碼 的技巧(二)使用非正規的索引字串達到保密效果

http://www.dotblogs.com.tw/ku3/archive/2011/08/23/33977.aspx

前言

前文分享了我對 Base64 的觀點,並提到正規 Base64 的主要用途在數據傳送,它的強項不在資料的保密,用 ASCII 字元呈現二進位資料反而在傳播的過程中更清澈、透明,而擴展了適用的範疇。雖然經過 Base64 編碼後的資料量大約多了 33%,但在小量資料的處理上它提供了簡潔的解決方案,像是:
  1. 在 MIME 標準下 Email 傳送時可把圖片、音效等資料直接嵌入信件本文,形成附檔。
    這是一封郵件的原始檔,可看到附檔用的就是 Base64 字串。
    image
  2. 在資料庫的文字欄位裡面,可以用文字型態存取圖像資料,像是會員頭像、身分頭銜標章圖示等。
    我的德州 Poker 玩家頭像圖片就是存在 MySQL 的文字欄位。
    image
  3. 經 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 可用來加密資料

如果刻意不用標準的索引字串,改用自行安排字元順序的字串為索引會發生什麼事呢?結果會是:
  1. 這個 Base64 系統還是會正常工作,也能編碼出一大串看起像是正常 Base64 編出來的字元集合。
  2. 但用標準 Base64 解碼無法解出資料內容,只有再度使用編碼時相同順序的索引字串才能正確解回。
  3. 換句話說資料有了隱密性,Base64 編碼有了附加價值。
因此如果我們把那 64 個字元重新排列組合產生新字串,並用這個新字串來做索引,經過 Base64 編碼程序產生的 Base64 字串就可以稱得上是加過密了。因為外人並不知道索引字串及其排列順序為何。要解出正確內容非得解碼的一端也用相同的索引字串才行,而描述這個字串順序的資訊就叫做 key 了,如何把這個字串順序(key)傳遞到解碼端,就是個技巧了。
  1. 把索引字串直接寫成常數,固定在編/解碼程序裡就沒有傳遞 key 的問題了。你我都知道這不是個好主意,因為沒有了 key 的彈性。
  2. 編碼時把索引字串附加到編碼輸出的字串中,解碼時先取出這個 key,然後再用來解開其餘的密文。這也不是很妥當,太明目張膽了很容易被洞悉夾帶的方式,而且編碼長度至少多了 64 Bytes,也會形成負擔。
  3. 永遠使用互動式介面處理,每次解碼時都彈出對話框要求輸入。這也不妥,因為多數情形下解碼是在安靜模式下進行的,而且長度 64 字元的 key 也不方便記在腦袋裡。
  4. 解碼函式使用 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 解回原狀了,此時這個機制已經初步可用了。但還不算專業,因為:
  1. 它用的原理還是 256 進位 → 64 進位的數底轉換,這個演算法太陽春了。
  2. 相同的 key 會產生唯一的編碼結果,如果把原始字串逐步增加一個字元(或刪減最後的字元)再觀察編碼結果,而分析比對的話,花了功夫是可能找到這個索引字串的,它的安全性不夠。
所以除了混亂索引字串之外還是要建立自己的演算法,這才算完整。

基因突變

如同生物演化的進程,由一個單獨的基因啟動事先規畫好的程序,就可以用極其輕小的 key 做為種子,再進一步根據這個基因 key 所蘊藏的演化法則去運作,例如產生那 64 字元的索引字串,不同的基因會演化出不同的結果,所以如果基因可以突變,達成每次編碼都能以新的索引變化出新的物種,即便是相同的原始資料(明文)也能在每次編碼之後產生完全不同的結果(密文),就能達到混淆的目的。
先看一下運用基因法的例子,下面是使用基因來衍生一個位元組陣列,然後再用該陣列內容來加密原始資料的方塊圖,這個例子可看到用 改良的 Base64 來加密 Unicode 字串 “1234567890ABCDEFG”及解密過程中發生在 Byte() 陣列中的變化。(左圖是編碼的邏輯,右圖為解碼)

image image

下回貼文

本文暫打住,下回要分享的是:
  1. 重寫 Base64 Encode/Decode 函式:因為 VB.net 的 Base64 是封裝在類別裡面的,它固定使用標準的 Base64 索引字串,我們要能置換掉它最好是完全掌控每一個環節。
  2. 建立自己的演算法:針對位元組陣列 Byte() 做位元運算,從而輸出一個改頭換面的(就像上面貼圖的內容)。
  3. 基因突變的做法:讓我們的 Base64 除了可以有 key 來加密外,還可以做到即便是相同的原始字串,即便是使用相同的 key 做加密也能讓每次輸出的密文不同。

[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. 本文暫打住,後續再把這些功能加上去。