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.mzph.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…