Crypto RSASSA-PSS algo in node.js - node.js

I am using crypto. I have public key, data and signature. All working fine with openssl command
openssl dgst -sha256 -sigopt rsa_padding_mode:pss -verify pk.pem -signature sigval1.sig data1.txt
But while using node crypto verify it always returning false
crypto.verify(
'RSA-SHA256',
Buffer.from(data),
newPublicKey,
Buffer.from(signature, 'binary'),
)
Requirement:
"alg": "RSASSA-PSS","hash": "SHA256"

In the OpenSSL statement, PSS is used as padding. NodeJS applies PKCS#1 v1.5 padding by default, so PSS must be explicitly specified in the third parameter in crypto.verify(...):
var verified = crypto.verify(
'RSA-SHA256',
Buffer.from(data, 'utf-8'),
{
key: newPublicKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
//saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN // default
},
Buffer.from(signature, 'binary')
);
Note that the OpenSSL statement uses the maximum possible salt length as the salt length, rather than the default given in RFC8017, which is the output length of the digest. This is not critical here in that crypto.verify() also uses the maximum salt length (crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN).

Related

error:0409A06E:rsa routines data too large for key size

I'm generating a SAML response and it needs to be encrypted and signed with public and private keys. I generated private.pem and public.pem in the terminal with the commands
openssl genrsa -out private.pem 2048
openssl rsa -in ./private.pem -pubout -out public.pem
Then in nodeJS.
encrypt: function(message) {
return new Promise(function (resolve, reject) {
var publicKey = require("fs").readFileSync(__dirname + "/public.pem", "utf8");
var encrypted = require("crypto").publicEncrypt(publicKey, new Buffer(message));
resolve(encrypted.toString("base64"));
});
},
Once I call the message encrypt(xml), I get the following error
{
library: 'rsa routines',
function: 'RSA_padding_add_PKCS1_OAEP_mgf1',
reason: 'data too large for key size',
code: 'ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE'
}
Objective:
I've to sign the message as per the demo here samltools.com (Mode: SignMessage), my SAML message looks like this. (see SAML Response section).
Sign the message
Base64Encode the message
The problem here is that you cannot directly encrypted with RSA, a piece of data which is larger than the key size.
Surprising I know, it surprised me too.
In reality very little payload data is encrypted directly with RSA or even elliptic curves.
You should be using RSA Diffie-Hellman to generate a shared secret.
Signature of the file, is really signature of the hash of the file.

Is there a way to create an AES cipher from a hashed key-secret?

Having lost the plaintext secret, but having the hashed key, it's posible to encript and decript in openssl like so (keys and iv are not the actual ones):
Input:
printf "ciphertext" | base64 -d | openssl enc -aes-256-cbc -d -nosalt -K "0000000000000000000000000000000000000000000000000000000000000000" -iv "00"
Output:
"plaintext"
Now, to be able to do this in a NodeJs application, openssl is called as child_process. As you can guess, spawning openssl calls is not very performant.
To be able to do it in node crypto, the plaintext "secret" is needed in creating the keys.
Is there a way yo generate the cipher from the hashed key?
I have tried doing it like so with no succes.
var crypto=require('crypto')
var iv = Buffer.alloc(16, 0);
var key = '0000000000000000000000000000000000000000000000000000000000000000'
var cipher=crypto.createDecipher('aes-256-cbc', newBuffer(key).toString('binary'), new Buffer('0000000000000000', 'hex').toString('binary'));
var enc = cipher.update("ciphertext", 'base64', 'utf8')
enc += cipher.final('utf8')
console.log(enc);
Output:
internal/crypto/cipher.js:164
const ret = this._handle.final();
^
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Try
new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex')
for the key and and
new Buffer('00000000000000000000000000000000', 'hex')
for the IV. Currently you are encoding to a binary string representation of the bytes, instead of (just) decoding the hexadecimal values to 32 bytes and 16 bytes respectively.
To use this you should use createDecipheriv as createDecipher will still generate the key using the password.

Node.js Crypto AES-128 encrypt stream with padding

I am successfully using NodeJS to encrypt a file in the following manner:
let cipher = crypto.createCipheriv('aes-128-cbc', key, iv),
crypted = cipher.update(file_data, 'binary');
return Buffer.concat([crypted, cipher.final()]);
This generates a file with a hash that exactly matches our target encryption, which is server-side OpenSSL command:
openssl aes-128-cbc -e -in file -out file.enc -nosalt -iv <iv_str> -K <k_str>
The goal is to upgrade our systems to use nodejs streams. Node documentation and other posts suggest the following way to encrypt streams:
cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
let file_stream = fs.createReadStream(local_file, 'binary'),
write_stream = fs.createWriteStream(`${local_file}.enc`, 'binary');
file_stream.pipe(cipher).pipe(write_stream);
however this ignores the cipher.final() appending. Without it the encryption always results in a file with the wrong hash. I've tried:
creating a Transform that would append the remaining padding. That doesn't work because you can't call final on a cipher that hasn't had update called on it
setting the autopadding with setAutoPadding. In all cases that results in the error Error: error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:data not multiple of block length
How can I cipher with streams in the same way I was originally encrypting?

NodeJS Crypto RS-SHA256 and JWT Bearer

In implementing an oauth2 stack utilizing passport and oauth2orize, in this case the issue is specifically in utilizing the oauth2orize jwt bearer. The oauth2orize jwt bearer is great in getting everything going, however it has the RSA SHA pieces marked as to do.
In attempting to put in the pieces for the RSA SHA encryption handling, I cannot get the signature to verify as verifier.verify always seems to return false. If anyone has cleared this hurdle, a little help would be super.
What I've done:
Created the private / public keys:
openssl genrsa -out private.pem 1024
//extract public key
openssl rsa -in private.pem -out public.pem -outform PEM -pubout
now the data to sign:
{"alg":"RS256","typ":"JWT"}{"iss": "myclient"}
I've tried multiple ways as to how to sign this, too many to list here, but my understanding of the correct signature is to sign the bas64 encoding of these items, so i ran base64 on {"alg":"RS256","typ":"JWT"} and base64 on {"iss": "myclient"} then ran base64 on those encodings. So the result is:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
eyJpc3MiOiAibXljbGllbnQifQ
then encode:
{eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9}.{eyJpc3MiOiAibXljbGllbnQifQ}
which gives me:
e2V5SmhiR2NpT2lKU1V6STFOaUlzSW5SNWNDSTZJa3BYVkNKOX0ue2V5SnBjM01pT2lBaWJYbGpiR2xsYm5RaWZRfQ
At this point I sign the above base64 by doing:
openssl sha -sha256 -sign priv.pem < signThis > signedData
Then I run base64 on that to get the data to pass into the signature part of the assertion.
I then pass in the object:
{
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiAibXljbGllbnQifQ.signedData"
}
now in the code base I have:
var crypto = require('crypto')
, fs = require('fs')
, pub = fs.readFileSync('/path/to/pub.pem')
, verifier = crypto.createVerify("RSA-SHA256");
verifier.update(JSON.stringify(data));
var result = verifier.verify(pub, signature, 'base64');
console.log('vf: ', result);
however, result is always false.
I do properly receive the data, the signature variable in the code is a match for what I'm passing in, I just always receive false and have exhausted all options I can think of on how to tweak this to get verifier.verify to return true. Thank you for the time and help!
I am not sure if this is exactly what you were looking for, but this will successfully create a JWT in a google api fashion using jwt-simple (which uses crypto and such):
var fs = require('fs')
, jwt = require('jwt-simple')
, keypath = '/path/to/your.pem'
, secret = fs.readFileSync( keypath, { encoding: 'ascii' })
, now = Date.now()
, payload = {
scope: 'https://www.googleapis.com/auth/<service>',
iss : '<iss_id>#developer.gserviceaccount.com',
aud : 'https://accounts.google.com/o/oauth2/token',
iat : now,
exp : now+3600
}
, token = jwt.encode( payload, secret, 'RS256' )
, decoded = jwt.decode( token, secret, 'RS256' );
console.log( token );
console.log( decoded );
I think this code sample is completely unsecure. If you look at the latest JWT code, it doesn't event use the secret on your decode call.
https://github.com/hokaccha/node-jwt-simple/blob/master/lib/jwt.js
It basically just decodes the second segment and returns it which means anyone could have changed the value and its not verified.

What's wrong with nodejs crypto decipher?

I have the following encrypted data:
U2FsdGVkX1+21O5RB08bavFTq7Yq/gChmXrO3f00tvJaT55A5pPvqw0zFVnHSW1o
The pass to decrypt it is: password
(it's the example from gibberish-aes)
In the command line using openssl:
echo "U2FsdGVkX1+21O5RB08bavFTq7Yq/gChmXrO3f00tvJaT55A5pPvqw0zFVnHSW1o" | openssl enc -d -aes-256-cbc -a -k password
The output is:
Made with Gibberish\n
With my NodeJS application:
var decipher = crypto.createDecipher('aes-256-cbc', "password");
var dec = decipher.update("U2FsdGVkX1+21O5RB08bavFTq7Yq/gChmXrO3f00tvJaT55A5pPvqw0zFVnHSW1o",
'base64', 'utf8');
dec += decipher.final('utf8');
I have the following error TypeError: DecipherFinal fail at the decipher.final line.
Am I missing something ? Thanks.
The encrypted data starts with a 8 byte "magic" indicating that there is a salt (the ASCII encoding of "Salted__"). Then the next 8 bytes is the salt. Now the bad news: Node.js does not seem to use the salt for the EVP_BytesToKey method:
int key_len = EVP_BytesToKey(cipher, EVP_md5(), NULL,
(unsigned char*) key_buf, key_buf_len, 1, key, iv);
That NULL is the salt.
This has been verified using a Java test application (using the right salt) - the result string was returned.
Please leave out the salt using the OpenSSL -nosalt switch and try again.
[EXAMPLE]
OpenSSL CLI:
openssl enc -aes-256-cbc -nosalt -a -k password
owlstead
Mh5yxIyZH+fSMTkSgkLa5w==
NodeJS crypto:
var crypto=require('crypto')
var cipher=crypto.createDecipher('aes-256-cbc', "password")
var enc = cipher.update("Mh5yxIyZH+fSMTkSgkLa5w==", 'base64', 'utf8')
enc += cipher.final('utf8')
[LATE EDIT] Note that using secret key derivation with a salt and large work factor may be paramount to security. You'd better use a very unique, high entropy password otherwise your encrypted data may be at risk.
[REALLY LATE EDIT] OpenSSL 1.1.0c changed the digest algorithm used in some internal components. Formerly, MD5 was used, and 1.1.0 switched to SHA256. Be careful the change is not affecting you in both EVP_BytesToKey and commands like openssl enc.

Resources