遊戲開發日誌 #07


看到題圖,就知道這次想說的是加密。

今次會比較深入地去說明一下加密到底在做什麼。
也會把本人最新的加密方法作較詳細的說明。

關於遊戲資料為何需要加密,可以先看<遊戲開發日誌 #04> 。



首先,什麼是加密?

加密最早出現在一些知識相關的文獻或書籍上,為了只把知識傳給認可的學徒,把獨特的文字取締,只有同門才可以解讀。
最有名的就是西洋的鍊金術和中國的鍊丹術。
鍊金術和鍊丹術都是現代化學的起源,西洋發展成科學,中國發展成醫學。雙方有很多共通點,例如尋求長生不老、改變質量等;黃金是共同的不滅象徵,金丹、金人等字也是代表了不老不死的仙丹和不老不死的人。

加密的基本,就是把本來的文字變成其他文字,但只要有足夠的樣本和文章的長度,還是可以解讀。古時懂得讀寫的人很少,所以加密的程度也不高,但當讀寫已不是高人貴俯的專利時,更複雜的加密法變得更重要。

只讀文章的第一個字母或生字、以每行文字數量轉換成代碼等等的方法開始出現,特別以必須要參考多個不地方才可以解讀的方法被使用得最多,例如特務電影中,看當天幾份報紙的某個專欄,再以當中的某些文字來得到完整訊息也是很常見。

而人類的歷史和文化中佔了最大比重的「戰爭」,對於情報的管理更是著重,因為只要被敵對國得知行軍情報,就等於被宣告戰敗沒分別。
在人類的特質中不可或缺的「七大罪」告訴我們,即使沒有人被收賣,也會有臥底去出賣情報,而解決方法很簡單,就是離開司令部的情報都是已加密的,只有接收方的司令部才有解密的方法,除非司令部中有間諜或臥底,否則情報流失也沒有任何問題了。

唯一要解決的是加密和解密的方法,由人去做的話就只會出現上述的問題,所以一部專門加密和解密的機器 - Enigma Machine 就此誕生了。


如果有看過 2014 年的電影 The Imitation Game 的話,應該會知道上面這部機器。

Enigma Machine,Enigma 是迷的意思,這是在二戰時大量被使用在軍報傳送上的加密和解密機器。Enigma Machine 有很多不同型號,但運作都沒有多大差異,只有複雜程度的分別。

這部加密解密機最強大的地方,就是以五次轉換來作字母的加密,每次鍵入字母後加密法都會改變,而加密法還可以自己更換和設定。

這樣說還不會太明白,所以做了上面這張圖作例子:
  1. 鍵入 Q
  2. Q 經過接線連到 R
  3. R 經過旋轉機 Roller 第一格連到 D
  4. D 經過旋轉機 Roller 第二格連到 G
  5. G 經過旋轉機 Roller 第三格連到 O
  6. O 回到接線連到 M
  7. 最後得出 M
  8. 旋轉機會像計數器一樣自動跳動一格
  9. 再鍵入 Q 的話已不會得出 M
接線可以自行更改,旋轉機可以更換,也可以先轉到一個組合。

只是這樣看的話好像不是太複雜,但這個加密機的加密可能性,已經不是沒有電腦的二戰時代的人類可以有能力解開了,因為其加密的可能性實在太多了:
  • 一個字母經過 5 重 26 個可能性
  • 26×26×26×26×26 = 11,881,376 個可能性
  • 如果加密一個 APPLE 生字,由於每次鍵入一個字母,旋轉機都會轉動一次,我數學太差已經不懂如何計算了,一定已經是數百億個可能性了。
就像電影中所說,德軍每一天都會把接線更改,也會把旋轉機的組合更改,所以對於英法等國家來說破解時限就只有一天,因為第二天的加密情報就已經不同,樣版不同的話之前一天的努力也是白廢。

解決法也像電影中所說,必須滿足兩個條件:
  1. 取得加密機/加密機的設計圖
  2. 取得每天加密的設定組合
到了現代,因為有電腦,在日常中已經使用了很多單向加密法,也就是不能解密的加密法,例如 SSL 就是最被廣泛使用的,包括信用咭。
但遊戲運作的資料不同,因為跟行軍情報一樣,需要解密來使用,所以就要使用可以解密的加密法了。
只要可以解密,就可以被破解。提到破解,大部份人都會認為問題是所需時間,但這只是單向加密法的情況,在雙向加密法上所需要的是足夠的樣本,有足夠的樣本才可以逆算出加密用的設定組合。
比較幸運的是,如果使用 Enigma Machine 的百份一加密組合也好,在遊戲中要取得足夠的樣本,完全破關 100 次也可以了。
在遊戲中最重要的資料,基本就是數字,數字就只有十個,所以加密的組合也不算多,如使用 Enigma Machine 的方法運作,就會有加密組合太小的問題,也就是逆算所需的樣本減少。

簡單說,加密法必須條件跟破解法必須條件是一樣的:
  1. 加密機制的設定
  2. 加密組的複雜性
在這裡,本人的加密法就以多重轉換機制以及複雜的加密組,把遊戲中的重要數值、儲存資料作加密和解密處理。


本人的雙向加密法,也是多重轉換,最重要的兩部份就是轉換 UTF8 Bytes[] 和 Encrypt Group 加密組,就等於 Enigma Machine 中的接線和旋轉機 Roller 一樣,到最後所有型態的資料都會加密成 String[]。

跟 Enigma Machine 一樣,每次加密同一樣的文字也會得出不同的加密結果,不同的是並非每次加密都會轉換加密組,而是每次從加密組中隨機取出加密碼。

在這裡先看看相關的程序吧。

<參數 Parameters>
// Settings
    private enum EncryptGroup
    {
        //0    1    2    3    4    5    6    7    8    9
        khj, PSw, Avy, kCZ, sxX, y2D, xWg, nDk, YSm, hQR,  // 0
        rzk, z4O, k8P, bG9, jAj, FGx, OCS, TS6, yYR, IKo,  // 1
        VZs, QnM, JkF, X4Q, oXR, vnm, WGi, k4z, Bh4, Wqf,  // 2
        acV, VQD, M8D, UWe, IdP, ghJ, wof, UGw, Xuk, rBN,  // 3
        DGU, GAh, ihz, ELq, XWJ, k5g, EuY, YeM, RFk, I5G,  // 4
        XQ2, nQp, NFB, QwS, MdP, RCd, K1k, n5U, cFn, V7H,  // 5
        wmV, HdN, zIB, Rh5, bZ0, qps, zVe, VrL, Vhy, caf,  // 6
        Lme, j2W, PSJ, bNk, S2d, Jqu, fwK, qM7, DDx, TcH,  // 7
        GKC, lhi, Ldu, nY3, mFn, lAa, UR9, prZ, mRW, W6H,  // 8
        hVG, JEa, xhe, yNX, URf, Hg7, c4o, WqD, nre, INU,  // 9
        oDg, nxU, yxp, v2Z, G7x, kYB, IKf, VGR, h0e, J3X,  // -
        QvN, XO2, fXY, sk5, aU1, NQL, SAu, rRr, Vyt, WEZ,  // .
        RsT, pJk, xP6, F9n, f3j, M1E, WvP, TYg, OkN, E3G,  // ""
        h3O, x0b, Zvz, TMU, VJm, FMq, aL9, YAa, Pu3, qKG   // null
    }
    private static readonly int     // Change follow int for your way
        encStrLength = 3,   // Length of each encrypt string
        encStrGroup = 10,   // how many group of each character (column)
        intMultiply = 2,
        intPlus = 1;

    // Don't modify if you don't know what it is.
    private static readonly EncryptGroup
        encGroup = new EncryptGroup();
    private static readonly string
        encryptRef = "0123456789-.";
最重要的加密組 EncryptGroup,使用 enum 是因為當中不會出現重複。
然後就是一些其他運作用的參數,例如整數再計算的 intMultiply 和 intPlus。
加密組的運作:
  • 每一行加密組都代表一個數字
  • 當加密一個數字時,會在該行隨機取一個加密碼
  • 所以每次加密同一個數字都會有不同的結果
  • 如要降低重複性,可以加大每一行的加密碼數量
加密組的設定:
  • encStrLength 每個加密碼的長度
  • encStrGroup 每行加密組中的加密碼數量
  • 可以使用 Encrypt Group Generator 自行生成加密組


<加密 Encrypt>
    // Functions
    private static int EncStrLine(string value)
    {
        for (int n = 0; n < encryptRef.Length; n++)
        {
            if (value == Convert.ToString(encryptRef[n]))
                return n;
        }
        return 999;
    }

    // Encrypt Methods
    public static string EncryptInt(this int value)
    {
        value = value * intMultiply + intPlus;
        string txt = value.ToString();
        int len = txt.Length;
        string newTxt = "";

        for (int i = 0; i < len; i++)
        {
            int r = UnityEngine.Random.Range(0, encStrGroup) +
                encStrGroup * EncStrLine(Convert.ToString(txt[i]));
            newTxt = newTxt + ((EncryptGroup)r).ToString();
        }

        return newTxt;
    }

    public static string[] EncryptString(this string value)
    {
        int len = 0;
        byte[] utf8Bytes;
        string[] newTxt;

        if (value == null || value == "")
        {
            newTxt = new string[1];
            len = value == null ? 1 : 0;
            int r = UnityEngine.Random.Range(0, encStrGroup) +
                encStrGroup * (encryptRef.Length + len);
            newTxt[0] = "" + (EncryptGroup)r;
        }
        else
        {
            utf8Bytes = System.Text.Encoding.UTF8.GetBytes(value);
            newTxt = new string[utf8Bytes.Length];
            for (int i = 0; i < utf8Bytes.Length; i++)
            {
                newTxt[i] = ((int)utf8Bytes[i]).EncryptInt();
            }
        }

        return newTxt;
    }
  • 先把來原的資料 .ToString() 轉成 string 後傳到 EncryptString()
  • 由於是 string,所以 null 和 "" 零長度都會分開處理
  • 如有內容的話,轉成 UTF8 Bytes[]
  • 把 UTF8 Bytes[] 中每個 int 逐一傳到 EncryptInt() 中
  • EncryptInt() 會把 int × intMultiply + intPlus 後轉成 string
  • 然後把每個位數從加密組中隨機取一個加密碼
  • 把整個加密後的 string 回傳到 EncryptString()
  • EncryptString() 一直把所有 Bytes[] 的加密碼記錄完成
  • 最後回傳一個 string[]
在這個加密過程中,一個字母會先變成 256 或 65536 (256,256) 或 16,777,216(256,256,256) 個組合的 UTF8 Bytes,然後每個位數會先再計算後,再隨機加密成 10 個可能性,這裡到底會有多少個可能性出現,我這個數學白癡真的不懂計算了。。。

如果把每行加密組中的加密碼數量加大至 100 的話,真的是無限可能性,因為連重複的機率也差不多沒有了,這樣要在遊戲運行中取得足夠樣本已經是完全不化算的事情了。

解密就不說了,只是反向流程,在下面有原始碼下載,可以自行研究。


<破解風險 Crack Risk>

上面說過,要成功解密就要取得加密機的設計和加密的設定組合,缺一不可。
在這裡可以下載到加密的設計,但設定組合因為可以自行更改,所以最終還是不能被破解。。。這樣想就錯了!

程序在打包時都只是包起來,只要反編譯就可以取得原始碼,也就是加密的設計和設定組合都可以直接取得;不過,解決方法也是很簡單,在 #4 的文章裡也有說過,就是代碼混淆 Obfuscator。

只要有代碼混淆,即使反編譯也沒法看得懂程序,非常可靠,這樣就可以保證設定組合不會外洩了。


<分享 Share>

  • Encrypt Method Script 的版本為 v3.22
  • Encrypt Group Generator 基本沒有變動
Script 放在 Project 中就可以使用。
使用方法和之前沒有分別,可以看<遊戲開發日誌 #04> 或程序中的說明。

v3.22 版本中把 EncryptFloat() EncryptBool 都刪了,因為已經不會用到。

留言

此網誌的熱門文章

[教學]一起來開發遊戲吧 - Unity C# 基礎

QUMARION

[教學]一起來開發遊戲吧(二) - Character Controller, Pool System