You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift AES GCM加密文件后Java解密出现标签不匹配问题

问题:Swift加密文件后Java解密出现AEADBadTagException标签不匹配

需要用Swift加密目录内约1000个文件,再通过Java解密。字符串原型测试正常,但将加密内容写入文件后,Java整体解密该文件时出现javax.crypto.AEADBadTagException标签不匹配错误;单独使用加密时打印的密文和标签则可正常解密,求问题原因。

Swift加密代码

static func encryptDecryptSingleTestFile() {
        
        do {
            //encrypt
            let data = try Data(contentsOf: URL(fileURLWithPath: filePath1))
            print("data in original file \(String(decoding: data, as: UTF8.self))")
            let sealedBox = try! AES.GCM.seal(data, using: symKey, nonce: nonce)
            let ciphertext = sealedBox.ciphertext.base64EncodedString()
            print("ciphertext of sealedbox in file: \(ciphertext)")
            let tag = sealedBox.tag
            print("tag of sealedbox in file: \(tag.base64EncodedString())")
            try sealedBox.combined!.write(to: URL(fileURLWithPath: filePath2))

            
            //DECRYPT - just for testing, actual decrypting will happen in java code
            let data2 = try Data(contentsOf: URL(fileURLWithPath: filePath2))
            print("data in encrypted file \(String(decoding: data2, as: UTF8.self))")
            let sealedBox2 = try AES.GCM.SealedBox(combined: data2)
            let decryptedData = try AES.GCM.open(sealedBox2, using: symKey)
            try decryptedData.write(to: URL(fileURLWithPath: filePath3))
            let ciphertext2 = sealedBox2.ciphertext.base64EncodedString()
            let dataInDecryptedFile = try Data(contentsOf: URL(fileURLWithPath: filePath3))
            print("data in decrypted file \(String(decoding: dataInDecryptedFile, as: UTF8.self))")

            
        } catch {
            print(error)
        }
    }

Swift输出

data in original file: abc
ciphertext of sealedbox in file: UoRsEQ==
tag of sealedbox in file: KaryGdpX0t1KwXaDFXEnkw==
data in encrypted file: ~�g�ա�R��@R�l)���W��J�v�q'�
data in decrypted file: abc

Java解密代码

import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

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

import org.apache.commons.io.FileUtils;

public class FileDecryptro {
    static String secret = "my-xxx-bit-secret-my-secret-my-s";
    static String nonce = "fv1nixTVoYpSvpdA";
    //below values are obtained from the swift encryptor class print statements
    static String tag = "KaryGdpX0t1KwXaDFXEnkw==";
    static String cipherText = "UoRsEQ==";

    public static void main(String args[]) throws Exception {
      
    //decrypt with cipher and tag - WORKS
        String decriptedValue = decrypt(cipherText,tag);
        System.out.println("output from cipher and tag:" + decriptedValue);
        
        //decrypt from file content which has nonce,tag,ciphertext combined. - DOES NOT WORK
        System.out.println("going to decrypt from file which has nonce, tag, ciphertext combined");

        String inputFileName = "a_e.txt";
        String inputFileContent = FileUtils.readFileToString(new File(inputFileName));///ciphertext and tag is included
        System.out.println("inputFileContent:"+inputFileContent);
        String decriptedValue2 = decrypt(inputFileContent.getBytes());
        System.out.println("output:" + decriptedValue2);
    
        
    }

    static String decrypt(byte[] ciphertextWithTagBytes) throws Exception {
    
           byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
           byte[] nonceBytes = Base64.getDecoder().decode(nonce);
           
              //decrypt
           Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
           SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
           GCMParameterSpec gcmSpec = new GCMParameterSpec(128, nonceBytes);
           cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
           byte[] plaintextBytes = cipher.doFinal(ciphertextWithTagBytes);
           String plaintext = new String(plaintextBytes, StandardCharsets.UTF_8);
           return plaintext;
           
    }

    static String decrypt(String cipherText, String tag) throws Exception {
    
    
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        byte[] nonceBytes = Base64.getDecoder().decode(nonce);
        
        
        //append tagBytes to cipherText
        byte[] tagBytes = Base64.getDecoder().decode(tag);
        Base64.Decoder decoder = Base64.getDecoder();
        byte[] decodedCipherText = decoder.decode(cipherText);
        byte[] decodedTag = decoder.decode(tag);
        byte[] ciphertextBytes = new byte[decodedCipherText.length + decodedTag.length];
        ByteBuffer.wrap(ciphertextBytes).put(decodedCipherText).put(decodedTag);
        
        //decrypt
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        GCMParameterSpec gcmSpec = new GCMParameterSpec(128, nonceBytes);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
        byte[] plaintextBytes = cipher.doFinal(ciphertextBytes);
        String plaintext = new String(plaintextBytes, StandardCharsets.UTF_8);
        return plaintext;
        
    }
    
  
 
}

Java输出

output from cipher and tag:abc

going to decrypt from file which has nonce, tag, ciphertext combined
inputFileContent:~�g�ա�R��@R�l)���W��J�v�q'�
Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!

问题原因及解决方法

核心原因:二进制数据被字符串转换损坏

Swift写入文件的sealedBox.combined!是原始二进制数据,包含nonce、密文、tag的完整字节序列。但Java端用FileUtils.readFileToString()将二进制文件内容转换为UTF-8字符串,这会导致数据损坏:

  • 二进制字节中存在大量非UTF-8合法字符,转换时会被替换为占位符(如�),丢失原始字节信息;
  • 最终传给解密函数的inputFileContent.getBytes()已经和Swift写入的原始二进制不一致,触发GCM标签验证失败。

而直接用打印的密文和tag解密正常,是因为这些数据是Base64编码后的合法字符串,没有经过二进制转字符串的损坏过程。

解决方法

修改Java端读取文件的方式,直接读取二进制字节数组,跳过字符串转换步骤:

// 替换原读取文件的代码
String inputFileName = "a_e.txt";
byte[] ciphertextWithTagBytes = FileUtils.readFileToByteArray(new File(inputFileName));
String decriptedValue2 = decrypt(ciphertextWithTagBytes);
System.out.println("output:" + decriptedValue2);

额外注意事项

  • 确保Swift和Java使用的nonce完全一致:如果加密时使用随机nonce,需要从combined数据中拆分出nonce(Swift的SealedBox(combined:)会自动拆分,Java端需要手动按长度截取:前12字节是GCM标准nonce长度);
  • 密钥一致性:Swift的symKey和Java的secret转成字节数组后必须完全相同,当前代码中用UTF-8编码,需确保两端编码方式一致。

内容的提问来源于stack exchange,提问作者nirav dinmali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 00:32:18