I try to make the app with PHP that creates ECDSA signature for some document and that signature is verified with Golang app.
I use private keys generated with openssl tool. It is prime256v1 curve key. Created with the command:
openssl ecparam -name prime256v1 -genkey -noout -out prime256v1-key.pem
In PHP i create signature using openssl_sign function.
And all my attempts to verify the signature with Golang fail. In Golang use the crypto/ecdsa, crypto/elliptic packages.
There is my code.
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"))
}
What do i do wrong? Can anyone show me working example where Golang verifies signatre created with PHP?
I tried to use different versions in openssl_sign instead of OPENSSL_ALGO_SHA256 . Tried OPENSSL_ALGO_SHA1, OPENSSL_ALGO_SHA512
The problem with your code seems to be, that you hash the string in PHP using MD5 before signing it using OPENSSL_ALGO_SHA256
, which hashes what you sign (the MD5 hash) again, while in your Go program, you only have the first of these 2 hashes. To fix this, I would remove the MD5
step in the PHP code and replace the h := md5.New()
line in your code with the hash used by your signature algorithm (h := sha256.New()
in your example).
To elaborate a bit more on what theses signing functions do, I would first like to break signing and verifying down into the following steps:
Now the call to openssl_sign
in your PHP code, does all the signing steps, while the call to ecdsa.Verify
in Go, only does the second and third step of the verification process. And this is why it takes a hash as the second argument. So to verify a signature, you must implement the first verification step yourself, namely generating the hash.
You must use the same hashing algorithm while signing and verifying, therefore you must use SHA256, not MD5, in your Go code (as you sign using OPENSSL_ALGO_SHA256
), otherwise the hashes will (generally) not match.
Also, I would recommend to not use MD5 for signatures, as it is no longer considered collision resistant (a hash collision is, when you have 2 different strings/files/... with the same hash). For more details about that, you can check the Wikipedia article on MD5, specifically the section "Collision vulnerabilities". This is a problem, as 2 messages with the same MD5 hash, will also have the same signature and an attacker could use the signature generated for one of the strings to trick you into thinking the other was signed (and therefore trust it).
Additionally, ecdsa.PrivateKey
can give you the corresponding public key, and you can call ecdsa.Verify
like this:
ecdsa.Verify(&prikey.PublicKey, data, &r, &s)
This saves you the trouble of copying all the data from the private key to a new object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With