Token安全存儲的幾種方式

文章目錄

  • 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();}}
}

代碼說明

  1. 生成密鑰對

    • 使用 KeyGenParameterSpec 定義密鑰的屬性。
    • 使用 KeyGenerator 生成密鑰對并存儲在 Keystore 中。
  2. 加密數據

    • 使用 Cipher 對數據進行加密。
    • 返回加密后的字節數組。
  3. 解密數據

    • 使用 Cipher 對加密數據進行解密。
    • 返回解密后的字符串。
  4. 存儲和讀取 Token

    • 將加密后的 Token 存儲在應用的私有目錄中(例如 SharedPreferences 或文件系統)。
    • 需要時,讀取加密數據并解密。

安全性建議

  1. 密鑰管理:確保密鑰的生成和使用過程安全,避免密鑰泄露。
  2. 存儲加密數據:將加密后的 Token 存儲在應用的私有目錄中,避免被其他應用訪問。
  3. 錯誤處理:在實際應用中,需要對各種異常情況進行處理,確保應用的穩定性和安全性。

加密后的幾種存儲方式

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. 代碼說明
  1. 加密和存儲 Token

    • 使用 KeystoreManager 加密 Token。
    • 將加密后的 Token(Base64 編碼)存儲到 SQLite 數據庫中。
  2. 讀取和解密 Token

    • 從數據庫中讀取加密后的 Token。
    • 解密 Token 并打印出來。
  3. TokenDatabaseHelper

    • 提供了 saveTokengetToken 方法,分別用于存儲和讀取 Token 數據。
5. 注意事項
  • 確保 KeystoreManager 類的 generateKeyencryptDatadecryptData 方法實現正確。
  • 數據庫的 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:適合存儲少量數據。建議先加密在存儲。
  • 云存儲服務:適合跨設備同步數據,但需要依賴第三方服務。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/901599.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/901599.shtml
英文地址,請注明出處:http://en.pswp.cn/news/901599.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL數據庫表的約束類型和使用

表完整約束性 約束條件 說明 PRIMARY KEY (PK) 標識該字段為該表的主鍵,是可以唯一的標識記錄,不可以為空 UNIQUENOT NULL (primary key) FOREIGN KEY (FK) 標識該字段為該表的外鍵,實現表與表之間的關聯 (foreign key) NULL …

Java 線程詳解 --線程概念、線程池、線程同步與安全機制

一、Java線程的概念 Java 線程的本質:每個線程對應一個操作系統線程,由操作系統調度。JVM 通過調用操作系統 API(如 Linux 的 pthread)創建線程。 關鍵點: ? 用戶態與內核態:線程調度依賴操作系統&#…

PCL 計算點云至平面距離(SIMD加速)

文章目錄 一、簡介二、實現代碼三、實現效果一、簡介 SIMD 是一種并行計算模型,其中“單指令”表示處理器在同一時刻執行相同的指令,而“多數據”則表示同一條指令操作多個數據元素(如數組中的多個元素或矩陣中的多個元素)。與傳統的串行計算不同,SIMD 能夠同時處理多個數…

Ubuntu 22.04 完美安裝 ABAQUS 教程:從零到上手,解決兼容問題

教程概述與安裝準備 本教程詳細介紹了在 Ubuntu 22.04 系統上安裝 ABAQUS 2023 及 ifort 2021 的步驟,并實現用戶子程序的鏈接。教程同樣適用于 ABAQUS 2021(需相應調整文件名和路徑)以及 Ubuntu 18.04 至 22.04 系統,盡管未在所有版本上測試。需要注意的是,Intel 的 One…

Spark-TTS(Text-to-Speech):基于大語言模型的語音合成革新者!!!

Spark-TTS:基于大語言模型的語音合成革新者 🚀 (全稱解析 核心特性 行業影響全解讀) 一、概念定義與技術定位 1. 英文全稱 Spark-TTS: An Efficient LLM-Based Text-to-Speech Model ? 關鍵詞解析: ? LLM-Based…

2025年十六屆藍橋杯Python B組原題及代碼解析

相關試題可以在洛谷上測試用例: 2025 十六屆 藍橋杯 Python B組 試題 A:攻擊次數 答案:103 print(103)代碼: # 初始化敵人的血量 x 2025# 初始化回合數 turn 0# 模擬攻擊過程 while x > 0:# 回合數加一turn 1# 第一個英…

Spring Boot項目中結合MyBatis實現MySQL的自動主從切換

原理解析 1. MySQL主從復制(Master-Slave Replication) 工作原理:MySQL主從復制通過二進制日志(binary log)來同步數據。主服務器記錄所有更改操作到二進制日志中,從服務器讀取這些日志并執行相應的SQL語…

【經驗記錄貼】使用配置文件提高項目的可維護性

mark一下。 整體修改前后如下: 課題: 在項目中有一個支持的文件類型的FILE_TYPE的定義, 這個是寫死在主程序中,每次增加可以支持的文件類型的時候,都需要去修改主程序中這個FILGE_TYPE的定義。 主程序修改其實不太花時…

用DeepSeek AI高效制作專業PPT

在當今職場中,制作精美而有力的PPT是展示想法、匯報工作和贏得機會的關鍵技能。然而,許多人花費過多時間在格式調整和內容組織上,而非專注于核心信息的傳達。DeepSeek AI作為新一代智能助手,能夠幫助您將PPT制作效率提升300%,同時顯著提高專業度。本文將詳細介紹如何利用D…

【AI學習從零至壹】語?模型及詞向量相關知識

語?模型及詞向量相關知識 ?然語?處理簡介?然語?理解(NLU)?然語??成(NLG)發展趨勢信息檢索技術布爾檢索與詞袋模型基于相關性的檢索 / TF-IDF舉例: 語?模型 / Language Model神經?絡語?模型Word2Vec訓練?法…

15.【.NET 8 實戰--孢子記賬--從單體到微服務--轉向微服務】--單體轉微服務--如何拆分單體

單體應用(Monolithic Application)是指將所有功能模塊集中在一個代碼庫中構建的應用程序。它通常是一個完整的、不可分割的整體,所有模塊共享相同的運行環境和數據庫。這種架構開發初期較為簡單,部署也較為方便,但隨著…

在ARM架構Mac上部署Python 3.12與Conda環境的全鏈路指南!!!

在ARM架構Mac上部署Python 3.12與Conda環境的全鏈路指南 🚀 (M1/M2芯片實測|含性能調優避坑手冊) 🌟 核心價值點 ? 原生ARM支持:突破Rosetta轉譯的性能損耗 ? 環境隔離:Conda虛擬環境管理多…

yml文件上傳并映射到實體類

文章目錄 功能背景功能需要前端開發組件選用組件嵌套和參數綁定上傳邏輯示例 后端開發接收邏輯解析邏輯省流純手動實現(不建議) 功能背景 開發一個配置文件解析功能,需要兼容老版本的配置文件。 功能需要 前端:兩個配置文件分別…

ElasticSearch中常用的數據類型

一、映射 Elasticsearch中通過映射來指定字段的數據類型,映射方式有2種,靜態映射和動態映射。 1.動態映射 使用動態映射時,無須指定字段的數據類型,Elasticshearch會自動根據字段內容來判斷映射到哪個數據類型。 比如&#xff…

【神經網絡結構的組成】深入理解 轉置卷積與轉置卷積核

🌈 個人主頁:十二月的貓-CSDN博客 🔥 系列專欄: 🏀《深度學習理論直覺三十講》_十二月的貓的博客-CSDN博客 💪🏻 十二月的寒冬阻擋不了春天的腳步,十二點的黑夜遮蔽不住黎明的曙光 …

CSS高度坍塌?如何解決?

一、什么是高度坍塌? 高度坍塌(Collapsing Margins)是指當父元素沒有設置邊框(border)、內邊距(padding)、內容(content)或清除浮動時,其子元素的 margin 會…

Web前端開發——格式化文本與段落(上)

一、學習目標 網頁內容的排版包括文本格式化、段落格式化和整個頁面的格式化,這是設計個網頁的基礎。文本格式化標記分為字體標記、文字修飾標記。字體標記和文字修飾標記包括對于字體樣式的一些特殊修改。段落格式化標記分為段落標記、換行記、水平分隔線標記等。…

關于PHP開源CMS系統ModStart的詳細介紹及使用指南

關于PHP開源CMS系統ModStart的詳細介紹及使用指南: 🔍 ModStart是什么? 基于Laravel框架開發的模塊化CMS系統采用Apache 2.0 開源協議,完全免費可商用特別適合需要快速搭建企業級網站/管理系統的開發者 🚀 核心優勢…

TCP標志位抓包

說明 TCP協議的Header信息,URG、ACK、PSH、RST、SYN、FIN這6個字段在14字節的位置,對應的是tcp[13],因為字節數是從[0]開始數的,14字節對應的就是tcp[13],因此在抓這幾個標志位的數據包時就要明確范圍在tcp[13] 示例1…

RK3588S開發板將SPI1接口改成GPIO

參考官方教程:ROC-RK3588S-PC 一.基本知識: 1.GPIO引腳計算: ROC-RK3588S-PC 有 5 組 GPIO bank:GPIO0~GPIO4,每組又以 A0~A7, B0~B7, C0~C7, D0~D7 作為編號區分,常用以下公式計算引腳:GPIO…