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.

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

报告相同问题?

悬赏问题

  • ¥15 网络科学导论,网络控制
  • ¥15 metadata提取的PDF元数据,如何转换为一个Excel
  • ¥15 关于arduino编程toCharArray()函数的使用
  • ¥100 vc++混合CEF采用CLR方式编译报错
  • ¥15 coze 的插件输入飞书多维表格 app_token 后一直显示错误,如何解决?
  • ¥15 vite+vue3+plyr播放本地public文件夹下视频无法加载
  • ¥15 c#逐行读取txt文本,但是每一行里面数据之间空格数量不同
  • ¥50 如何openEuler 22.03上安装配置drbd
  • ¥20 ING91680C BLE5.3 芯片怎么实现串口收发数据
  • ¥15 无线连接树莓派,无法执行update,如何解决?(相关搜索:软件下载)