gpt4 book ai didi

PHP 创建 ECDSA 签名并使用 Golang 验证

转载 作者:IT王子 更新时间:2023-10-29 01:28:09 25 4
gpt4 key购买 nike

我尝试使用 PHP 制作应用程序,为某些文档创建 ECDSA 签名,并使用 Golang 应用程序验证该签名。

我使用由 openssl 工具生成的私钥。它是 prime256v1 曲线 key 。使用命令创建:

openssl ecparam -name prime256v1 -genkey -noout -out prime256v1-key.pem

在 PHP 中,我使用 openssl_sign 函数创建签名。

我用 Golang 验证签名的所有尝试都失败了。在 Golang 中使用 crypto/ecdsa、crypto/elliptic 包。

这是我的代码。

PHP

<?php

$stringtosign = "my test string to sign";

// Privcate key was geerated with openssl tool with the command
// openssl ecparam -name prime256v1 -genkey -noout -out prime256v1-key.pem
$cert = file_get_contents('prime256v1-key.pem');

$prkey = openssl_pkey_get_private($cert);

// we sign only hashes, because Golang lib can wok with hashes only
$stringtosign = md5($stringtosign);

// we generate 64 length signature (r and s 32 bytes length)
while(1) {

openssl_sign($stringtosign, $signature, $prkey, OPENSSL_ALGO_SHA256);

$rlen = ord(substr($signature,3,1));

$slen = ord(substr($signature,5+$rlen,1));

if ($slen != 32 || $rlen != 32) {
// try other signature if length is not 32 for both parts
continue;
}
$r = substr($signature,4,$rlen);
$s = substr($signature,6+$rlen,$slen);

$signature = $r.$s;

break;
}
openssl_free_key($prkey);

$signature = bin2hex($signature);

echo $signature."\n";

语言

package main

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"math/big"

"crypto/x509"

"encoding/pem"
)

func main() {
stringtosign := "my test string to sign"

// This is outpur of PHP app. Signature generated by PHP openssl_sign
signature := "18d5c1d044a4a752ad91bc06499c72a590b2842b3d3b4c4b1086bfd0eea3e7eb5c06b77e15542e5ba944f3a1a613c24eabaefa4e2b2251bd8c9355bba4d14640"

// NOTE . Error verificaion is skipped here

// Privcate key was geerated with openssl tool with the command
// openssl ecparam -name prime256v1 -genkey -noout -out prime256v1-key.pem
prikeybytes, _ := ioutil.ReadFile("prime256v1-key.pem")

p, _ := pem.Decode(prikeybytes)

prikey, _ := x509.ParseECPrivateKey(p.Bytes)

signatureBytes, _ := hex.DecodeString(signature)

// make MD5 hash
h := md5.New()
io.WriteString(h, stringtosign)
data := h.Sum(nil)

// build key and verify data
r := big.Int{}
s := big.Int{}
// make signature numbers
sigLen := len(signatureBytes)
r.SetBytes(signatureBytes[:(sigLen / 2)])
s.SetBytes(signatureBytes[(sigLen / 2):])

curve := elliptic.P256()

// make public key from private key
x := big.Int{}
y := big.Int{}
x.SetBytes(prikey.PublicKey.X.Bytes())
y.SetBytes(prikey.PublicKey.Y.Bytes())
rawPubKey := ecdsa.PublicKey{Curve: curve, X: &x, Y: &y}

v := ecdsa.Verify(&rawPubKey, data, &r, &s)

if v {
fmt.Println("Success verify!")
return
}

fmt.Println(fmt.Sprintf("Signatire doed not match"))

}

我做错了什么?任何人都可以向我展示 Golang 验证用 PHP 创建的签名的工作示例吗?

我尝试在 openssl_sign 中使用不同版本而不是 OPENSSL_ALGO_SHA256 。试过 OPENSSL_ALGO_SHA1、OPENSSL_ALGO_SHA512

最佳答案

您的代码的问题似乎是,您在使用 OPENSSL_ALGO_SHA256 对其进行签名之前使用 MD5 对 PHP 中的字符串进行哈希处理,这会再次对您签名的内容(MD5 哈希)进行哈希处理,而在您的 Go 中程序,你只有这两个哈希中的第一个。要解决此问题,我将删除 PHP 代码中的 MD5 步骤,并将代码中的 h := md5.New() 行替换为您的签名使用的哈希算法(h := sha256.New() 在你的例子中)。

为了详细说明这些签名函数的作用,我首先想将签名和验证分解为以下步骤:

  • 签名:
    1. 散列消息
    2. 使用私钥加密消息的散列(这个加密的散列就是签名)
  • 验证:
    1. 散列消息
    2. 使用公钥解密签名(这会产生签名时加密的散列)。
    3. 比较计算的和解密的哈希值。如果它们匹配,则签名正确。

现在调用openssl_sign在您的 PHP 代码中,执行所有签名步骤,同时调用 ecdsa.Verify在 Go 中,只执行验证过程的第二步和第三步。这就是为什么它将散列作为第二个参数的原因。因此要验证签名,您必须自己实现第一个验证步骤,即生成哈希。

您在签名和验证时必须使用相同的哈希算法,因此您必须在您的 Go 代码中使用 SHA256 而不是 MD5(因为您使用 OPENSSL_ALGO_SHA256 签名),否则哈希值(通常)不匹配。

此外,我建议不要将 MD5 用于签名,因为它不再被认为是抗冲突的(哈希冲突是,当您有 2 个不同的字符串/文件/...具有相同的哈希值时)。有关更多详细信息,您可以查看 Wikipedia article on MD5 ,特别是“碰撞漏洞”部分。这是一个问题,因为具有相同 MD5 哈希的 2 条消息也将具有相同的签名,并且攻击者可以使用为其中一个字符串生成的签名来欺骗您认为另一个字符串已签名(因此信任它)。

此外,ecdsa.PrivateKey可以给你对应的公钥,你可以调用ecdsa.Verify像这样:

ecdsa.Verify(&prikey.PublicKey, data, &r, &s)

这为您省去了将所有数据从私钥复制到新对象的麻烦。

关于PHP 创建 ECDSA 签名并使用 Golang 验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52255715/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com