首页 > 解决方案 > Android 应用安全问题:Android API 级别 24 及以上的凭据存储用户名密码未加密

问题描述

为在此问题的早期描述中对细节的轻描淡写而道歉。这是更详细的问题:

我们的 Android 应用程序未能通过安全扫描,因为它使用 ECB 密码的高级加密标准 (AES) 加密算法加密用户名和密码并在本地存储。Sonarcube 参考声明我们需要使用没有填充的 Galois/Counter Mode (GCM)。但是,这不适用于 API 级别 24 及更高级别。

我们正在尝试使用...解决此问题

Cipher.getInstance(“AES/GCM/NoPadding”);

...但在 API 级别 24 及更高级别没有运气。加密没有发生。

更多详情:

我们在 App Login 期间使用 keystore 对用户 ID 和密码进行加密,这可以正常工作。但是,当我们在应用程序的另一部分对其进行解密时,我们会得到AEADBadTagException。

这是加密的代码(这很好用):

    @RequiresApi(api = Build.VERSION_CODES.M)
    private void encryptText() {
    try {
        final byte[] encryptedText = encryptor
                .encryptText("MY_ALIAS",mPsd.toString());
    } catch (UnrecoverableEntryException | NoSuchAlgorithmException | NoSuchProviderException | KeyStoreException | IOException | NoSuchPaddingException | InvalidKeyException e) {
    } catch (InvalidAlgorithmParameterException | SignatureException | IllegalBlockSizeException | BadPaddingException e) {
        e.printStackTrace();
    }
}

这是加密器类(这似乎也可以正常工作):

public class EnCryptor {
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";

private byte[] encryption;
private byte[] iv;

public EnCryptor() {
}
@RequiresApi(api = Build.VERSION_CODES.M)
public byte[] encryptText(final String alias, final String textToEncrypt)
        throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException,
        NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException,
        InvalidAlgorithmParameterException, SignatureException, BadPaddingException,
        IllegalBlockSizeException {

    final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
    cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(alias));

    iv = cipher.getIV();
    encryption = cipher.doFinal(textToEncrypt.getBytes("UTF-8"));
    SharedPreferencesManager.getInstance().saveEncrypt(Base64.encodeToString(encryption, Base64.DEFAULT));
    SharedPreferencesManager.getInstance().saveEncrypted_iv(Base64.encodeToString(iv, Base64.DEFAULT));
    return (encryption);

}

@RequiresApi(api = Build.VERSION_CODES.M)
@NonNull
private SecretKey getSecretKey(final String alias) throws NoSuchAlgorithmException,
        NoSuchProviderException, InvalidAlgorithmParameterException {

    final KeyGenerator keyGenerator = KeyGenerator
            .getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
    keyGenerator.init(new KeyGenParameterSpec.Builder(alias,
            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .build());
    return keyGenerator.generateKey();
}
public byte[] getEncryption() {
    return encryption;
}
public byte[] getIv() {
    return iv;
}
} 

但是,稍后在应用程序的另一部分尝试解密密码时,我们会遇到错误。

来自 Fragment 的方法调用:

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private char[] decryptText() {
    try {
        String txt = decryptor.decryptData("MY_ALIAS",
                Base64.decode(SharedPreferencesManager.getInstance().getEncrypt(), Base64.DEFAULT),
                Base64.decode(SharedPreferencesManager.getInstance().getEncrypted_iv(), Base64.DEFAULT));
        return txt.toCharArray();
    } catch (UnrecoverableEntryException | NoSuchAlgorithmException |
            KeyStoreException | NoSuchPaddingException | NoSuchProviderException |
            IOException | InvalidKeyException e) {
    } catch (IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }
    return "".toCharArray();
}

这是引发异常的 Decryptor 类

public class DeCryptor {

private static final String TRANSFORMATION = "AES/GCM/NoPadding";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private KeyStore keyStore;
public DeCryptor() throws CertificateException, NoSuchAlgorithmException, KeyStoreException,
        IOException {
    initKeyStore();
}
private void initKeyStore() throws KeyStoreException, CertificateException,
        NoSuchAlgorithmException, IOException {
    keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
    try {
        keyStore.load(null);
    } catch (java.security.cert.CertificateException e) {
        e.printStackTrace();
    }
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public String decryptData(final String alias, final byte[] encryptedData, final byte[] encryptionIv)
        throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException,
        NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException,
        BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {

    final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
    final GCMParameterSpec spec = new GCMParameterSpec(128, encryptionIv);
    cipher.init(Cipher.DECRYPT_MODE, getSecretKey(alias), spec);
    return new String(cipher.doFinal(encryptedData), "UTF-8");
}
private SecretKey getSecretKey(final String alias) throws NoSuchAlgorithmException,
        UnrecoverableEntryException, KeyStoreException {
    return ((KeyStore.SecretKeyEntry) keyStore.getEntry(alias, null)).getSecretKey();
}
}

这是我们遇到AEADBadTagException 的地方。

标签: androidsecurityencryptionpassword-encryption

解决方案


使用以下行进行解密:

IvParameterSpec ivSpec = new IvParameterSpec(encryptionIv)

而不是

final GCMParameterSpec spec = new GCMParameterSpec(128, encryptionIv)

推荐阅读