SMTPサーバーにアクセスするための認証情報データをAndroid app?これは安全ではありません。アプリケーションを逆コンパイルすることで確認できるからです。
Androidキーストアシステムをこの目的と方法で使用することは可能ですか?そして最も重要なことは、Androidキーストアはroot化されたデバイスで効果的ですか?
Androidアプリケーションでは、SharedPreferencesにデータを保存できますが、このデータは実際にはファイルに保存されているため、電話へのルートアクセス権を持つすべての人がアクセスできます。つまり、必要に応じてセキュリティリークが発生します。資格情報またはその他の機密データを保存します。
他の人がこのデータをプレーンテキストで見ないようにするための解決策は、データを保存する前に暗号化することです。 API18からAndroidは、データを暗号化および復号化するキーを格納できるKeyStoreを導入しました。
API 23までの問題は、AESキーをKeyStoreに保存できなかったため、暗号化に最も信頼できるキーは、秘密キーと公開キーを使用したRSAでした。
だから私が思いついた解決策は:
23未満のAPIの場合
API 23以降の場合
また、暗号化用に生成されたIVを追加しました。
コード:
public class KeyHelper{
private static final String RSA_MODE = "RSA/ECB/PKCS1Padding";
private static final String AES_MODE_M = "AES/GCM/NoPadding";
private static final String KEY_ALIAS = "KEY";
private static final String AndroidKeyStore = "AndroidKeyStore";
public static final String SHARED_PREFENCE_NAME = "SAVED_TO_SHARED";
public static final String ENCRYPTED_KEY = "ENCRYPTED_KEY";
public static final String PUBLIC_IV = "PUBLIC_IV";
private KeyStore keyStore;
private static KeyHelper keyHelper;
public static KeyHelper getInstance(Context ctx){
if(keyHelper == null){
try{
keyHelper = new KeyHelper(ctx);
} catch (NoSuchPaddingException | NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | KeyStoreException | CertificateException | IOException e){
e.printStackTrace();
}
}
return keyHelper;
}
public KeyHelper(Context ctx) throws NoSuchPaddingException,NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException, CertificateException, IOException {
this.generateEncryptKey(ctx);
this.generateRandomIV(ctx);
if(Android.os.Build.VERSION.SDK_INT < Android.os.Build.VERSION_CODES.M){
try{
this.generateAESKey(ctx);
} catch(Exception e){
e.printStackTrace();
}
}
}
private void generateEncryptKey(Context ctx) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException, CertificateException, IOException {
keyStore = KeyStore.getInstance(AndroidKeyStore);
keyStore.load(null);
if(Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.M){
if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore);
keyGenerator.init(
new KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false)
.build());
keyGenerator.generateKey();
}
} else{
if (!keyStore.containsAlias(KEY_ALIAS)) {
// Generate a key pair for encryption
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 30);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx)
.setAlias(KEY_ALIAS)
.setSubject(new X500Principal("CN=" + KEY_ALIAS))
.setSerialNumber(BigInteger.TEN)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.build();
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, AndroidKeyStore);
kpg.initialize(spec);
kpg.generateKeyPair();
}
}
}
private byte[] rsaEncrypt(byte[] secret) throws Exception{
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
// Encrypt the text
Cipher inputCipher = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL");
inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inputCipher);
cipherOutputStream.write(secret);
cipherOutputStream.close();
return outputStream.toByteArray();
}
private byte[] rsaDecrypt(byte[] encrypted) throws Exception {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(KEY_ALIAS, null);
Cipher output = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL");
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());
CipherInputStream cipherInputStream = new CipherInputStream(
new ByteArrayInputStream(encrypted), output);
ArrayList<Byte> values = new ArrayList<>();
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
values.add((byte)nextByte);
}
byte[] bytes = new byte[values.size()];
for(int i = 0; i < bytes.length; i++) {
bytes[i] = values.get(i).byteValue();
}
return bytes;
}
private void generateAESKey(Context context) throws Exception{
SharedPreferences pref = context.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
String enryptedKeyB64 = pref.getString(ENCRYPTED_KEY, null);
if (enryptedKeyB64 == null) {
byte[] key = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(key);
byte[] encryptedKey = rsaEncrypt(key);
enryptedKeyB64 = Base64.encodeToString(encryptedKey, Base64.DEFAULT);
SharedPreferences.Editor edit = pref.edit();
edit.putString(ENCRYPTED_KEY, enryptedKeyB64);
edit.apply();
}
}
private Key getAESKeyFromKS() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException{
keyStore = KeyStore.getInstance(AndroidKeyStore);
keyStore.load(null);
SecretKey key = (SecretKey)keyStore.getKey(KEY_ALIAS,null);
return key;
}
private Key getSecretKey(Context context) throws Exception{
SharedPreferences pref = context.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
String enryptedKeyB64 = pref.getString(ENCRYPTED_KEY, null);
byte[] encryptedKey = Base64.decode(enryptedKeyB64, Base64.DEFAULT);
byte[] key = rsaDecrypt(encryptedKey);
return new SecretKeySpec(key, "AES");
}
public String encrypt(Context context, String input) throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
Cipher c;
SharedPreferences pref = context.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
String publicIV = pref.getString(PUBLIC_IV, null);
if(Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.M){
c = Cipher.getInstance(AES_MODE_M);
try{
c.init(Cipher.ENCRYPT_MODE, getAESKeyFromKS(), new GCMParameterSpec(128,Base64.decode(publicIV, Base64.DEFAULT)));
} catch(Exception e){
e.printStackTrace();
}
} else{
c = Cipher.getInstance(AES_MODE_M);
try{
c.init(Cipher.ENCRYPT_MODE, getSecretKey(context),new GCMParameterSpec(128,Base64.decode(publicIV, Base64.DEFAULT)));
} catch (Exception e){
e.printStackTrace();
}
}
byte[] encodedBytes = c.doFinal(input.getBytes("UTF-8"));
return Base64.encodeToString(encodedBytes, Base64.DEFAULT);
}
public String decrypt(Context context, String encrypted) throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
Cipher c;
SharedPreferences pref = context.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
String publicIV = pref.getString(PUBLIC_IV, null);
if(Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.M){
c = Cipher.getInstance(AES_MODE_M);
try{
c.init(Cipher.DECRYPT_MODE, getAESKeyFromKS(), new GCMParameterSpec(128,Base64.decode(publicIV, Base64.DEFAULT)));
} catch(Exception e){
e.printStackTrace();
}
} else{
c = Cipher.getInstance(AES_MODE_M);
try{
c.init(Cipher.DECRYPT_MODE, getSecretKey(context), new GCMParameterSpec(128,Base64.decode(publicIV, Base64.DEFAULT)));
} catch (Exception e){
e.printStackTrace();
}
}
byte[] decodedValue = Base64.decode(encrypted.getBytes("UTF-8"), Base64.DEFAULT);
byte[] decryptedVal = c.doFinal(decodedValue);
return new String(decryptedVal);
}
public void generateRandomIV(Context ctx){
SharedPreferences pref = ctx.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
String publicIV = pref.getString(PUBLIC_IV, null);
if(publicIV == null){
SecureRandom random = new SecureRandom();
byte[] generated = random.generateSeed(12);
String generatedIVstr = Base64.encodeToString(generated, Base64.DEFAULT);
SharedPreferences.Editor edit = pref.edit();
edit.putString(PUBLIC_IV_PERSONAL, generatedIVstr);
edit.apply();
}
}
private String getStringFromSharedPrefs(String key, Context ctx){
SharedPreferences prefs = ctx.getSharedPreferences(MyConstants.APP_SHAREDPREFS, 0);
return prefs.getString(key, null);
}
}
注:これはAPI 18以降のみです
ルート権限を取得されたデバイスのセキュリティに関する質問については、次の論文をお勧めします。
SMTP認証情報を暗号化し、暗号化された値をアプリケーションスペースにローカルに保存できます(共有設定など)。暗号化に使用されるキーは、キーストアに保存できます。
詳細については、以下を参照してください。 Android KeyStoreを使用して任意の文字列を安全に保存するにはどうすればよいですか?