LoginSignup
172
163

More than 3 years have passed since last update.

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

Last updated at Posted at 2016-01-26

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

172
163
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
172
163