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

