安卓开发基于KeyStore对数据进行加解密

news2024/11/17 2:27:18

问题背景

在我们App开发过程中,可能会涉及到一些敏感和安全数据需要加密的情况,比如登录token的存储。我们往往会使用一些加密算法将这些敏感数据加密之后再保存起来,需要取出来的时候再进行解密。
此时就会有一个问题:用于加解密的Key该如何存储?
为了保证安全性,Android提供了KeyStore系统来保存Key,本文就浅探一下KeyStore及其使用方法。

问题分析

话不多说,直接上代码:
1、测试加解密过程
(1)com.baorant.databindingdemo.EncryptUtil

package com.baorant.databindingdemo;

import android.content.Context;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import android.util.Base64;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Calendar;

import javax.crypto.Cipher;
import javax.security.auth.x500.X500Principal;

/**
 * 基于KeyStore的本地加解密方法
 */
public class EncryptUtil {
    private static final String TAG = "EncryptUtil";
    private static EncryptUtil encryptUtilInstance;

    private KeyStore keyStore;

    private Context context;
    // 单位年
    private final int maxExpiredTime = 1000;

    private String x500PrincipalName = "CN=MyKey, O=Android Authority";

    // RSA有加密字符长度限制,所以需要分段加密
    private int rsaEncryptBlock = 244;
    private int rsaDecryptBlock = 256;

    private EncryptUtil() {
    }

    public static EncryptUtil getInstance() {
        if (encryptUtilInstance == null) {
            synchronized (EncryptUtil.class) {
                if (encryptUtilInstance == null) {
                    encryptUtilInstance = new EncryptUtil();
                }
            }
        }
        return encryptUtilInstance;
    }

    public void init(Context context, String x500PrincipalName) {
        this.context = context;
        this.x500PrincipalName = x500PrincipalName;
    }

    public void initKeyStore(String alias) {
        synchronized (EncryptUtil.class) {
            try {
                if (null == keyStore) {
                    keyStore = KeyStore.getInstance("AndroidKeyStore");
                    keyStore.load(null);
                }
                createNewKeys(alias);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void createNewKeys(String alias) {
        if (TextUtils.isEmpty(alias)) {
            return;
        }
        try {
            if (keyStore.containsAlias(alias)) {
                return;
            }
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                // Create new key
                Calendar start = Calendar.getInstance();
                Calendar end = Calendar.getInstance();
                end.add(Calendar.YEAR, maxExpiredTime);
                KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
                        .setAlias(alias)
                        .setSubject(new X500Principal(x500PrincipalName))
                        .setSerialNumber(BigInteger.ONE)
                        .setStartDate(start.getTime())
                        .setEndDate(end.getTime())
                        .build();
                KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
                generator.initialize(spec);
                generator.generateKeyPair();
            } else {
                KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
                keyPairGenerator.initialize(
                        new KeyGenParameterSpec.Builder(
                                alias,
                                KeyProperties.PURPOSE_DECRYPT)
                                .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                                .setUserAuthenticationRequired(false)
                                .build());
                keyPairGenerator.generateKeyPair();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void clearKeystore(String alias) {
        try {
            keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);
            keyStore.deleteEntry(alias);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加密方法
     *
     * @param needEncryptWord  需要加密的字符串
     * @param alias            加密秘钥
     * @return
     */
    public String encryptString(String needEncryptWord, String alias) {
        if (TextUtils.isEmpty(needEncryptWord) || TextUtils.isEmpty(alias)) {
            return "";
        }
        String encryptStr = "";
        synchronized (EncryptUtil.class) {
            initKeyStore(alias);
            try {
                PublicKey publicKey = keyStore.getCertificate(alias).getPublicKey();
                Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                inCipher.init(Cipher.ENCRYPT_MODE, publicKey);

                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int offSet = 0;
                int inputLen = needEncryptWord.length();
                byte[] inputData = needEncryptWord.getBytes();
                for (int i = 0; inputLen - offSet > 0; offSet = i * rsaEncryptBlock) {
                    byte[] cache;
                    if (inputLen - offSet > rsaEncryptBlock) {
                        cache = inCipher.doFinal(inputData, offSet, rsaEncryptBlock);
                    } else {
                        cache = inCipher.doFinal(inputData, offSet, inputLen - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    ++i;
                }
                byte[] encryptedData = out.toByteArray();
                out.close();

                encryptStr = Base64.encodeToString(encryptedData, Base64.URL_SAFE);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return encryptStr;
    }


    /**
     * 解密方法
     *
     * @param needDecryptWord 需要解密的字符串
     * @param alias           key的别称
     * @return
     */
    public String decryptString(String needDecryptWord, String alias) {
        if (TextUtils.isEmpty(needDecryptWord) || TextUtils.isEmpty(alias)) {
            return "";
        }
        String decryptStr = "";
        synchronized (EncryptUtil.class) {
            initKeyStore(alias);
            try {
                PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);
                Cipher outCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                outCipher.init(Cipher.DECRYPT_MODE, privateKey);

                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int offSet = 0;
                byte[] encryptedData = Base64.decode(needDecryptWord, Base64.URL_SAFE);
                int inputLen = encryptedData.length;
                for (int i = 0; inputLen - offSet > 0; offSet = i * rsaDecryptBlock) {
                    byte[] cache;
                    if (inputLen - offSet > rsaDecryptBlock) {
                        cache = outCipher.doFinal(encryptedData, offSet, rsaDecryptBlock);
                    } else {
                        cache = outCipher.doFinal(encryptedData, offSet, inputLen - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    ++i;
                }
                byte[] decryptedData = out.toByteArray();
                out.close();

                decryptStr = new String(decryptedData, 0, decryptedData.length, "UTF-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return decryptStr;
    }
}

(2)测试代码,获取目标内容的keystore加密结果存储本地:

    private void testEncrypt() {
        // 需要加密的字符串
        String src = "baorant";
        // 自定义alias别名
        String alias = "myKey";
        EncryptUtil.getInstance().initKeyStore(alias);
        // 加密后的字符串,可以本地存储
        String encryptedString = EncryptUtil.getInstance().encryptString(src, alias);
        Log.d("baorant", encryptedString);
        String decryptedString = EncryptUtil.getInstance().decryptString(encryptedString, alias);
        Log.d("baorant", decryptedString);
    }

运行结果如下:
在这里插入图片描述

(3)本地存储keystore加密后的内容,运行时进行解密拿出来使用即可。

    private void testDecrypt() {
        String alias = "myKey";
        String encryptedString = "s633gcoXu4fcyS6IsMg5Gi5brsvULgvF5-UIh8RFXJhpbb1rQpeaIDSVUCSQ-Qf5ErkrH9lTO2wi\n" +
                "_mo4IZo4ibk65DMw7TvGKgpDj_0YhD070EouMKExF3u2bLk2X6yt20WD4He1cxfmtYv42gM869zh\n" +
                "SRtYUy4JVe3W7I9r3i68Q3HWUPZu7381yxvStgEPIvwx49EpSXPCNJuC6MYFQNarUFkEDmLii31U\n" +
                "VRK0zIY1TVZSH27nP4qsB9ujbVZbCeKXlaPxPfY67G6BYmdXVFmk5c0CKIo0mZDYQ5NS7rz7gmM0\n" +
                "PBCt-Rdm4Xt5C5k0nAMojmQqq8o2mJBOYWVjBg==";
        String decryptedString = EncryptUtil.getInstance().decryptString(encryptedString, alias);
        Log.d("baorant", decryptedString);
    }

运行结果如下:
在这里插入图片描述

问题解决

本文初步介绍了Android基于KeyStore对数据进行加解密的基本过程和用法,用兴趣的同学可以进一步深入研究。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/692612.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络—应用层

文章目录 网络应用基本原理网络应用体系结构~三种结构网络应用进程通信网络应用需求 WEB应用HTTP协议Cookie技术WEB缓存/代理服务器技术 Email应用SMTP协议Email消息格式&#xff08;SMTP传输消息的格式&#xff09;POP3协议IMAP DNS应用DNS协议 网络应用基本原理 网络应用体系…

adb 工具

小白初如嵌入式的坑&#xff0c;对于串口调试工具&#xff0c;之前一直只知道有SecureCRT这一种方式&#xff1b; 但是在最近一次的使用过程中&#xff0c;发现抢占资源的情况出现&#xff0c;两个进程之间相互打架&#xff0c;这个时候需要 查看top&#xff08;嵌入式板子资…

Kernel panic – not syncing: Attempted to kill init! Redhat6.2故障修复

1、Linux环境 Redhat6.2&#xff0c; Linux version 2.6.32-220.el6.i686 (mockbuildx86-003.build.bos.redhat.com) (gccversion 4,4.5 20110214 (Red Hat 4.4.5-6) (GCC) ) #1 SMP Wed Nov 9 08:02:18 EST 2011 2、故障现象 修改TCP相关文件&#xff0c;重启完Linux系统之…

银河麒麟V10搭建达梦8数据库

大家好&#xff0c;我是早九晚十二&#xff0c;目前是做运维相关的工作。写博客是为了积累&#xff0c;希望大家一起进步&#xff01; 我的主页&#xff1a;早九晚十二 目前&#xff0c;国产化服务器越来越多&#xff0c;操作系统的国产化使得大家对中间件都有了需求&#xff0…

alpha值大小对lasso回归结果的影响

LASSO 回归 的特点是在拟合广义线性模型的同时进行变量筛选&#xff08;variable selection&#xff09;和复杂度调整&#xff08;regularization&#xff09;。因此不论目标因变量&#xff08;dependent/response varaible&#xff09;是连续的&#xff08;continuous&#xf…

【FFmpeg实战】视频基础和MP4容器解封装

转载自原文地址&#xff1a;https://juejin.cn/post/6901218687081480206 一、视频的编解码方式 谈到视频&#xff0c;我们经常会听到视频编解码、H.264等诸如此类的内容。那么视频编解码是什么意思呢&#xff1f; 视频编解码&#xff0c;其实就是对视频数据进行压缩和解压缩…

ros版本KF-GINS(带有rviz可视化结果显示及文件生成)

原始的KF-GINS是基于读写文件实现的&#xff0c;在此基础上改进了ros版本&#xff0c;将原始数据文件转换为rosbag格式&#xff0c;并实现了rviz下的可视化结果显示&#xff0c;代码已共享至github https://github.com/slender1031/kf-gins-ros 感谢武汉大学卫星导航定位技术研…

IT培训的背后,是“韭菜”的躺赚梦!

本文只谈"骗局"&#xff0c;不谈其他&#xff0c;绝不引战&#xff0c;如有错误&#xff0c;希望指出我会及时改正。 实际也有大量做得好的、值得肯定的培训机构&#xff0c;这里需要大家仔细鉴别。 导语 为什么要写这篇文章呢&#xff0c;近些年培训这个话题也比较…

Mhz_c1f靶场-简记

靶机 名称&#xff1a;mhz_cxf:clf download url&#xff1a;https://www.vulnhub.com/entry/mhz_cxf-c1f,471/ IP地址探测 sudo netdiscover -i eth1 -r 192.168.56.0/24 sudo netdiscover -i eth1 -r 192.168.56.0/24 Currently scanning: 192.168.56.0/24 | Screen …

ARM Trace32(劳特巴赫) 使用介绍 1 - Veloce 环境中使用 Trace32 介绍

文章目录 背景1.1 Trace 启动1.1.1 Trace32 系统相关设置 1.2 Trace 常用命令1.2.2 加在bin文件1.2.3 寄存器常用命令1.2.4 内存(寄存器)数据修改 1.3 变量相关命令 背景 Veloce是一款基于FPGA的高速仿真器&#xff0c;可用于验证芯片设计和软件开发, 它和 Trace32 都可以用于…

16.RocketMQ之消费重试以及原理

highlight: arduino-light 1.4 消费重试 对于顺序消息,当消费者消费消息失败后,消费者会在本地自动不断进行消息重试,每次间隔时间为 1 秒,重试最大值是 Integer.MAX_VALUE。 对于无序消息(普通、定时、延时、事务消息)当消费者消费消息失败时可以通过设置返回状态达到重试的目…

湿地环境监测物联网解决方案

湿地作为生态系统的关键组成部分&#xff0c;发挥着涵养水源、调节气候、改善环境、维护生物多样性等生态功能。湿地提供了独特的生境&#xff0c;为许多鸟类、鱼类和其他野生动物提供了栖息地和食物来源。此外&#xff0c;湿地还具有保持水量平衡和水质净化的重要功能&#xf…

最优控制:代数黎卡提方程ARE(Algebraic Riccati Equation)

本文介绍代数黎卡提方程的Matlab解法&#xff0c;包括直接求解和迭代求解 问题描述&#xff1a; 一、数值解法 可以看出&#xff0c;ARE方程是关于P的一个非线性方程&#xff0c;当系统矩阵维度较高时&#xff0c;难以求解&#xff0c;但是MATLAB给出了求解ARE的函数care % 系…

颜色聚合向量 Color Co-ccurrence Vector 介绍以及MATLAB代码实现

这件事情的起因是我想复习一下我在亚太杯数学建模当中使用过的CCV这种方法&#xff0c;但是CSDN平台上找了半天都没有&#xff0c;所以后来决定Google一下&#xff0c;终于找到了&#xff0c;甚至还有实现的代码&#xff0c;因此放上来。原文见Dr. Mahmoud Attia的博客。 一、…

JAVA中的伪共享与缓存行

一.伪共享与缓存行 1.CPU缓存架构 CPU 是计算机的心脏&#xff0c;所有运算和程序最终都要由它来执行。 主内存&#xff08;RAM&#xff09;是数据存放的地方&#xff0c;CPU 和主内存之间有好几级缓存&#xff0c;因为即使直接访问主内存也是非常慢的。 CPU的速度要远远大…

一图看懂CodeArts Board 5大特性,带你玩转看板服务

华为云看板服务CodeArts Board&#xff0c;通过构建研发效能度量体系&#xff0c;实现软件研发过程的可视化、软件交付的可管理可跟踪可量化&#xff0c;及时识别研发过程的堵塞点和改进点&#xff0c;通过数据驱动运营和治理&#xff0c;不断提升企业的软件能力和研发效能。

详解JAVA序列化

目录 1.什么是序列化 2.JAVA中的序列化 2.1.成员变量必须可序列化 2.2.transient关键字&#xff0c;可避免被序列化 2.3.无法更新状态 2.4.serialVersionUID 3.JDK序列化算法 4.序列化在实际中的一些应用 1.什么是序列化 序列化就是将对象转换为二进制格式的过程。对象…

Maven安装和配置详细教程

Maven安装和配置详细教程 1、Maven简介 Maven 是 Apache 软件基金会的一个开源项目,是一个优秀的项目构建工具,它用来帮助开发者管理项目中的 jar,以及 jar 之间的依赖关系、完成项目的编译、测试、打包和发布等工作。 2、Maven下载 点击Maven下载官方地址下载Maven。或者去…

postman持续集成-Jenkins自动构建

自动构建&#xff0c;就是设置一个定时器&#xff0c;定时时间到&#xff0c; Jenkins 自动执行测试用例 比如说,我设置下午五点,那么jenkins就是自动执行命令,自动生成报告,后续还可加上邮箱,会把报告发至邮箱 1. Jenkins 首页&#xff0c;点击任务名&#xff1a;如&#xff…

数据库—关系代数

传统的集合运算 在数据库中的关系代数运算有以下三种基本运算 并交差 必须满足两个表之间的属性个数必须一样。&#xff08;必须具有相容性&#xff09; 投影与选择运算 投影&#xff1a;π L _L L​( R ) 解释->π是投影符号&#xff0c;L是R表中的属性列&#xff0c;从…