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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 06:21:29