Go适配Java AES/CFB8解密异常:密钥处理与结果排查
Go实现与Java兼容的AES/CFB8解密问题解决
问题背景
Java端采用以下规则加密数据:
- 加密模式:AES/CFB8/NoPadding
- 使用固定IV
- 密钥处理流程:原始密钥 → MD5哈希 → 转为32位小写十六进制字符串 → 该字符串的UTF-8字节数组作为AES密钥
当前Go解密代码无报错,但解密结果仍为密文,按Java逻辑处理密钥后问题依旧。
Java端核心代码
public static final String KEY_ALGORITHM = "AES"; public static final String AES_ALGORITHM = "AES/CFB8/NoPadding"; public static final String DIGEST_ALGORITHM = "MD5"; public static final byte[] INITIAL_VECTOR = { -25, 9, -119, 91, -90, 112, 98, -40, 65, -106, -1, 96, 118, -13, 88, 85 };
package com.crypto; import static com.CryptoConstant.AES_ALGORITHM; import static com.CryptoConstant.DIGEST_ALGORITHM; import static com.CryptoConstant.INITIAL_VECTOR; import static com.CryptoConstant.KEY_ALGORITHM; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Slf4j @Component public class MessageCrypto { @Value("${design.secret.key}") private String designSecretKey; private static String md5(final String input) throws NoSuchAlgorithmException { final MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM); final byte[] messageDigest = md.digest(input.getBytes()); final BigInteger number = new BigInteger(1, messageDigest); return String.format("%032x", number); } private Cipher initCipher(final int mode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException { final SecretKeySpec skeySpec = new SecretKeySpec(md5(designSecretKey).getBytes(), KEY_ALGORITHM); final IvParameterSpec initialVector = new IvParameterSpec(INITIAL_VECTOR); final Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(mode, skeySpec, initialVector); return cipher; } public String encrypt(final String dataToEncrypt) { log.info("Processing encrypt..."); byte[] encryptedData = {}; try { final Cipher cipher = initCipher(Cipher.ENCRYPT_MODE); final byte[] encryptedByteArray = cipher.doFinal(dataToEncrypt.getBytes()); encryptedData = Base64.getEncoder().encode(encryptedByteArray); } catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException e) { log.error("Encryption error: {} ", e); } log.info("Processed encrypt..."); return new String(encryptedData); } public String decrypt(final String encryptedData) { log.info("Processing decrypt..."); String decryptedData = ""; try { final Cipher cipher = initCipher(Cipher.DECRYPT_MODE); final byte[] encryptedByteArray = Base64.getDecoder().decode(encryptedData.getBytes()); final byte[] decryptedByteArray = cipher.doFinal(encryptedByteArray); decryptedData = new String(decryptedByteArray, StandardCharsets.UTF_8); } catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException e) { log.error("Decryption error: {} ", e); } log.info("Processed decrypt..."); return decryptedData; } }
当前Go解密代码(存在问题)
func decrypt(key []byte, secure string) (decoded string, err error) { //Remove base64 encoding: cipherText, err := base64.StdEncoding.DecodeString(secure) //IF DecodeString failed, exit: if err != nil { return } //Create a new AES cipher with the key and encrypted message block, err := aes.NewCipher(key) //IF NewCipher failed, exit: if err != nil { return } //IF the length of the cipherText is less than 16 Bytes: if len(cipherText) < aes.BlockSize { err = errors.New("ciphertext block size is too short") return } iv := cipherText[:aes.BlockSize] cipherText = cipherText[aes.BlockSize:] fmt.Println("before deciphering: ", string(cipherText)) //Decrypt the message stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(cipherText, cipherText) return string(cipherText), err }
尝试的密钥处理代码(存在问题)
key := "94k/IwqJQ5wf4Yt5JZmbW85r2x246rI3g3LZbTI80Vo=" key_decr := md5.Sum([]byte(key)) key = hex.EncodeToString(key_decr[:]) log.Println("key:", key) decrypt(key, secureText)
问题分析与修正
1. 密钥处理错误
Java中是将MD5生成的32位小写十六进制字符串转成UTF-8字节数组作为AES密钥,而非直接使用MD5哈希的原始字节。当前Go代码中,调用decrypt时传入的是字符串key,但decrypt函数需要的是该字符串的[]byte(即[]byte(key)),而非原密钥的Base64解码结果。
2. IV使用错误
Java使用固定IV,但当前Go代码错误地从密文开头截取IV,这与Java逻辑完全不符,必须使用和Java一致的固定IV。
3. CFB8模式适配
Go标准库的cipher.NewCFBDecrypter默认实现的是CFB128(块大小16字节),但Java用的是CFB8(块大小1字节),需要手动实现CFB8的流解密逻辑。
修正后的完整Go代码
package main import ( "crypto/aes" "crypto/cipher" "crypto/md5" "encoding/base64" "encoding/hex" "errors" "fmt" "log" ) // 对应Java的INITIAL_VECTOR,将有符号byte转为无符号uint8(Go的byte是uint8) var fixedIV = []byte{231, 9, 137, 91, 166, 112, 98, 216, 65, 150, 255, 96, 118, 243, 88, 85} // cfb8Decrypter 实现CFB8模式的解密流 type cfb8Decrypter struct { block cipher.Block prevBlock []byte } func newCFB8Decrypter(block cipher.Block, iv []byte) *cfb8Decrypter { if len(iv) != block.BlockSize() { panic("iv length must equal block size") } return &cfb8Decrypter{ block: block, prevBlock: append([]byte(nil), iv...), } } func (d *cfb8Decrypter) XORKeyStream(dst, src []byte) { if len(dst) < len(src) { panic("dst length less than src") } for i := 0; i < len(src); i++ { // 加密当前块(初始为IV,后续为前一个密文块) var cipherBlock [aes.BlockSize]byte d.block.Encrypt(cipherBlock[:], d.prevBlock) // 取第一个字节和明文异或 dst[i] = src[i] ^ cipherBlock[0] // 更新prevBlock:左移1字节,末尾追加当前密文字节 copy(d.prevBlock, d.prevBlock[1:]) d.prevBlock[len(d.prevBlock)-1] = src[i] } } func decrypt(aesKey []byte, secure string) (string, error) { // Base64解码密文 cipherText, err := base64.StdEncoding.DecodeString(secure) if err != nil { return "", err } // 创建AES块 block, err := aes.NewCipher(aesKey) if err != nil { return "", err } // 初始化CFB8解密流 stream := newCFB8Decrypter(block, fixedIV) // 解密:直接在原切片上修改 stream.XORKeyStream(cipherText, cipherText) return string(cipherText), nil } func main() { // 原始密钥(对应Java的designSecretKey) originalKey := "94k/IwqJQ5wf4Yt5JZmbW85r2x246rI3g3LZbTI80Vo=" // 待解密的密文(Java加密后的Base64字符串) secureText := "..." // 替换为实际密文 // 按Java逻辑处理密钥:MD5→32位小写十六进制→UTF-8字节数组 md5Sum := md5.Sum([]byte(originalKey)) keyHex := hex.EncodeToString(md5Sum[:]) aesKey := []byte(keyHex) // 解密 result, err := decrypt(aesKey, secureText) if err != nil { log.Fatal(err) } fmt.Println("解密结果:", result) }
内容的提问来源于stack exchange,提问作者Pramit Pakhira
相关产品推荐
相关产品推荐

