Сбой проверки подписи ECDSA (из Ellipti c JS lib) на сервере Golang (с использованием пакета ecdsa по умолчанию) - PullRequest
0 голосов
/ 24 марта 2020

Подпись, созданная Ellipti c JS Lib , не может быть подтверждена на моем Golang сервере. Мой Golang сервер использует пакет по умолчанию crypto/ecdsa .

Вот код генерации подписи в TypeScript.

import * as elliptic from "elliptic";
import { KEYUTIL } from "jsrsasign";
import fetch from "node-fetch";

function String2Hex(tmp) {
  var str = "";
  for (var i = 0; i < tmp.length; i++) {
    str += tmp[i].charCodeAt(0).toString(16);
  }
  return str;
}

const ec = new elliptic.ec("p256");

const keyPair = KEYUTIL.generateKeypair("EC", "secp256r1");
const privKeyPEM = KEYUTIL.getPEM(keyPair.prvKeyObj, "PKCS8PRV");
const pubKeyPEM = KEYUTIL.getPEM(keyPair.pubKeyObj);

// @ts-ignore
const key = ec.keyFromPrivate(privKeyPEM);
const msg = JSON.stringify(pubKeyPEM);
const msgHex = String2Hex(msg);
const signature = key.sign(msgHex);

const body = JSON.stringify({
  jsonrpc: "2.0",
  method: "TEST_PKI",
  params: {
    publicKey: pubKeyPEM,
    signature: signature.toDER()
  },
  id: "random-str"
});

fetch("http://localhost:8080/api/test-chain", {
  method: "POST",
  body,
  headers: { "Content-Type": "application/json" }
})
  .then(res => res.json())
  .then(res => {
    if (res.error) {
      console.log(res.error.message);
    } else {
      console.log("success");
    }
  });

Этот код просто генерирует ключ сопрягает и генерирует подпись над простым текстом строки (точнее говоря, эта простая строка является PEM-закодированным текстом ключа publi c, но не важно, какой текст строки был использован).

Тогда , он отправляет эту подпись с ключом publi c в кодировке PEM на сервер. Сервер читает этот код PEM publi c и проверяет подпись.

А вот код сервера:

func VerifySignature(publicKey string, target interface{}, signatureBytes []byte) error {
    targetBytes, err := json.Marshal(target)
    if err != nil {
        return errors.WithStack(err)
    }

    encodedTarget := hex.EncodeToString(targetBytes)

    var signature struct {
        R *big.Int
        S *big.Int
    }
    _, err = asn1.Unmarshal(signatureBytes, &signature)
    if err != nil {
        return err
    }

    pubKey, err := getPubKey(publicKey)
    if err != nil {
        return err
    }

    valid := ecdsa.Verify(pubKey, []byte(encodedTarget), signature.R, signature.S)
    if !valid {
        return errors.New("wrong signature")
    }

    return nil
}

В этом коде сервер проверяет подпись, используя функция по умолчанию ecdsa.Verify.

Я проверил, что все входные параметры одинаковы как на стороне клиента, так и на стороне сервера. Итак, я уверен, что при отправке запроса произошла ошибка. Значения подписи R и S и значение encodedTarget хорошо поступают на сервер. Однако сервер продолжает возвращать false для проверки. (Конечно, я уже проверил функцию verify библиотеки Ellipti c, которая возвращает true, как и предполагалось)

Кроме того, я проверил построчно обе функции verify в Ellipti c * Пакет 1069 * lib и crypto/ecdsa.

Вот функция verify библиотеки Ellipti c:

EC.prototype.verify = function verify(msg, signature, key, enc) {
  msg = this._truncateToN(new BN(msg, 16));
  key = this.keyFromPublic(key, enc);
  signature = new Signature(signature, 'hex');

  // Perform primitive values validation
  var r = signature.r;
  var s = signature.s;
  if (r.cmpn(1) < 0 || r.cmp(this.n) >= 0)
    return false;
  if (s.cmpn(1) < 0 || s.cmp(this.n) >= 0)
    return false;

  // Validate signature
  var sinv = s.invm(this.n);
  var u1 = sinv.mul(msg).umod(this.n);
  var u2 = sinv.mul(r).umod(this.n);

  if (!this.curve._maxwellTrick) {
    var p = this.g.mulAdd(u1, key.getPublic(), u2);
    if (p.isInfinity())
      return false;

    return p.getX().umod(this.n).cmp(r) === 0;
  }

  // NOTE: Greg Maxwell's trick, inspired by:
  // https://git.io/vad3K

  var p = this.g.jmulAdd(u1, key.getPublic(), u2);
  if (p.isInfinity())
    return false;

  // Compare `p.x` of Jacobian point with `r`,
  // this will do `p.x == r * p.z^2` instead of multiplying `p.x` by the
  // inverse of `p.z^2`
  return p.eqXToP(r);
};

А вот Verify функция crypto/ecdsa:

func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
    // See [NSA] 3.4.2
    c := pub.Curve
    N := c.Params().N

    if r.Sign() <= 0 || s.Sign() <= 0 {
        return false
    }
    if r.Cmp(N) >= 0 || s.Cmp(N) >= 0 {
        return false
    }
    e := hashToInt(hash, c)

    return verify(pub, c, e, r, s)
}

// Verify calls verifyGeneric
func verifyGeneric(pub *PublicKey, c elliptic.Curve, e, r, s *big.Int) bool {
    var w *big.Int
    N := c.Params().N
    if in, ok := c.(invertible); ok {
        w = in.Inverse(s)
    } else {
        w = new(big.Int).ModInverse(s, N)
    }

    u1 := e.Mul(e, w)
    u1.Mod(u1, N)
    u2 := w.Mul(r, w)
    u2.Mod(u2, N)

    // Check if implements S1*g + S2*p
    var x, y *big.Int
    if opt, ok := c.(combinedMult); ok {
        x, y = opt.CombinedMult(pub.X, pub.Y, u1.Bytes(), u2.Bytes())
    } else {
        x1, y1 := c.ScalarBaseMult(u1.Bytes())
        x2, y2 := c.ScalarMult(pub.X, pub.Y, u2.Bytes())
        x, y = c.Add(x1, y1, x2, y2)
    }

    if x.Sign() == 0 && y.Sign() == 0 {
        return false
    }
    x.Mod(x, N)

    return x.Cmp(r) == 0
}

Я не крипто-человек, поэтому я не знаю, что означают эти переменные. Однако я печатал построчно и обнаруживал некоторые различия между ними.

Во-первых, результат this._truncateToN(new BN(msg, 16)); в Ellipti c отличается от результата hashToInt(hash, c) из crypto/ecdsa.

Во-вторых, var sinv = s.invm(this.n); значение в эллиптике c отличается от w = in.Inverse(s) из crypto/ecdsa.

И из-за этих различий значения u1 в обоих значениях и x в Golang (p.x в Ellipti c) различаются. Таким образом, в конечном итоге это приводит к другому результату проверки.

Итак, как мне решить эту проблему? Как я могу исправить свой код? Должен ли я найти другую библиотеку JS? (Если тогда, какая библиотека может быть полезна?) Пожалуйста, дайте мне несколько советов. Спасибо!

...