Trying to symmetrically encrypt a value for storage in the client (httpOnly cookie) and having an issue decrypting - node.js

I am trying to encrypt a value on my server with a private key to store it on the client within an httpOnly cookie.
I am having trouble with the encryption/decryption lifecycle
function encrypt(input) {
const encryptedData = crypto.privateEncrypt(
privateKey,
Buffer.from(input)
)
return encryptedData.toString('base64')
}
function decrypt(input) {
const decryptedData = crypto.privateDecrypt(
{ key: privateKey },
Buffer.from(input, 'base64'),
)
return decryptedData.toString()
}
const enc = encrypt('something moderately secret')
const dec = decrypt(enc)
console.log(dec) // 'something moderately secret'
However the crypto.privateDecrypt function is throwing with
Error: error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error
Side question, is it safe to reuse the same private key the server uses to sign JWTs. It's an rsa key generated using ssh-keygen -t rsa -b 4096 -m PEM -f RS256.key

So, you don't use crypto.privateEncrypt() with crypto.privateDecrypt(). That's not how they work. Those functions are for asymmetric encryption, not for symmetric encryption. You use either of these two pairs:
crypto.publicEncrypt() ==> crypto.privateDescrypt()
crypto.privateEncrypt() ==> crypto.publicDecrypt()
So, that's why you're getting the error you're getting. The nodejs doc for crypto.privateDecript() says this:
Decrypts buffer with privateKey. buffer was previously encrypted using the corresponding public key, for example using crypto.publicEncrypt().
If what you really want is symmetric encryption, there are a bunch of options in the crypto module for that. There are some examples shown here: https://www.section.io/engineering-education/data-encryption-and-decryption-in-node-js-using-crypto/ and https://fireship.io/lessons/node-crypto-examples/#symmetric-encryption-in-nodejs.

Related

Reading public and private key from stored files in node js

I wanted to encrypt and decrypt a message in node using public and private keys stored in my system. I was using the following java code to read the file and use the keys.
Java Code:
byte[] keyBytes = Files.readAllBytes(new File(publicKeyFileName).toPath());
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
publicKey=kf.generatePublic(spec);
I am able to use the above java method without any issues to read the public key from file. However, I want to achieve similar functionality in node.
I have tried using crypto for achieving the same but it gives me error while passing the key to publicEncrypt method.
Node:
var encryptStringWithRsaPublicKey = function(toEncrypt, relativeOrAbsolutePathToPublicKey) {
var absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey);
var publicKey = fs.read(absolutepath, "utf-8");
console.log(publicKey);
var buffer = Buffer.from(toEncrypt);
var encrypted = crypto.publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
};
Error
internal/crypto/cipher.js:43
return method(toBuf(key), buffer, padding, passphrase);
^
Error: error:0906D06C:PEM routines:PEM_read_bio:no start line
Please help. Thanks
Your problem is located in the file format you are actually using with Java. You probably save the
private and the public in encoded ("byte array") to a file and rebuild the keys e.g. with
X509EncodedKeySpec.
This format is not compatible to Node.JS and you have 3 ways to solve it:
a) you write the keys in Java with re neccessary format for usage in Node.JS
b) you write a converter in Node.JS to get the correct format
c) you convert the files with a tool like OPENSSL.
Here I show you the "c-way" as you are handling just one keypair and probably don't need a programatically solution.
Let's say you have two files with the private key ("rsa_privatekey_2048.der") and the public key ("rsa_publickey_2048.der").
In OPENSSL you are using the command line with
openssl rsa -inform der -in rsa_privatekey_2048.der -outform pem -out rsa_privatekey_2048.pem
openssl rsa -inform der -pubin -in rsa_publickey_2048.der -outform pem -RSAPublicKey_out -out rsa_publickey_2048.pem
to convert the files to their PEM-encoded formats.
Below you can find the two sample files I created.
rsa_privatekey_2048.pem:
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAmbeKgSAwVe0nZ84XlbDhMkUDjx1C0duA16MkzHTg1uh9SouO
KK0e3gPtTJ9LssaHlXSYhjpMDMWGO6ujd85XRosI2u9eSMNRYY25AQuBriSTVdi9
BHqWAuWuo6VuvTrkgWTL69vNWvLXTOkTiIyrgnhiavjNvm4UVy2AcO2Y3ER+dKgJ
pQAYlEP1jvuQuf6dfNdSBoN0DZbxZXYbQqoA9R/u0GZHCXY+r8A54RejG34pnnuH
koyROZz5H9LbKGOiaETryornQ1TRvB/p9tgIoCJFI71WsKsqeWQPG3Ymg/FoEWXN
Y0yopZEjpkZa3tU+hrOmAFIRg+/bedKfjYFi/QIDAQABAoIBAD3XZ3N3fbq0BExw
z3A7jv3oYfwrq3w+MOGQEvfmdaZANlfNOU4ICAkNz2QqGgw8bsOj+tDVl070EILl
FIjYjKgmu1NJRcdEPPNgTvOqq2th75xz6+dnYf6cZNwVbC3ZCaE86gVjkoRqek/I
3UDsRvvgbsfWfP+Fzc0c0zWbgQnsK6qivU1uzJX+5xsvgQlZboeZOO2lsdQMgfnu
iGlW1bVVM4Sy7AngqfiKMzihUnYEBIi0Y+mfxAPcBLUW8mrOvIOPPuNNUPxUtkBF
bDEzZ6loXCLLD8UBqXeDbCUPPFdTGcc7INhVgFdl2FL6rHB0+p6eUt8MI/XkZI2d
2AnkBUkCgYEA34cKLs2l5bPjyKybbj6ZG7RhDRsnPypEGU63DMV21twISqt7ZQNv
i3iTP+FYHM3ImECbNRIOZpyLuWLPmh5+5egQH13jRDempoxVSVcghbIserlCz2EU
nD2V6ZKuaDbn395O6Qe/PE/yKHLWbXwJrBBm+o7GGNm/Jd3KJib23PcCgYEAsAxB
esEsxxL8hqg/qf+ij7JJt492svpK/6QXhqeeP/FVOtYSgoNtSrXh9qahGpzMSF0m
FqwIgrOX0RkK3v6ofGVfIKObxOVyhwddS1Ru+SnjBFnTMKS57q0WNrIrBNM6Q0xE
Wd3tiljwmg8xF90U/BXu+m0v5XWKxSn7VLiCBqsCgYEAgl0xtSY/EP6fZJQ2ek+L
4DqNN6WUeCRgXxonbA1mR91AALyOVNVyIreJuYHlb7ccvJ9BZexH9dRrMQ3N4ibS
/6cecAzD1S9XxF6oBwQHdbH6ewC9VFFcQdsxKW5gxWrwRQJUp1fbUoOVyb1gDa5/
vZg7VvoZ0rh74Mu/cAzdgPUCgYEAiNANdwt29AKyUyefykpLGCczGL8aPP88l6z7
R38uAX1Ygg/pdJoUrnHo+FkIbHkcXMRfHFF3j7NoMWynwSLg50OUiPX80SiLN5qm
iytDzskZjsEL2gq6IF1NHRabTfWlmrVDjR9mQhTabq+NtIDwlPOqs9100nrlbFIy
6uU0z18CgYEAkDxQg5UjOzbYIighp/e0LkfKlwUE/cMtISUq1A6Yio9AKZYdBV8p
dd4osUW0gZvDflXlQaBIaNlOwl035lyEZtyuKmFh1oSmoir/TTMbIk0avSgKCLGg
fnhabaQRHl4RdXWcEsioRv3aZUsGkb46Y8xODyAUPRHBPhBsd71gnZ8=
-----END RSA PRIVATE KEY-----
rsa_publickey_2048.pem:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAmbeKgSAwVe0nZ84XlbDhMkUDjx1C0duA16MkzHTg1uh9SouOKK0e
3gPtTJ9LssaHlXSYhjpMDMWGO6ujd85XRosI2u9eSMNRYY25AQuBriSTVdi9BHqW
AuWuo6VuvTrkgWTL69vNWvLXTOkTiIyrgnhiavjNvm4UVy2AcO2Y3ER+dKgJpQAY
lEP1jvuQuf6dfNdSBoN0DZbxZXYbQqoA9R/u0GZHCXY+r8A54RejG34pnnuHkoyR
OZz5H9LbKGOiaETryornQ1TRvB/p9tgIoCJFI71WsKsqeWQPG3Ymg/FoEWXNY0yo
pZEjpkZa3tU+hrOmAFIRg+/bedKfjYFi/QIDAQAB
-----END RSA PUBLIC KEY-----
There's potentially a few issues with your code or the encryption key you're using:
You're using fs.read incorrectly as Node is asynchronous and it needs a callback function to properly read the file.
The encryption key you're using is formatted incorrectly for crypto.publicEncrypt. You must have the proper RSA headers.
I modified your code to use fs.readFile properly instead in the standard Node callback form, and here's an example encryption key in the correct RSA format to use:
var path = require('path');
var crypto = require('crypto');
var fs = require('fs');
var encryptStringWithRsaPublicKey = function(toEncrypt, relativeOrAbsolutePathToPublicKey, callback) {
var absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey);
fs.readFile(absolutePath, 'utf-8', (err, publicKey) => {
// The value of `publicKey` is in the callback, not the return value
console.log(publicKey);
var buffer = Buffer.from(toEncrypt);
var encrypted = crypto.publicEncrypt(publicKey, buffer);
if (err) {
callback(err);
} else {
callback(null, encrypted.toString("base64"));
}
});
};
encryptStringWithRsaPublicKey('hello world', 'test.pub', (err, encrypted) => {
// If you're using a callback in a function,
// the original function must have a callback as well
console.log(encrypted);
});
Example encryption key at test.pub (must have the RSA headers as shown below):
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA+xGZ/wcz9ugFpP07Nspo6U17l0YhFiFpxxU4pTk3Lifz9R3zsIsu
ERwta7+fWIfxOo208ett/jhskiVodSEt3QBGh4XBipyWopKwZ93HHaDVZAALi/2A
+xTBtWdEo7XGUujKDvC2/aZKukfjpOiUI8AhLAfjmlcD/UZ1QPh0mHsglRNCmpCw
mwSXA9VNmhz+PiB+Dml4WWnKW/VHo2ujTXxq7+efMU4H2fny3Se3KYOsFPFGZ1TN
QSYlFuShWrHPtiLmUdPoP6CV2mML1tk+l7DIIqXrQhLUKDACeM5roMx0kLhUWB8P
+0uj1CNlNN4JRZlC7xFfqiMbFRU9Z4N6YwIDAQAB
-----END RSA PUBLIC KEY-----
As of 2020, there are also other ways of making the code cleaner, such as with using the Promises version of the fs module and async / await, though I wanted to keep this answer as simple as possible for now.

Create RSA-SHA256 sign with PEM key

I've been creating a gateway for a legacy service, this legacy service needs a signature as a body parameter of a PUT request, in order to create this sign I need to follow the following steps:
Create a hash with certain text as data, this hash needs to be SHA256.
Encrypt the result of the hash using RSA with a PEM key
Encode the result of the RSA to Base64
Following the previous steps I create the following code
export class Signature {
// class body
public static sign(text: string){
const key = readFileSync('key.pem')
const passphrase = '12345678'
const createdSign = createSign('RSA-SHA256')
createdSign.write(text)
createdSign.end()
return createdSign.sign({ key, passphrase }).toString('base64')
}
}
But I'm not sure if this the correct implementation, taking into consideration the previous steps, and the existence of the hash API in NodeJS.
If someone could tell me if I'm correctly implementing this algorithm.

How to generate a PEM-formatted Key from a 64Byte raw hex-formatted Key

I have the following problem:
After recreating the public key from a signed transaction, I try to encrypt some payload with it.
However the node.js-module named "crypto" is expecting a pem-formatted key in the publicEncrypt(key, payload) function.
My Question:
How can I create the pem-formatted key from a raw hex-encoded key?
Here is the recreated 64 Byte public key:
9f9f445051e788461952124dc08647035c0b31d51f6b4653485723f04c9837adb275d41731309f6125c14ea1546d86a27158eec4164c00bab4724eed925e9c60
Information:
I know, that a pem-format-key consists of base64 encoded data, a header and a footer.
-----BEGIN RSA PUBLIC KEY-----
BASE64 ENCODED DATA
-----END RSA PUBLIC KEY-----
I have also found out that within the base64 encoded data the following DER-structure is present:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
So the only question is how to get from the raw hex-encoded key to this DER-structure.
I would appreciate any help!
Problem solved
Thanks to Maarten Bodewes and his comment regarding the key being secp256k1 and not RSA.
After some further research, I finally managed to encrypt/decrypt a message asymmetrically with secp256k1 keys.
With the help of Cryptos ECDH class I managed to create a key-object and then assign the private key to it. When assigned, you can easily derive the public key with getPublicKey(). All participants would create a key object for themselves and assign their private keys to it. Then they share their retrieved public keys (in my case over a shared medium). In addition I used a npm-package named standard-ecies which provides the ECIES encryption-scheme.
Code:
const crypto = require('crypto');
const ecies = require('standard-ecies');
var buffer = new Buffer("Hello World");
var ecdh = crypto.createECDH('secp256k1');
ecdh.setPrivateKey(privateKey);
var encryptedText = ecies.encrypt(ecdh.getPublicKey(), buffer);
var decryptedText = new Buffer(ecies.decrypt(ecdh, encryptedText));
I should have noticed this, because crypto's encryption function (link to the api-doc) clearly works only with RSA keys and not with secp256k1 keys.
Anyway if someone has a similar issue, I hope this answer helps!

how to generate encrypted JWE with node-jose

I'm using node-jose v0.11.0 (https://www.npmjs.com/package/node-jose) for JWK and JWE operations. I have an RSA key in JWK format that I can load into a JWK key store and also extract again. However, when I try to encrypt anything, I get into the "error2", "unsupported algorithm". How is it possible that RSA is an unsupported algorithm?
import * as jose from "node-jose";
const webkey = {
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "a024254d-0321-459f-9530-93020ce9d54a",
"key_ops": [
"encrypt"
],
"n": "jkHgYN98dlR2w7NX-gekCWaCdbxs7X4XXh52DVQrK--krwUYqRbBIUEw1bV8KX0ox6TLt-e6wpYsYYFUItSd5ySqohHRMq1IhyE2zpEC95BA9V7VrFUYnczf1bd5c-aR079aoz5JPXfqx01TzNfxWBb04SlRjsmJeY1v6JrDUI5U0FSOmnJTb3tSS6Szrvi_qOyViYp4v9V2_OVYy45kF_LQQy-pr-kP4gapXL235cieeTW6UvkhzaPT2D-JKyzVjjjgnfRXr8Ox9I9c4wpef2-5nPPeafB5EnOMpJE11KzO_8xxiTGUywPPLQagBvY35gkhQbYS2dv3NGIVSLZHFw"
}
]
};
console.log("webkey", webkey);
//generate key store from public JWK
jose.JWK.asKeyStore(webkey)
.then((result) => {
console.log("Key Store", JSON.stringify(result.toJSON()));
let keyStore = result;
//get the key to encrypt
const encryptionKey: jose.JWK.Key = keyStore.get(webkey.keys[0].kid);
const output = jose.util.base64url.encode("Hello World");
const output2 = jose.util.asBuffer(output);
//encrypting content
jose.JWE.createEncrypt(encryptionKey)
.update(output2)
.final()
.then((jweInGeneralSerialization) => {
console.log("Encryption result", JSON.stringify(jweInGeneralSerialization));
}, (error) => {
console.log("error2", error.message);
});
}, (error) => {
console.log("error1", error.message);
})
The output is as follows:
'webkey', Object{keys: [Object{kty: ..., e: ..., kid: ..., key_ops: ..., n: ...}]}
'Key Store', '{"keys":[{"kty":"RSA","kid":"a024254d-0321-459f-9530-93020ce9d54a","key_ops":["encrypt"],"e":"AQAB","n":"jkHgYN98dlR2w7NX-gekCWaCdbxs7X4XXh52DVQrK--krwUYqRbBIUEw1bV8KX0ox6TLt-e6wpYsYYFUItSd5ySqohHRMq1IhyE2zpEC95BA9V7VrFUYnczf1bd5c-aR079aoz5JPXfqx01TzNfxWBb04SlRjsmJeY1v6JrDUI5U0FSOmnJTb3tSS6Szrvi_qOyViYp4v9V2_OVYy45kF_LQQy-pr-kP4gapXL235cieeTW6UvkhzaPT2D-JKyzVjjjgnfRXr8Ox9I9c4wpef2-5nPPeafB5EnOMpJE11KzO_8xxiTGUywPPLQagBvY35gkhQbYS2dv3NGIVSLZHFw"}]}'
'error2', 'unsupported algorithm'
Update
I digged around a bit in the actual code and found in "basekey.js" that the error is thrown because the algorithms of the library are empty.
Object.defineProperty(this, "encrypt", {
value: function(alg, data, props) {
// validate appropriateness
if (this.algorithms("encrypt").indexOf(alg) === -1) {
console.log("Algorithm USED", alg
);
console.log("All algorithms", this.algorithms("encrypt"))
return Promise.reject(new Error("unsupported algorithm"));
}
The output here is:
'Algorithm USED', 'A128CBC-HS256'
'All algorithms', []
I have an example that I added to another question:
node-jose explanation / example?
I used node-jose in a research proof, for a reflection of my c# code, I only created signed and Encrypted tokens for decryption and verification, on my server ( written in c#).
I need to use symetric secret key or asymetric public private key pair
?
I used RSA keys for Asymmetric signatures and key wrapping the Symmetric encryption details of the content. The Encryption algorithm for content encryption is a Symmetric one. The node-jose package generated the Symmetric key. The Key Wrap algorithm encrypted the Symmetric key.
The C# code I have decrypts and validates the token signature. Please note: I used the functions of the package to do all the work.
Here are my runkit notebooks for my workups:
for signing (JWS) https://runkit.com/archeon2/5bd66a8e7ee3b70012ec2e39
for encrypting (JWE) https://runkit.com/archeon2/5bd6736ff36b39001313262a
In my final, I combined the two, creating a signed token, then used the output as the payload for the encrypted one (JWS + JWE). I was successful using the c# server code in decrypting, and validating the created tokens.
JWS + JWE : https://runkit.com/archeon2/jws-jwe-integration
How i need to generate and where i need to store keys in my server
node app to then allow me to sign and verify my tokens ?
var store = jose.JWK.createKeyStore();
await store.generate("RSA",2048,{alg:"RS256", key_ops:["sign", "decrypt", "unwrap"]});
lkey = (await store.get());
var key = lkey.toJSON(); //get public key to exchange
key.use = "sig";
key.key_ops=["encrypt","verify", "wrap"];
var pubKey = await jose.JWK.asKey(key);
key = null;
The Keystore can be serialized to JSON, so my concept would be to store this in Session Storage, or Local storage in a browser. Then retrieve the JSON representation and read in the Keystore.
var store= await jose.JWK.asKeyStore({"keys":[{"kty":"RSA","kid":"h9VHWShTfENF6xwjF3FR_b-9k1MvBvl3gnWnthV0Slk","alg":"RS256","key_ops":["sign","decrypt","unwrap"],"e":"AQAB","n":"l61fUp2hM3QxbFKk182yI5wTtiVS-g4ZxB4SXiY70sn23TalKT_01bgFElICexBXYVBwEndp6Gq60fCbaBeqTEyRvVbIlPlelCIhtYtL32iHvkkh2cXUgrQOscLGBm-8aWVtZE3HrtO-lu23qAoV7cGDU0UkX9z2QgQVmvT0JYxFsxHEYuWBOiWSGcBCgH10GWj40QBryhCPVtkqxBE3CCi9qjMFRaDqUg6kLqY8f0jtpY9ebgYWOmc1m_ujh7K6EDdsdn3D_QHfwtXtPi0ydEWu7pj1vq5AqacOd7AQzs4sWaTmMrpD9Ux43SVHbXK0UUkN5z3hcy6utysiBjqOwQ","d":"AVCHWvfyxbdkFkRBGX225Ygcw59fMLuejYyVLCu4qQMHGLO4irr7LD8EDDyZuOdTWoyP7BkM2e7S367uKeDKoQ6o1LND2cavgykokaI7bhxB0OxhVrnYNanJ1tCRVszxHRi78fqamHFNXZGB3fr4Za8frEEVJ5-KotfWOBmXZBvnoXbYbFXsKuaGo121AUCcEzFCGwuft75kPawzNjcdKhItfFrYh45OQLIO08W0fr_ByhxzWMU7yFUCELHSX5-4GT8ssq1dtvVgY2G14PbT67aYWJ2V571aSxM8DTwHrnB9tI8btbkXWt9JyVoQq13wDdo5fVN-c_5t07HBIaPoAQ","p":"8nLGa9_bRnke1w4paNCMjpdJ--eOUpZYbqEa8jnbsiaSWFwxZiOzUakIcpJ3iO0Bl28JEcdVbo7DE7mZ4M3BkOtm577cNuuK8243L7-k1a71X_ko2mQ3yF4rG2PzWAH_5P4wca1uk0Jj3PmhbkXDI6f_btm1X7Vw_U1K6jRhNbE","q":"oCe94Bed1Wzh-xgNq0hz52Z6WLf9eQlNxLzBbYkpLc_bGj9vMeGNO10qdxhWPi8ClkW9h5gBiFEk2s6aEWYRvIoZjrMYXD7xzyTNC5zcsikjNhM3FVj-kVdqUJy25o9uqgn2IwTvQr5WSKuxz37ZSnItEqK5SEgpCpjwEju_XhE","dp":"jAe2ir-0ijOSmGtZh2xMgl7nIFNRZGnpkZwDUDwSpAabJ-W3smKUQ2n5sxLdb3xUGv7KojYbJcvW6CGeurScQ_NycA9QaXgJvSe_QBjUP4bZuiDSc7DGdzfMdfl4pzAgeEZH_KBK6UrDGvIjRumMF6AEbCXaF_lX1TU7O6IdM0E","dq":"fDU2OjS2sQ5n2IAYIc3oLf-5RVM0nwlLKhil_xiQOjppF9s4lrvx96dSxti2EjYNUJQ34JBQJ_OenJ_8tx-tA8cq-RQHAYvDp75H1AjM1NO4vjh60PCbRgdAqdJQu1FkJzXgkdpC4UWSz3txRJaBWQ5hzIEtJ1Tnl5NzJQD3crE","qi":"3EoKqhKh5mwVGldSjwUGX7xnfQIfkQ4IETsQZh9jcfOFlf9f8rT2qnJ7eeJoXWlm5jwMnsTZAMg4l3rUlbYmCdg10zGA5PDadnRoCnSgMBF87d0mVYXxM1p2C-JmLJjqKhJObr3wndhvBXUImo_jV6aHismwkUjc1gSx_b3ajyU"},{"kty":"RSA","kid":"h9VHWShTfENF6xwjF3FR_b-9k1MvBvl3gnWnthV0Slk","use":"verify","alg":"RS256","key_ops":["encrypt","verify","wrap"],"e":"AQAB","n":"l61fUp2hM3QxbFKk182yI5wTtiVS-g4ZxB4SXiY70sn23TalKT_01bgFElICexBXYVBwEndp6Gq60fCbaBeqTEyRvVbIlPlelCIhtYtL32iHvkkh2cXUgrQOscLGBm-8aWVtZE3HrtO-lu23qAoV7cGDU0UkX9z2QgQVmvT0JYxFsxHEYuWBOiWSGcBCgH10GWj40QBryhCPVtkqxBE3CCi9qjMFRaDqUg6kLqY8f0jtpY9ebgYWOmc1m_ujh7K6EDdsdn3D_QHfwtXtPi0ydEWu7pj1vq5AqacOd7AQzs4sWaTmMrpD9Ux43SVHbXK0UUkN5z3hcy6utysiBjqOwQ","use":"sig"}]});
How can i know which one use between OCT, EC, RSA etc ?
For this, the need your token serves may dictate this. I needed the receiver to be the one who could see the contents, so I chose RSA, for Asymmetric keys. Forgery is a bit harder.
These notebooks are somewhat a work in progress. Please review with care, as this is my interpretation and how I worked out what I needed. My hope is that they give some guidance.

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.

Resources