Android資料儲存安全實踐
0×00 資料儲存安全
Android作業系統自問世以來憑藉其開放性和易用性成為當前智慧手機的主流作業系統之一,作為與人們關係最密切的智慧裝置,越來越多的通訊錄、簡訊、視訊等隱私資料以明文的方式儲存在手機中,這些資料雖然有鎖屏密碼或者指紋保護,但是由於Android系統自身的安全性,專業人士可以毫不費力的獲取到手機資料映象,個人隱私面臨洩露風險。另一方面,日益繁榮的移動網際網路應用也是基於使用者資料和應用程式構成,如何保護這些使用者資料安全性是應用發展的基石。隨著人們對資料安全重視,如何更好地保護使用者資料成為移動應用開發者的一大挑戰。
本文以Android開發實踐出發,由淺入深討論Android資料的儲存、加密等實現方法供移動開發進行參考。並結合自身經驗探討對Android資料安全的一些思考。
0×01常用資料儲存方法及例項
檔案
儲存資料最直接的方法就是以檔案的形式儲存在手機中,Android開發主要基於Java語言,因此,在檔案讀寫等基本操作相同,檔案操作和資料流來源於java.IO.*,但是對於Android而言,開發者需要注意一下幾點:
1、檔案目錄 Android許可權管理中各個應用程式有獨立的儲存空間,儲存結構如下:
2、常見檔案目錄及路徑
/data/data/(packageName)/cache目錄 應用快取檔案,目錄獲取方法:File cache = getCacheDir() /data/data/(packageName)/files目錄,即應用一般檔案,目錄獲取方法:File file = getFilesDir() /data/data/(packageName)/shared_prefs目錄,存放應用SharedPreference檔案目錄位置 /data/data/(packageName)/databases目錄,應用資料庫目錄(SQLite) /storage/emulated/0/sdcard內建sd卡目錄,獲取方法:String sdcard = getInnerSDCardPath() /storage/extSdCard外接sd卡目錄,獲取方法:String exsdcard = Environment.getExternalStorageDirectory().getPath()
在Android手機中,獲取預設sd卡目錄方法明確,但是由於Android手機本身不一定支援外接sd卡,或者有/沒有插入外接sd卡,因此在獲取外sd卡時需要留心有坑,一是避免異常,二是分清內建和外接。
關鍵:位置。通過檔案儲存使用者或者應用資料時,首先要遵循Android開發的規則,在應用目錄中根據檔案的型別選擇儲存的外接。在sd卡中存放時,避免直接儲存在根目錄下,這樣做是避免造成使用者手機檔案管理的混亂;二是避免檔案被修改、刪除等。
資料庫
Android 資料庫採用SQLite,SQLite 是一款內建到移動裝置上的輕量型的資料庫,是遵守ACID(原子性、一致性、隔離性、永續性)的關聯式資料庫管理系統。Android開發中可以通過SQLiteOpenHelper或者自定義類SQLiteOpenHelper來實現資料儲存查詢修改的功能。此外SQLite資料庫支援加密操作,通過sqlite3.exe或者SQLiteConnection均可對資料庫進行加密操作。SQLiteEncrypt、SQLiteCrypt、SQLCipher等工具提供對資料庫的加密操作,但是前兩個需要收費,SQLCipher是開源工具,GitHub地址為: ofollow,noindex" target="_blank">SQLCipher ;通過SQLiteConnection類加密方法如下:
SQLiteConnection conn = new SQLiteConnection("Data Source=TestDatabase.sqlite;Version=3;"); conn.SetPassword("password"); conn.open();
SharedPreferences儲存
SharedPreferences儲存方式是Android中儲存輕量級資料的一種方式,內部以Map方式進行儲存,儲存的資料以xml格式存放在本地的/data/data/(packagename)/shared_prefs資料夾下。SharedPreference<key,value>value支援Java的基本操作型別,如Boolean、Int,Float等,檔案輕量級資料要求儲存資料value大小不能太大,資料太大會給系統GC、記憶體帶來壓力,甚至造成Activity程式卡頓。
SharedPreferences pref = getSharedPreferences("test", MODE_PRIVATE); SharedPreferences.Editor editor=pref.edit(); SharedPreferences.Editor editor=pref.edit();editor.putString("name", "root");//儲存字串 editor.putInt("age", 12);//儲存整型資料 editor.commit(); //putXXX 方法中第一個引數是key,第二引數為value
SharedPreferences pref = getSharedPreferences(“setting”, 0); pref.getInt("key_name", -1); // getting Integer pref.getFloat("key_name", null); // getting Float pref.getLong("key_name", null); // getting Long //getXXX方法第一個引數表示key名稱,第二個表示value預設值
0×02 Android加密演算法及實現
DES,對稱加密,同理有3DES,3DES在DES的基礎上進行3重加密,以犧牲效率來提高加密安全性。
//DES加密[] encrypt([] data,String key){ { [] bkey = key.getBytes(); // 初始化向量IvParameterSpec iv = IvParameterSpec(bkey); DESKeySpec desKey = DESKeySpec(bkey); // 建立密匙工廠,把DESKeySpec轉換成securekey SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(); SecretKey securekey = keyFactory.generateSecret(desKey); Cipher cipher = Cipher.getInstance(); // 用密匙初始化Cipher物件cipher.init(Cipher., securekey, iv); // 現在,獲取資料並加密 // 加密操作cipher.doFinal(data); } (Throwable e) { e.printStackTrace(); } ; }
//DES解密[] decrypt([] src, String key) Exception { [] bkey = key.getBytes(); // 初始化向量IvParameterSpec iv = IvParameterSpec(bkey); // 建立一個DESKeySpec物件DESKeySpec desKey = DESKeySpec(bkey); // 建立密匙工廠SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(); // 把DESKeySpec物件轉換成SecretKey物件SecretKey securekey = keyFactory.generateSecret(desKey); // Cipher物件實際完成解密操作Cipher cipher = Cipher.getInstance(); // 用密匙初始化Cipher物件cipher.init(Cipher., securekey, iv); // 真正開始解密操作cipher.doFinal(src); }
AES 高階加密標準,用來替代DES的對稱加密演算法
//AES 加密[] encrypt([] data, [] key) { { KeyGenerator kgen = KeyGenerator.getInstance();// 建立AES的Key生產者kgen.init(128, SecureRandom(key));// 128位的key生產者SecretKey secretKey = kgen.generateKey();// 根據key生成金鑰[] enCodeFormat = secretKey.getEncoded();// 返回基本編碼格式的金鑰SecretKeySpec aesKey = SecretKeySpec(enCodeFormat, );// 轉換為AES金鑰Cipher cipher = Cipher.getInstance();// 建立密碼器cipher.init(Cipher., aesKey);// 初始化為加密模式的密碼器 // 加密cipher.doFinal(data); }(NoSuchAlgorithmException e){ e.printStackTrace(); } (NoSuchPaddingException e) { e.printStackTrace(); }(InvalidKeyException e) { e.printStackTrace(); } (IllegalBlockSizeException e) { e.printStackTrace(); } (BadPaddingException e) { e.printStackTrace(); } ; }//AES 解密[] decrypt([] data, [] key) { { KeyGenerator kgen = KeyGenerator.getInstance();// 建立AES的Key生產者kgen.init(128, SecureRandom(key)); SecretKey secretKey = kgen.generateKey();// 根據使用者密碼,生成一個金鑰[] enCodeFormat = secretKey.getEncoded();// 返回基本編碼格式的金鑰SecretKeySpec aesKey = SecretKeySpec(enCodeFormat, );// 轉換為AES專用金鑰Cipher cipher = Cipher.getInstance();// 建立密碼器cipher.init(Cipher., aesKey);// 初始化為解密模式的密碼器 //解密cipher.doFinal(data); } (NoSuchAlgorithmException e) { e.printStackTrace(); } (NoSuchPaddingException e) { e.printStackTrace(); } (InvalidKeyException e) { e.printStackTrace(); } (IllegalBlockSizeException e) { e.printStackTrace(); } (BadPaddingException e) { e.printStackTrace(); } ; }
對稱加密特點是實現效率快,但是由於加/解密金鑰相同,在金鑰儲存、分發、安全各方面出現許多問題,例如金鑰管理,金鑰洩露。基於此,將加密金鑰和解密金鑰分開,形成客戶端端使用公鑰加密,服務端用私鑰解密的非對稱加密,將加解密金鑰分開,加密金鑰不必擔心洩露風險。常用的非對稱加密演算法如RSA。
RSA加解密實現
// 生成 public and private keysKeyPair buildKeyPair() NoSuchAlgorithmException { keySize = 2048; KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(); keyPairGenerator.initialize(keySize); keyPairGenerator.genKeyPair(); } //RSA 加密 [] encrypt(PrivateKey privateKey, [] data) Exception { Cipher cipher = Cipher.getInstance(); cipher.init(Cipher., privateKey); //加密cipher.doFinal(data); } //RSA 解密 [] decrypt(PublicKey publicKey, [] enData) Exception { Cipher cipher = Cipher.getInstance(); cipher.init(Cipher., publicKey); //解密cipher.doFinal(enData); }
在常用資料加密方法中,通常也會遇到md5、sha-256演算法等,但是這些演算法是明文的hash值,雜湊演算法和加密演算法的本質是是否可逆,即由密文通過運算得到明文。特別注意,base64編碼是一種編碼格式,除了增加可讀性難度沒有任何安全性。
0×03 儲存安全進階
在上文中介紹了常用的Android資料儲存方式和加密演算法,通過直觀的介紹進入到Android儲存安全中,在實際的應用中資料儲存安全性問題是一個複製的系統性問題,不僅僅表現在開發中,從資料結構到編碼以及金鑰的生成和管理都會涉及到資料儲存安全。
檔案的隱藏 Android建立隱藏檔案或者資料夾,在檔名或者資料夾名字前加一個“.”號即可(這裡是英文輸入法下的.號),隱藏檔案/資料夾可直接進行讀寫。這是一個容易被開發者忽略的問題,乍一看好像沒什麼難度,問題在於開發者和使用者視角問題。由於Android手機預設帶檔案檢視器,因此使用者可以輕鬆檢視、修改sdcard目錄下的檔案,當使用隱藏檔案是最大的作用是避免使用者誤操作。
金鑰的儲存 如果將金鑰儲存到手機檔案中,或者通過硬編碼的方式寫在程式碼中,容易被逆向出來,在通常情況下,採用對稱加密金鑰需要儲存在使用者手機中,這和安全性想違背。通常最好的方式是不要保有金鑰,通過固定資料或者字串做加密金鑰因子,例如使用者唯一賬號屬性等。
編碼方式 Android程式碼主要有Java編碼,打包檔案時Java程式碼打包成dex檔案防到安裝包檔案中,但是dex檔案容易被逆向回smali程式碼或者Java檔案。雖然目前混淆和加殼甚至是虛擬機器保護(VMP)技術已經很成熟,簡單逆向工作無法獲取程式碼邏輯和硬編碼字串,但是Java程式碼依然存在很高的安全風險。因此,將加解密相關操作通過Native程式碼實現很有必要,不僅保證效率而且在so保護技術之上安全性更高。
0×04 Android資料安全思考
隨著移動網際網路深入發展,目前移動應用正在發生質的改變。相比繁榮初始的粗狂、野蠻,現在的移動應用開始考慮安全和質量,特別是當前我國網際網路資訊保安的大形勢,資料安全關乎企業和應用的生存的前提,保護應用資料安全至關重要。在Android資料儲存安全中,由於Android系統的安全機制,使用者獲取root許可權後可以訪問手機所有目錄,包括應用私有目錄,因此,資料儲存要考慮到一個白盒環境,或者非可信環境。這種情況下,資料加密的金鑰成為關鍵。一機一密、動態金鑰、金鑰白盒等手段各有優缺點。一機一密需要保護金鑰生成方法邏輯;動態金鑰需要考慮金鑰時效性,有效性以及鏈路安全;金鑰白盒由於目前沒有廣泛認可,在相容性安全性方面有待考驗。
(後面有時間針對一機一密、動態金鑰、金鑰白盒單獨介紹)
*本文作者:root001,轉載請註明來自FreeBuf.COM