Token安全存储的几种方式小结

 更新时间:2025年04月18日 10:21:42   作者:一一Null  
在现代 Web 应用中,身份认证与授权是确保系统安全性的重要部分,Token被广泛应用,作为实现身份认证的主要方式,然而,如何安全地存储这些 Token,是每个开发者在构建前端应用时必须考虑的问题,本文将深入探讨Token安全存储的几种方式,需要的朋友可以参考下

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

代码说明

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

    @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. 代码说明

  1. 加密和存储 Token

    • 使用 KeystoreManager 加密 Token。
    • 将加密后的 Token(Base64 编码)存储到 SQLite 数据库中。
  2. 读取和解密 Token

    • 从数据库中读取加密后的 Token。
    • 解密 Token 并打印出来。
  3. TokenDatabaseHelper

    • 提供了 saveToken 和 getToken 方法,分别用于存储和读取 Token 数据。

4. 注意事项

  • 确保 KeystoreManager 类的 generateKeyencryptData 和 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安全存储方式的资料请关注脚本之家其它相关文章!

相关文章

  • Mybatis-Plus CRUD操作方法

    Mybatis-Plus CRUD操作方法

    通用 Service CRUD 封装 IService 接口,进一步封装 CRUD 采用 get 查询、remove 删除 、list 查询集合、page 分页的前缀命名方式区分 Mapper 层避免混淆,这篇文章主要介绍了Mybatis-Plus CRUD的相关知识,需要的朋友可以参考下
    2023-10-10
  • MyBatis中特殊符号的转义

    MyBatis中特殊符号的转义

    编写SQL中会用到<,>,,>= 等,但是在mybatis中不可以这么写,与xml文件的元素冲突,所以需要转义,本文主要介绍了MyBatis中特殊符号的转义,主要介绍了两种转义方式,感兴趣的可以了解一下
    2024-01-01
  • Java设计模式中的门面模式详解

    Java设计模式中的门面模式详解

    门面模式又叫外观模式(Facade Pattern),主要用于隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口,本文通过实例代码给大家介绍下java门面模式的相关知识,感兴趣的朋友一起看看吧
    2022-09-09
  • MyBatis使用动态SQL标签的小陷阱

    MyBatis使用动态SQL标签的小陷阱

    MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架,MyBatis越来越受大家的喜爱了。下面给大家分享MyBatis使用动态SQL标签的小陷阱,感兴趣的朋友一起看看吧
    2016-10-10
  • mybatis中的延迟加载类型及设定详解

    mybatis中的延迟加载类型及设定详解

    这篇文章主要介绍了mybatis中的延迟加载类型及设定详解,MyBatis中的延迟加载,也称为懒加载,是指在进行关联查询时,按照设置延迟规则推迟对关联对象的select查询,延迟加载可以有效的减少数据库压力,需要的朋友可以参考下
    2023-10-10
  • Mybatis一对多与多对一查询处理详解

    Mybatis一对多与多对一查询处理详解

    这篇文章主要给大家介绍了关于Mybatis一对多与多对一查询处理的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • Java解析微信获取手机号信息的示例步骤

    Java解析微信获取手机号信息的示例步骤

    在微信中,用户手机号的获取通常是通过微信小程序的getPhoneNumber接口来实现的,下面通过一个基于Java的示例,展示了如何接收并解密从微信小程序传递过来的加密手机号信息,感兴趣的朋友一起看看吧
    2024-06-06
  • Spring Boot的FailureAnalyzer机制及如何解救应用启动危机

    Spring Boot的FailureAnalyzer机制及如何解救应用启动危机

    本文探讨了FailureAnalyzer工具,它不仅能帮助我们快速识别和处理代码中的错误,还能极大地提升我们的开发效率,通过详细的实例分析,我们了解了FailureAnalyzer如何通过自定义逻辑应对不同类型的异常,让程序员能够更好地定位问题并迅速找到解决方案,感兴趣的朋友一起看看吧
    2025-01-01
  • Java使用Swing实现一个模拟电脑计算器

    Java使用Swing实现一个模拟电脑计算器

    Java Swing 是一个用于创建 Java GUI(图形用户界面)的框架,它提供了一系列的 GUI 组件和工具,可以用于创建桌面应用程序,包括按钮、文本框、标签、表格等等,本文给大家介绍了Java使用Swing实现一个模拟计算器,感兴趣的同学可以自己动手尝试一下
    2024-05-05
  • springboot的@Value中#和$区别详解

    springboot的@Value中#和$区别详解

    这篇文章主要介绍了springboot的@Value中#和$区别详解,@Value注解的作用主要可以给属性直接赋值、也可以读取配置文件中的值给属性赋值,需要的朋友可以参考下
    2023-11-11

最新评论