douchi0028 2018-10-01 22:45
浏览 569
已采纳

从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
}
  • 写回答

1条回答 默认 最新

  • douying7289 2018-10-02 13:42
    关注

    Normally an encrypted message looks like IV+ciphertext+tag, not ciphertext+IV+tag. When one deviates from the convention one runs into all kinds of issues :-)

    Have you seen what happens to the iv slice after the call append(ciphertext, tag...)? You essentially overwrite the iv with the tag:

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

    As a quick fix, make a copy of the iv before calling append():

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

    More info on slices can be found here.

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

报告相同问题?

悬赏问题

  • ¥30 使用C++实现ATM系统
  • ¥20 求帮,直连能连上oracle12,但是thinkphp6就是报错
  • ¥15 paddleocr运行报错
  • ¥15 怎么用 matlab 设计滞后-超前串联校正网络
  • ¥15 MFC引用C#生成的dll,将dll放置到非exe程序目录,如何操作
  • ¥15 C#创建webservice接口,三方通过多次跳转访问本方服务,获取wsdl文档,wsdl中ip地址为局域网内本机地址而非三方直接访问的地址。
  • ¥15 关于#wireshark#的问题:需要安卓app流量数据集要安卓流量做包序列长度的实验,比如某些流量是在看视频还是在发评论
  • ¥15 Smail语句如何使用判断语句跳过验证卡密界面
  • ¥15 关于#wireshark#的问题:并且能够给数据做标注,如这个流量是在看视频或者是在转账
  • ¥15 运筹优化,gurobi,python