代码之家  ›  专栏  ›  技术社区  ›  Brian Leishman

aes-256-gcm从PHP到Golang的解密

  •  1
  • Brian Leishman  · 技术社区  · 7 年前

    我在PHP中使用了一个加密函数

    function Encrypt(?string $Content, string $Key): string {
        return openssl_encrypt($Content, 'aes-256-gcm', $Key, OPENSSL_RAW_DATA, $IV = random_bytes(16), $Tag, '', 16) . $IV . $Tag;
    }
    

    与解密函数配对

    function Decrypt(?string $Ciphertext, string $Key): ?string {
        if (strlen($Ciphertext) < 32)
            return null;
    
        $Content = substr($Ciphertext, 0, -32);
        $IV = substr($Ciphertext, -32, -16);
        $Tag = substr($Ciphertext, -16);
    
        try {
            return openssl_decrypt($Content, 'aes-256-gcm', $Key, OPENSSL_RAW_DATA, $IV, $Tag);
        } catch (Exception $e) {
            return null;
        }
    }
    

    我将加密函数中加密的数据存储到数据库中,现在我正试图在Go中解密这些值,但是我得到了 cipher: message authentication failed 我不知道我错过了什么。

    c := []byte(`encrypted bytes of sorts`) // the bytes from the db
    
    content := c[:len(c)-32]
    iv := c[len(c)-32 : len(c)-16]
    tag := c[len(c)-16:]
    
    block, err := aes.NewCipher(key[:32])
    if err != nil {
        panic(err.Error())
    }
    
    aesgcm, err := cipher.NewGCMWithNonceSize(block, 16)
    if err != nil {
        panic(err.Error())
    }
    
    fmt.Println(aesgcm.NonceSize(), aesgcm.Overhead()) // making sure iv and tag are both 16 bytes
    
    plaintext, err := aesgcm.Open(nil, iv, append(content, tag...), nil)
    if err != nil {
        panic(err.Error())
    }
    

    值得注意的是,我使用的密钥不是32字节(它要大得多),因为我不知道需要/应该是32字节的密钥,所以我不完全确定PHP用它做了什么(比如将它截断为32字节,而用32字节的输出对它进行散列,而不是其他东西)。

    在看 Open

    // copied from C:\Go\src\crypto\cipher\gcm.go, Go version 1.11
    func (g *gcm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
        if len(nonce) != g.nonceSize {
            panic("cipher: incorrect nonce length given to GCM")
        }
    
        if len(ciphertext) < gcmTagSize {
            return nil, errOpen
        }
        if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(g.cipher.BlockSize())+gcmTagSize {
            return nil, errOpen
        }
    
        tag := ciphertext[len(ciphertext)-gcmTagSize:]
        ciphertext = ciphertext[:len(ciphertext)-gcmTagSize]
    
        var counter, tagMask [gcmBlockSize]byte
        g.deriveCounter(&counter, nonce)
    
        g.cipher.Encrypt(tagMask[:], counter[:])
        gcmInc32(&counter)
    
        var expectedTag [gcmTagSize]byte
        g.auth(expectedTag[:], ciphertext, data, &tagMask)
    
        ret, out := sliceForAppend(dst, len(ciphertext))
    
        if subtle.ConstantTimeCompare(expectedTag[:], tag) != 1 {
            // The AESNI code decrypts and authenticates concurrently, and
            // so overwrites dst in the event of a tag mismatch. That
            // behavior is mimicked here in order to be consistent across
            // platforms.
            for i := range out {
                out[i] = 0
            }
            return nil, errOpen
        }
    
        g.counterCrypt(out, ciphertext, &counter)
    
        return ret, nil
    }
    

    使用上述函数的PHP示例

    $Key = 'outspoken outburst treading cramp cringing';
    
    echo bin2hex($Enc = Encrypt('yeet', $Key)), '<br>'; // 924b3ba418f49edc1757f3fe88adcaa7ec4c1e7d15811fd0b712b0b091433073f6a38d7b
    var_export(Decrypt($Enc, $Key)); // 'yeet'
    

    c, err := hex.DecodeString(`924b3ba418f49edc1757f3fe88adcaa7ec4c1e7d15811fd0b712b0b091433073f6a38d7b`)
    if err != nil {
        panic(err.Error())
    }
    key := []byte(`outspoken outburst treading cramp cringing`)
    
    content := c[:len(c)-32]
    iv := c[len(c)-32 : len(c)-16]
    tag := c[len(c)-16:]
    
    block, err := aes.NewCipher(key[:32])
    if err != nil {
        panic(err.Error())
    }
    
    aesgcm, err := cipher.NewGCMWithNonceSize(block, 16)
    if err != nil {
        panic(err.Error())
    }
    
    ciphertext := append(content, tag...) // or `ciphertext := content`, same error
    
    plaintext, err := aesgcm.Open(nil, iv, ciphertext, nil)
    if err != nil {
        panic(err.Error()) // panic: cipher: message authentication failed
    }
    
    1 回复  |  直到 7 年前
        1
  •  1
  •   rustyx    7 年前

    通常加密的消息看起来像 IV+ciphertext+tag ciphertext+IV+tag . 当一个人偏离惯例时,他会遇到各种各样的问题:-)

    iv 通话结束后 append(ciphertext, tag...) ? tag :

    Before:
    924b3ba4 18f49edc1757f3fe88adcaa7ec4c1e7d 15811fd0b712b0b091433073f6a38d7b
    After:
    924b3ba4 15811fd0b712b0b091433073f6a38d7b 15811fd0b712b0b091433073f6a38d7b
    

    作为一个快速解决方案,制作一份 append()

    iv := make([]byte, 16)
    copy(iv, c[len(c)-32 : len(c)-16])
    

    更多关于切片的信息可以找到 here .

    推荐文章