我尝试用PHP创建应用程序,为某些文档创建ECDSA签名,并使用Golang应用程序验证该签名.
我使用openssl工具生成的私钥.它是prime256v1曲线键.使用命令创建:
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";
Golang
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创建的signatre的工作示例吗?
我试图在openssl_sign中使用不同的版本而不是OPENSSL_ALGO_SHA256.试过OPENSSL_ALGO_SHA1,OPENSSL_ALGO_SHA512
解决方法:
您的代码的问题似乎是,您使用MD5在使用MD5对字符串进行哈希处理之前,使用OPENSSL_ALGO_SHA256对其进行哈希处理,这会再次对您签名(MD5哈希)进行哈希处理,而在您的Go程序中,您只拥有这些中的第一个哈希值.要解决这个问题,我会删除PHP代码中的MD5步骤,并使用签名算法使用的哈希替换代码中的h:= md5.New()行(示例中为h:= sha256.New()) .
为了详细说明这些签名函数的作用,我首先要打破签名并验证以下步骤:
>签字:
>哈希消息
>使用私钥加密消息的哈希(此加密哈希是签名)
>验证:
>哈希消息
>使用公钥解密签名(这会产生签名时加密的哈希).
>比较计算和解密的哈希值.如果匹配,则签名是正确的.
现在,在PHP代码中调用openssl_sign
,执行所有签名步骤,而在Go中调用ecdsa.Verify
,只执行验证过程的第二步和第三步.这就是为什么它将哈希作为第二个参数.因此,要验证签名,您必须自己实现第一个验证步骤,即生成哈希.
在签名和验证时必须使用相同的散列算法,因此您必须在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)
这样可以省去将私钥中的所有数据复制到新对象的麻烦.