php - 从 PHP 到 Golang 的 aes-256-gcm 解密
问题描述
我有一个在 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 字节输出的东西与其他东西进行散列一样)。
查看 Go 源代码中的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
}
解决方案
通常一个加密的消息看起来像IV+ciphertext+tag
,不是ciphertext+IV+tag
。当一个人偏离约定时,就会遇到各种问题:-)
你有没有看到调用后iv
切片append(ciphertext, tag...)
发生了什么?您基本上用以下内容覆盖iv
了tag
:
Before:
924b3ba4 18f49edc1757f3fe88adcaa7ec4c1e7d 15811fd0b712b0b091433073f6a38d7b
After:
924b3ba4 15811fd0b712b0b091433073f6a38d7b 15811fd0b712b0b091433073f6a38d7b
iv
作为一个快速修复,在调用之前制作一个副本append()
:
iv := make([]byte, 16)
copy(iv, c[len(c)-32 : len(c)-16])
有关切片的更多信息,请参见此处。
推荐阅读
- c# - 有没有办法使用 C# 中的 MailMessage 或任何类似的方法来设置电子邮件个人资料图片?
- php - 弹出显示数据库中的条目列表,以便在按下编辑按钮时从中选择
- azure - 容器如何~读取~存储在(安装的)秘密卷中的秘密?
- unity3d - 脚本不适用于多个相同的对象
- android - Coroutines:在特定的 CoroutineContext 上运行 Deferred
- r - 获取数据框中每一行的升序列名列表
- asp.net-core - .NET Core 新的 SelectList 返回 null
- python-3.x - 如何创建多个 Python 脚本并同时运行它们?
- c# - 如何使用 EditText 字段(纯文本)的输入在服务器上编写/创建文本文件?
- redux - 为什么 rxjs 史诗不起作用?