IvParameterSpec 和 GCMParameterSpec 之间的区别 AES/GCM/NoPadding

Difference between IvParameterSpec and GCMParameterSpec with AES/GCM/NoPadding

我正在使用 AES/GCM/NoPadding 算法加密 Android 上的一些数据(API 19 及以后),然后再解密回来。

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

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

我面临的问题是在 Android API 19 使用上面的算法并用 GCMParameterSpec 初始化密码我得到一个 NoSuchAlgorithmException,我没有指定任何供应商本人允许 Android 为我选择一个可以支持我的算法的供应商。在 21+ 上,算法可用。 这就是我初始化的方式(类似于解密),整个 class 在这个 post.

的末尾被 posted
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BIT, iv));

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

那么改变这些参数会发生什么?我还能获得 GCM 的所有相同优势吗?

因为尝试使用错误的密钥时抛出的异常是不同的。 On API 19 当使用 IvParameterSpec 时抛出 BadPaddingException,on API 21+ 当使用 GCMParameterSpec 时抛出 AEADBADTagException

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

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

如果以上在 API19 上不可能,那么我有什么可能的选择来用作加密算法和使用策略(AES/CBC/PKCS5Padding 与 HMAC?)来验证完整性钥匙。

完整 class 代码:

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);
        }
    }
}

此外,请随时提出对所提供 class 的改进建议以及您的回答,谢谢。

I also want to know when I try to decrypt and use a wrong key.

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

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

So what happens by changing these parameters? Do I still get all the same benefits of GCM?

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

Is it correct and secure to use just the IvParameterSpec through all the Android API levels and verify the integrity through BadPaddingException? I do not want to have different implementations for different platforms so I would want to use one only.

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

Also, on API 21+, if I encrypt using GCMParameterSpec and then later use IvParameterSpec to decrypt it decrypts fine! and the same vice versa. How is that working?

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


代码注释:

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