【问题标题】:Difference between IvParameterSpec and GCMParameterSpec with AES/GCM/NoPaddingIvParameterSpec 和 GCMParameterSpec 与 AES/GCM/NoPadding 的区别
【发布时间】:2018-09-20 04:31:26
【问题描述】:

我正在使用AES/GCM/NoPadding 算法加密 Android 上的一些数据(API 19 及更高版本),然后再将其解密。

我使用的密钥大小是 32 字节,是提供给我的

除了加密之外,我还想知道我何时尝试解密并使用了错误的密钥。这就是为什么我更喜欢使用 GCM 作为我的模式来获得验证完整性的好处(我相信可以安全地假设密文或密钥有问题是否会导致错误的解密异常而不是乱码)

我面临的问题是,在 Android API 19 上,使用上述算法并使用 GCMParameterSpec 初始化密码,我得到一个 NoSuchAlgorithmException,我自己没有指定任何提供程序,允许 Android 为我选择一个可以支持我的算法。在 21+ 上,算法可用。 这就是我的初始化方式(解密类似),整个类都贴在这篇文章的末尾。

cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BIT, iv));

但是,如果我使用IvParameterSpec(iv) 作为我的AlgorithmParameters 而不是GCMParameterSpec,那么代码可以正常工作。

那么通过更改这些参数会发生什么?我还能获得 GCM 的所有相同好处吗?

因为尝试使用错误的键时抛出的异常是不同的。在 API 19 上,BadPaddingException 在使用 IvParameterSpec 时抛出,在 API 21+ 上,AEADBADTagException 在使用 GCMParameterSpec 时抛出。

在所有 Android API 级别中仅使用 IvParameterSpec 并通过 BadPaddingException 验证完整性是否正确且安全?我不想针对不同的平台有不同的实现,所以我只想使用一个。

另外,在 API 21+ 上,如果我使用 GCMParameterSpec 加密,然后使用 IvParameterSpec 解密它就可以解密!反之亦然。效果如何?

如果在 API 19 上无法实现上述操作,那么我有哪些可能的选项可用作加密算法和使用策略(AES/CBC/PKCS5Padding with HMAC?)来验证密钥的完整性。

全类代码:

import android.util.Base64;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

final class Encryption {
    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH_BIT = 128;
    private static final int IV_LENGTH_BYTE = 12;

    private final SecureRandom secureRandom;
    private Cipher cipher;
    private final Charset charset = StandardCharsets.UTF_8;

    public Encryption() {
        secureRandom = new SecureRandom();
    }

    public String encrypt(byte[] key, String rawData) throws Exception {
        try {
            byte[] iv = new byte[IV_LENGTH_BYTE];
            secureRandom.nextBytes(iv);

            cipher = Cipher.getInstance(ALGORITHM);
            //This is where I switch to IvParameterSpec(iv)
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BIT, iv));

            byte[] encrypted = cipher.doFinal(rawData.getBytes(charset));

            ByteBuffer byteBuffer = ByteBuffer.allocate(1 + iv.length + encrypted.length);
            byteBuffer.put((byte) iv.length);
            byteBuffer.put(iv);
            byteBuffer.put(encrypted);
            return Base64.encodeToString(byteBuffer.array(), Base64.NO_WRAP);
        } catch (Exception e) { //ignore this SO
            throw new Exception(e);
        }
    }


    public String decrypt(byte[] key, String encryptedData) throws Exception {
        try {
            ByteBuffer byteBuffer = ByteBuffer.wrap(Base64.decode(encryptedData, Base64.NO_WRAP));

            int ivLength = byteBuffer.get();
            byte[] iv = new byte[ivLength];
            byteBuffer.get(iv);
            byte[] encrypted = new byte[byteBuffer.remaining()];
            byteBuffer.get(encrypted);

            cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BIT, iv));
            byte[] decrypted = cipher.doFinal(encrypted);

            //Paranoia
            Arrays.fill(iv, (byte) 0);
            Arrays.fill(rawEncryptionKey, (byte) 0);
            Arrays.fill(encrypted, (byte) 0);

            return new String(decrypted, charset);
        } catch (Exception e) { //ignore this SO
            // On API 19 BadPaddingException is thrown when IvParameterSpec is used
            // On API 21+ AEADBADTagException is thrown
            throw new Exception("could not decrypt", e);
        }
    }
}

另外,请随时提出改进所提供课程的建议以及您的答案,谢谢。

【问题讨论】:

    标签: java android encryption cryptography aes-gcm


    【解决方案1】:

    我还想知道我何时尝试解密并使用了错误的密钥。

    没关系,但请理解,无效标签可能意味着标签本身已更改、密文已更改、IV 已更改、AAD 已更改或密钥确实不正确。

    您还可以在解密之前使用密钥检查值或类似的东西来检查密钥大小是否正确。但请注意,攻击者也可以更改该检查值。

    那么通过更改这些参数会发生什么?我还能获得 GCM 的所有好处吗?

    当然可以,但是 GCM 进行了改造,使其在很大程度上兼容,但仍有更多配置选项(主要是标签大小) - 如果您需要配置它。 AEADBADTagExceptionBadPaddingException,因此代码应该适用于每个,即使 AEADBADTagException 更具体。

    在所有 Android API 级别中仅使用 IvParameterSpec 并通过 BadPaddingException 验证完整性是否正确且安全?我不想针对不同的平台有不同的实现,所以我只想使用一个。

    当然。请注意,只有标签可以抛出BadPaddingException,因此这样的异常确实可以正确识别身份验证问题。

    另外,在 API 21+ 上,如果我使用 GCMParameterSpec 加密,然后使用 IvParameterSpec 解密它就可以解密!反之亦然。效果如何?

    您的代码正在针对每种类型的参数规范运行,因为您指定了与默认值相同的标记大小:128 位。它不适用于较小的标签尺寸。


    代码cmets:

    • charset 应该是一个常量 (static final);
    • 键不应作为字节数组传递,而应作为SecretKey 实例传递;
    • IV 应始终为 12 字节,因此无需传达 IV 大小;
    • 如果您确实传达了 IV 大小,那么您需要检查它是否是一个有效值,目前攻击者可以控制该字节(并让您创建一个大 IV 或抛出ArrayIndexOutOfBounds 异常);
    • 在处理异常时,您需要区分代码问题(GCM 算法不可用)和输入相关问题(大小错误) - 我写了一个小入门作为答案 here
    • 目前您的代码可以处理小消息;对于较大的消息,某种流式传输会很好。

    【讨论】:

      猜你喜欢
      • 2018-02-19
      • 2021-09-24
      • 2016-11-14
      • 2021-11-23
      • 2021-12-05
      • 2018-02-11
      • 2015-10-29
      • 2019-05-30
      • 1970-01-01
      相关资源
      最近更新 更多