Golang中cipher消息认证失败问题排查

Golang中cipher消息认证失败问题排查 我正在使用 crypto/cipher 对密码进行加密和解密。客户端加密密码并通过网络发送到远程服务器。当服务器接收到密码时,在解密过程中会出现“cipher: message authentication failed”错误。此问题仅在通过网络通信时出现。

当客户端和服务器在同一台机器上运行时,不会出现此问题。

我遵循了 https://golang.org/src/crypto/cipher/gcm.go 中提到的标准实现:

  • Nonce 的长度为 12,并且每次使用 rand 函数生成新的 nonce。
  • 加密实现了 gcm.Seal(nonce, nonce, []byte(passwordStr), nil)
  • 解密实现了 gcm.Open(nil, nonce, encrypted, nil)。错误出现在此方法中。

如果有人遇到过同样的问题,请告知。


更多关于Golang中cipher消息认证失败问题排查的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

你是如何将数据从客户端发送到服务器的?

更多关于Golang中cipher消息认证失败问题排查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我也遇到了同样的问题。@nandinimurthy 你解决了吗?你是怎么做的?

这个问题仅在通过网络通信时出现。

如果你仅在通过网络发送加密字符串时遇到此问题,或许你应该注意数据的编码,例如:

urlencoder.io

如何在 Golang 中对字符串进行 URL 编码

Golang URL 编码示例。学习如何在 Golang 中将字符串编码为 URL 编码格式。Go 的 net/url 包提供了 QueryEscape()PathEscape()QueryUnescape()PathUnescape() 等函数来执行 URL 编码和解码。

How to URL Encode a String in Golang

这是一个典型的网络传输中数据损坏或格式处理不当导致的认证失败问题。当你在同一台机器上测试时,数据在内存中传递,不会出现问题;但通过网络传输时,数据可能被修改或序列化/反序列化过程中出现错误。

主要排查方向:

1. 网络传输中的数据损坏

确保加密后的字节数据在网络传输中没有被修改。使用 base64 或十六进制编码进行传输:

// 客户端加密并编码
func encryptAndEncode(password string, key []byte) (string, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return "", err
    }
    
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }
    
    nonce := make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return "", err
    }
    
    ciphertext := gcm.Seal(nonce, nonce, []byte(password), nil)
    // 使用 base64 编码确保网络传输安全
    return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// 服务器端解码并解密
func decodeAndDecrypt(encodedCiphertext string, key []byte) (string, error) {
    // 先解码 base64
    ciphertext, err := base64.StdEncoding.DecodeString(encodedCiphertext)
    if err != nil {
        return "", err
    }
    
    block, err := aes.NewCipher(key)
    if err != nil {
        return "", err
    }
    
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }
    
    nonceSize := gcm.NonceSize()
    if len(ciphertext) < nonceSize {
        return "", fmt.Errorf("ciphertext too short")
    }
    
    nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
    plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        return "", err
    }
    
    return string(plaintext), nil
}

2. 检查 nonce 的处理

你的代码中 gcm.Seal(nonce, nonce, ...) 将 nonce 作为 dst 的第一个参数,这会将 nonce 前置到密文中。但在解密时,你直接使用原始 nonce 而不是从密文中提取:

// 加密端
nonce := make([]byte, 12)
if _, err := rand.Read(nonce); err != nil {
    return nil, err
}
// nonce 会作为前缀添加到密文中
ciphertext := gcm.Seal(nonce, nonce, []byte(password), nil)

// 传输 ciphertext

// 解密端 - 错误的方式
// gcm.Open(nil, nonce, encrypted, nil) // 这里使用的 nonce 可能不对

// 解密端 - 正确的方式
func decrypt(ciphertext, key []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("ciphertext too short")
    }
    
    nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
    return gcm.Open(nil, nonce, ciphertext, nil)
}

3. 完整示例代码

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "io"
)

type CryptoHandler struct {
    key []byte
}

func NewCryptoHandler(key string) *CryptoHandler {
    // 确保密钥长度正确(16, 24 或 32 字节)
    keyBytes := []byte(key)
    if len(keyBytes) != 16 && len(keyBytes) != 24 && len(keyBytes) != 32 {
        panic("key must be 16, 24 or 32 bytes")
    }
    return &CryptoHandler{key: keyBytes}
}

func (c *CryptoHandler) Encrypt(plaintext string) (string, error) {
    block, err := aes.NewCipher(c.key)
    if err != nil {
        return "", err
    }
    
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }
    
    nonce := make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return "", err
    }
    
    ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
    return base64.StdEncoding.EncodeToString(ciphertext), nil
}

func (c *CryptoHandler) Decrypt(encodedCiphertext string) (string, error) {
    ciphertext, err := base64.StdEncoding.DecodeString(encodedCiphertext)
    if err != nil {
        return "", err
    }
    
    block, err := aes.NewCipher(c.key)
    if err != nil {
        return "", err
    }
    
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }
    
    nonceSize := gcm.NonceSize()
    if len(ciphertext) < nonceSize {
        return "", fmt.Errorf("ciphertext too short")
    }
    
    nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
    plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        return "", err
    }
    
    return string(plaintext), nil
}

// 使用示例
func main() {
    handler := NewCryptoHandler("32-byte-long-key-here-123456789")
    
    // 客户端加密
    encrypted, err := handler.Encrypt("myPassword123")
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Encrypted (for network): %s\n", encrypted)
    
    // 服务器解密
    decrypted, err := handler.Decrypt(encrypted)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Decrypted: %s\n", decrypted)
}

4. 网络传输调试

在发送和接收端添加调试日志:

// 发送前
fmt.Printf("Sending %d bytes, base64: %s\n", len(ciphertext), base64.StdEncoding.EncodeToString(ciphertext))

// 接收后
fmt.Printf("Received %d bytes, base64: %s\n", len(receivedData), base64.StdEncoding.EncodeToString(receivedData))

比较两端的 base64 输出是否完全一致。如果不一致,说明网络传输过程中数据被修改。

确保双方使用相同的密钥,并且 nonce 是真正随机的。网络传输中常见的编码问题(如 JSON 字符串转义、特殊字符处理)也可能导致数据损坏。

回到顶部