Golang AES解密无法还原完整UUID问题求助及原因分析
问题原因
- 单块加密限制:你直接调用
aes.Cipher的Encrypt/Decrypt方法,该方法仅支持处理16字节的单个AES数据块。你的UUID长度为36字节,超出单块容量,因此只有前16字节被加密,剩余20字节在加密过程中保持初始的0值。 - 空字符截断:解密后字节数组中存在大量
\x00空字符,Go将字节数组转换为字符串时,会把\x00视为字符串终止标记,导致后续内容无法显示。
解决办法
AES作为块加密算法,必须配合分组模式(如GCM、CBC)和填充方案才能处理任意长度的数据。推荐使用GCM模式,它同时提供加密和数据完整性认证,安全性更高。
以下是修正后的GCM模式实现代码:
package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/hex" "fmt" "io" ) func main() { key := "thisis32bitlongpassphraseimusing" // 32字节对应AES-256 pt := "a30a1777-e9f4-ed45-4755-add00172ebae" ct, err := EncryptAESGCM([]byte(key), []byte(pt)) if err != nil { panic(err) } fmt.Println("明文:", pt) fmt.Println("密文(hex):", hex.EncodeToString(ct)) decrypted, err := DecryptAESGCM([]byte(key), ct) if err != nil { panic(err) } fmt.Println("解密结果:", string(decrypted)) } // EncryptAESGCM 使用AES-GCM模式加密数据 func EncryptAESGCM(key, plaintext []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } // GCM模式需要随机Nonce,推荐长度12字节 gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return nil, err } // 加密:密文 = Nonce + GCM加密结果(包含认证标签) ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) return ciphertext, nil } // DecryptAESGCM 使用AES-GCM模式解密数据 func DecryptAESGCM(key, ciphertext []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } nonceSize := gcm.NonceSize() if len(ciphertext) < nonceSize { return nil, fmt.Errorf("密文长度不足") } nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) if err != nil { return nil, err // 验证失败,可能密文被篡改 } return plaintext, nil }
关键说明
- Nonce:每次加密必须使用唯一的随机Nonce,不能重复使用,否则会导致密钥泄露。GCM推荐Nonce长度为12字节。
- 密文结构:加密后的密文包含Nonce + 加密数据 + 认证标签,解密时需要先分离Nonce。
- 安全性:GCM模式会自动验证数据完整性,若密文被篡改,解密会直接报错,避免恶意数据被解密。
如果需要使用CBC模式(需手动处理填充),可以参考以下实现:
package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/hex" "fmt" "io" ) // PKCS7填充 func pkcs7Pad(data []byte, blockSize int) []byte { padding := blockSize - len(data)%blockSize padText := make([]byte, padding) for i := range padText { padText[i] = byte(padding) } return append(data, padText...) } // PKCS7去填充 func pkcs7Unpad(data []byte) ([]byte, error) { length := len(data) if length == 0 { return nil, fmt.Errorf("空数据") } padding := int(data[length-1]) if padding > length || padding == 0 { return nil, fmt.Errorf("填充无效") } return data[:length-padding], nil } func EncryptAESCBC(key, plaintext []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } // CBC需要IV,长度等于块大小(16字节) iv := make([]byte, aes.BlockSize) if _, err := io.ReadFull(rand.Reader, iv); err != nil { return nil, err } // 填充明文到块大小倍数 paddedPlaintext := pkcs7Pad(plaintext, aes.BlockSize) mode := cipher.NewCBCEncrypter(block, iv) ciphertext := make([]byte, len(paddedPlaintext)) mode.CryptBlocks(ciphertext, paddedPlaintext) // 密文 = IV + 加密数据 return append(iv, ciphertext...), nil } func DecryptAESCBC(key, ciphertext []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } if len(ciphertext) < aes.BlockSize { return nil, fmt.Errorf("密文长度不足") } iv := ciphertext[:aes.BlockSize] ciphertext = ciphertext[aes.BlockSize:] mode := cipher.NewCBCDecrypter(block, iv) plaintext := make([]byte, len(ciphertext)) mode.CryptBlocks(plaintext, ciphertext) // 去除填充 return pkcs7Unpad(plaintext) } func main() { key := "thisis32bitlongpassphraseimusing" pt := "a30a1777-e9f4-ed45-4755-add00172ebae" ct, err := EncryptAESCBC([]byte(key), []byte(pt)) if err != nil { panic(err) } fmt.Println("明文:", pt) fmt.Println("密文(hex):", hex.EncodeToString(ct)) decrypted, err := DecryptAESCBC([]byte(key), ct) if err != nil { panic(err) } fmt.Println("解密结果:", string(decrypted)) }
内容的提问来源于stack exchange,提问作者Ananda G
相关产品推荐
相关产品推荐

