Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP create ECDSA signature and verify with Golang

Tags:

php

go

ecdsa

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

like image 537
Roman Gelembjuk Avatar asked Sep 10 '18 10:09

Roman Gelembjuk


1 Answers

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:

  • Signing:
    1. Hash the message
    2. Encrypt the message's hash using the private key (this encrypted hash is the signature)
  • Verifying:
    1. Hash the message
    2. Decrypt the signature using the public key (this yields the hash which was encrypted while signing).
    3. Compare the calculated and decrypted hashes. If they match, then the signature is correct.

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.

like image 95
Leon Avatar answered Nov 03 '22 16:11

Leon