Search code examples
python-3.xtypescriptdigital-signatureecdsa

Digital Signature Signing in Python and Verification in Nodejs


I am using Python Cryptography and Nodejs Crypto libraries. I wish to digitally sign a signature in Python and verify it in Nodejs.

Example Key Pairs: Obtained from Hashicorp Vault (Type: ecdsa-p521)

PRIVATE_KEY="-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIB1ZsDtu9EeMUma3p0M4jWrA4jJrBirDyhLfATvXVMWNDmrH5EX49T\ndOzAsKq92IXi2g1HtdIaYBBTde5lkBNfUK+gBwYFK4EEACOhgYkDgYYABAESgmpD\nCcElrFK/HPwJaNUkSly25hcQe0KnSFoIIlIBVba5jPxz2erUDIPr34RINUb3LS7j\n5MxELHl/VINKFdNhzgGJT4IWzL9VR6p8auN4+DNy7lLr4veBvB3yY87MkfaRWqlB\nyjpMre0vudtOAHUbZ6F6l4BPBPIAQaYHHiuNWSrjlg==\n-----END EC PRIVATE KEY-----"

PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBEoJqQwnBJaxSvxz8CWjVJEpctuYXEHtCp0haCCJSAVW2uYz8c9nq1AyD69+ESDVG9y0u4+TMRCx5f1SDShXTYc4BiU+CFsy/VUeqfGrjePgzcu5S6+L3gbwd8mPOzJH2kVqpQco6TK3tL7nbTgB1G2ehepeATwTyAEGmBx4rjVkq45Y=\n-----END PUBLIC KEY-----"

Python

import base64
from os import urandom
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec, utils
from cryptography.hazmat.primitives.serialization import (
    load_pem_private_key,
    load_pem_public_key,
)
from cryptography.hazmat.backends import default_backend
from cryptography.exceptions import InvalidSignature


def sign() -> "dict[str, str]":
    # ecdsa signing
    privKey = load_pem_private_key(
        PRIVATE_KEY.encode(),
        password=None,
        backend=default_backend,
    )
    msg = urandom(12)

    chosen_hash = hashes.SHA256()
    hasher = hashes.Hash(chosen_hash, backend=default_backend)
    hasher.update(msg)
    digest = hasher.finalize()

    signature = privKey.sign(
        digest,
        ec.ECDSA(utils.Prehashed(chosen_hash)),
    )

    return {
        "msg": base64.b64encode(msg).decode(),
        "sign": base64.b64encode(signature).decode(),
    }


def verify(signObj: "dict[str, str]") -> bool:
    pubKey = load_pem_public_key(
        PUBLIC_KEY.encode(),
        backend=default_backend,
    )
    try:
        pubKey.verify(
            base64.b64decode(signObj["sign"].encode()),
            base64.b64decode(signObj["msg"].encode()),
            ec.ECDSA(hashes.SHA256()),
        )
    except InvalidSignature:
        return False

    return True


if __name__ == "__main__":
    res = sign()
    print(res)
    print(verify(res))

By itself, signing and verification works.

Typescript:

import * as crypto from "crypto";

const algo = "sha256";
const base64 = "base64";

class SignObj {
  readonly msg: string;
  readonly sign: string;

  constructor(msg: string, sign: string) {
    this.msg = msg;
    this.sign = sign;
  }
}

function sign(): SignObj {
  const msg = crypto.randomBytes(12);

  const hash = crypto.createHash(algo).update(msg).end();
  const hashDigest = hash.digest();

  const signer = crypto.createSign(algo);
  signer.write(hashDigest);
  signer.end();

  const sign = signer.sign(PRIVATE_KEY, base64);

  return new SignObj(msg.toString(base64), sign);
}

function verify(signObj: SignObj): boolean {
  const hash = crypto
    .createHash(algo)
    .update(Buffer.from(signObj.msg, base64))
    .end();
  const hashDigest = hash.digest();

  const verifier = crypto.createVerify(algo);
  verifier.write(hashDigest);
  verifier.end();

  return verifier.verify(PUBLIC_KEY, Buffer.from(signObj.sign, base64));
}

console.log(verify(sign()));

Again, by itself, signing and verification works.

But if we were to manually pluck in the values, e.g., result from Python to Typescript

{
    'msg': 'Oz4ZRvzcTRs7CEKV', 
    'sign': 'MIGGAkEkbIqN8uPwzMf50NiqXKuSdT4c+8Lchz7o5eKPfiPp3jMDtW1TqQseCJw7/8mIcvTRdYeFlYsf58vFbxkk/jpi/QJBBkw9MKelCXcEPecM5/5HqJrkD2XijtNgj7AKzsMTv/Y7721S6hnb48sBKj6DTQ9Dfnsf1uPZNb4KIa7KhwX5ebw='
}

Place into Typescript

const res = new SignObj(
  "Oz4ZRvzcTRs7CEKV",
  "MIGGAkEkbIqN8uPwzMf50NiqXKuSdT4c+8Lchz7o5eKPfiPp3jMDtW1TqQseCJw7/8mIcvTRdYeFlYsf58vFbxkk/jpi/QJBBkw9MKelCXcEPecM5/5HqJrkD2XijtNgj7AKzsMTv/Y7721S6hnb48sBKj6DTQ9Dfnsf1uPZNb4KIa7KhwX5ebw="
);
console.log(res);
console.log(verify(res)); // --> False

Not too sure what went wrong. Any help would be helpful. Thank you in advance~


Solution

  • As explained by @Topaco,

    The sign()-method in the Python code does not hash implicitly, while the sign()-method in the NodeJS code hashes implicitly. Therefore, in the NodeJS code, the message itself must be passed and not the hash

    Hence, the following function should be modified to as such:

    function verify(signObj) {
      /*const hash = crypto
        .createHash(algo)
        .update(Buffer.from(signObj.msg, base64))
        .end();
      const hashDigest = hash.digest();*/
      const msg = Buffer.from(signObj.msg, base64)
    
      const verifier = crypto.createVerify(algo);
      verifier.write(msg);
      verifier.end();
    
      return verifier.verify(PUBLIC_KEY, Buffer.from(signObj.sign, base64));
    }