4

Given "notify" ,"publicKey" and "sign" , it doesnt pass the VerifyPKCS1v15 in go . That 's mycode , is there something wrong ?

  package main
    import (                                                                                                                                                                     
        "crypto"
        "crypto/sha1"
        "crypto/rsa"
        "crypto/x509"
        "encoding/base64"
        "encoding/pem"
        "fmt"
    )
    func main() {
        notify := `YFSGlJTpNYakrZuZqZ55dcA5mVUb/JQBr3hdDjODsAVSdoVVytIagk9Wt0CD/uX+7jGL9pqev8/u0I0ZBKEmz5huXp8TdZSnskCZ7GTeHNW0VPJcW8OcBxAValA0jQSv2mBP+tc1r6mdvf66GEzhvgBfTnp3Sp7V3dijJ9bNstIDyrGm/BlByhcMr3UqXjTFJaui6t5TxvZhCuSV9sg+xVVA+sR3uFI78b5lKomg5Vu31EBZvXASlFfaOc4StltRUH2aSiRqjnbXe8dlRZO0Ih44htYs2QfehzeQnPHtTwNHUvtVIVcIdI/7j9yfy5es13QeIgfKghY/ENUnB2V7iA==`
        sign := `s8XIN2TyC5niX1HFPDXOQj2eRvhW2qMPOdDuuXlOspYhxkjxunV4Ytgcw8GXg761HSbk4e5QsgKpU+vM2ggLhYni2GfXhGBVj/P13B6JhMmdrucU8ktlaH+fJGUmc3rqGMU3qiQgNAh/8PV1BS/5li7qzXHc0tgKL1zRgeu1CVw=`
        notifyData, err := base64.StdEncoding.DecodeString(notify)
        if err != nil {
            fmt.Println("error1:", err)
            return
        }
        signData, err := base64.StdEncoding.DecodeString(sign)
        if err != nil {
            fmt.Println("error2:", err)
            return
        }
        publicKey := []byte(`-----BEGIN PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2kcrRvxURhFijDoPpqZ/IgPlA
    gppkKrek6wSrua1zBiGTwHI2f+YCa5vC1JEiIi9uw4srS0OSCB6kY3bP2DGJagBo
    Egj/rYAGjtYJxJrEiTxVs5/GfPuQBYmU0XAtPXFzciZy446VPJLHMPnmTALmIOR5
    Dddd1Zklod9IQBMjjwIDAQAB
    -----END PUBLIC KEY-----
    `)
        block, _ := pem.Decode(publicKey)
        if block == nil {
            fmt.Println("pem error :")
            return
        }
        public, err := x509.ParsePKIXPublicKey(block.Bytes)
        if err != nil {
            fmt.Println("public key error :", err)
            return
        }
        pub := public.(*rsa.PublicKey)
        fmt.Println(pub.N)

        h := sha1.New()
        h.Write([]byte(notifyData))
        digest := h.Sum(nil)

        err = rsa.VerifyPKCS1v15(pub, crypto.SHA1, digest, signData)
        if err == nil {
            fmt.Println("OK")
        } else {
            fmt.Println("verify fail", err)
        }
    }  

P.S. This is php code, and it'll pass with the same data.

<?php                                                                                                                                                                        
$pubKey = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2kcrRvxURhFijDoPpqZ/IgPlA
gppkKrek6wSrua1zBiGTwHI2f+YCa5vC1JEiIi9uw4srS0OSCB6kY3bP2DGJagBo
Egj/rYAGjtYJxJrEiTxVs5/GfPuQBYmU0XAtPXFzciZy446VPJLHMPnmTALmIOR5
Dddd1Zklod9IQBMjjwIDAQAB
-----END PUBLIC KEY-----";
$pubRes = openssl_get_publickey($pubKey);
//通知数据
$notify_data = "YFSGlJTpNYakrZuZqZ55dcA5mVUb/JQBr3hdDjODsAVSdoVVytIagk9Wt0CD/uX+7jGL9pqev8/u0I0ZBKEmz5huXp8TdZSnskCZ7GTeHNW0VPJcW8OcBxAValA0jQSv2mBP+tc1r6mdvf66GEzhvgBfTnp3Sp7V3dijJ9bNstIDyrGm/BlByhcMr3UqXjTFJaui6t5TxvZhCuSV9sg+xVVA+sR3uFI78b5lKomg5Vu31EBZvXASlFfaOc4StltRUH2aSiRqjnbXe8dlRZO0Ih44htYs2QfehzeQnPHtTwNHUvtVIVcIdI/7j9yfy5es13QeIgfKghY/ENUnB2V7iA==";
//签名
$sign = "s8XIN2TyC5niX1HFPDXOQj2eRvhW2qMPOdDuuXlOspYhxkjxunV4Ytgcw8GXg761HSbk4e5QsgKpU+vM2ggLhYni2GfXhGBVj/P13B6JhMmdrucU8ktlaH+fJGUmc3rqGMU3qiQgNAh/8PV1BS/5li7qzXHc0tgKL1zRgeu1CVw=";
$data = base64_decode($notify_data);
$maxlength = 128;
$output = '';
while ($data) {
    $input = substr($data, 0, $maxlength);
    $data = substr($data, $maxlength);
    openssl_public_decrypt($input, $out, $pubRes, OPENSSL_PKCS1_PADDING);
    $output .= $out;
}
if (openssl_verify($output, base64_decode($sign), $pubRes)) {
    echo "success";
}else{
    echo "fail";
}
?>
frank.lin
  • 1,614
  • 17
  • 26

1 Answers1

14

You appear to have several different problems in your code.

  1. There is no need to truncate the data to 128 characters in your PHP code as you are not doing the same in your go code. This discrepancy will cause the bytes to be different and therefore the computed signatures to be different.

  2. You are using the openssl_public_decrypt function to sign the data. While this does work in theory it's error prone. You are also using a public-key to sign the data, which is wrong - only private-keys can sign. It's much better to use PHP's openssl_sign function.

Another source of error could be your signing code that uses the private key, which is not shown here.

PHP's and Go's public key cryptography should be entirely compatible. To test this I've created the following identical signing scripts in both PHP and Go.

<?php

$data = "TEST DATA TO COMPUTE";

$privKeyPEM = "-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAK3ADijXKw72+YbC5QKK2y7IosCp7rWOhTf8Ph07ZA0KjdbKtfL/
7dmNKjSP6EkC/DJUWfZJNLIlGOtDLLA/AnsCAwEAAQJAQj9kJrZDuKT6ZyOQZfPD
tobRZ1xjo93/dWU72bF3aHDo4ILMy2Kigy5yhZU0ZGjOuPv5eUOLRe/yxYQf6B5J
AQIhANbhfZ4QJC8dLXAqcsxOXuLgztzbKixUre0gnhiVSd1hAiEAzv+sHJ4PMjKs
Iuf6/nUI9XFgQQRd+NGRovyHRZC18VsCIAX7AKQFjvxAs6MLi2ZkR//IgfljoCjb
snuHDN9iSEwBAiEAmAc1XCtGE+Mdg+GG+T3xn3pubDIN5oHcia0YmKIIzsMCIEy1
fWM5cIJ9bAUExKB6MV8PF+9EjDvXzbSk1/Ycta8z
-----END RSA PRIVATE KEY-----";

// Parse private key
$privkey = openssl_pkey_get_private($privKeyPEM);
if (!$privkey) {
    exit("Could not parse private key");
}

// Compute the signature
$signature = '';
$ok = openssl_sign($data, $signature, $privkey, OPENSSL_ALGO_SHA1); //SHA1 of $data is computed automatically by this function
if (!$ok) {
    exit("Could not compute signature");
}

// Print the output
print base64_encode($signature);

And the same thing in Go:

package main

import (
    "crypto"
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha1"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "fmt"
    "log"
)

const (
    data = "TEST DATA TO COMPUTE"

    privKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAK3ADijXKw72+YbC5QKK2y7IosCp7rWOhTf8Ph07ZA0KjdbKtfL/
7dmNKjSP6EkC/DJUWfZJNLIlGOtDLLA/AnsCAwEAAQJAQj9kJrZDuKT6ZyOQZfPD
tobRZ1xjo93/dWU72bF3aHDo4ILMy2Kigy5yhZU0ZGjOuPv5eUOLRe/yxYQf6B5J
AQIhANbhfZ4QJC8dLXAqcsxOXuLgztzbKixUre0gnhiVSd1hAiEAzv+sHJ4PMjKs
Iuf6/nUI9XFgQQRd+NGRovyHRZC18VsCIAX7AKQFjvxAs6MLi2ZkR//IgfljoCjb
snuHDN9iSEwBAiEAmAc1XCtGE+Mdg+GG+T3xn3pubDIN5oHcia0YmKIIzsMCIEy1
fWM5cIJ9bAUExKB6MV8PF+9EjDvXzbSk1/Ycta8z
-----END RSA PRIVATE KEY-----`
)

func main() {

    // Parse private key into rsa.PrivateKey
    PEMBlock, _ := pem.Decode([]byte(privKeyPEM))
    if PEMBlock == nil {
        log.Fatal("Could not parse Private Key PEM")
    }
    if PEMBlock.Type != "RSA PRIVATE KEY" {
        log.Fatal("Found wrong key type")
    }
    privkey, err := x509.ParsePKCS1PrivateKey(PEMBlock.Bytes)
    if err != nil {
        log.Fatal(err)
    }

    // Compute the sha1
    h := sha1.New()
    h.Write([]byte(data))

    // Sign the data
    signature, err := rsa.SignPKCS1v15(rand.Reader, privkey, crypto.SHA1, h.Sum(nil))
    if err != nil {
        log.Fatal(err)
    }

    // Print the results
    fmt.Print(base64.StdEncoding.EncodeToString(signature))
}

You can verify that these do, indeed, produce the same output and sign the same data in the same way.

We can also use both PHP and Go to verify the signatures. Here are a set of PHP and Go scripts that will both read a signature from standard input and verify it.

<?php  

$data = "TEST DATA TO COMPUTE";

$pubKeyPEM = "-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAK3ADijXKw72+YbC5QKK2y7IosCp7rWO
hTf8Ph07ZA0KjdbKtfL/7dmNKjSP6EkC/DJUWfZJNLIlGOtDLLA/AnsCAwEAAQ==
-----END PUBLIC KEY-----";

// Parse public key
$pubkey = openssl_pkey_get_public($pubKeyPEM);
if (!$pubkey) {
    exit("Could not parse public key");
}

// Read the signature from stdin
$stdin = file_get_contents("php://stdin");
$signature = base64_decode($stdin);

// Verify the signature
$ok = openssl_verify($data, $signature, $pubkey, OPENSSL_ALGO_SHA1); //SHA1 of $data is computed automatically by this function
if ($ok == 1) {
    print "OK\n"; // it worked!
  exit(0);
}
else if ($ok == 0) {
  exit("Signature verification failed");
}
else {
  exit("Error verifying signature");
}

And the same verification code in Go:

package main

import (
    "crypto"
    "crypto/rsa"
    "crypto/sha1"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "fmt"
    "io/ioutil"
    "log"
    "os"
)

const (
    data = "TEST DATA TO COMPUTE"

    pubKeyPEM = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAK3ADijXKw72+YbC5QKK2y7IosCp7rWO
hTf8Ph07ZA0KjdbKtfL/7dmNKjSP6EkC/DJUWfZJNLIlGOtDLLA/AnsCAwEAAQ==
-----END PUBLIC KEY-----`
)

func main() {

    // Parse public key into rsa.PublicKey
    PEMBlock, _ := pem.Decode([]byte(pubKeyPEM))
    if PEMBlock == nil {
        log.Fatal("Could not parse Public Key PEM")
    }
    if PEMBlock.Type != "PUBLIC KEY" {
        log.Fatal("Found wrong key type")
    }
    pubkey, err := x509.ParsePKIXPublicKey(PEMBlock.Bytes)
    if err != nil {
        log.Fatal(err)
    }

    // compute the sha1
    h := sha1.New()
    h.Write([]byte(data))

    // Read the signature from stdin
    b64 := base64.NewDecoder(base64.StdEncoding, os.Stdin)
    signature, err := ioutil.ReadAll(b64)
    if err != nil {
        log.Fatal(err)
    }

    // Verify
    err = rsa.VerifyPKCS1v15(pubkey.(*rsa.PublicKey), crypto.SHA1, h.Sum(nil), signature)
    if err != nil {
        log.Fatal(err)
    }

    // It verified!
    fmt.Println("OK")
}

We can mix and match these different scripts together and verify that PHP and Go are indeed fully compatible:

$ go run go-sign.go | go run go-verify.go
OK
$ go run go-sign.go | php php-verify.php
OK
$ php php-sign.php | php php-verify.php
OK
$ php php-sign.php | go run go-verify.go
OK
phayes
  • 1,432
  • 1
  • 12
  • 11
  • Thank you .I know where my problom is . And yes, there is some differenc between php and go code . I miss the decrpt in go. And I wander what is it in go like `openssl_public_decrypt` in php? – frank.lin Aug 13 '14 at 03:07
  • Go's equivalent of php's `openssl_public_decrypt` is here (http://golang.org/src/pkg/crypto/rsa/rsa.go#L376). However, the function isn't exported so you can't use it directly. What's your use case? If you actually want to encrypt and decrypt data you're much better off using a symmetric cipher such as AES. – phayes Aug 13 '14 at 11:38
  • @phayes: that's not quite the same. `openssl_public_decrypt` decrypts with a public key. One generally doesn't want to do this, but because PHP provided a function, people use it. – JimB Aug 13 '14 at 13:56