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

Swift AES GCM加密后Java解密失败:Base64拼接异常解决

AES GCM跨语言解密问题:Swift加密带填充密文在Java解密失败

我实现了Swift的AES GCM加密函数,加密"abc"时生成无填充的密文,加密"a"时生成带"=="填充的密文。用Java解密"abc"的密文正常,但解密"a"的密文时抛出Input byte array has incorrect ending byte异常。尝试用AES/GCM/PKCS5Padding初始化Cipher时,提示找不到支持该填充的提供者。

Swift加密代码

static func encrypt() {
        
    let plain = "a" // 另一个测试字符串 "abc"
  
    static let secret = "my-xxx-bit-secret-my-secret-my-s"
    static let nonceString = "fv1nixTVoYpSvpdA"
    static let nonce = try! AES.GCM.Nonce(data: Data(base64Encoded: nonceString)!)
    static let symKey = SymmetricKey(data: secret.data(using: .utf8)!)

    let sealedBox = try! AES.GCM.seal(plain.data(using: .utf8)!, using: symKey, nonce: nonce)
    let ciphertext = sealedBox.ciphertext.base64EncodedString()
    let tag = sealedBox.tag
    print("ciphertext: .\(ciphertext).")
    print("tag: \(tag.base64EncodedString())")
             
}

Java解密代码(原错误版本)

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;

public class StringDecryptor {
    public static void main(String[] args) throws Exception {
    
        String actualText1 = "abc";
        String cipherText1 = "UoRs";
        String tag1 = "7VhlWAPpKka0CkmpshyOjw==";
        decryptSimpleString(cipherText1, tag1);
        
        String actualText2 = "a";
        String cipherText2 = "Ug==";
        String tag2 = "hkjeGS301OgQyGqdGDuHAA==";
        decryptSimpleString(cipherText2, tag2);
     }
    
    public static void decryptSimpleString(String cipherText, String tag) throws Exception {
        String secret = "my-xxx-bit-secret-my-secret-my-s";
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        
        String nonce = "fv1nixTVoYpSvpdA";
        byte[] nonceBytes = Base64.getDecoder().decode(nonce);
        
        byte[] tagBytes = Base64.getDecoder().decode(tag);
        
        String ciphterTextWithTag = cipherText + tag;
        byte[] ciphertextBytes = Base64.getDecoder().decode(ciphterTextWithTag);

        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);
        System.out.println("plain text was "+plaintext);
    }
}

原代码错误输出

plain text was abc

Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has incorrect ending byte at 4
    at java.base/java.util.Base64$Decoder.decode0(Base64.java:875)
    at java.base/java.util.Base64$Decoder.decode(Base64.java:566)
    at java.base/java.util.Base64$Decoder.decode(Base64.java:589)
    at com.mydomain.crypto.StringDecryptor.decryptSimpleString(StringDecryptor.java:34)
    at com.mydomain.crypto.StringDecryptor.main(StringDecryptor.java:21)

问题根源

错误出在直接拼接Base64编码后的密文和标签,然后整体解码。当密文的Base64结尾有填充符==时,拼接标签的Base64后,整个字符串的Base64格式会失效(因为填充符只能出现在Base64字符串的末尾)。比如Ug== + hkjeGS301OgQyGqdGDuHAA==变成Ug==hkjeGS301OgQyGqdGDuHAA==,这不是合法的Base64字符串,解码时就会报错。

另外,GCM模式本身不需要填充(AES/GCM是流密码模式,自动处理任意长度的明文),所以AES/GCM/PKCS5Padding本身就是不被支持的,因为GCM不需要填充。

修复后的Java解密代码

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class StringDecryptor {
    public static void main(String[] args) throws Exception {
    
        String actualText1 = "abc";
        String cipherText1 = "UoRs";
        String tag1 = "7VhlWAPpKka0CkmpshyOjw==";
        decryptSimpleString(cipherText1, tag1);
        
        String actualText2 = "a";
        String cipherText2 = "Ug==";
        String tag2 = "hkjeGS301OgQyGqdGDuHAA==";
        decryptSimpleString(cipherText2, tag2);
     }
    
    public static void decryptSimpleString(String cipherText, String tag) throws Exception {
        String secret = "my-xxx-bit-secret-my-secret-my-s";
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        
        String nonce = "fv1nixTVoYpSvpdA";
        byte[] nonceBytes = Base64.getDecoder().decode(nonce);
        
        // 分别解码密文和标签的Base64,得到原始字节数组
        byte[] ciphertextBytes = Base64.getDecoder().decode(cipherText);
        byte[] tagBytes = Base64.getDecoder().decode(tag);
        
        // 拼接密文字节和标签字节,形成Cipher需要的输入
        byte[] ciphertextWithTag = new byte[ciphertextBytes.length + tagBytes.length];
        System.arraycopy(ciphertextBytes, 0, ciphertextWithTag, 0, ciphertextBytes.length);
        System.arraycopy(tagBytes, 0, ciphertextWithTag, ciphertextBytes.length, tagBytes.length);

        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(ciphertextWithTag);
        String plaintext = new String(plaintextBytes, StandardCharsets.UTF_8);
        System.out.println("解密得到明文:"+plaintext);
    }
}

关键说明

  • GCM模式下,AES加密不需要填充,必须使用AES/GCM/NoPadding,PKCS5Padding不适用于GCM模式,因为GCM会自动处理任意长度的明文。
  • 跨语言加密解密时,要确保字节级别的拼接,而不是Base64字符串的拼接,Base64的填充符会破坏拼接后的字符串合法性。

内容的提问来源于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.25 22:25:01