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~
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));
}