关于VerifyPKCS1v15(base64和rsa检查)

时间:2014-08-12 09:37:52

标签: go base64 rsa

鉴于“notify”,“publicKey”和“sign”,它没有通过VerifyPKCS1v15。那是我的代码,有什么问题吗?

  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。 这是php代码,它将传递相同的数据。

<?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";
}
?>

1 个答案:

答案 0 :(得分:11)

您的代码中似乎有几个不同的问题。

  1. 您不需要在PHP代码中将数据截断为128个字符,因为您在go代码中没有这样做。这种差异将导致字节不同,因此计算的签名不同。

  2. 您正在使用openssl_public_decrypt功能对数据进行签名。虽然这在理论上确实有效但却容易出错。您还使用公钥对数据进行签名,这是错误的 - 只有私钥才能签名。使用PHP的openssl_sign函数要好得多。

  3. 另一个错误来源可能是您使用私钥的签名代码,此处未显示。

    PHP和Go的公钥加密应完全兼容。为了测试这个,我在PHP和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);
    

    在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))
    }
    

    您可以验证这些确实产生相同的输出并以相同的方式签署相同的数据。

    我们也可以使用PHP和Go来验证签名。以下是一组PHP和Go脚本,它们都将从标准输入读取签名并进行验证。

    <?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");
    }
    

    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")
    }
    

    我们可以将这些不同的脚本混合在一起,并确认PHP和Go确实完全兼容:

    $ 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