Cocos2d-x 資源加密解密實踐總結
本文乃Siliphen原創,轉載請註明出處:http://blog.csdn.NET/stevenkylelee
本文針對的是cocos2d-x 3.4 版本進行研究。
做加密解密的思路
加密解密演算法的簡單介紹
首先,加密解密應該是一個單獨的話題,一般不會涉及具體使用的引擎、框架和技術。
加密演算法有Base64,DES等。
Base64的原理類似於凱撒密碼,啥是凱撒密碼呢,就是一個字元用另一個字元來代替。
比如:a用i代替,b用k代替,以此類推。加密和解密的過程就是一個互相對映的過程。
DES是一種使用金鑰的加密演算法。
DES和Base64這種無金鑰的演算法在使用上的區別是:
DES演算法本身是可以不保密的,只要保密金鑰即可,金鑰才是解密的關鍵。
Base64如果用於加密的話,演算法本身就是金鑰。要保密演算法。
MD5,SHA等是摘要演算法。
Base64,DES都可以把密文還原出明文。
而MD5,SHA則不能對演算法作用後的輸出還原出原始資料。
摘要演算法一般做身份驗證。
我們做資源加密是要選擇能把密文還原出明文的演算法。
設計自己的加密解密演算法
這屬於密碼學的內容了,可深可淺。
可以借鑑凱撒密碼的原理自己對每個位元組的內容進行對映來加密。
也可以對1位元組,2位元組,4位元組的資料,用某個數進行異或運算來加密。
異或的特性是,用數A對內容B進行異或得到內容C,再用數A對內容C異或可以得到內容B。
這個特性可以被用來加密。同時,數A的作用就是金鑰了。
設計的加密演算法不同,加密演算法的使用的介面也不同。
如果是用上面的2種思路做加密演算法的話,加密演算法的實現可以“in place”操作,
就是可以直接在密文所在的記憶體進行解密,不單獨分配記憶體來儲存解密後的資料。
如果加密演算法很複雜,無法“就地還原”,就需要新申請記憶體來儲存解密資料。
當然,執行速度一般是”就地還原“快。
我覺得,如果可以的話,最好是自己設計加密演算法。
使用現成的DES之類的演算法,金鑰是關鍵。
如果是自己設計一個用金鑰加密的演算法,金鑰和演算法本身都是不公開的。
破解者要獲取資源明文要經過如下步驟:
1.在程式中找到金鑰的常量。
2.看懂反出來的彙編還原出演算法,或者想辦法利用加密演算法的彙編程式碼。
其中步驟2會加大解密者解密的難度,這是自己設計加密演算法比用現成的DES之類著名演算法的好處。
加密解密的運用機制
我見過有一些人加密是對欄位內容進行加密。
比如,他用xml儲存資料,他只對儲存的值進行加密。
內容類似這樣 <Entity Hp = "密文" AttackValue = "密文" >
這並不是一種好的運用加密的方式,因為這種方式會:
1.透露程式的配置檔案結構。XML、json等。
2.透露了程式可能使用的資料結構。看以上內容可猜想Entity實體類有2個欄位,Hp(血量),AttackValue(攻擊力)
更徹底的加密方式是,對整個xml、json等檔案進行加密。
這樣別人就不會知道關於你的資料的一丁點資訊。
這種做法不是直接用資料解析庫封裝的類似LoadFile的函式直接讀取檔案。
而是,先把加密檔案用檔案讀取方法讀入記憶體,在記憶體解密後,再把解密資料傳給解析庫的解析函式。
這種做法要求解析庫有對記憶體進行解析的介面。
比如,若用XML解析庫,就會棄用LoadFile,要求有Parse方法。
有一種情況可能無法對整個資料檔案進行加密。
就是使用sqlite等資料庫的時候,這時候只能對欄位進行加密了。
有些人設計加密機制會要求配置程式本身。
比如:程式需要一個配置(一個開關變數或者條件編譯)來決定是否對資源進行解密。
釋出時,把資源都加密好,開啟程式對資源的解密。
開發時,資源不必加密,遮蔽程式對資源的解密。
這種做法需要來回設定解密開關,比較繁瑣。
有一種實現思路更有擴充套件性、更靈活、使用更方便。
那就是自己設計檔案頭。
熟悉Win32程式設計的人可能會知道PE檔案格式。
Windows執行一個exe之前,會檢查PE檔案頭,
取得EXE檔案的資料常量、程式碼區塊等資訊後,系統才能正確執行一個EXE。
我們可以設計一個加密檔案頭,加密檔案頭有一個欄位標識該檔案是否是被加密過的。
解密演算法可以通過判斷加密頭的識別符號來決定是否進行解密、採用哪種解密演算法等。
這是一種資料驅動式的解密機制,解密資訊都儲存在加密資料本身中。
對於沒有被加密過的資料,程式判斷沒有檔案加密頭標識,就不執行解密。
這樣,程式可以同時讀取未加密的資料和已經加密的資料,而不需要對程式進行任何修改。
注意:加密頭的識別符號應該足夠特殊,保證和未加密的資料不會產生衝突。
若未加密的資料剛好出現了加密頭的內容,那麼程式就會對未加密的資料進行解密。
在Cocos2d-x 中做加密解密的研究過程
我的專案配置是用CSV檔案。解析器,用的是我自己寫的CSV解析器:
在遊戲中這部分的解密,比較容易實現,就是對整個檔案加密,
遊戲啟動時把加密檔案讀入記憶體進行解密,然後把解密資料傳到解析器進行解析。
一些由遊戲產生的需要儲存的資料,我用XML進行儲存。加解密思路也如上。
但是,對於圖片、動畫,粒子、cocostudio1.6匯出的UI 等資料,加密就不那麼簡單了。
因為,像 Sprite::create 這樣的介面,並沒有提供一個從記憶體獲取紋理資料來初始化精靈的方式,
而是直接從磁碟檔案中讀。使用者在外部是無法干預檔案資料的讀取載入的。
遊戲資源的加密要怎麼做?——只有修改引擎了。- -!
我覺得,修改引擎並不是一個好的做法。
引擎應該由引擎提供商來維護,而不是靠使用者自己來動手。
使用者自己動手會造成專案維護升級困難。
自己把引擎改得亂七八糟,想升級引擎到新的版本會變成很麻煩。
就算是修改了一點,升級引擎版本一次,就要做一次修改,這樣也很麻煩。
要修改引擎,首先要跟蹤除錯,
以cocos2d-x 3.4 作為試驗版本,從Sprite::create入口,一步步跟下去,
看看引擎是在哪裡載入檔案資料的。
經過除錯跟蹤,Sprite::create的呼叫堆疊如下:
getData
FileUtilsWin32::getDataFromFile
Image::initWithImageFile
TextureCache::addImage
Sprite::initWithFile
Sprite::create
在windows上,精靈載入圖片,是呼叫到了
CCFileUtils-win32.cpp 這個檔案中的
static Data getData(const std::string& filename, bool forString) 函式。
這個函式是一個非類成員靜態函式。
再除錯下其他的資料載入,cocostudio1.6做的動畫的呼叫堆疊是:
cocos2d::FileUtilsWin32::getFileData
cocostudio::DataReaderHelper::addDataFromFile
cocostudio::ArmatureDataManager::addArmatureFileInfo
經過了多種檔案型別載入的跟蹤除錯後,總結,
基本上檔案的載入函式有2個,都在CCFileUtils-win32.cpp檔案中,
1.FileUtilsWin32::getFileData
2.getData
要做解密的話,就是修改這2個函式了。
值得注意的一點是:getData函式的簽名是:
static Data getData(const std::string& filename, bool forString)
forString這個引數,用於標識讀入的檔案是不是一個文字檔案。
如果forString為真的話,buffer就要多分配一個位元組,用來放置C語言的字串結束符 \0
截圖一下引擎的程式碼:
要做解密,我們的解密資料記憶體也要用forString來判斷是否多分配一個位元組的記憶體。
FileUtils單件類,很多函式是虛擬函式。FileUtils::getInstance,
在Windows平臺上,返回的是 FileUtilsWin32子類物件。
在Android平臺上,返回FileUtilsAndroid子類物件。
Cocos2d-x 3.4 實現在Windows和Android平臺上的解密需要修改2個檔案:
Windows 平臺的:cocos\platform\win32\CCFileUtils-win32.cpp
Android 平臺的:cocos\platform\android\CCFileUtils-android.cpp
這2個檔案裡面都有 getData 和 getFileData 函式,都需要修改之。
檔案資料的讀取在不同平臺上有不同的實現,
如果要在目標平臺上做資源加密,就需要修改目標平臺上的讀取實現。
FileUtils是一個檔案讀取基類,提供有預設的實現,一些平臺會提供特定的實現。
通過多型,FileUtils讓我們的遊戲客戶端程式碼不需要對特定平臺做特定處理,
也不需要關心FileUtils子類的型別。
比較遺憾的是,由於時間關係,目前我沒有找到對音訊檔案解密的方法。
除錯跟蹤發現,Cocos2d-x 在Windows上播放音訊是直接呼叫了Win32 API,這些介面API是輸入一個檔案路徑來播放音訊的。
之前有提到,做加密解密需要應用資料的介面從記憶體中獲取資料。
介面直接讀磁碟檔案,就沒有辦法干預檔案的載入進行資料解密了。
我覺得引擎的設計者,可以提供一個介面,利用觀察者模式,
讓引擎使用者可以在不修改引擎本身的情況下,干預資料的讀取進行資料變換來實現解密。
畢竟自己改引擎不是一個好的做法。
加密解密支援工具
一般要做資源資料加密,大體會做2件事:
1.寫一個工具,能對磁碟檔案進行加密。這個工具,可能需要遍歷資料夾、可批量選擇檔案等功能。
2.在軟體程式中加入解密功能,對工具加密後的資料進行解密。
工具能方便操作、提高生產力,很重要。
這裡,我自己做了2個工具。
1.對cocos2d-x引擎進行修改和恢復的工具,使其具備解密功能。如下:
2.對磁碟檔案進行加密解密的工具,方便打包時的批量篩選操作。如下:
這個工具能識別出檔案是否已經被加密,原理是用了之前說的”加密檔案頭“設計。
這樣可以防止對已被加密後的檔案再次執行加密,防止對未被加密的檔案執行解密。
降低誤操作的可能性同時,也直觀地顯示檔案的加密狀態。
如果沒有”加密檔案頭“,工具程式就無法識別一個檔案到底是否被加密過。
加密解密的額外開銷
做了資源加密會降低程式的效能,原因很簡單,需要對檔案資料進行額外的處理。
稍微複雜一點的加密演算法,可能要申請記憶體空間來儲存解密資料。delete , new 都是耗時操作。
資源的加密也可能會使得apk包體體積增大。這是為什麼呢?
原因是,用Cocos2d-x 開發,資源會有很多*.plist *.ExportJson *.tmx 等檔案。
plist 可能是配置,也可能是粒子。
*.ExportJson 等檔案是 Cocostudio1.6 做出的動畫、UI 匯出檔案。
*.tmx 是Tilemap輸出的地圖格式。
以上說的這些檔案,都是用文字儲存資料的,可以用Windows記事本開啟檢視內容。
apk包實際上是一個壓縮包,壓縮演算法對於有大量冗餘資料的檔案壓縮率是很高的。
我之前寫過一篇關於壓縮演算法的文章《檔案壓縮與解壓:哈夫曼編碼》有興趣的同學可以看看。
我做過一個試驗,600多KB的 UI *.plist *.ExportJson 檔案,打成apk包後,實際上只佔60多KB。
這是因為壓縮演算法能對這些配置的文字檔案有很高的壓縮率。
如果我們的加密演算法會降低冗餘資料。
比如:一個檔案的內容是“111111....",10萬個1。
我們的加密演算法為了增加安全性,會把這些內容加密成為:"sfsadfsa...."。
解密者很難從無規律的不重複的資料推出原先的”111111....“的內容,
但同時對這樣的資料,壓縮率也會很低。
我之前設計了一個使用金鑰加密的演算法,會消除冗餘資料,
對於都是同樣的一個字元的內容,可以加密成一個看似無規律的亂七八糟的內容,
其實還是有規律可言,只是無規律迴圈節很大而已。
然後,這些加密後的資料,幾乎就不能壓縮了。
加密資源之後輸出的apk包相比沒加密資源的包,體積增加不少。
對於這種情況,可以僅對圖片等關鍵資源加密,而不對非關鍵性的文字檔案加密。
或者降低演算法的加密強度,保留資料冗餘性。
相關文章
- 加密訪問資源方法總結加密
- Go 加密解密演算法總結Go加密解密演算法
- Python字串加密解密方法總結薦Python字串加密解密
- Cocos2d-x客戶端資源加密客戶端加密
- 【加密】Cocos2d-x PNG圖片資源加密(修改版)加密
- 《Java加密與解密的藝術》讀後總結Java加密解密
- 開源加密解密庫比較加密解密
- java實現DES資料加密與解密Java加密解密
- java使用DES加密方式,實現對資料的加密解密Java加密解密
- 周源:視訊加密和DRM實施實踐加密
- Datapump資料遷移的實踐總結
- RESTful API實踐總結RESTAPI
- hadoop實踐總結Hadoop
- Linux實踐總結Linux
- 加密解密加密解密
- 主資料管理的7個實踐總結
- Flutter 的加密和解密資料Flutter加密解密
- AES實現財務資料的加密解密儲存加密解密
- 小程式初實踐總結
- React Hooks工程實踐總結ReactHook
- iOS App 瘦身實踐總結iOSAPP
- react-redux實踐總結ReactRedux
- 軟體工程實踐總結軟體工程
- 前端優化實踐總結前端優化
- iOS程式碼實踐總結iOS
- PHP加密解密PHP加密解密
- js加密解密JS加密解密
- Unity 加密解密Unity加密解密
- Java加密解密Java加密解密
- Oracle ----加密解密Oracle加密解密
- AES 加密&解密加密解密
- AES加密解密加密解密
- PHP實現摩斯電碼加密解密PHP加密解密
- dbms_obfuscation_toolkit(資料加密解密)加密解密
- 資料的加密和解密初識加密解密
- 【Web總結】資源儲存Web
- Taro實踐 - 深度開發實踐體驗及總結
- ⚠️Flutter 效能優化實踐 總結⚠️Flutter優化