我们如何安全地存储凭据数据以访问Android应用中的smtp-server?这些数据是常量,只有开发人员应该知道它们。目前它们存储在代码中,但这不安全,因为可以通过反编译来看到它们。
是否可以将Android Keystore System用于此目的以及如何使用?最重要的是,Android Keystore在root设备上是否有效?
答案 0 :(得分:8)
在Android应用程序中,您可以将数据存储在SharedPreferences中,但由于此数据实际存储在文件中,因此任何对手机具有root访问权限的人都可以访问它。这意味着如果要存储凭据或任何其他敏感数据,则会发生安全漏洞。
为了避免其他人以纯文本格式查看此数据,解决方案是在存储数据之前加密数据。从API 18开始,Android推出了KeyStore,它能够存储用于加密和解密数据的密钥。
问题直到API 23导致您无法在KeyStore中存储AES密钥,因此最可靠的加密密钥是使用私钥和公钥的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及更高版本
答案 1 :(得分:1)
关于有关root设备安全性的问题,我建议您使用以下文件:
答案 2 :(得分:0)
您可以加密smtp凭据并在应用程序空间中本地存储加密值(例如,在共享首选项中)。用于加密的密钥可以存储在密钥存储区中。
有关详细信息,请参阅:How Can I Use the Android KeyStore to securely store arbitrary strings?