Verify signature on a file nodejs - node.js

I don't understand why verify always returns false for me... running openssl will return that everything is just dandy
$ openssl dgst -md5 -verify mykey.pub -signature signature message.txt
Verified OK
However, running my code in the node results in the verification to be false
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
//pass in arguments
var args = process.argv.slice(2);
fs.readFile(args[0], 'ascii', function(err,signature){
if(err){console.log(err);}
fs.readFile(args[1], 'ascii', function(err,message){
if(err){console.log(err);}
fs.readFile(args[2], 'ascii', function(err,publickey){
if(err){console.log(err);}
verify(signature, message, publickey);
});
});
});
//verify function
var verify = function(signature, message, publickey){
//everything prints out right
console.log(signature.toString() + '\n');
console.log(message.toString() + '\n');
console.log(publickey.toString() + '\n');
//using md5
var verifier = crypto.createVerify('md5');
verifier.update(message.toString());
var WHAT = verifier.verify(publickey.toString(), signature.toString(), 'binary');
console.log(WHAT);
};
Looking at the output results in this
$ node verifyHash signature message.txt mykey.pub
B`⌂ pgfs☼st;3_V1I☻l♂[V5 =C♠~o▲§►rH`KZ7#♦♠LiQ⌂xFw
▼♣"↓d;.H4+↕$WZF▲◄Ow▲r⌂,
j]U↕6►vQm$7v&^^uF↨/ma2F→*n
►¶o'$jN!☼↑☺aV+↔e^qH▲A►rmx.
HEllo
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDgn0PrHxivu0zgG8pp66yMwxJ
MyYsdocVNpZ+673WlRlN0NKQRkI7+F7rYMG4KWL0pDBeOahOggxNVTNV9cxkCKce
Gp37+ZED5HiHKDll4tVoGVSDLaW0BBVe1TzfJSS64fvN/OssyjKffD5ExpLE4O5o
Vv7robQ0JxzYfbz2FQIDAQAB
-----END PUBLIC KEY-----
false
What am I doing wrong?

You cannot read the signature as ASCII, as it contains binary data (byte values > 128). These will get stripped then [1].
As the verifier is a stream.Writable the easiest solution is to use fs.createReadStream and then pipe to pipe it into the verifier.
See this REPL transcript:
> crypto = require('crypto'); undefined
undefined
> fs = require('fs'); undefined
undefined
> verifier = crypto.createVerify('md5'); undefined
undefined
> fs.createReadStream('file').pipe(verifier); undefined
undefined
> verifier.verify(fs.readFileSync('publickey.pem'), fs.readFileSync('signature.sign'), 'binary');
true
[1]
> new Buffer(fs.readFileSync('signature.sign', 'binary')).toString('hex');
'3632c3a6c28dc2806fc2bb563dc3bb51c38b537dc2871ac394c288405dc2b87634c2b5c3be53157273c2bfc3acc2b9c398c2a05e506ec3a6c3b2c2a9c3bf4bc280c28133c298c281c39a07c3acc2a612c2b0c2bec38fc28ac2bbc2b941504fc3bc22c2a0c3910325c38c5f581d4c7f4fc3a3c389c2b4c3b72e36c29b3d29c295c2b4c28755c38158c3af0f0e08c3bbc29f3bc3bc5e57c288c29d287ec3bf1bc39864c2867cc3867bc3a03ec3bd5a2806c3bd55c2a0c29a12c3aac2b675c284500504c38832c291c383c3b933222961c2b3c3bac2a3583737c3861cc392c39373c2bac298c2b96b6c1dc29f3fc2b1c387c397c39719c29fc39ec28c02c29d3cc396c2abc3923bc28e621032c3bec298c2a877c3bbc2ae6fc38cc3b4c2b1c387c390c39d03753bc28ac2b0c2b64f3fc3a7c3a254c398c3b91ec2935922c3aac2bdc2aa41c295c39519c2b8c3bdc2a02d74c2bfc38ec3aa60c2b87c433e1dc28b2351c3b8c2b54a237cc29703521dc3a3c2b34958c2a4c3a9c2a410c2b8c39e5dc3af2c6a27c2987e'
> new Buffer(fs.readFileSync('signature.sign', 'ascii')).toString('hex');
'3632660d006f3b563d7b514b537d071a5408405d387634357e531572733f6c3958205e506e6672297f4b00013318015a076c2612303e4f0a3b3941504f7c22205103254c5f581d4c7f4f634934772e361b3d291534075541586f0f0e087b1f3b7c5e57081d287e7f1b5864067c467b603e7d5a28067d55201a126a367504500504483211437933222961337a23583737461c5253733a18396b6c1d1f3f31475757191f5e0c021d3c562b523b0e6210327e1828777b2e6f4c743147505d03753b0a30364f3f67625458791e1359226a3d2a41155519387d202d743f4e6a60387c433e1d0b235178354a237c1703521d6333495824692410385e5d6f2c6a27187e'

You can also try to use Windows/macOS npm package util sign-check for executables/files signature verification.
It uses built-in OS mechanisms to verify is code signed (codesign --verify command for macOS and WinVerifyTrust API implementation for Windows).
For sign verification you can just use:
const SignCheck = require('sign-check');
const somePath = 'some/path/for/test';
SignCheck.checkMac(somePath).then(
(isSigned) => {
console.log('File sign status ' + isSigned);
},
(error) => {
console.log(error);
}
);
Or checkWin function for Windows platform.

Related

Make Nodejs encrypt and decrypt function up to date

I'm trying to make my small function up to date, it generate 2 warnings:
(node:8944) [DEP0106] DeprecationWarning: crypto.createDecipher is deprecated.
(node:8944) Warning: Use Cipheriv for counter mode of aes-256-ctr:
Code:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
password = 'd6F3Efeq';
function encrypt(text){
var cipher = crypto.createCipher(algorithm,password)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher(algorithm,password)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
This encrypt and decrypt wonderfully, but it generate some errors. I tried the new syntax, and I have a hard time figuring it out. If someone could provide an up to date demo, you're the best. Thank you
PS: I don't want to use createDecipherIv, I only want to use a key if this make sense
Install crypto-js:
npm i --save crypto-js
and use it:
const crypto = require('crypto-js'),
password = 'd6F3Efeq';
function encrypt(text){
const result = crypto.AES.encrypt(text, password);
return result.toString();
}
function decrypt(text){
const result = crypto.AES.decrypt(text, password);
return result.toString(crypto.enc.Utf8);
}
or You can use cryptr

Mimicking the openssl call in node with another library

I am running this openssl code to generate a token to be used to encrypt my hls stream.
"openssl rand 16 > '" + folder + "/master.key' && echo '" + hex + "' | xxd -r -p > '" + folder + "/master.key'"
The openssl library has issues on windows for I am looking for a way to mimic the above call in a different node library.
I have tried crypto with node js with the call below but it doesn't seem to work.
require('crypto').randomBytes(16, function(err, buffer) {
var hex = buffer.toString('hex');
console.log(hex);
});
Can anyone suggest a way to do this?
Thanks
function hex2bin(hex){
return new Buffer(hex, "hex");
}
require('crypto').randomBytes(16, function(err, buffer) {
var hex = buffer.toString('hex');
fs.writeFile(folder + "/master.key", hex2bin(hex), function (err) {
});
});
Sussed it

ECDSA signatures between Node.js and WebCrypto appear to be incompatible?

I'm using the following example for signing + verifying in Node.js: https://github.com/nodejs/node-v0.x-archive/issues/6904. The verification succeeds in Node.js but fails in WebCrypto. Similarly, a message signed using WebCrypto fails to verify in Node.js.
Here's the code I used to verify a signature produced from the Node.js script using WebCrypto - https://jsfiddle.net/aj49e8sj/. Tested in both Chrome 54.0.2840.27 and Firefox 48.0.2
// From https://github.com/nodejs/node-v0.x-archive/issues/6904
var keys = {
priv: '-----BEGIN EC PRIVATE KEY-----\n' +
'MHcCAQEEIF+jnWY1D5kbVYDNvxxo/Y+ku2uJPDwS0r/VuPZQrjjVoAoGCCqGSM49\n' +
'AwEHoUQDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNhB8i3mXyIMq704m2m52FdfKZ2\n' +
'pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==\n' +
'-----END EC PRIVATE KEY-----\n',
pub: '-----BEGIN PUBLIC KEY-----\n' +
'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNh\n' +
'B8i3mXyIMq704m2m52FdfKZ2pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==\n' +
'-----END PUBLIC KEY-----\n'
};
var message = (new TextEncoder('UTF-8')).encode('hello');
// Algorithm used in Node.js script is ecdsa-with-SHA1, key generated with prime256v1
var algorithm = {
name: 'ECDSA',
namedCurve: 'P-256',
hash: {
name: 'SHA-1'
}
};
// Signature from obtained via above Node.js script
var sig64 = 'MEUCIQDkAtiomagyHFi7dNfxMrzx/U0Gk/ZhmwCqaL3TimvlswIgPgeDqgZNqfR5/FZZASYsczUAhGSXjuycLhWnvk20qKc=';
// Decode base64 string into ArrayBuffer
var b64Decode = (str) => Uint8Array.from(atob(str), x => x.charCodeAt(0));
// Get base64 string from public key
const key64 = keys.pub.split('\n')
.filter(x => x.length > 0 && !x.startsWith('-----'))
.join('');
// Convert to buffers
var sig = b64Decode(sig64);
var keySpki = b64Decode(key64);
// Import and verify
// Want 'Verification result: true' but will get 'false'
var importKey = crypto.subtle.importKey('spki', keySpki, algorithm, true, ['verify'])
.then(key => crypto.subtle.verify(algorithm, key, sig, message))
.then(result => console.log('Verification result: ' + result));
Related question with a similar issue using SHA-256 instead of SHA-1: Generating ECDSA signature with Node.js/crypto
Things I've checked:
I decoded the Node.js keys and verified they have the same OID as keys generated via WebCrypto. This tells me I'm using the correct curves.
SHA-1 is explicitly identified as the hash to use in both locations.
ECDSA is explicitly identified in both Node.js and WebCrypto.
How can I successfully verify the signature received from Node.js and vice versa - verify a signature in Node.js produced from WebCrypto? Or are the implementations of the standard subtly different in such a way that makes them incompatible?
Edit:
WebCrypto signature (64 bytes): uTaUWTfF+AjN3aPj0b5Z2d1HybUEpV/phv/P9RtfKaGXtcYnbgfO43IRg46rznG3/WnWwJ2sV6mPOEnEPR0vWw==
Node.js signature (71 bytes): MEUCIQDkAtiomagyHFi7dNfxMrzx/U0Gk/ZhmwCqaL3TimvlswIgPgeDqgZNqfR5/FZZASYsczUAhGSXjuycLhWnvk20qKc=
Verified Node.js signature is DER encoded and WebCrypto signature is not.
Having not used either of these libraries I can't say for certain, but one possibility is that they don't use the same encoding type for the signature. For DSA/ECDSA there are two main formats, IEEE P1363 (used by Windows) and DER (used by OpenSSL).
The "Windows" format is to have a preset size (determined by Q for DSA and P for ECDSA (Windows doesn't support Char-2, but if it did it'd probably be M for Char-2 ECDSA)). Then both r and s are left-padded with 0 until they meet that length.
In the too small to be legal example of r = 0x305 and s = 0x810522 with sizeof(Q) being 3 bytes:
// r
000305
// s
810522
For the "OpenSSL" format it is encoded under the rules of DER as SEQUENCE(INTEGER(r), INTEGER(s)), which looks like
// SEQUENCE
30
// (length of payload)
0A
// INTEGER(r)
02
// (length of payload)
02
// note the leading 0x00 is omitted
0305
// INTEGER(s)
02
// (length of payload)
04
// Since INTEGER is a signed type, but this represented a positive number,
// a 0x00 has to be inserted to keep the sign bit clear.
00810522
or, compactly:
Windows: 000305810522
OpenSSL: 300A02020305020400810522
The "Windows" format is always even, always the same length. The "OpenSSL" format is usually about 6 bytes bigger, but can gain or lose a byte in the middle; so it's sometimes even, sometimes odd.
Base64-decoding your sig64 value shows that it is using the DER encoding. Generate a couple signatures with WebCrypto; if any don't start with 0x30 then you have the IEEE/DER problem.
After many hours finally find a solution with zero dependences!!
In browser:
// Tip: Copy & Paste in the console for test.
// Text to sign:
var source = 'test';
// Auxs
function length(hex) {
return ('00' + (hex.length / 2).toString(16)).slice(-2).toString();
}
function pubKeyToPEM(key) {
var pem = '-----BEGIN PUBLIC KEY-----\n',
keydata = '',
bytes = new Uint8Array( key );
for (var i = 0; i < bytes.byteLength; i++) {
keydata += String.fromCharCode( bytes[ i ] );
}
keydata = window.btoa(keydata);
while(keydata.length > 0) {
pem += keydata.substring(0, 64) + '\n';
keydata = keydata.substring(64);
}
pem = pem + "-----END PUBLIC KEY-----";
return pem;
}
// Generate new keypair.
window.crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-384" }, true, ["sign", "verify"])
.then(function(keypair) {
// Encode as UTF-8
var enc = new TextEncoder('UTF-8'),
digest = enc.encode(source);
// Sign with subtle
window.crypto.subtle.sign({ name: "ECDSA", hash: {name: "SHA-1"} }, keypair.privateKey, digest)
.then(function(signature) {
signature = new Uint8Array(signature);
// Extract r & s and format it in ASN1 format.
var signHex = Array.prototype.map.call(signature, function(x) { return ('00' + x.toString(16)).slice(-2); }).join(''),
r = signHex.substring(0, 96),
s = signHex.substring(96),
rPre = true,
sPre = true;
while(r.indexOf('00') === 0) {
r = r.substring(2);
rPre = false;
}
if (rPre && parseInt(r.substring(0, 2), 16) > 127) {
r = '00' + r;
}
while(s.indexOf('00') === 0) {
s = s.substring(2);
sPre = false;
}
if(sPre && parseInt(s.substring(0, 2), 16) > 127) {
s = '00' + s;
}
var payload = '02' + length(r) + r +
'02' + length(s) + s,
der = '30' + length(payload) + payload;
// Export public key un PEM format (needed by node)
window.crypto.subtle.exportKey('spki', keypair.publicKey)
.then(function(key) {
var pubKey = pubKeyToPEM(key);
console.log('This is pubKey -> ', pubKey);
console.log('This is signature -> ', der);
});
// For test, we verify the signature, nothing, anecdotal.
window.crypto.subtle.verify({ name: "ECDSA", hash: {name: "SHA-1"} }, keypair.publicKey, signature, digest)
.then(console.log);
});
});
In node:
const crypto = require('crypto');
// ----------------------------------------------------------------------------
// Paste from browser!
var puKeyPem = '-----BEGIN PUBLIC KEY-----\n' +
'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEmDubwJuORpMMoMnvv59W8tU8PxPChh75\n' +
'vjlfVB2+tPY5KDy1I0ohz2US+2K1T/ROcDCSRAjyONRzzwVBm9S6bqbk3KuaT2KG\n' +
'ikoe0KLfTeQtdEUyq8J0aEOKRXoCJLZq\n' +
'-----END PUBLIC KEY-----';
var hexSign = '306402305df22aa5f4e7200b7c264c891cd3a8c5b4622c25872020832d5bb3d251773592020249a46a8349754dc58c47c4cbb7c9023053b929a98f5c8cccf2c1a4746d82fc751e044b1f76dffdf9ef73f73bee1499c5e20aadddda41e3373760b8b0f3c1bbb2';
// ----------------------------------------------------------------------------
var verifier = crypto.createVerify('sha1'),
digest = 'test';
verifier.update(digest);
verifier.end();
console.log(verifier.verify(puKeyPem, hexSign, 'hex'));
// ----------------------------------------------------------------------------
Now you can generate compatible (nodejs vs webcrypto) keys and signatures without modifying them. The following example is for RSA, but ECDSA should be very similar - the essence is in the types/formats/encodings.
Generate key pair (nodejs):
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'der'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'der'
}
});
console.log('PRIVATE', Buffer.from(privateKey).toString('base64'));
console.log('PUBLIC', Buffer.from(publicKey).toString('base64'));
Sign message (nodejs):
const signature = crypto.sign(
'sha256',
Buffer.from('The quick brown fox jumps over the lazy dog', 'utf8'),
{
key: crypto.createPrivateKey({
key: Buffer.from('...base64 encoded private key', 'base64'),
format: 'der',
type: 'pkcs8'
}),
padding: crypto.constants.RSA_PKCS1_PADDING,
dsaEncoding: 'ieee-p1363'
}
);
console.log('SIGNATURE', signature.toString('base64'));
Verify message (webcrypto) - You have to alter the Buffer functions in vanilla js:
(async () => console.log(await crypto.subtle.verify(
{ name: 'RSASSA-PKCS1-v1_5' },
await crypto.subtle.importKey(
'spki',
Buffer.from('...base64 encoded public key', 'base64'),
{ name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'} },
false,
[ 'verify' ]
),
Buffer.from('...base64 encoded signature', 'base64'),
Buffer.from('The quick brown fox jumps over the lazy dog', 'utf8')
)))();

Generating ECDSA signature with Node.js/crypto

I have code that generates a concatenated (r-s) signature for the ECDSA signature using jsrsasign and a key in JWK format:
const sig = new Signature({ alg: 'SHA256withECDSA' });
sig.init(KEYUTIL.getKey(key));
sig.updateHex(dataBuffer.toString('hex'));
const asn1hexSig = sig.sign();
const concatSig = ECDSA.asn1SigToConcatSig(asn1hexSig);
return new Buffer(concatSig, 'hex');
Seems to work. I also have code that uses SubtleCrypto to achieve the same thing:
importEcdsaKey(key, 'sign') // importKey JWK -> raw
.then((privateKey) => subtle.sign(
{ name: 'ECDSA', hash: {name: 'SHA-256'} },
privateKey,
dataBuffer
))
These both return 128-byte buffers; and they cross-verify (i.e. I can verify jsrsasign signatures with SubtleCrypto and vice versa). However, when I use the Sign class in the Node.js crypto module, I seem to get something quite different.
key = require('jwk-to-pem')(key, {'private': true});
const sign = require('crypto').createSign('sha256');
sign.update(dataBuffer);
return sign.sign(key);
Here I get a buffer of variable length, roughly 70 bytes; it does not cross-verify with jsrsa (which bails complaining about an invalid length for an r-s signature).
How can I get an r-s signature, as generated by jsrsasign and SubtleCrypto, using Node crypto?
The answer turns out to be that the Node crypto module generates ASN.1/DER signatures, while other APIs like jsrsasign and SubtleCrypto produce a “concatenated” signature. In both cases, the signature is a concatenation of (r, s). The difference is that ASN.1 does so with the minimum number of bytes, plus some payload length data; while the P1363 format uses two 32-bit hex encoded integers, zero-padding them if necessary.
The below solution assumes that the “canonical” format is the concatenated style used by SubtleCrypto.
const asn1 = require('asn1.js');
const BN = require('bn.js');
const crypto = require('crypto');
const EcdsaDerSig = asn1.define('ECPrivateKey', function() {
return this.seq().obj(
this.key('r').int(),
this.key('s').int()
);
});
function asn1SigSigToConcatSig(asn1SigBuffer) {
const rsSig = EcdsaDerSig.decode(asn1SigBuffer, 'der');
return Buffer.concat([
rsSig.r.toArrayLike(Buffer, 'be', 32),
rsSig.s.toArrayLike(Buffer, 'be', 32)
]);
}
function concatSigToAsn1SigSig(concatSigBuffer) {
const r = new BN(concatSigBuffer.slice(0, 32).toString('hex'), 16, 'be');
const s = new BN(concatSigBuffer.slice(32).toString('hex'), 16, 'be');
return EcdsaDerSig.encode({r, s}, 'der');
}
function ecdsaSign(hashBuffer, key) {
const sign = crypto.createSign('sha256');
sign.update(asBuffer(hashBuffer));
const asn1SigBuffer = sign.sign(key, 'buffer');
return asn1SigSigToConcatSig(asn1SigBuffer);
}
function ecdsaVerify(data, signature, key) {
const verify = crypto.createVerify('SHA256');
verify.update(data);
const asn1sig = concatSigToAsn1Sig(signature);
return verify.verify(key, new Buffer(asn1sig, 'hex'));
}
Figured it out thanks to
https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-asn-1
ECDSA signatures between Node.js and WebCrypto appear to be incompatible?

Node.js function to generate RSA keys using Open SSL

I'm using Node.js as a server side language and I would like to generate an RSA key pairs for any user that registers himself on my website. I'm using a module called keypair. It's working fine for keys with small sizes but when I generate keys with 2048 in size, it's taking so long to perform it so I would like to use Open SSL directly from Node.js using Node's child_process as described in the script down below :
var cp = require('child_process')
, assert = require('assert');
var privateKey, publicKey;
publicKey = '';
cp.exec('openssl genrsa 2048', function(err, stdout, stderr) {
assert.ok(!err);
privateKey = stdout;
console.log(privateKey);
makepub = cp.spawn('openssl', ['rsa', '-pubout']);
makepub.on('exit', function(code) {
assert.equal(code, 0);
console.log(publicKey);
});
makepub.stdout.on('data', function(data) {
publicKey += data;
});
makepub.stdout.setEncoding('ascii');
makepub.stdin.write(privateKey);
makepub.stdin.end();
});
This is working and more faster in key pairs generation than the Node.js keypair module so the issue I'm having is that I don't understand this code (if it's writing files on the server side and reading keys from them or not?) and I would like to turn this script into a function that returns a JSON or an array as result that holds the public and private key.
So any suggestion is welcome, thank you in advance.
Try this.. Moved the code around a little. Uses tmp file, which is deleted, possibly could be done without the tmp file, but this should work.
var cp = require('child_process')
, assert = require('assert')
, fs = require('fs')
;
// gen pub priv key pair
function genKeys(cb){
// gen private
cp.exec('openssl genrsa 2048', function(err, priv, stderr) {
// tmp file
var randomfn = './' + Math.random().toString(36).substring(7);
fs.writeFileSync(randomfn, priv);
// gen public
cp.exec('openssl rsa -in '+randomfn+' -pubout', function(err, pub, stderr) {
// delete tmp file
fs.unlinkSync(randomfn);
// callback
cb(JSON.stringify({public: pub, private: priv}, null, 4));
});
});
}
genKeys(console.log);
You can simply use the small rsa-json module
It's really easy to use and it's asynchronous:
var createRsaKeys = require('rsa-json');
createRsaKeys({bits: 1024}, function(err, keyPair) {
console.log(keyPair.private);
console.log(keyPair.public);
});
rsa-json does not make a direct uses of OpenSSL RSA_generate_key but uses ssh-keygen (from OpenSSH) which is a wrapper around OpenSSL. There is no direct security difference (see this for more information).
PS: Have a look at the only 48 lines of code composing rsa-json.
If you really want to use OpenSSL, you can have a look at the ursa module but:
It's not asynchronous
Is not maintained, the last commit was from Dec 21, 2012
The project is heavy, it does too much things like sugary stuff (base64 encoding, etc.).
It's have embedded C++ OpenSSL wrapper inside it, initialized during installation.
PS: keypair uses native JS, that's why it's very slow. It's not recommended with Node.js which is not good at performing CPU-intensive operations (but good with non-blocking events).

Resources