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
相关产品推荐
相关产品推荐

