情况

我正在尝试实现一个结构(CryptoService)从主程序流中隐藏加密/解密。我已经实现了“正常”函数和 base64 变体,它们应该将密码编码为它的 base64 等价物,反之亦然。这样做是因为我们的内部网络协议(protocol)使用换行符 \n 作为分隔符。

见下面的实现代码

问题

写完下面的代码后,我开始测试它。起初它进展顺利,并且 en- 和解密工作但很快我开始注意到解密过程中“随机发生”的错误: cipher: message authentication failed 。现在重要的事实是:错误 只有 DecryptBase64 func 返回。但是 go 中的 base64 使用非常简单,无需担心,所以我不知道问题出在哪里。

代码

下面是我的 CryptoService 实现的代码和相关的测试文件。我试图在不删除上下文的情况下尽可能地清理代码(删除注释、额外的输入检查等)。尽管如此,代码还是很多,所以感谢所有阅读它的人 - 非常感谢您的帮助!

cryptoservice.go

type CryptoService struct {
    gcm cipher.AEAD
}

func NewCryptoService(key []byte) (cs *CryptoService, err error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }
    return &CryptoService{
        gcm: gcm,
    }, nil
}

func (cs CryptoService) Encrypt(plain []byte) (cipher []byte, err error) {
    nonce := make([]byte, cs.gcm.NonceSize())
    _, err = io.ReadFull(rand.Reader, nonce)
    if err != nil {
        return nil, err
    }

    cipher = cs.gcm.Seal(nil, nonce, plain, nil)
    cipher = append(nonce, cipher...)

    return cipher, nil
}

func (cs CryptoService) EncryptBase64(plain []byte) (base64Cipher []byte, err error) {
    cipher, err := cs.Encrypt(plain)
    if err != nil {
        return nil, err
    }

    base64Cipher = make([]byte, base64.StdEncoding.EncodedLen(len(cipher)))
    base64.StdEncoding.Encode(base64Cipher, cipher)

    return
}

func (cs CryptoService) Decrypt(cipher []byte) (plain []byte, err error) {
    nonce := cipher[0:cs.gcm.NonceSize()]
    ciphertext := cipher[cs.gcm.NonceSize():]

    plain, err = cs.gcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        return nil, err
    }

    return
}

func (cs CryptoService) DecryptBase64(base64Cipher []byte) (plain []byte, err error) {
    cipher := make([]byte, base64.StdEncoding.DecodedLen(len(base64Cipher)))
    _, err = base64.StdEncoding.Decode(cipher, base64Cipher)
    if err != nil {
        return nil, err
    }

    return cs.Decrypt(cipher)
}

cryptoservice_test.go
  • TestCryptoService_EncryptDecryptRoundtrip 工作正常
  • TestCryptoService_EncryptBase64DecryptBase64Roundtrip “有时”失败(另请注意,它并不总是在相同的测试用例上失败)

  • 附加信息:动态测试用例创建中的 FastRandomString(n int) 函数实际上只是来自以下接受答案的复制和过Go:How to generate a random string of a fixed length in golang?
    func TestCryptoService_EncryptDecryptRoundtrip(t *testing.T) {
        tests := []struct {
            name   string
            aeskey string
            text   string
        }{
            {"Simple 1", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", "Text"},
            {"Simple 2", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", "Some random content"},
            {"Simple 3", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua."},
    
            {"Dynamic 1", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", FastRandomString(32)},
            {"Dynamic 2", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", FastRandomString(1024)},
            {"Dynamic 3", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", FastRandomString(1024 * 64)},
            {"Dynamic 4", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", FastRandomString(1024 * 256)},
        }
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                for i := 0; i < 1000; i++ {
                    key, _ := hex.DecodeString(tt.aeskey)
                    cs, _ := NewCryptoService(key)
                    cipher, err := cs.Encrypt([]byte(tt.text))
                    if err != nil {
                        t.Errorf("CryptoService.Encrypt() error = %v", err)
                        return
                    }
    
                    plain, err := cs.Decrypt(cipher)
                    if err != nil {
                        t.Errorf("CryptoService.Decrypt() error = %v", err)
                        return
                    }
    
                    plainStr := string(plain)
                    if plainStr != tt.text {
                        t.Errorf("CryptoService.Decrypt() plain = %v, want = %v", plainStr, tt.text)
                        return
                    }
                }
            })
        }
    }
    
    func TestCryptoService_EncryptBase64DecryptBase64Roundtrip(t *testing.T) {
        tests := []struct {
            name   string
            aeskey string
            text   string
        }{
            {"Simple 1", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", "Text"},
            {"Simple 2", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", "Some random content"},
            {"Simple 3", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua."},
    
            {"Dynamic 1", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", FastRandomString(32)},
            {"Dynamic 2", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", FastRandomString(1024)},
            {"Dynamic 3", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", FastRandomString(1024 * 64)},
            {"Dynamic 4", "c4cc0dfc4ae0e45c045727f84ffd373127453bc232230bf1386972ac692436c1", FastRandomString(1024 * 256)},
        }
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                for i := 0; i < 1000; i++ {
                    key, _ := hex.DecodeString(tt.aeskey)
                    cs, _ := NewCryptoService(key)
                    cipher, err := cs.EncryptBase64([]byte(tt.text))
                    if err != nil {
                        t.Errorf("CryptoService.EncryptBase64() error = %v", err)
                        return
                    }
    
                    plain, err := cs.DecryptBase64(cipher)
                    if err != nil {
                        t.Errorf("CryptoService.DecryptBase64() error = %v", err)
                        return
                    }
    
                    plainStr := string(plain)
                    if plainStr != tt.text {
                        t.Errorf("CryptoService.DecryptBase64() plain = %v, want = %v", plainStr, tt.text)
                        return
                    }
                }
            })
        }
    }
    

    最佳答案

    GopherSlack 社区的某个人提出了解决方案:
    StdEncoding 填充它的结果,在这种情况下,当(在加密过程中)需要填充输出时会导致解密问题。因此,您应该在此示例中使用 RawStdEncoding

    谢谢您的帮助! :)

    关于go - Go 中的 AES-GCM + Base64 后无法解密,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47382035/

    10-12 00:21
    查看更多