go - Go中AES-GCM + Base64后无法解密

标签 go cryptography base64

情况

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

见下面的实现代码

问题

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

代码

下面是我的 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) 函数实际上只是从接受的答案中复制粘贴: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/

相关文章:

java - 如何创建base64Binary数据?

pointers - [Golang]指针类型、指针类型和结构体类型调用方法有什么不同?

go - 使用反射获取指针接收器的go-gin的handlerFunc

dictionary - 分配给 nil 映射中的条目

C++ 加密库在 Apple Swift 中的使用

java - java中AES解密中的奇怪字符

sql - 如何使用 GORM 从多对多关系相关的其他表中过滤包含实体的表?

c - MD4 RFC1320 给出错误的哈希值

javascript - 下载 IE9 的 base64 编码图像

android - 为 URI 使用编码图像