000 SQLCipher
在移動端,不管是iOS還是Android,開發人員用的最多的本地數據庫非SQlite莫屬了。SQLite是一個輕量的、跨平臺的、開源的數據庫引擎,它的在讀寫效率、消耗總量、延遲時間和整體簡單性上具有的優越性,使其成為移動平臺數據庫的最佳解決方案。
但是,用SQLite保存一些不是很敏感的信息還可以接受,存儲敏感信息就值得商榷了,免費版的SQLite中的信息是明文存放的,你甚至直接用文本編輯器打開都可以看到敏感內容,比如下面這個:
其實SQLite是提供了加密功能的,即SQLite Encryption Extension (SEE),但是,要收費!還不便宜,2000刀。碼農們窮啊,于是大家尋找免費的解決方案,最終SQLCipher成了比較理想的選擇,因為它免費、開源,而其它家的SQLiteEncrypt、SQLiteCrypt等還是要收費。
SQLCipher也有收費版本,但收費版本只是在集成、依賴等易用性方面有優勢,功能和免費差不多,因此免費版的SQLCipher成了Coder們的選擇對象。
SQLCipher采用的是數據庫文件整體加密的策略,使用256-bit AES加密,從算法角度來看是相當強悍了。于是,加密后的文件看起來及時這樣了:
完全是天書嘛,ok,可以洗洗睡了。但真的就安全了嗎?呵呵。
001 謀攻篇
密碼學中有一句比較經典的話:一切秘密寓于密鑰之中。只要我們拿到了密鑰,剩下的問題都不是問題了。下面我們就來分析一下SQLCipher的密鑰和加密過程。 根據官方的描述,SQLCipher是整體加密方案:
Transparent – An application doesn’t require any special knowledge of the underlying database security. Applications use the standard SQLite API to manipulate tables using SQL. Behind the scenes the library silently manages the security.
On-the-fly – SQLCipher encrypts and decrypts in chunks called pages, as needed, so it doesn’t operate on the database all at one time. This means SQLCipher starts up and closes down quickly performs very well even with very large databases works with SQLite indexing (i.e. retrieving a single record using an indexed search can incur as little as 5% overhead above a standard SQLite database)
它并不是對表或者列進行加密,也就是說他的數據庫密鑰只有一個,拿下這一個密鑰就成。
SQLCipher在iOS上是這樣用的:
1)在項目中加入sqlite3.h和sqlite3.m;
2)使用下面代碼連接數據庫:
其中的key就是我們需要的密鑰!然后通過sqlite3_key函數將該key與數據庫關聯起來,接下來就可以執行sql語句操作了。程序員肯定覺得好用啊,就增加了兩行語句就完成了加密功能,直接拷貝官方的代碼到自己的工程中,把“StrongPassword”換成自己的密鑰就成。真實容易啊,那么我攻擊也很easy啦。
一般來說,對付這種程度的加密,只需要使用靜態分析即可。對于iOS程序,將程序拖到ida中,找到sqlite3_key函數相關引用地址,順藤摸瓜,基本就搞定了。下面就是某應用中定位該函數在setKey方法中,該方法是在它的數據open方法中調用的,可以看到已經明文硬編碼了數據庫的密碼(圖中抹紅部分)。
拿到密碼后有個偷懶的方法可以直接打開加密數據庫:使用SQLiteManager,它支持SQLCipher加密的數據庫,會提示輸入數據庫密碼,不需要自己寫程序了,而且瀏覽數據也很方便。
對于Android版的程序,如果沒加殼的話,也是很好分析的。Android調用SQLCipher使用下面的方式:
openOrCreateDatabase方法的第二個參數就是密鑰了。在jeb反編譯出來的代碼中尋找相關的方法就可以摸到相應的數據庫密鑰。
到這兒程序員可能要想了,我不硬編碼看你咋整,我在代碼中經過加減乘除、異或后在算出key,或者更狠一點用用戶口令等進行動態解密變換,密鑰就不會在程序中出現了。
但攻擊者還有n多招數還沒上場了,比如動態調試,在輸入key的方法入口打斷點,對key的明文進行攔截。 還有就是hook技術,對于Android上的應用,我們可以使用CydiaSubstrate,使用MS.hookClassLoad 方法將openOrCreateDatabase進行hook處理:
public class Main {
static void hookCryptoKey() {
MS.hookClassLoad("net.sqlcipher.database.SQLiteDatabase",
new MS.ClassLoadHook() {
public void classLoaded(Class arg0) {
Log.d("MyHook", "##### Class Loaded ");
Method openOrCreateDatabase;
try {
openOrCreateDatabase = arg0.getMethod(
"openOrCreateDatabase",
new Class[] { File.class, String.class, SQLiteDatabase.CursorFactory.class });
} catch (NoSuchMethodException e) {
openOrCreateDatabase = null;
Log.d("MyHook",
"##### Unable to find method ");
}
if (openOrCreateDatabase != null) {
Log.d("MyHook", "##### Found method ");
try {
final MS.MethodPointer old = new MS.MethodPointer();
MS.hookMethod(arg0,
openOrCreateDatabase,
new MS.MethodHook() {
public Object invoked(Object arg0,
Object... args)
throws Throwable {
Log.d("MyHook", "##### Error: " + Thread.currentThread().getStackTrace());
Log.d("MyHook",
"###### Method hooked, stealing key: " + args[1]);
return old.invoke(arg0, args);
}
}, old);
} catch (Exception e) {
System.out
.println("#### Unable to find class ");
}
}
}
});
}
static void initialize() {
hookCryptoKey();
}
}
這段代碼可以將openOrCreateDatabase方法鉤住并將第二個參數key在日志中打印出來,無視你前期經過了多少隱藏、變換。至于Substrate的用法這里就不細講了,各位看官可以參考網上的資料。當然,你也可以嘗試其它hook框架,如Xposed。
程序員可能不服氣,我還可以上加殼,在iOS端我也可以上混淆、lua動態網上加載等手段啊?不過這貌似超出了SQLCipher攻防的地盤了,是程序本地保護的大范疇,有機會再研究。
002 防
寫到這兒,其實對本地數據庫加密是感到沮喪的,這完全就是把門鎖和鑰匙放在一起的做法嘛,不管你在本地上任何軟的手段(硬件另說,如果你的密鑰、算法都放在硬件中,比如USBKey中,安全性應該是有本質上的提升),不管是簡單變換還是混淆、加殼、反調試、反hook,都難保不被攻擊者破解掉,不過是或多或少延緩找到鑰匙的時間罷了。
因此,建議除了以下情況,不要使用SQLCipher之類的本地數據庫加密:
非敏感數據,愛咋用咋用
用戶自己的數據,可以采用用戶口令、指紋等方式直接或間接保護,因為這種情況下key其實是隨用戶走的,可以做到key和算法、數據的分離
其它情況推薦數據放在APP的服務端,將客戶端做瘦,將安全的防護重點放在通信和服務端的防護上。
* 本文原創作者:titan39,屬FreeBuf(FreeBuf.COM)原創獎勵計劃文章,未經許可禁止轉載