Android development encrypts and decrypts data based on KeyStore

Question background

During our App development process, some sensitive and secure data may need to be encrypted, such as the storage of login tokens. We often use some encryption algorithms to encrypt these sensitive data and then save them, and then decrypt them when they need to be taken out.
At this point, there will be a question: how should the Key used for encryption and decryption be stored?
In order to ensure security, Android provides the KeyStore system to save the Key. This article will briefly explore the KeyStore and its usage.

Problem Analysis

Not much to say, directly on the code:
1. Test the encryption and decryption process
(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;

/**
 * Local encryption and decryption method based on KeyStore
 */
public class EncryptUtil {
    private static final String TAG = "EncryptUtil";
    private static EncryptUtil encryptUtilInstance;

    private KeyStore keyStore;

    private Context context;
    // unit year
    private final int maxExpiredTime = 1000;

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

    // RSA has a limit on the length of encrypted characters, so segmental encryption is required
    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(
                                aliases,
                                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();
        }
    }

    /**
     * encryption method
     *
     * @param needEncryptWord The string that needs to be encrypted
     * @param alias Encryption key
     * @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;
    }


    /**
     * Decryption method
     *
     * @param needDecryptWord The string that needs to be decrypted
     * @param alias key's alias
     * @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) Test the code to obtain the keystore encryption result of the target content and store it locally:

 private void testEncrypt() {
        // string to be encrypted
        String src = "baorant";
        // custom alias alias
        String alias = "myKey";
        EncryptUtil.getInstance().initKeyStore(alias);
        // The encrypted string can be stored locally
        String encryptedString = EncryptUtil.getInstance().encryptString(src, alias);
        Log.d("baorant", encryptedString);
        String decryptedString = EncryptUtil.getInstance().decryptString(encryptedString, alias);
        Log.d("baorant", decryptedString);
    }

The result of the operation is as follows:

(3) Store the encrypted content of the keystore locally, and decrypt it at runtime to use it.

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

The result of the operation is as follows:

Problem Solved

This article initially introduces the basic process and usage of Android’s encryption and decryption of data based on KeyStore, and students who are interested can further study it.