文章目錄
- 1. EncryptedSharedPreferences
- 示例代碼
- 2. SQLCipher
- 示例代碼
- 3.使用 Android Keystore加密后存儲
- 示例代碼
- 1. 生成密鑰對
- 2. 使用 KeystoreManager
- 代碼說明
- 安全性建議
- 加密后的幾種存儲方式
- 1. 加密后采用 SharedPreferences存儲
- 2. 加密后采用SQLite數據庫存儲
- 1. TokenDatabaseHelper 類
- 2. MainActivity 中的實現
- 4. 代碼說明
- 5. 注意事項
- 3. 加密后采用內部文件存儲
- 4. 云存儲服務
- 示例代碼(使用 Firebase)
- 總結
1. EncryptedSharedPreferences
EncryptedSharedPreferences
是一個開源庫,用于對 SharedPreferences
進行加密存儲,提供了更高的安全性。
示例代碼
// 創建 EncryptedSharedPreferences
MasterKeys.KeyPair keyPair = MasterKeys.generateKeyPair(context, MasterKeys.AES256_GCM_SPEC);
String keyAlias = keyPair.getAlias();EncryptedSharedPreferences encryptedSharedPreferences = EncryptedSharedPreferences.create(context,"encrypted_prefs",keyAlias,EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);// 存儲 Token
SharedPreferences.Editor editor = encryptedSharedPreferences.edit();
editor.putString("token", token);
editor.apply();// 獲取 Token
String token = encryptedSharedPreferences.getString("token", null);
2. SQLCipher
SQLCipher
是一個開源庫,用于對 SQLite 數據庫進行加密存儲,適用于需要更高安全性的場景。
示例代碼
// 初始化 SQLCipher 數據庫
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(new File(context.getFilesDir(), "encrypted.db"),"password", // 數據庫密碼null
);// 創建表并存儲 Token
db.execSQL("CREATE TABLE IF NOT EXISTS tokens (token TEXT)");
db.execSQL("INSERT INTO tokens (token) VALUES (?)", new Object[]{token});
db.close();
3.使用 Android Keystore加密后存儲
Keystore 提供了硬件級別的加密保護,即使設備被 Root,也很難獲取存儲在 Keystore 中的密鑰。
非常適合存儲 Token、密碼等敏感信息。
不過使用 Keystore 比較復雜,需要生成密鑰對、加密和解密數據等操作。而加密和解密操作會帶來一定的性能開銷。
示例代碼
1. 生成密鑰對
在應用首次啟動時,生成一個密鑰對并存儲在 Keystore 中。
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;public class KeystoreManager {private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";private static final String KEY_ALIAS = "myAppKeyAlias";private static final String ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;private static final String ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM;private static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE;private static final String ENCRYPTION_TRANSFORMATION = ENCRYPTION_ALGORITHM + "/"+ ENCRYPTION_BLOCK_MODE + "/" + ENCRYPTION_PADDING;private KeyStore keyStore;public KeystoreManager() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);keyStore.load(null);}public void generateKey() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_ALIAS,KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).setBlockModes(ENCRYPTION_BLOCK_MODE).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE).build());keyGenerator.generateKey();}public byte[] encryptData(String data) throws Exception {Cipher cipher = Cipher.getInstance(ENCRYPTION_TRANSFORMATION);cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());return cipher.doFinal(data.getBytes());}public String decryptData(byte[] encryptedData) throws Exception {Cipher cipher = Cipher.getInstance(ENCRYPTION_TRANSFORMATION);cipher.init(Cipher.DECRYPT_MODE, getSecretKey());return new String(cipher.doFinal(encryptedData));}private SecretKey getSecretKey() throws UnrecoverableEntryException, KeyStoreException {return (SecretKey) keyStore.getKey(KEY_ALIAS, null);}
}
2. 使用 KeystoreManager
在你的應用中,使用 KeystoreManager
來存儲和讀取 Token。
import android.os.Bundle;
import android.util.Log;import androidx.appcompat.app.AppCompatActivity;import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);try {KeystoreManager keystoreManager = new KeystoreManager();// 生成密鑰對(只需在首次啟動時調用一次)keystoreManager.generateKey();// 加密 TokenString accessToken = "your_access_token_here";byte[] encryptedAccessToken = keystoreManager.encryptData(accessToken);//存儲請參考下述的幾種方式// 解密 TokenString decryptedAccessToken = keystoreManager.decryptData(encryptedAccessToken);Log.d(TAG, "Encrypted Token: " + Base64.encodeToString(encryptedAccessToken, Base64.DEFAULT));Log.d(TAG, "Decrypted Token: " + decryptedAccessToken);} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | UnrecoverableEntryException | InvalidAlgorithmParameterException e) {e.printStackTrace();}}
}
代碼說明
-
生成密鑰對:
- 使用
KeyGenParameterSpec
定義密鑰的屬性。 - 使用
KeyGenerator
生成密鑰對并存儲在 Keystore 中。
- 使用
-
加密數據:
- 使用
Cipher
對數據進行加密。 - 返回加密后的字節數組。
- 使用
-
解密數據:
- 使用
Cipher
對加密數據進行解密。 - 返回解密后的字符串。
- 使用
-
存儲和讀取 Token:
- 將加密后的 Token 存儲在應用的私有目錄中(例如
SharedPreferences
或文件系統)。 - 需要時,讀取加密數據并解密。
- 將加密后的 Token 存儲在應用的私有目錄中(例如
安全性建議
- 密鑰管理:確保密鑰的生成和使用過程安全,避免密鑰泄露。
- 存儲加密數據:將加密后的 Token 存儲在應用的私有目錄中,避免被其他應用訪問。
- 錯誤處理:在實際應用中,需要對各種異常情況進行處理,確保應用的穩定性和安全性。
加密后的幾種存儲方式
1. 加密后采用 SharedPreferences存儲
SharedPreferences
是 Android 中一種輕量級的存儲方式,適合存儲少量的鍵值對數據。你可以將加密后的 Token 存儲到 SharedPreferences
中。
// 存儲加密后的 Token 到 SharedPreferences
SharedPreferences sharedPreferences = getSharedPreferences("MyAppPreferences", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("encryptedToken", Base64.encodeToString(encryptedAccessToken, Base64.DEFAULT));
editor.apply();
從 SharedPreferences
中讀取時:
SharedPreferences sharedPreferences = getSharedPreferences("MyAppPreferences", MODE_PRIVATE);
String encryptedToken = sharedPreferences.getString("encryptedToken", null);
if (encryptedToken != null) {byte[] encryptedAccessToken = Base64.decode(encryptedToken, Base64.DEFAULT);// 然后可以對 encryptedAccessToken 進行解密等操作
}
2. 加密后采用SQLite數據庫存儲
如果應用中有數據庫(如 SQLite),也可以將加密后的 Token 存儲到數據庫中。這種方式適合需要結構化存儲的場景。
1. TokenDatabaseHelper 類
以下是 TokenDatabaseHelper
類的完整代碼,用于創建和管理 SQLite 數據庫:
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;public class TokenDatabaseHelper extends SQLiteOpenHelper {private static final String DATABASE_NAME = "token.db";private static final int DATABASE_VERSION = 1;private static final String TABLE_TOKENS = "tokens";private static final String COLUMN_ID = "id";private static final String COLUMN_TOKEN = "token";public TokenDatabaseHelper(Context context) {super(context, DATABASE_NAME, null, DATABASE_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {String createTable = "CREATE TABLE " + TABLE_TOKENS + "("+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"+ COLUMN_TOKEN + " TEXT" + ")";db.execSQL(createTable);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS " + TABLE_TOKENS);onCreate(db);}public void saveToken(String token) {SQLiteDatabase db = this.getWritableDatabase();ContentValues values = new ContentValues();values.put(COLUMN_TOKEN, token);db.insert(TABLE_TOKENS, null, values);db.close();}public String getToken() {String token = null;SQLiteDatabase db = this.getReadableDatabase();Cursor cursor = db.query(TABLE_TOKENS, new String[]{COLUMN_TOKEN}, null, null, null, null, null);if (cursor != null && cursor.moveToFirst()) {token = cursor.getString(cursor.getColumnIndex(COLUMN_TOKEN));}cursor.close();db.close();return token;}
}
2. MainActivity 中的實現
在 MainActivity
中,我們將使用 TokenDatabaseHelper
來存儲和讀取加密后的 Token。
以下是完整的代碼:
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;import androidx.appcompat.app.AppCompatActivity;import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private TokenDatabaseHelper dbHelper;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化數據庫幫助類dbHelper = new TokenDatabaseHelper(this);try {KeystoreManager keystoreManager = new KeystoreManager();// 生成密鑰對(只需在首次啟動時調用一次)keystoreManager.generateKey();// 加密 TokenString accessToken = "your_access_token_here";byte[] encryptedAccessToken = keystoreManager.encryptData(accessToken);// 將加密后的 Token 存儲到數據庫String encodedToken = Base64.encodeToString(encryptedAccessToken, Base64.DEFAULT);dbHelper.saveToken(encodedToken);// 從數據庫中讀取 TokenString retrievedToken = dbHelper.getToken();if (retrievedToken != null) {byte[] retrievedEncryptedToken = Base64.decode(retrievedToken, Base64.DEFAULT);// 解密 TokenString decryptedAccessToken = keystoreManager.decryptData(retrievedEncryptedToken);Log.d(TAG, "Decrypted Token: " + decryptedAccessToken);}} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | UnrecoverableEntryException | InvalidAlgorithmParameterException e) {e.printStackTrace();}}
}
4. 代碼說明
-
加密和存儲 Token
- 使用
KeystoreManager
加密 Token。 - 將加密后的 Token(Base64 編碼)存儲到 SQLite 數據庫中。
- 使用
-
讀取和解密 Token
- 從數據庫中讀取加密后的 Token。
- 解密 Token 并打印出來。
-
TokenDatabaseHelper
- 提供了
saveToken
和getToken
方法,分別用于存儲和讀取 Token 數據。
- 提供了
5. 注意事項
- 確保
KeystoreManager
類的generateKey
、encryptData
和decryptData
方法實現正確。 - 數據庫的
COLUMN_TOKEN
字段存儲的是 Base64 編碼后的加密數據,確保在存儲和讀取時正確處理編碼和解碼。 - 如果需要支持多條 Token 數據,可以在
getToken
方法中添加邏輯,例如按時間戳排序或指定特定的 Token。
3. 加密后采用內部文件存儲
如果 Token 數據較大,或者需要更安全的存儲方式,可以將其存儲到內部存儲中。內部存儲是私有的,其他應用無法訪問。
// 存儲到內部存儲
File file = new File(getFilesDir(), "encryptedToken.txt");
FileOutputStream fos = new FileOutputStream(file);
fos.write(encryptedAccessToken);
fos.close();
從內部存儲中讀取時:
File file = new File(getFilesDir(), "encryptedToken.txt");
FileInputStream fis = new FileInputStream(file);
byte[] encryptedAccessToken = new byte[(int) file.length()];
fis.read(encryptedAccessToken);
fis.close();
4. 云存儲服務
如果需要跨設備同步 Token,可以考慮使用云存儲服務,如 Firebase、Dropbox 等。
示例代碼(使用 Firebase)
// 初始化 Firebase 數據庫
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference tokensRef = database.getReference("tokens");// 存儲 Token
tokensRef.child("userToken").setValue(token);// 獲取 Token
tokensRef.child("userToken").addListenerForSingleValueEvent(new ValueEventListener() {@Overridepublic void onDataChange(DataSnapshot dataSnapshot) {String token = dataSnapshot.getValue(String.class);// 使用 Token}@Overridepublic void onCancelled(DatabaseError databaseError) {// 處理錯誤}
});
總結
- EncryptedSharedPreferences:提供加密的
SharedPreferences
,適合存儲少量敏感數據。 - SQLCipher:提供加密的 SQLite 數據庫,適合需要更高安全性的場景。
- SQLite 數據庫:適合存儲結構化數據,支持復雜查詢。建議先加密在存儲。
- 文件存儲:適合存儲簡單的文本數據,確保文件權限為
MODE_PRIVATE
。建議先加密在存儲。 - SharedPreferences:適合存儲少量數據。建議先加密在存儲。
- 云存儲服務:適合跨設備同步數據,但需要依賴第三方服務。