Token安全存储的几种方式小结,
Token安全存储的几种方式小结,
目录
- 1. EncryptedSharedPreferences
- 示例代码
- 2. SQLCipher
- 示例代码
- 3.使用 Android Keystore加密后存储
- 示例代码
- 1. 生成密钥对
- 2. 使用 KeystoreManager
- 代码说明
- 安全性建议
- 加密后的几种存储方式
- 1. 加密后采用 SharedPreferences存储
- 2. 加密后采用SQLite数据库存储
- 1. TokenDatabaseHelper 类
- 2. MainActivity 中的实现
- 3. 代码说明
- 4. 注意事项
- 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"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { KeystoreManager keystoreManager = new KeystoreManager(); // 生成密钥对(只需在首次启动时调用一次) keystoreManager.generateKey(); // 加密 Token String accessToken = "your_access_token_here"; byte[] encryptedAccessToken = keystoreManager.encryptData(accessToken); //存储请参考下述的几种方式 // 解密 Token String 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); } @Override public void onCreate(SQLiteDatabase db) { String createTable = "CREATE TABLE " + TABLE_TOKENS + "(" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + COLUMN_TOKEN + " TEXT" + ")"; db.execSQL(createTable); } @Override public 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; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化数据库帮助类 dbHelper = new TokenDatabaseHelper(this); try { KeystoreManager keystoreManager = new KeystoreManager(); // 生成密钥对(只需在首次启动时调用一次) keystoreManager.generateKey(); // 加密 Token String accessToken = "your_access_token_here"; byte[] encryptedAccessToken = keystoreManager.encryptData(accessToken); // 将加密后的 Token 存储到数据库 String encodedToken = Base64.encodeToString(encryptedAccessToken, Base64.DEFAULT); dbHelper.saveToken(encodedToken); // 从数据库中读取 Token String retrievedToken = dbHelper.getToken(); if (retrievedToken != null) { byte[] retrievedEncryptedToken = Base64.decode(retrievedToken, Base64.DEFAULT); // 解密 Token String decryptedAccessToken = keystoreManager.decryptData(retrievedEncryptedToken); Log.d(TAG, "Decrypted Token: " + decryptedAccessToken); } } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | UnrecoverableEntryException | InvalidAlgorithmParameterException e) { e.printStackTrace(); } } }
3. 代码说明
加密和存储 Token
- 使用
KeystoreManager
加密 Token。 - 将加密后的 Token(Base64 编码)存储到 SQLite 数据库中。
- 使用
读取和解密 Token
- 从数据库中读取加密后的 Token。
- 解密 Token 并打印出来。
TokenDatabaseHelper
- 提供了
saveToken
和getToken
方法,分别用于存储和读取 Token 数据。
- 提供了
4. 注意事项
- 确保
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() { @Override public void onDataChange(DataSnapshot dataSnapshot) { String token = dataSnapshot.getValue(String.class); // 使用 Token } @Override public void onCancelled(DatabaseError databaseError) { // 处理错误 } });
总结
- EncryptedSharedPreferences:提供加密的
SharedPreferences
,适合存储少量敏感数据。 - SQLCipher:提供加密的 SQLite 数据库,适合需要更高安全性的场景。
- SQLite 数据库:适合存储结构化数据,支持复杂查询。建议先加密在存储。
- 文件存储:适合存储简单的文本数据,确保文件权限为
MODE_PRIVATE
。建议先加密在存储。 - SharedPreferences:适合存储少量数据。建议先加密在存储。
- 云存储服务:适合跨设备同步数据,但需要依赖第三方服务。
以上就是Token安全存储的几种方式小结的详细内容,更多关于Token安全存储方式的资料请关注3672js教程其它相关文章!
您可能感兴趣的文章:- 保证Redis中存储的Token安全性的示例详解
- React配置Redux并结合本地存储设置token方式
- Vue中使用localStorage存储token并设置时效
- vue项目中存储与使用后端传递过来的token
- Redis妙用之存储用户token问题
用户点评