java - IvParameterSpec 和 GCMParameterSpec 与 AES/GCM/NoPadding 的区别

标签 java android encryption cryptography aes-gcm

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

我使用的 key 大小是 32 字节并提供给我

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

我面临的问题是,在 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 的所有相同优势吗?

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

通过所有 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);
        }
    }
}

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

最佳答案

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

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

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

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

关于java - IvParameterSpec 和 GCMParameterSpec 与 AES/GCM/NoPadding 的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49755584/

相关文章:

java - 无法将 double 值转换为 boolean 值(这里是菜鸟)

android - 使用 Uri 或 Assets 中的文件创建 MediaPlayer 时出错

Android 浏览器无法正确处理新行 (ASCII x0A) 字符

java - 解密错误 : javax. crypto.IllegalBlockSizeException:使用填充密码解密时,输入长度必须是 8 的倍数

linux - 在 Amazon Glacier 中存储本地加密的增量 ZFS 快照

java - 如何在Java 1.6中将UTC日期转换为本地日期

java - JMH AnnotationProcessor NullPointerException

Java 堆空间错误,我无法在 Java 中处理大型 xlsx 文件

android - 如何从PC提取apk文件?

Android - 在sqlite数据库中存储敏感数据