Edited at

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

More than 3 years have passed since last update.

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