Help us understand the problem. What is going on with this article?

Android Keystoreを使って秘匿情報を保持する

Androidのアプリで、tokenやユーザの個人情報などの秘匿情報をセキュアに保持するために、Android Keystore を使って暗号化してShared preferenceやsqliteに保持する簡単なコードサンプル。

Android Keystore System

Android Keystore Systemを使って情報を暗号化して保持する手順は以下のようになる。

Android Keystoreのロード

  mKeyStore = KeyStore.getInstance("AndroidKeyStore");
  mKeyStore.load(null);

鍵ペアの生成

鍵ペアがなければKeyPairGeneratorを使って新しくpublid/private keyを生成する。

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_RSA, KEY_PROVIDER);
keyPairGenerator.initialize(
    new KeyGenParameterSpec.Builder(...) /* key parameters */
    .build());
keyPairGenerator.generateKeyPair();

暗号化

public keyで暗号化したデータを保持する(サンプルコードでは暗号化と同時にBase64 encodeしている)。
CipherOutputStreamに暗号データが出力される。

PublicKey publicKey = keyStore.getCertificate(alias).getPublicKey();

Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(
    outputStream, cipher);
cipherOutputStream.write(plainText.getBytes("UTF-8"));
cipherOutputStream.close();

復号

保持したデータはprivate keyで復号して利用する。CipherInputStreamから復号データを読み出すことができる。

PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);

Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
CipherInputStream cipherInputStream = new CipherInputStream(
    new ByteArrayInputStream(Base64.decode(encryptedText, Base64.DEFAULT)), cipher);

サンプルコード

MainActivity.java
package jp.gr.java_conf.fofn.sample.keystoreprovider;

import android.app.Activity;
import android.os.Bundle;

import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;

public class MainActivity extends Activity {
    private static final String TAG = "KeyStoreProviderSample";

    private static final String KEY_PROVIDER = "AndroidKeyStore";
    private static final String KEY_ALIAS = "sample key";
    private static final String ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";

    private KeyStore mKeyStore = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        prepareKeyStore();

        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                TextView view;
                view = (TextView)findViewById(R.id.text);
                String plainText = view.getText().toString();

                // encrypt
                String encryptedText = encryptString(mKeyStore, KEY_ALIAS, plainText);
                view = (TextView)findViewById(R.id.enc);
                view.setText(encryptedText);

                // decrypt
                String decryptedText = decryptString(mKeyStore, KEY_ALIAS, encryptedText);
                view = (TextView)findViewById(R.id.dec);
                view.setText(decryptedText);
            }
        });
    }

    private void prepareKeyStore() {
        try {
            mKeyStore = KeyStore.getInstance("AndroidKeyStore");
            mKeyStore.load(null);
            createNewKey(mKeyStore, KEY_ALIAS);
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    /**
     * Create new key pair if needed.
     *
     * Create RSA key pair for encryption/decryption using RSA OAEP.
     * See KeyGenParameterSpec document.
     *
     * @param keyStore key store
     * @param alias key alias
     */
    private void createNewKey(KeyStore keyStore, String alias) {
        try {
            // Create new key if needed
            if (!keyStore.containsAlias(alias)) {
                KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_RSA, KEY_PROVIDER);
                keyPairGenerator.initialize(
                    new KeyGenParameterSpec.Builder(
                        alias,
                        KeyProperties.PURPOSE_DECRYPT)
                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                        .build());
                keyPairGenerator.generateKeyPair();
            }
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    /**
     * Encrypt string text
     *
     * @param keyStore key store used
     * @param alias key alias
     * @param plainText string to be encrypted
     *
     * @return base64 encoded cipher text
     */
    private String encryptString(KeyStore keyStore, String alias, String plainText) {
        String encryptedText = null;
        try {
            PublicKey publicKey = keyStore.getCertificate(alias).getPublicKey();

            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            CipherOutputStream cipherOutputStream = new CipherOutputStream(
                outputStream, cipher);
            cipherOutputStream.write(plainText.getBytes("UTF-8"));
            cipherOutputStream.close();

            byte [] bytes = outputStream.toByteArray();
            encryptedText = Base64.encodeToString(bytes, Base64.DEFAULT);
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
        return encryptedText;
    }

    /**
     * Decrypt base64 encoded cipher text
     *
     * @param keyStore key store used
     * @param alias key alias
     * @param encryptedText base64 encoded cipher text
     *
     * @return plain text string
     */
    private String decryptString(KeyStore keyStore, String alias, String encryptedText) {
        String plainText = null;
        try {
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);

            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            CipherInputStream cipherInputStream = new CipherInputStream(
                new ByteArrayInputStream(Base64.decode(encryptedText, Base64.DEFAULT)), cipher);

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            int b;
            while ((b = cipherInputStream.read()) != -1) {
                outputStream.write(b);
            }
            outputStream.close();
            plainText = outputStream.toString("UTF-8");
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
        return plainText;
    }
}

実行中のScreenshot:
screen_1.png

追記 (2020-02-26)

Jetpack Securityを使えば手軽にShared preferenceやFileなどのデータを暗号化できる環境が整いました!
https://android-developers.googleblog.com/2020/02/data-encryption-on-android-with-jetpack.html

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away