PHP создает ECDSA-подпись и проверяет с помощью Golang - PullRequest
0 голосов
/ 10 сентября 2018

Я пытаюсь создать приложение с PHP, которое создает подпись ECDSA для некоторого документа, и эта подпись проверяется приложением Golang.

Я использую закрытые ключи, сгенерированные с помощью инструмента openssl. Это простой ключ кривой 256v1. Создано с помощью команды:

openssl ecparam -name prime256v1 -genkey -noout -out prime256v1-key.pem

В PHP я создаю подпись с помощью функции openssl_sign.

И все мои попытки проверить подпись с Голангом провалились. В Голанге используются пакеты 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"))

}

Что я делаю не так? Может кто-нибудь показать мне рабочий пример, где Голанг проверяет подпись, созданную с помощью PHP?

Я пытался использовать разные версии в openssl_sign вместо OPENSSL_ALGO_SHA256. Пробовал OPENSSL_ALGO_SHA1, OPENSSL_ALGO_SHA512

1 Ответ

0 голосов
/ 10 сентября 2018

Проблема с вашим кодом заключается в том, что вы хэшируете строку в PHP, используя MD5, прежде чем подписать ее, используя OPENSSL_ALGO_SHA256, который снова хэширует то, что вы подписываете (хэш MD5), тогда как в вашей программе Go вы имеете только первый из этих 2 хэшей. Чтобы исправить это, я удалил бы шаг MD5 в коде PHP и заменил строку h := md5.New() в вашем коде хэшем, используемым вашим алгоритмом подписи (h := sha256.New() в вашем примере).

Чтобы подробнее рассказать о том, что делают эти функции подписи, сначала я хотел бы разбить подпись и проверку на следующие шаги:

  • Подписание:
    1. Хеширование сообщения
    2. Зашифровать хеш сообщения с помощью закрытого ключа (этот зашифрованный хэш является подписью)
  • Проверка:
    1. Хеш сообщения
    2. Расшифруйте подпись с помощью открытого ключа (это дает хэш, который был зашифрован при подписании).
    3. Сравните вычисленные и расшифрованные хэши. Если они совпадают, тогда подпись верна.

Теперь вызов openssl_sign в вашем PHP-коде выполняет все шаги подписи, в то время как вызов ecdsa.Verify в Go выполняет только второй и третий этап. процесса проверки. И именно поэтому он принимает хэш в качестве второго аргумента. Таким образом, чтобы проверить подпись, вы должны самостоятельно выполнить первый шаг проверки, а именно, создать хеш.

Вы должны использовать один и тот же алгоритм хеширования при подписании и проверке, поэтому вы должны использовать SHA256, а не MD5, в своем коде Go (как вы подписываете, используя OPENSSL_ALGO_SHA256), иначе хеши (как правило) не будут совпадать.

Кроме того, я бы рекомендовал не использовать MD5 для подписей, так как он больше не считается устойчивым к коллизиям (хеш-коллизия возникает, когда у вас есть 2 разные строки / файлы / ... с одним и тем же хешем). Подробнее об этом вы можете прочитать в статье Википедии о MD5 , в частности, в разделе «Уязвимости при столкновениях». Это проблема, поскольку 2 сообщения с одинаковым хешем MD5 также будут иметь одинаковую подпись, и злоумышленник может использовать подпись, сгенерированную для одной из строк, чтобы заставить вас думать, что другая подписана (и, следовательно, доверяет ей).

Кроме того, ecdsa.PrivateKey может дать вам соответствующий открытый ключ, и вы можете позвонить ecdsa.Verify следующим образом:

ecdsa.Verify(&prikey.PublicKey, data, &r, &s)

Это избавляет вас от необходимости копировать все данные из закрытого ключа в новый объект.

...