zoukankan      html  css  js  c++  java
  • 一手遮天 Android

    项目地址 https://github.com/webabcd/AndroidDemo
    作者 webabcd

    一手遮天 Android - 常用工具: 在 KeyStore 中保存秘钥

    示例如下:

    /utils/Demo1.java

    /**
     * 在 KeyStore 中保存秘钥(你不必再关心秘钥如何保存,以及如何保证秘钥的安全了),秘钥是无法导出的(所以很安全),app 卸载后秘钥会被自动清除
     */
    
    package com.webabcd.androiddemo.utils;
    
    import androidx.annotation.RequiresApi;
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Build;
    import android.os.Bundle;
    import android.security.keystore.KeyGenParameterSpec;
    import android.security.keystore.KeyProperties;
    import android.widget.TextView;
    
    import com.webabcd.androiddemo.R;
    
    import java.security.KeyStore;
    
    import javax.crypto.Cipher;
    import javax.crypto.KeyGenerator;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.GCMParameterSpec;
    
    @RequiresApi(api = Build.VERSION_CODES.M)
    public class Demo1 extends AppCompatActivity {
    
        private String _keyStoreProvider = "AndroidKeyStore"; // 固定值(在 KeyStore 中保存秘钥)
        private String _keyAlias = "myKey"; // 秘钥的别名
    
        private TextView _textView1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_utils_demo1);
    
            _textView1 = findViewById(R.id.textView1);
    
            // 我这个例子主要是借用 KeyStore 生成一个 16 字节的数据(这样做的意义就是,由于我拿不到 KeyStore 中的秘钥的值,所以我可以用这个 16 字节的结果当做秘钥,其也是很安全的)
            try {
                // 加密一个 3 字节的数据,指定 gcm 标签长度为 104,所以会得到一个 16 字节的结果
                String xxx = "abc";
    
                byte[] resultBytes = encrypt(xxx.getBytes("UTF-8"));
                String resultString = Helper.bytesToHexString(resultBytes);
                _textView1.append("加密后的数据:" + resultString);
                _textView1.append("
    ");
    
                resultBytes = decrypt(resultBytes);
                resultString = Helper.bytesToString(resultBytes);
                _textView1.append("解密后的数据:" + resultString);
    
            } catch (Exception ex) {
                _textView1.setText(ex.toString());
            }
        }
    
        // 加密
        private byte[] encrypt(byte[] input) {
            try {
                byte[] iv = new byte[12];
                for (int i = 0; i < iv.length; i ++)
                {
                    iv[i] = (byte)(100);
                }
    
                Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
                // 第 1 个参数用于指定 gcm 的标签长度(可能的值有:96, 104, 112, 120, 128)
                //   我这里指定了标签长度为 104 也就是说如果加密空字节数据,则加密结果为 13 个字节,如果加密 3 个字节的数据,则加密结果为 16 个字节
                // 第 2 个参数用于指定一个 12 字节的 iv,默认是不支持手动指定的,每次都随机生成,通过 cipher.getIV() 获取
                //   a) 如果你手动指定了 iv 则会收到 Caller-provided IV not permitted 错误
                //   b) 如果非要手动指定 iv 的话,则在构造 KeyGenParameterSpec 对象的时候要加上 setRandomizedEncryptionRequired(false)
                GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(104, iv);
                cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(), gcmParameterSpec);
                byte[] output = cipher.doFinal(input);
    
                return output;
            } catch (Exception ex) {
                _textView1.setText(ex.toString());
                return null;
            }
        }
    
        // 解密
        private byte[] decrypt(byte[] input) {
            try {
                byte[] iv = new byte[12];
                for (int i = 0; i < iv.length; i ++)
                {
                    iv[i] = (byte)(100);
                }
    
                Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
                GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(104, iv);
                cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), gcmParameterSpec);
                byte[] output = cipher.doFinal(input);
    
                return output;
            } catch (Exception ex) {
                _textView1.setText(ex.toString());
                return null;
            }
        }
    
        // 从 KeyStore 中获取指定别名的 SecretKey,如果没有则新生成一个
        private SecretKey getSecretKey() {
            SecretKey secretKey = loadSecretKey();
            if (secretKey == null) {
                secretKey = generateSecretKey();
            }
            return secretKey;
        }
    
        // 在 KeyStore 中生成指定别名的 SecretKey
        private SecretKey generateSecretKey() {
            try {
                KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, _keyStoreProvider);
                KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(_keyAlias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                        .setRandomizedEncryptionRequired(false) // 加密的时候需要支持手动指定 iv(默认是不支持的)
                        .build();
                keyGenerator.init(keyGenParameterSpec);
                SecretKey secretKey = keyGenerator.generateKey();
    
                // 在 KeyStore 中保存的秘钥是无法拿到它的具体值的,通过如下方式获取只会返回 null
                // byte[] keyData = secretKey.getEncoded();
    
                return secretKey;
            } catch (Exception ex) {
                _textView1.setText(ex.toString());
                return null;
            }
        }
    
        // 加载 KeyStore 中的指定别名的 SecretKey
        private SecretKey loadSecretKey() {
            try {
                KeyStore keyStore = KeyStore.getInstance(_keyStoreProvider);
                keyStore.load(null);
    
                if (!keyStore.containsAlias(_keyAlias)) {
                    return null;
                }
    
                KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry)keyStore.getEntry(_keyAlias, null);
                SecretKey secretKey = secretKeyEntry.getSecretKey();
    
                // 在 KeyStore 中保存的秘钥是无法拿到它的具体值的,通过如下方式获取只会返回 null
                // byte[] keyData = secretKey.getEncoded();
    
                return secretKey;
            } catch (Exception ex) {
                _textView1.setText(ex.toString());
                return null;
            }
        }
    }
    

    /layout/activity_utils_demo1.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
    </LinearLayout>
    

    项目地址 https://github.com/webabcd/AndroidDemo
    作者 webabcd

  • 相关阅读:
    整理诗稿有感
    穿越校园有感
    晚饭后独自散步有感
    漫步锦里有感
    世界经理人: 三个重要法则让你彻底改变!
    看艺人名字作诗有感
    如果你不想成为默默无闻的人,那么规划生涯
    技术人员PK管理人员的博弈论
    英雄气概
    与君相识天涯有感
  • 原文地址:https://www.cnblogs.com/webabcd/p/android_utils_Demo1.html
Copyright © 2011-2022 走看看