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

I have an encryption function that I use in 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;
}

Paired with a decryption function

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;
    }
}

I store data encrypted from the encryption function into my db, and now I'm trying to decrypt those same values in Go, but I'm getting cipher: message authentication failed and I can't figure out what I'm missing.

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())
}

It's worth noting that the key I'm using isn't 32 bytes (it's way bigger), as I didn't know the key needed/should be 32 bytes, so I'm not entirely sure what PHP is doing with it (as in truncating it to 32 vs hashing it with something having 32 bytes of output vs some other thing).

Looking at the Open function from the Go source, it looks like the tag should be the last "tag size" bytes of the text, so that's why I'm appending the tag to the ciphertext after parsing the pieces.

// 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 example using the functions above

$Key = 'outspoken outburst treading cramp cringing';

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

Go

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
}
查看全部
douchi0028
douchi0028
2018/10/01 22:45
  • encryption
  • openssl
  • php
  • 点赞
  • 收藏
  • 回答
    私信
满意答案
查看全部

1个回复