尝试在 GCM 模式下解密消息时出现 AEADBadTagException
got AEADBadTagException while trying to decrypt message in GCM mode
我正在编写一个有很多安全限制的应用程序:
它需要安全地存储加密文件,并且必须能够解密它们。此外,操作员需要能够在没有应用程序的情况下解密文件。
为了存档,我在我的电脑上生成了一个 KeyPair
,将 public 部分放在我的应用程序中,在应用程序中生成了一个 AES
SecretKey
密钥,使用我的 public 密钥加密并保存它(供操作员使用),并将未加密的密钥放入 AndroidKeyStore
.
为了加密消息,我从 KeyStore
收到 SecretKey
,加密我的消息,获取我使用的 IV
以及加密的 SecretKey,并将它们写入定义的顺序为字节数组 (iv->encryptedSecretKey->encryptedMessage)。
为了解密,我反向尝试相同的操作:获取字节数组,读取 iv 和 encryptedSecretKey,然后将其余的 (encryptedMessage) 传递给我的密码进行解密。
问题是,cipher.doFinal(encryptedMessage)
正在抛出一个
javax.crypto.AEADBadTagException
这是由 android.security.KeyStoreException: Signature/MAC verification failed
.
引起的
我已经检查过加密的消息和我要解密的消息完全一样。我不知道我做错了什么。
我用的class是这样的:
package my.company.domain;
import android.content.Context;
import android.content.SharedPreferences;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.support.annotation.NonNull;
import android.util.Base64;
import android.util.Log;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class CryptoHelper {
public static final String TAG = CryptoHelper.class.getSimpleName();
private static final String KEY_ALIAS = "OI1lTI1lLI1l0";
private static final char[] KEY_PASSWORD = "Il0VELI1lO".toCharArray();
private static final String PREF_NAME = "CryptoPrefs";
private static final String KEY_ENCRYPTED_SECRET = "encryptedSecret";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final int IV_SIZE = 12;
private static final int IV_BIT_LEN = IV_SIZE * 8;
//generate 128 bit key (16), other possible values 192(24), 256(32)
private static final int AES_KEY_SIZE = 16;
private static final String AES = KeyProperties.KEY_ALGORITHM_AES;
private static final String AES_MODE = AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;
private static final String RSA = KeyProperties.KEY_ALGORITHM_RSA;
private static final String RSA_MODE = KeyProperties.KEY_ALGORITHM_RSA + "/" + KeyProperties.BLOCK_MODE_ECB + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;
private static final String RSA_PROVIDER = "AndroidOpenSSL";
private final Context mContext;
private final SharedPreferences mPrefs;
private SecureRandom mSecureRandom;
private KeyStore mAndroidKeyStore;
private PublicKey mPublicKey;
private byte[] mEncryptedSecretKey;
public CryptoHelper(Context context) {
mContext = context;
mSecureRandom = new SecureRandom();
mPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
try {
mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
mAndroidKeyStore.load(null);
} catch (KeyStoreException e) {
Log.wtf(TAG, "Could not get AndroidKeyStore!", e);
} catch (Exception e) {
Log.wtf(TAG, "Could not load AndroidKeyStore!", e);
}
}
public void reset() throws KeyStoreException {
mAndroidKeyStore.deleteEntry(KEY_ALIAS);
}
public byte[] encrypt(byte[] message){
SecretKey secretKey = getSecretKey();
try {
Cipher cipher = Cipher.getInstance(AES_MODE);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
byte[] cryptedBytes = cipher.doFinal(message);
byte[] iv = cipher.getIV();
byte[] encryptedSecretKey = getEncryptedSecretKey();
ByteBuffer buffer = ByteBuffer.allocate(IV_BIT_LEN + encryptedSecretKey.length + cryptedBytes.length);
buffer
.put(iv)
.put(encryptedSecretKey)
.put(cryptedBytes);
return buffer.array();
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
public byte[] encrypt(String message){
return encrypt(message.getBytes(StandardCharsets.UTF_8));
}
public byte[] decrypt(byte[] bytes){
ByteBuffer buffer = ByteBuffer.wrap(bytes);
byte[] iv = new byte[IV_SIZE];
buffer.get(iv);
byte[] unused = getEncryptedSecretKey();
buffer.get(unused);
byte[] encryptedMessage = new byte[bytes.length - IV_SIZE - unused.length];
buffer.get(encryptedMessage);
try {
Cipher cipher = Cipher.getInstance(AES_MODE);
GCMParameterSpec parameterSpec = new GCMParameterSpec(IV_BIT_LEN, iv);
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), parameterSpec);
byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
return decryptedMessage;
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
public String decryptToString(byte[] bytes){
return new String(decrypt(bytes), StandardCharsets.UTF_8);
}
public byte[] decrypt(FileInputStream fileToDecrypt){
byte[] buffer = null;
try {
buffer = new byte[fileToDecrypt.available()];
fileToDecrypt.read(buffer);
buffer = decrypt(buffer);
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}
public PublicKey getPublicKey() {
if (null == mPublicKey) {
mPublicKey = readPublicKey();
}
return mPublicKey;
}
public byte[] getEncryptedSecretKey() {
if (null == mEncryptedSecretKey){
mEncryptedSecretKey = Base64.decode(mPrefs.getString(KEY_ENCRYPTED_SECRET, null), Base64.NO_WRAP);
}
return mEncryptedSecretKey;
}
private void saveEncryptedSecretKey(byte[] encryptedSecretKey){
String base64EncryptedKey = Base64.encodeToString(encryptedSecretKey, Base64.NO_WRAP);
mPrefs.edit().putString(KEY_ENCRYPTED_SECRET, base64EncryptedKey).apply();
}
protected SecretKey getSecretKey(){
SecretKey secretKey = null;
try {
if (!mAndroidKeyStore.containsAlias(KEY_ALIAS)){
generateAndStoreSecureKey();
}
secretKey = (SecretKey) mAndroidKeyStore.getKey(KEY_ALIAS, KEY_PASSWORD);
} catch (KeyStoreException e) {
Log.wtf(TAG, "Could not check AndroidKeyStore alias!", e);
secretKey = null;
} catch (GeneralSecurityException e) {
e.printStackTrace();
secretKey = null;
}
return secretKey;
}
private void generateAndStoreSecureKey()
throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, KeyStoreException, BadPaddingException, IllegalBlockSizeException {
SecretKey secretKey = generateSecureRandomKey();
PublicKey publicKey = getPublicKey();
Cipher keyCipher = Cipher.getInstance(RSA_MODE, RSA_PROVIDER);
keyCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedSecretKeyBytes = keyCipher.doFinal(secretKey.getEncoded());
saveEncryptedSecretKey(encryptedSecretKeyBytes);
KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_VERIFY)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
mAndroidKeyStore.setEntry(KEY_ALIAS, new KeyStore.SecretKeyEntry(secretKey), keyProtection);
}
protected PublicKey readPublicKey() {
DataInputStream dis = null;
PublicKey key = null;
try {
dis = new DataInputStream(mContext.getResources().getAssets().open("public_key.der"));
byte[] keyBytes = new byte[dis.available()];
dis.readFully(keyBytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory facotory = KeyFactory.getInstance(RSA);
key = facotory.generatePublic(spec);
} catch (Exception e) {
key = null;
} finally {
if (null != dis) {
try {
dis.close();
} catch (IOException e) {
Log.wtf(TAG, "Cannot Close Stream!", e);
}
}
}
return key;
}
@NonNull
protected SecretKey generateSecureRandomKey() {
return new SecretKeySpec(generateSecureRandomBytes(AES_KEY_SIZE), AES);
}
@NonNull
protected byte[] generateSecureRandomBytes(int byteCount) {
byte[] keyBytes = new byte[byteCount];
mSecureRandom.nextBytes(keyBytes);
return keyBytes;
}
}
我是这样测试的:
@Test
public void testCrypto() throws Exception {
CryptoHelper crypto = new CryptoHelper(InstrumentationRegistry.getTargetContext());
crypto.reset();
String verySecretOpinion = "we're all doomed";
byte[] encrypt = crypto.encrypt(verySecretOpinion);
Assert.assertNotNull("Encrypted secret is Null!", encrypt);
Assert.assertFalse("encrypted Bytes are the same as Input!", new String(encrypt, StandardCharsets.UTF_8).equals(verySecretOpinion));
String decryptedString = crypto.decryptToString(encrypt);
Assert.assertNotNull("Decrypted String must be Non-Null!", decryptedString);
Assert.assertEquals("Decrypted String doesn't equal encryption input!", verySecretOpinion, decryptedString);
}
顺便说一句,minSdkVersion 是 25,比 Marshmallow 高
更新:
- 修复了
Cipher.DECRYPT_MODE
至 ENCRYPT_MODE
保存 SecretKey 的问题,感谢 James K Polk 的评论
- 如果我从
BlockMode
GCM
切换到 BlockMode CBC
(并将 GCMParameterSpec
更改为 IvParamterSpec
但松开 GCM 验证,它会起作用模式。
操作界面至少有两个问题。首先,您使用错误的密码模式对密钥进行 RSA 加密:您本应使用加密模式却使用了解密模式。其次,您使用的是没有任何填充的 RSA。需要使用real padding模式,推荐OEAP padding模式之一
调整用于保存结果的缓冲区大小时,加密端发生错误:
ByteBuffer buffer = ByteBuffer.allocate(IV_BIT_LEN + encryptedSecretKey.length + cryptedBytes.length);
分配太多 space。 IV_BIT_LEN
可能应该更改为 IV_SIZE
以获得正确大小的 ByteBuffer
。
最后一个错误是在解密端设置GCMParameterSpec
时没有考虑到GCM认证标签长度。您在这一行中初始化了标签长度
GCMParameterSpec parameterSpec = new GCMParameterSpec(IV_BIT_LEN, iv);
但这是不正确的,标签长度与 IV 无关。由于您没有在加密端明确设置 GCMParameterSpec,因此您获得了默认标签长度,恰好是 128。
您可以通过调用在加密端检索标签长度
cipher.getParameters().getParameterSpec(GCMParameterSpec.class)
获取参数规范。从这里您可以检索标签长度和 iv。您可能应该将标记长度(16 字节 = 128 位)视为硬编码常量而不传输它。接收方应该类似地假设标签长度为 128 位。
我正在编写一个有很多安全限制的应用程序: 它需要安全地存储加密文件,并且必须能够解密它们。此外,操作员需要能够在没有应用程序的情况下解密文件。
为了存档,我在我的电脑上生成了一个 KeyPair
,将 public 部分放在我的应用程序中,在应用程序中生成了一个 AES
SecretKey
密钥,使用我的 public 密钥加密并保存它(供操作员使用),并将未加密的密钥放入 AndroidKeyStore
.
为了加密消息,我从 KeyStore
收到 SecretKey
,加密我的消息,获取我使用的 IV
以及加密的 SecretKey,并将它们写入定义的顺序为字节数组 (iv->encryptedSecretKey->encryptedMessage)。
为了解密,我反向尝试相同的操作:获取字节数组,读取 iv 和 encryptedSecretKey,然后将其余的 (encryptedMessage) 传递给我的密码进行解密。
问题是,cipher.doFinal(encryptedMessage)
正在抛出一个
javax.crypto.AEADBadTagException
这是由 android.security.KeyStoreException: Signature/MAC verification failed
.
我已经检查过加密的消息和我要解密的消息完全一样。我不知道我做错了什么。
我用的class是这样的:
package my.company.domain;
import android.content.Context;
import android.content.SharedPreferences;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.support.annotation.NonNull;
import android.util.Base64;
import android.util.Log;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class CryptoHelper {
public static final String TAG = CryptoHelper.class.getSimpleName();
private static final String KEY_ALIAS = "OI1lTI1lLI1l0";
private static final char[] KEY_PASSWORD = "Il0VELI1lO".toCharArray();
private static final String PREF_NAME = "CryptoPrefs";
private static final String KEY_ENCRYPTED_SECRET = "encryptedSecret";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final int IV_SIZE = 12;
private static final int IV_BIT_LEN = IV_SIZE * 8;
//generate 128 bit key (16), other possible values 192(24), 256(32)
private static final int AES_KEY_SIZE = 16;
private static final String AES = KeyProperties.KEY_ALGORITHM_AES;
private static final String AES_MODE = AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;
private static final String RSA = KeyProperties.KEY_ALGORITHM_RSA;
private static final String RSA_MODE = KeyProperties.KEY_ALGORITHM_RSA + "/" + KeyProperties.BLOCK_MODE_ECB + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;
private static final String RSA_PROVIDER = "AndroidOpenSSL";
private final Context mContext;
private final SharedPreferences mPrefs;
private SecureRandom mSecureRandom;
private KeyStore mAndroidKeyStore;
private PublicKey mPublicKey;
private byte[] mEncryptedSecretKey;
public CryptoHelper(Context context) {
mContext = context;
mSecureRandom = new SecureRandom();
mPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
try {
mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
mAndroidKeyStore.load(null);
} catch (KeyStoreException e) {
Log.wtf(TAG, "Could not get AndroidKeyStore!", e);
} catch (Exception e) {
Log.wtf(TAG, "Could not load AndroidKeyStore!", e);
}
}
public void reset() throws KeyStoreException {
mAndroidKeyStore.deleteEntry(KEY_ALIAS);
}
public byte[] encrypt(byte[] message){
SecretKey secretKey = getSecretKey();
try {
Cipher cipher = Cipher.getInstance(AES_MODE);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
byte[] cryptedBytes = cipher.doFinal(message);
byte[] iv = cipher.getIV();
byte[] encryptedSecretKey = getEncryptedSecretKey();
ByteBuffer buffer = ByteBuffer.allocate(IV_BIT_LEN + encryptedSecretKey.length + cryptedBytes.length);
buffer
.put(iv)
.put(encryptedSecretKey)
.put(cryptedBytes);
return buffer.array();
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
public byte[] encrypt(String message){
return encrypt(message.getBytes(StandardCharsets.UTF_8));
}
public byte[] decrypt(byte[] bytes){
ByteBuffer buffer = ByteBuffer.wrap(bytes);
byte[] iv = new byte[IV_SIZE];
buffer.get(iv);
byte[] unused = getEncryptedSecretKey();
buffer.get(unused);
byte[] encryptedMessage = new byte[bytes.length - IV_SIZE - unused.length];
buffer.get(encryptedMessage);
try {
Cipher cipher = Cipher.getInstance(AES_MODE);
GCMParameterSpec parameterSpec = new GCMParameterSpec(IV_BIT_LEN, iv);
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), parameterSpec);
byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
return decryptedMessage;
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
public String decryptToString(byte[] bytes){
return new String(decrypt(bytes), StandardCharsets.UTF_8);
}
public byte[] decrypt(FileInputStream fileToDecrypt){
byte[] buffer = null;
try {
buffer = new byte[fileToDecrypt.available()];
fileToDecrypt.read(buffer);
buffer = decrypt(buffer);
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}
public PublicKey getPublicKey() {
if (null == mPublicKey) {
mPublicKey = readPublicKey();
}
return mPublicKey;
}
public byte[] getEncryptedSecretKey() {
if (null == mEncryptedSecretKey){
mEncryptedSecretKey = Base64.decode(mPrefs.getString(KEY_ENCRYPTED_SECRET, null), Base64.NO_WRAP);
}
return mEncryptedSecretKey;
}
private void saveEncryptedSecretKey(byte[] encryptedSecretKey){
String base64EncryptedKey = Base64.encodeToString(encryptedSecretKey, Base64.NO_WRAP);
mPrefs.edit().putString(KEY_ENCRYPTED_SECRET, base64EncryptedKey).apply();
}
protected SecretKey getSecretKey(){
SecretKey secretKey = null;
try {
if (!mAndroidKeyStore.containsAlias(KEY_ALIAS)){
generateAndStoreSecureKey();
}
secretKey = (SecretKey) mAndroidKeyStore.getKey(KEY_ALIAS, KEY_PASSWORD);
} catch (KeyStoreException e) {
Log.wtf(TAG, "Could not check AndroidKeyStore alias!", e);
secretKey = null;
} catch (GeneralSecurityException e) {
e.printStackTrace();
secretKey = null;
}
return secretKey;
}
private void generateAndStoreSecureKey()
throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, KeyStoreException, BadPaddingException, IllegalBlockSizeException {
SecretKey secretKey = generateSecureRandomKey();
PublicKey publicKey = getPublicKey();
Cipher keyCipher = Cipher.getInstance(RSA_MODE, RSA_PROVIDER);
keyCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedSecretKeyBytes = keyCipher.doFinal(secretKey.getEncoded());
saveEncryptedSecretKey(encryptedSecretKeyBytes);
KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_VERIFY)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
mAndroidKeyStore.setEntry(KEY_ALIAS, new KeyStore.SecretKeyEntry(secretKey), keyProtection);
}
protected PublicKey readPublicKey() {
DataInputStream dis = null;
PublicKey key = null;
try {
dis = new DataInputStream(mContext.getResources().getAssets().open("public_key.der"));
byte[] keyBytes = new byte[dis.available()];
dis.readFully(keyBytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory facotory = KeyFactory.getInstance(RSA);
key = facotory.generatePublic(spec);
} catch (Exception e) {
key = null;
} finally {
if (null != dis) {
try {
dis.close();
} catch (IOException e) {
Log.wtf(TAG, "Cannot Close Stream!", e);
}
}
}
return key;
}
@NonNull
protected SecretKey generateSecureRandomKey() {
return new SecretKeySpec(generateSecureRandomBytes(AES_KEY_SIZE), AES);
}
@NonNull
protected byte[] generateSecureRandomBytes(int byteCount) {
byte[] keyBytes = new byte[byteCount];
mSecureRandom.nextBytes(keyBytes);
return keyBytes;
}
}
我是这样测试的:
@Test
public void testCrypto() throws Exception {
CryptoHelper crypto = new CryptoHelper(InstrumentationRegistry.getTargetContext());
crypto.reset();
String verySecretOpinion = "we're all doomed";
byte[] encrypt = crypto.encrypt(verySecretOpinion);
Assert.assertNotNull("Encrypted secret is Null!", encrypt);
Assert.assertFalse("encrypted Bytes are the same as Input!", new String(encrypt, StandardCharsets.UTF_8).equals(verySecretOpinion));
String decryptedString = crypto.decryptToString(encrypt);
Assert.assertNotNull("Decrypted String must be Non-Null!", decryptedString);
Assert.assertEquals("Decrypted String doesn't equal encryption input!", verySecretOpinion, decryptedString);
}
顺便说一句,minSdkVersion 是 25,比 Marshmallow 高
更新:
- 修复了
Cipher.DECRYPT_MODE
至ENCRYPT_MODE
保存 SecretKey 的问题,感谢 James K Polk 的评论 - 如果我从
BlockMode
GCM
切换到 BlockModeCBC
(并将GCMParameterSpec
更改为IvParamterSpec
但松开 GCM 验证,它会起作用模式。
操作界面至少有两个问题。首先,您使用错误的密码模式对密钥进行 RSA 加密:您本应使用加密模式却使用了解密模式。其次,您使用的是没有任何填充的 RSA。需要使用real padding模式,推荐OEAP padding模式之一
调整用于保存结果的缓冲区大小时,加密端发生错误:
ByteBuffer buffer = ByteBuffer.allocate(IV_BIT_LEN + encryptedSecretKey.length + cryptedBytes.length);
分配太多 space。 IV_BIT_LEN
可能应该更改为 IV_SIZE
以获得正确大小的 ByteBuffer
。
最后一个错误是在解密端设置GCMParameterSpec
时没有考虑到GCM认证标签长度。您在这一行中初始化了标签长度
GCMParameterSpec parameterSpec = new GCMParameterSpec(IV_BIT_LEN, iv);
但这是不正确的,标签长度与 IV 无关。由于您没有在加密端明确设置 GCMParameterSpec,因此您获得了默认标签长度,恰好是 128。
您可以通过调用在加密端检索标签长度
cipher.getParameters().getParameterSpec(GCMParameterSpec.class)
获取参数规范。从这里您可以检索标签长度和 iv。您可能应该将标记长度(16 字节 = 128 位)视为硬编码常量而不传输它。接收方应该类似地假设标签长度为 128 位。