AES是目前应用最广泛的分组加密算法之一,CFB模式作为流加密模式的一种,适合处理流式数据场景。很多团队在同时使用Go和Ruby开发不同服务时,需要实现两端AES CFB模式加密互通,但经常会因为密钥长度、填充方式等细节不匹配导致解密失败。本文主要讲解密钥长度匹配的相关策略,帮助开发者快速实现两端加密互通。

一、AES CFB模式密钥长度基本要求
AES算法本身支持的密钥长度只有三种:128位(16字节)、192位(24字节)、256位(32字节),CFB模式作为AES的工作模式,本身不限制密钥长度,但底层AES算法要求密钥必须符合上述三种长度。如果传入的密钥长度不符合要求,两端语言都会直接报错或者返回错误结果。
密钥长度不匹配的常见问题
- Go中如果密钥长度不对,crypto/aes包会返回
key size must be 16, 24 or 32 bytes错误 - Ruby中如果密钥长度不对,OpenSSL会抛出
key length too short或者相关的密钥校验错误 - 两端密钥长度不一致时,加密后的结果完全不同,无法互相解密
二、密钥长度匹配的核心策略
1. 统一使用标准密钥长度
最稳妥的方式是直接生成符合AES要求的16、24、32字节长度的密钥,避免后续处理。比如生成32字节的随机密钥用于256位AES加密,两端直接使用该密钥即可,不需要额外处理。
2. 非标准长度密钥的哈希处理
如果现有系统的密钥长度不符合要求,不要直接截断或者填充,推荐使用哈希算法将密钥转换为固定长度。比如使用SHA256对原始密钥做哈希,得到32字节的结果,刚好匹配256位AES的密钥要求,两端使用相同的哈希算法处理原始密钥即可保证一致。
注意哈希算法必须两端统一,比如都使用SHA256,不要一端用MD5一端用SHA256,否则处理后的密钥完全不同。
3. 明确CFB模式的参数对齐
CFB模式需要指定段大小(segment size),Go和Ruby的默认CFB段大小都是8位(1字节),如果一端修改了段大小,另一端也需要同步修改,否则也无法解密。同时IV(初始化向量)的长度必须是16字节(AES分组长度),两端需要使用相同的IV,或者约定IV的传递方式。
三、Go端AES CFB加密实现示例
下面是Go使用256位密钥(32字节)、CFB模式加密的实现,密钥通过SHA256处理原始字符串得到,保证长度符合要求:
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"fmt"
)
// 加密函数,原始密钥可以是任意长度,内部用SHA256处理成32字节
func encrypt(plainText string, originKey string) (string, error) {
// 对原始密钥做SHA256哈希,得到32字节的密钥
keyHash := sha256.Sum256([]byte(originKey))
key := keyHash[:]
// 生成16字节的IV,实际场景中可以和密文一起传递
iv := []byte("1234567890abcdef")
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// CFB模式加密
stream := cipher.NewCFBEncrypter(block, iv)
cipherText := make([]byte, len(plainText))
stream.XORKeyStream(cipherText, []byte(plainText))
// 返回十六进制编码的密文,方便传递
return hex.EncodeToString(cipherText), nil
}
// 解密函数
func decrypt(cipherTextHex string, originKey string) (string, error) {
keyHash := sha256.Sum256([]byte(originKey))
key := keyHash[:]
iv := []byte("1234567890abcdef")
cipherText, err := hex.DecodeString(cipherTextHex)
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
stream := cipher.NewCFBDecrypter(block, iv)
plainText := make([]byte, len(cipherText))
stream.XORKeyStream(plainText, cipherText)
return string(plainText), nil
}
func main() {
originKey := "my_custom_key_123" // 原始密钥长度不符合32字节要求
plainText := "hello_aes_cfb_test"
cipherText, err := encrypt(plainText, originKey)
if err != nil {
fmt.Println("加密失败:", err)
return
}
fmt.Println("密文:", cipherText)
decrypted, err := decrypt(cipherText, originKey)
if err != nil {
fmt.Println("解密失败:", err)
return
}
fmt.Println("解密结果:", decrypted)
}四、Ruby端AES CFB加密实现示例
Ruby使用OpenSSL库实现AES CFB加密,同样用SHA256处理原始密钥,保证和Go端的密钥一致:
require 'openssl'
require 'digest'
# 加密函数,原始密钥处理后保证32字节长度
def encrypt(plain_text, origin_key)
# 对原始密钥做SHA256哈希,得到32字节密钥
key = Digest::SHA256.digest(origin_key)
# 16字节IV,和Go端保持一致
iv = "1234567890abcdef"
cipher = OpenSSL::Cipher.new('aes-256-cfb')
cipher.encrypt
cipher.key = key
cipher.iv = iv
cipher_text = cipher.update(plain_text) + cipher.final
# 返回十六进制编码的密文
cipher_text.unpack1('H*')
end
# 解密函数
def decrypt(cipher_text_hex, origin_key)
key = Digest::SHA256.digest(origin_key)
iv = "1234567890abcdef"
cipher = OpenSSL::Cipher.new('aes-256-cfb')
cipher.decrypt
cipher.key = key
cipher.iv = iv
cipher_text = [cipher_text_hex].pack('H*')
cipher.update(cipher_text) + cipher.final
end
origin_key = "my_custom_key_123"
plain_text = "hello_aes_cfb_test"
cipher_text = encrypt(plain_text, origin_key)
puts "密文: #{cipher_text}"
decrypted = decrypt(cipher_text, origin_key)
puts "解密结果: #{decrypted}"五、互通测试与注意事项
运行上述两端代码,使用相同的原始密钥和明文,得到的密文是一致的,解密后也能得到正确的明文。测试时需要注意以下几点:
- IV必须两端完全一致,实际生产环境中可以把IV放在密文前面一起传递,解密时提取前16字节作为IV
- 哈希算法必须两端一致,不要Go用SHA256,Ruby用SHA512
- 避免对明文做额外的填充,CFB模式是流加密模式,不需要像CBC模式那样做PKCS5/PKCS7填充,两端都不要加填充逻辑
- 编码方式统一,比如都使用十六进制编码传递密文,不要一端用base64一端用十六进制
只要按照上述密钥长度匹配策略实现,Go和Ruby之间的AES CFB模式加密就可以稳定互通,满足跨服务的数据加密需求。