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);
サンプルコード
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;
}
}
追記 (2020-02-26)
Jetpack Securityを使えば手軽にShared preferenceやFileなどのデータを暗号化できる環境が整いました!
https://android-developers.googleblog.com/2020/02/data-encryption-on-android-with-jetpack.html