【问题标题】:Possible faults in AES implementation in AndroidAndroid 中的 AES 实现中可能存在的错误
【发布时间】:2015-12-29 08:18:36
【问题描述】:

我正在尝试在 Android 中实现 AES 加密,它使用密码生成SecretKey。我通过了同样的byte[] 作为密码的初始化向量和使用 PBKDF2 生成 SecretKey 时的盐。

用户每次需要加密/解密时都会提供密码。

到目前为止,我只需要在我的数据库中加密一个值(如果这有什么不同的话)。

问题:

  1. 我想知道是否使用与 IV 相同的 byte[] 和 salt 会削弱加密?
  2. 除了 GCM 提供的数据完整性功能之外,是否还有理由从 CBC 切换到 GCM?
  3. 我了解到 CBC 容易受到 BEAST 攻击,如下所示,每条消息使用新的随机 IV 来缓解 BEAST 攻击?

当前源代码:

public class AesEncryption {
    private static final int KEY_SIZE = 16;
    private static final int OUTPUT_KEY_LENGTH = 256;
    private static final int ITERATIONS = 1000;

    private String mPassphraseOrPin;

    public AesEncryption(String passphraseOrPin) {
        mPassphraseOrPin = passphraseOrPin;
    }

    public void encrypt(String id, String textToEncrypt) throws Exception {
        byte[] iv = getIv();
        SecretKey secretKey = generateKey(iv);

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));

        byte[] cipherText = cipher.doFinal(textToEncrypt.getBytes("utf-8"));
        byte[] ivCipherText = arrayConcat(iv, cipherText);
        String encryptedText = Base64.encodeToString(ivCipherText, Base64.NO_WRAP);

        storeEncryptedTextInDb(id, encryptedText);
    }

    public String decrypt(String id) throws Exception {
        String encryptedText = getEncryptedTextFromDb(id);

        byte[] ivCipherText = Base64.decode(encryptedText, Base64.NO_WRAP);
        byte[] iv = Arrays.copyOfRange(ivCipherText, 0, KEY_SIZE);
        byte[] cipherText = Arrays.copyOfRange(ivCipherText, KEY_SIZE, ivCipherText.length);

        SecretKey secretKey = generateKey(iv);

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
        String decrypted = new String(cipher.doFinal(cipherText), "utf-8");

        return decrypted;
    }

    public SecretKey generateKey(byte[] salt) throws Exception {
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec keySpec = new PBEKeySpec(mPassphraseOrPin.toCharArray(), salt, ITERATIONS, OUTPUT_KEY_LENGTH);
        SecretKey tmp = secretKeyFactory.generateSecret(keySpec);
        return new SecretKeySpec(tmp.getEncoded(), "AES");
    }

    private byte[] getIv() {
        byte[] salt = new byte[KEY_SIZE];
        new SecureRandom().nextBytes(salt);
        return salt;
    }

    private byte[] arrayConcat(byte[] one, byte[] two) {
        byte[] combined = new byte[one.length + two.length];
        for (int i = 0; i < combined.length; ++i) {
            combined[i] = i < one.length ? one[i] : two[i - one.length];
        }
        return combined;
    }
}

【问题讨论】:

  • 我假设是本地 Android 数据库?
  • @MaartenBodewes 是的。我正在考虑将加密文本保存到 SharedPreferences 中。
  • 好的,在这种情况下,传输中的数据不会受到攻击(例如,CBC padding oracle 攻击)。不幸的是,我无法进行全面的安全审查——答案不是那样。一般来说,看看平台本身提供的保护选项是值得的。

标签: android cryptography aes cbc-mode aes-gcm


【解决方案1】:

我想知道是否使用与 IV 相同的 byte[] 和 salt 会削弱加密?

是的。

对于盐:如果你不随机化盐,那么攻击者可以预先计算一个包含密码和密码哈希的表。这被称为彩虹表。此外,如果任何人拥有相同的密码,就会产生相同的密钥。强烈建议为每个用户生成一个 salt,如果可行的话,在每次重新加密值时生成一个新的 salt。

对于 IV:如果您重新加密包含相同明文的起始块,则密文将重复块。攻击者可以使用它从中提取信息。简单的例子:加密“是”或“否”两次将明显区别于先加密“是”然后加密“否”。通常,您应该生成一个随机 IV 并将其与密文一起存储。如果盐(以及密钥)是随机的,即使也是推荐的。当然,这取决于您的威胁模型是否会对现实世界产生影响。

除了 GCM 提供的数据完整性功能之外,是否还有理由从 CBC 切换到 GCM?

GCM 提供明文的完整性和真实性。从功能上讲,它只是 CTR 模式下带有身份验证标签的 AES。如果您需要明文的完整性和真实性(可能还有其他经过验证的数据或 AAD),这取决于您的威胁模型。否则它不会添加任何功能。

如果您只是对数据保密,那么您可能不需要 GCM。如果您想保护它免受攻击者所做的更改,那么您确实需要它。但是,在这种情况下,您还需要防止重放攻击。

我了解到 CBC 容易受到 BEAST 攻击,是否使用新的随机 IV 每条消息(如下所示)来缓解 BEAST 攻击?

BEAST 攻击是针对 SSL/TLS 的基于浏览器的攻击。根据定义,它不适用于数据库加密,尤其是对于静态数据。可能会引发大量攻击,但 BEAST 依赖于 TLS 连接中的动态数据。


注意事项:

  • 基于长度的攻击经常被遗忘,因为密码/密码模式不能防御它们。它们可能仍然适用。与 CBC 相比,GCM 会泄露更多关于明文长度的信息。

  • 对于攻击者来说,查看一个值是否被重新加密也可能很有趣。

  • 1000 不再被视为安全迭代计数/工作因子。您可能想要升级它(并制定升级策略)。

【讨论】:

  • 非常感谢@Maarten 提供的信息丰富的答案。所以我应该对每个加密值使用随机盐并将其与加密文本一起存储?
  • 是的。我建议您也对版本指示器进行编码,以防您以后想要升级(例如,使用更高的工作系数)。要么派生 IV(就像密钥一样),要么将其存储在旁边,如果你有空间的话。
  • 添加版本控制似乎是个好主意。在旧三星 S3 设备上使用以下代码进行加密,工作因子为 1000,大约需要 1200 毫秒。增加工作系数有显着的好处吗?如果我增加它,我肯定会想出一些依赖于设备规格的策略,这会给已经很复杂的算法添加“额外的逻辑”。功因数是否有上限,超过上限则认为增益最小?
  • 用户和攻击者的工作因素是相同的,尽管攻击者可以使用高度针对性的系统。 PBKDF2 的功因数是线性的,因此实际上没有上限。如果您不想增加工作因素/迭代次数,您可以使用其他措施,例如确保用户提供的密码足够安全。强密码仍然是最好的防线。目前最小的迭代次数约为 40K,但我可以看到如此高的迭代次数在 Android 设备上可能是个问题。最大重试次数也很好,但不适用于静态数据。
猜你喜欢
  • 2016-07-02
  • 1970-01-01
  • 1970-01-01
  • 2019-04-18
  • 2013-10-11
  • 2019-12-22
  • 2012-01-21
  • 2018-12-31
  • 2016-07-30
相关资源
最近更新 更多