首页 > 其他分享 >Android基于KeyStore对数据进行加解密

Android基于KeyStore对数据进行加解密

时间:2023-06-19 23:00:46浏览次数:52  
标签:KeyStore String getInstance 加解密 alias offSet import Android EncryptUtil

问题背景

在我们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);
    }

运行结果如下: WCS79IRP~L154{5_T4}M~X3.png (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);
    }

运行结果如下: ![PZZE81@OAXZ96L}MAIW2G8.png

问题解决

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

标签:KeyStore,String,getInstance,加解密,alias,offSet,import,Android,EncryptUtil
From: https://blog.51cto.com/baorant24/6518079

相关文章

  • Android面试涨薪攻略指南:Android面试必知必会Java知识点
    前言大多数面试者,虽然看起来工作努力,但他们表现出来的能力水平,却不足以通过面试,或拿到期望的薪资。在我看来,造成这种情况的原因,主要有这么两方面:第一,“知其然不知其所以然”。做了几年技术,开发了一些业务应用,但没有思考过这些技术选择背后的逻辑。所以,公司很难定位你日后的成长潜力......
  • Android大厂面试题以及答案整理(2022年2月份更新),助你轻松拿下高薪offer
    前言想必现在有许多朋友,都在为即将到来的金三银四做准备,不知道各位朋友是否十足的把握能拿到自己心仪的Offer呢?下面无偿分享一份包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目,熟悉本文中列出的知识点会大大增加通过前两轮......
  • 字节跳动Android开发高工面试:海量Android大厂高频面试题精编解析
    前言最近跟我的一些读者交流,有一位读者的经历让我记忆深刻:“有一次和大学同学聚会,和几个在BAT的同学聊了聊技术,发现自己在创业公司这几年,完全是吃老本的状态,没有什么机会精进技术,同样是工作了三年,和同学的差距越来越大”我继续问他,他说真正让他受打击的是这个月的一次面试。“面的......
  • 一封给Android开发者 UI 自动化测试上手指南
    介绍Android测试支持库包含UI自动化模块,它可以对Android应用进行自动黑盒测试。在APILevel18中引入了自动化模块,它允许开发者在组成应用UI的控件上模仿用户行为。在这个教程中,将展示如何使用此模块来创建和执行一个基本的UI测试,选择默认的计算器模块进行测试。先决条件在使用前,需......
  • 你曾遇到的某大厂奇葩问题:Android组件化开发,组件间的Activity页面跳转
    组件化开发有什么好处?1、当项目越来越大时,app的业务越来越复杂,会出现业务功能复杂混乱,各功能块、页面相互依赖,相互调用太多导致耦合度高,而采用组件化开发,我们就可以将功能模块合理的划分,降低功能耦合度。2、不采用组件化开发时,编译速度缓慢,修改一个页面布局编译一下还得等几分钟。......
  • Android面试题2022最新整理(共计4176页PDF)包含腾讯、字节、百度、小米、阿里等大厂面试
    前言最近在准备面试,然后复习下之前写过的项目,书籍,笔记,文章。一看很多知识点都没有印象,最可拍的是连自己为了防止忘记写的文章竟然都感觉不是自己写的。有些开始怀疑人生了。好了,废话少说,现在是求职高峰期,我把我收集到的资料分享给大家。也祝到家有个好工作。(本文资料适合1-3年)从......
  • 墙裂推荐,Android 开发百大框架源码精编解析
    为什么要读源码?源码也是目前大厂面试比较喜欢问的,研究过源码要从广度和深度去挖掘。为什么要进行源码分析。其中包括下面一些好处:学习Android源码有助于我们学习其中的设计模式、思想、架构。熟悉整个源码的架构,有助于我们更加正确地调用Android提供的SDK,写出高效正确的代码。学......
  • Android 代码优化:“这个需求很简单,怎么实现我不管”
    背景before:在我们APP启动过程,我们可能常常有这样的需求:在APP第一次进入的时候根据网络请求的结果弹一个对话框式的广告,ok~很简单,那么代码大致就是这样(这边都是伪代码,为了方便理解):@OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState){......
  • 十年老司机帮你整理最全Android中需要知道的Java集合框架
    前言子曰:温故而知新,可以为师矣。做android已经有好一段时间了,今天突然看到代码中写的各种用来存储数据的ArrayList、管理Activity的LinkedList、用来Retrofit请求数据时多个参数拼接的HashMap。也许使用已经成为了一种习惯,可是使用他的理由又开始在脑海中慢慢淡化了,故写一篇文章来......
  • Android-Kotlin-印章类
    上一篇博客介绍了,Android-Kotlin-枚举enum;由于枚举和印章类有相似之处,所以两者对比一下:Kotlin的枚举,重点区分的数据本身Kotlin的印章类,重点区分的是数据类型(类)枚举类的定义:packagecn.kotlin.kotlin_oop09/***定义人的性别枚举类*/enumclassMyEnumPersonSex{......