简体   繁体   English

使用AES / GCM / NoPadding的IvParameterSpec和GCMParameterSpec之间的差异

[英]Difference between IvParameterSpec and GCMParameterSpec with AES/GCM/NoPadding

I am using AES/GCM/NoPadding algorithm to encrypt some data on Android (API 19 and onwards) and then later decrypt it back. 我使用AES/GCM/NoPadding算法加密Android上的一些数据(API 19及以后版本),然后再将其解密。

The key size I use is 32 bytes and is provided to me 我使用的密钥大小是32字节,并提供给我

In addition to the encryption, I also want to know when I try to decrypt and use a wrong key . 除了加密, 我还想知道我何时尝试解密并使用错误的密钥 Which is why I prefer to use GCM as my mode to get the benefits of verifying integrity (I believe its safe to assume whether the ciphertext or the key whichever is faulty would result in a bad decrypt exception rather than garbled text) 这就是为什么我更喜欢使用GCM作为我的模式来获得验证完整性的好处(我相信可以安全地假设密文或密钥中的哪一个是错误的会导致解密异常而不是乱码文本)

The problem I face is that on Android API 19 using the algorithm above and initializing the cipher with GCMParameterSpec I get a NoSuchAlgorithmException , I do not specify any provider myself allowing Android to pick one for me which can support my algorithm. 我面临的问题是在Android API 19上使用上面的算法并使用GCMParameterSpec初始化密码我得到NoSuchAlgorithmException ,我没有指定任何提供者自己允许Android为我选择一个可以支持我的算法。 On 21+ the Algorithm is available. 21+以上的算法可用。 This is how I am initializing(similar for decryption), the entire class is posted at the end of this post. 这就是我初始化的方法(类似于解密),整个课程在本文末尾发布。

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

However, if I use IvParameterSpec(iv) as my AlgorithmParameters instead of GCMParameterSpec then the code works fine. 但是,如果我使用IvParameterSpec(iv)作为我的AlgorithmParameters而不是GCMParameterSpec那么代码工作正常。

So what happens by changing these parameters? 那么通过改变这些参数会发生什么? Do I still get all the same benefits of GCM? 我仍然可以获得GCM的所有好处吗?

Because the exceptions thrown are different when attempting to use a wrong key. 因为在尝试使用错误的密钥时抛出的异常是不同的。 On API 19 BadPaddingException is thrown when IvParameterSpec is used, on API 21+ AEADBADTagException is thrown when GCMParameterSpec is used. 在API 19上,当使用IvParameterSpec时抛出BadPaddingException ,在API 21+上使用AEADBADTagException抛出GCMParameterSpec

Is it correct and secure to use just the IvParameterSpec through all the Android API levels and verify the integrity through BadPaddingException ? 通过所有Android API级别仅使用IvParameterSpec并通过BadPaddingException验证完整性是否正确和安全? I do not want to have different implementations for different platforms so I would want to use one only. 我不想为不同的平台有不同的实现,所以我只想使用一个。

Also, on API 21+, if I encrypt using GCMParameterSpec and then later use IvParameterSpec to decrypt it decrypts fine! 此外,在API 21+上,如果我使用GCMParameterSpec加密,然后使用IvParameterSpec解密它解密就好了! and the same vice versa. 反之亦然。 How is that working? 这怎么样?

If the above is not possible on API 19 then what are my possible options to use as an encryption algorithm and a strategy to use( AES/CBC/PKCS5Padding with HMAC?) to verify the integrity of the key. 如果在API 19上无法实现上述目标,那么我可以选择使用哪种加密算法和策略( AES/CBC/PKCS5Padding与HMAC?)来验证密钥的完整性。

Full class code: 全班代码:

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

Also, feel free to suggest improvements to the provided class along with your answer, thanks. 另外,请随意建议改进所提供的课程以及您的答案,谢谢。

I also want to know when I try to decrypt and use a wrong key. 我也想知道我何时尝试解密并使用错误的密钥。

That's OK, but please understand that an invalid tag can mean that the tag itself was altered, the ciphertext was altered, the IV was altered, the AAD was altered or that indeed the key is incorrect. 这没关系,但请理解,无效标签可能意味着标签本身被更改,密文被更改,IV被更改,AAD被更改或确实密钥不正确。

You could also use a key check value or something similar to check if the key size is correct or not before decrypting. 您还可以使用密钥检查值或类似的东西来检查密钥大小是否正确,然后再进行解密。 But note that an adversary could also alter that check value. 但请注意,对手也可以改变该检查值。

So what happens by changing these parameters? 那么通过改变这些参数会发生什么? Do I still get all the same benefits of GCM? 我仍然可以获得GCM的所有好处吗?

For sure, but GCM was retrofitted in such a way that it was largely compatible but still has more configuration options (mainly the tag size) - if you require to configure that. 可以肯定的是,GCM的改造方式基本上是兼容的,但仍然有更多的配置选项(主要是标签尺寸) - 如果你需要配置它。 The AEADBADTagException is a BadPaddingException so the code should work for each, even though AEADBADTagException is more specific. AEADBADTagExceptionBadPaddingException因此代码应该适用于每个,即使AEADBADTagException更具体。

Is it correct and secure to use just the IvParameterSpec through all the Android API levels and verify the integrity through BadPaddingException ? 通过所有Android API级别仅使用IvParameterSpec并通过BadPaddingException验证完整性是否正确和安全? I do not want to have different implementations for different platforms so I would want to use one only. 我不想为不同的平台有不同的实现,所以我只想使用一个。

For sure. 当然。 Note that only the tag could throw a BadPaddingException , so such an exception does correctly identify a problem with the authentication. 请注意,只有标记可能会抛出BadPaddingException ,因此这样的异常会正确识别身份验证问题。

Also, on API 21+, if I encrypt using GCMParameterSpec and then later use IvParameterSpec to decrypt it decrypts fine! 此外,在API 21+上,如果我使用GCMParameterSpec加密,然后使用IvParameterSpec解密它解密就好了! and the same vice versa. 反之亦然。 How is that working? 这怎么样?

Your code is running for each type of parameter specification because you specified the same tag size as the default: 128 bits. 您的代码针对每种类型的参数规范运行,因为您指定的标记大小与默认值相同:128位。 It wouldn't work with a smaller tag size. 它不适用于较小的标签大小。


Code comments: 代码评论:

  • charset should be a constant ( static final ); charset应该是一个常数( static final );
  • keys should not be passed as byte arrays but as SecretKey instances; 密钥不应作为字节数组传递,而应作为SecretKey实例传递;
  • the IV should always be 12 bytes, so the IV size doesn't need to be communicated; IV应始终为12个字节,因此不需要传送IV大小;
  • if you do communicate the IV size then you need to check if it is a valid value, currently an adversary can control that byte (and let you create a large IV or throw in ArrayIndexOutOfBounds exception); 如果你确实传达了IV大小,那么你需要检查它是否是一个有效值,目前攻击者可以控制该字节(让你创建一个大的IV或抛出ArrayIndexOutOfBounds异常);
  • when processing exceptions you will want to differentiate between code issues (GCM algorithm not available) and input related issues (bad size); 处理异常时,您需要区分代码问题(GCM算法不可用)和输入相关问题(不良大小);
  • currently your code is OK for small messages; 目前您的代码适用于小消息; some kind of streaming would be nice for larger messages. 某种流式传输对于较大的消息会很好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM