doueta6642 2017-11-19 21:10 采纳率: 0%
浏览 538
已采纳

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

Situation

I'm trying to implement a structure (the CryptoService) hiding en/decryption from the main program flow. I have implemented "normal" functions and base64 variants that should encode the cipher to it's base64 equivalent and visa-versa in decryption. This is done because our internal network protocol uses line-feed as delimiter.

See code of implementation below

Problem

After writing the code below i started testing it. At first it went well and en- and decryption worked but soon I started noticing "randomly occurring" errors during the decryption process: cipher: message authentication failed. Now the important fact: the errors ONLY returned from the DecryptBase64 func. But the base64 usage in go is pretty straight-forward and not much to worry about so I don't have any idea where the problems lies.

Code

Below you see the code for my CryptoService implementation and the associated test file. I have tried to clean the code up as much as possible (remove comments, additional input checks, etc.) without removing context. Nevertheless it's much code so thanks to all that read it - really appreciate your help!

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 works fine
  • TestCryptoService_EncryptBase64DecryptBase64Roundtrip fails "sometimes" (also note that it doesn't always fail on the same test-cases)

Additional info: The FastRandomString(n int) func in the Dynamic-Test-Case creation is effectively only a copy-and-past from the accepted answer at: 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
                }
            }
        })
    }
}
  • 写回答

1条回答 默认 最新

  • douji4948 2017-11-19 22:14
    关注

    Someone from the GopherSlack community came up with the solution:

    StdEncoding pads it's results which in this case caused decryption problems when (during the encryption process) padding the output was necessary. Therefore you should use RawStdEncoding in this example.

    Thanks for the help! :)

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥30 Matlab打开默认名称带有/的光谱数据
  • ¥50 easyExcel模板 动态单元格合并列
  • ¥15 res.rows如何取值使用
  • ¥15 在odoo17开发环境中,怎么实现库存管理系统,或独立模块设计与AGV小车对接?开发方面应如何设计和开发?请详细解释MES或WMS在与AGV小车对接时需完成的设计和开发
  • ¥15 CSP算法实现EEG特征提取,哪一步错了?
  • ¥15 游戏盾如何溯源服务器真实ip?需要30个字。后面的字是凑数的
  • ¥15 vue3前端取消收藏的不会引用collectId
  • ¥15 delphi7 HMAC_SHA256方式加密
  • ¥15 关于#qt#的问题:我想实现qcustomplot完成坐标轴
  • ¥15 下列c语言代码为何输出了多余的空格