Implementing Poloniex crypto using Node.js - node.js

I am trying to implement the Poloniex account notification websocket API in Node.js
https://docs.poloniex.com/#websocket-api
The command is:
{ "command": "subscribe", "channel": "1000", "key": "", "payload": "nonce=", "sign": "").hexdigest()>" }
The first parts are easy however I am stuck on the sign parameter. This involves
1) encrypting my secret using hmca_sha512
2) updating it with the nonce?
3) converting it to a hex digest?
So far my code reads as follows however the last line has me stuck:
ws2.on('open', function open() {
var nonce = Date.now();
ws.send(JSON.stringify({ 'command': 'subscribe',
'channel': 1000,
'key': apiKey,
'payload': `nonce=${nonce}`,
**'sign': "<hmac_sha512(secret).update("nonce=<epoch ms>").hexdigest()>"**
}));
});
I think I can achieve the hmac_sha512 by using:
const crypto = require('crypto');
const hmac = crypto.createHmac('sha512', apiSecret);
I think I can convert a value to a hex digest using:
.toString('hex');
I am not sure of how to implement the .update("nonce=") part. I can partially update this to be as below however am lost on the "update()" function.
.update(`nonce=${nonce}`)

Related

Why can I sign and verify JWT's with a RSA-algorithm using EC-key-pairs?

I use the crypto core-module in node.js to generate an EC-key-pair.
I then use this key-pair to sign and verify JWT's.
I would expect the signing/verification to only work when using an EC-algorithm.
However it seems to be working with any algorithm except HMAC.
From my understanding, this shouldn't be possible.
Can anyone explain this to me?
Thank you for reading my question.
const crypto = require('crypto')
const jwt = require('jsonwebtoken')
const keyPair = crypto.generateKeyPairSync('ec', {
namedCurve: 'secp256k1',
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
})
const token = jwt.sign({}, keyPair.privateKey, {
algorithm: 'ES256' // I expect this to work, but it seems to be also working with e.g. "RS256" or "PS512", which I don't understand.
})
const verify = jwt.verify(token, keyPair.publicKey)
This made me curious too, so I tried to dig up sources.
If you follow what happens during signing a jwt, you will eventually get to this:
function createKeySigner(bits) {
return function sign(thing, privateKey) {
checkIsPrivateKey(privateKey);
thing = normalizeInput(thing);
// Even though we are specifying "RSA" here, this works with ECDSA
// keys as well.
var signer = crypto.createSign('RSA-SHA' + bits);
var sig = (signer.update(thing), signer.sign(privateKey, 'base64'));
return fromBase64(sig);
}
}
This doesn't answer why though. So digging deeper, crypto is implemented in C, and I think this is the relevant part. If I understand correctly, the reason it works mostly comes down to the fact that it is just read as a string/buffer.

How to generate RSA public key using exponent (e) and modulus (n) to verify the signature of a json web token?

My goal is to verify my id_token in JSON Web Token (JWT) format using jsonwebtoken verify method, which requires a public key as the second argument. My web application is a node js application.
According to Ping ID's documentation, I am supposed to generate the RSA public key using the exponent(e) and modulus(n). Here is an example that is applicable for my id_token:
{
"kty": "RSA",
"kid": "IlOPtWXUcpiPttmr-K7DmehzeRM",
"use": "enc",
"n": "qv2XCvfUfW0bG547B1xieE0-GN8xLuCdzGcIWsYMP-fn1vR2ptR7XOp_kW-etlxSDT2MVyzdXbG9eQCgeBk-Ajgbyn4AaFScJt9ibGyE-5hUvkSJRTP-jlJjlPniYsKcjEY3C-QzyRcEIHoOHOEuevIFwVvKNRgEVYyx3CmkmIXcfw35R1tORNjCec_NA6dawx_LPpS0endjNz2m_iijLquKenrsKSKVnBprfVtBh_myuNQD5CfhBnzZRmAUfr0PoVMDBb0r_rWaV1Q64zQWSeCql7CSWq4U8RNhogd0eCZOOv45plIUwoxkdNg0Rzkp-OEtKRLaHonJ_OZ_sxa8-w",
"e": "AQAB"
},
The question is, how do I generate the public key using these values? Is there a node js library that does this?
I am new to cryptography, so pardon my beginner's question.
You can use node's Crypto module to easily convert a jwk to a public key(pem), without using external dependencies.
import crypto from 'crypto';
let publicKey = crypto.createPublicKey({
key: {
"kty": "RSA",
"kid": "IlOPtWXUcpiPttmr-K7DmehzeRM",
"n": "qv2XCvfUfW0bG547B1xieE0-GN8xLuCdzGcIWsYMP-fn1vR2ptR7XOp_kW-etlxSDT2MVyzdXbG9eQCgeBk-Ajgbyn4AaFScJt9ibGyE-5hUvkSJRTP-jlJjlPniYsKcjEY3C-QzyRcEIHoOHOEuevIFwVvKNRgEVYyx3CmkmIXcfw35R1tORNjCec_NA6dawx_LPpS0endjNz2m_iijLquKenrsKSKVnBprfVtBh_myuNQD5CfhBnzZRmAUfr0PoVMDBb0r_rWaV1Q64zQWSeCql7CSWq4U8RNhogd0eCZOOv45plIUwoxkdNg0Rzkp-OEtKRLaHonJ_OZ_sxa8-w",
"e": "AQAB"
}, format: 'jwk'
});
You can then use this to verify the jwt.
let pem = publicKey.export({ type: 'pkcs1', format: 'pem' });
jwt.verify(jwtToken, pem, (err, decoded) => {
})
If you want to use jsonwebtoken's verify method, then you can do it by converting your jwk into a pem string, for example, using jwt-to-pem. Using the example on the package's website, it goes like this:
First you install
npm i jwt-to-pem -D
And then you convert and use
import * as jwtToPem from 'jwt-to-pem';
import {verify} from 'jsonwebtoken';
let jwk = {
"kty": "RSA",
"kid": "IlOPtWXUcpiPttmr-K7DmehzeRM",
"use": "enc",
"n": "qv2XCvfUfW0bG547B1...",
"e": "AQAB"
};
let pem_str = jwtToPem(jwk);
//Finally, verification for some token
verify(id_token, pem_str);
And that's all. I saw in node's crypto documentation that you should be able to do it without external dependencies, but I didn't manage it!

How to decrypt a value in frontend which is encrypted in the backend (nodejs)?

Backend developers have encrypted a value in nodejs using crypto module. The code is shown below:
const _encrypt = async function(text){
var cipher = crypto.createCipher('aes-256-cbc','123|a123123123123123#&12')
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
console.log("in generic function....encrpted val", crypted)
return crypted;
}
I need to decrypt this value in the front end (Angular). So I tried decrypting like below:
let bytes = CryptoJS.AES.decrypt("e0912c26238f29604f5998fa1fbc78f6",'123|a123123123123123#&12');
if(bytes.toString()){
let m = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
console.log("data ",m);
}
using hardcoded value. But Im getting Error: Malformed UTF-8 data error. Can anybody please tell me how to decrypt this in angular side?
This is a tricky enough one.. the crypto.createCipher function creates a key and IV from the password you provide (See the createCipher documentation for details).
This is implemented using the OpenSSL function EVP_BytesToKey.
A JavaScript implementation is available here: openssl-file.. we'll use this to get a key and IV from the password.
So there are two steps here:
Get a key and IV from your password.
Use these with Crypto.js to decode your encoded string.
Step 1: Get key and IV (Run in Node.js )
const EVP_BytesToKey = require('openssl-file').EVP_BytesToKey;
const result = EVP_BytesToKey(
'123|a123123123123123#&12',
null,
32,
'MD5',
16
);
console.log('key:', result.key.toString('hex'));
console.log('iv:', result.iv.toString('hex'));
Step 2: Decrypt string:
const encryptedValues = ['e0912c26238f29604f5998fa1fbc78f6', '0888e0558c3bce328cd7cda17e045769'];
// The results of putting the password '123|a123123123123123#&12' through EVP_BytesToKey
const key = '18bcd0b950de300fb873788958fde988fec9b478a936a3061575b16f79977d5b';
const IV = '2e11075e7b38fa20e192bc7089ccf32b';
for(let encrypted of encryptedValues) {
const decrypted = CryptoJS.AES.decrypt({ ciphertext: CryptoJS.enc.Hex.parse(encrypted) }, CryptoJS.enc.Hex.parse(key), {
iv: CryptoJS.enc.Hex.parse(IV),
mode: CryptoJS.mode.CBC
});
console.log('Ciphertext:', encrypted);
console.log('Plain text:', decrypted.toString(CryptoJS.enc.Utf8));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
Note that if you change the password you need to generate a new key and iv using EVP_BytesToKey.
I should note that createCipher is now deprecated, so use with caution. The same applies to EVP_BytesToKey.

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.

How to create a LetsEncrypt compatible JWT with Node.js?

I am trying to POST a JSON Web Token to the Lets Encrypt new registration endpoint using Node.js. How do I create this token? This is some code I've been experimenting with to try to generate a token that Let's Encrypt's webserver will accept:
var jwt = require('jsonwebtoken');
var jws = require('jws');
var crypto = require('crypto');
var pem = require('pem');
var jose = require('node-jose');
var keystore = jose.JWK.createKeyStore();
var key;
var props = {
//kid: 'gBdaS-adsfasdfasdfsa',
alg: 'HS256',
//use: 'enc',
n: "pK7LuT2hxkWnYRl1Tcw9iAy9-_TqvHp2wh6EcHq_wglsNmtpxAe9gNGZevWu6T2O1aEmPYkgy7Q1meKNifenFuWicDcSSenkMM0JApfdveiVqjBA81EL0Y76T8i2JolggGXbiSa_ZRGwG-0FPDSIX3Jy5mQgOn-t-zrhD9yLDn2N7zzFqCBOtxzrwz1HEtN8QWZAFAzOceyyL6C791lGOk9SYYekxyuZkwkzhDEsoqR7fN6hmu6IfIU8hF5kt8M_Gef30wt5dUESvcTNdmQmq_L1QYA8qYO6-T0mC0zIpHpwQnANYOSZBCz1uE-vwS17MlfnUwGkPHJXWThlMZqZmQ",
e: "AQAB"
};
keystore.generate("oct", 256, props).
then(function(result) {
console.log(result);
var obj = {
header: {
alg: "HS256",
jwk: result,
nonce: "kajdfksajdf39393"
},
payload: {
"resource": "new-reg",
"contact": [
"mailto:cert-admin#example.com",
"tel:+12025551212"
]
},
secret: 'has a van',
};
const signature = jws.sign(obj);
console.log(signature);
});
}
This actually does generate a valid JWT:
eyJhbGciOiJIUzI1NiIsImp3ayI6eyJrdHkiOiJvY3QiLCJraWQiOiJXeVFRNFNJV2I4SGJIM2ZrQnRLOHdnR1NibU9zaGZNeUNWODZyTWdoOTR3IiwiYWxnIjoiSFMyNTYiLCJuIjoicEs3THVUMmh4a1duWVJsMVRjdzlpQXk5LV9UcXZIcDJ3aDZFY0hxX3dnbHNObXRweEFlOWdOR1pldld1NlQyTzFhRW1QWWtneTdRMW1lS05pZmVuRnVXaWNEY1NTZW5rTU0wSkFwZmR2ZWlWcWpCQTgxRUwwWTc2VDhpMkpvbGdnR1hiaVNhX1pSR3dHLTBGUERTSVgzSnk1bVFnT24tdC16cmhEOXlMRG4yTjd6ekZxQ0JPdHh6cnd6MUhFdE44UVdaQUZBek9jZXl5TDZDNzkxbEdPazlTWVlla3h5dVprd2t6aERFc29xUjdmTjZobXU2SWZJVThoRjVrdDhNX0dlZjMwd3Q1ZFVFU3ZjVE5kbVFtcV9MMVFZQThxWU82LVQwbUMweklwSHB3UW5BTllPU1pCQ3oxdUUtdndTMTdNbGZuVXdHa1BISlhXVGhsTVpxWm1RIiwiZSI6IkFRQUIifSwibm9uY2UiOiJrYWpkZmtzYWpkZjM5MzkzIn0.eyJyZXNvdXJjZSI6Im5ldy1yZWciLCJjb250YWN0IjpbIm1haWx0bzpjZXJ0LWFkbWluQGV4YW1wbGUuY29tIiwidGVsOisxMjAyNTU1MTIxMiJdfQ.RiHTdM_k1eLUJaGx4b59w8-hEQ-J0SpZjPIeGWhh1yg
However, when I try to POST it to the new registration endpoint, I get the following error:
{ "type": "urn:acme:error:malformed", "detail": "Parse error reading JWS", "status": 400 }
The testing code is a collection of code snippets I've put together after Googling this for a few hours. I understand there are LetsEncrypt servers I can run, but don't want to do that. I want to actually generate the requests and callbacks directly in Node.js because I want to run all this from AWS Lambda functions (no servers involved here).
I did find one example of a JWT token that actually seems to work, sort of. I say "sort of" because the response from this example is:
{ "type": "urn:acme:error:badNonce", "detail": "JWS has invalid anti-replay nonce 5H63XwyOHKpAETFpHR8stXSkhkqhlAY1xV7VsCnOrs", "status": 400}
This at least tells me the JWT token is being parsed and the Nonce is being looked at. When I decode this JWT, I see this:
It looks like this guy used RSA 256 to create this JWT. I'm not sure where the values "e" and "n" came from?
How do I recreate the above working sample with Node.JS / Jose?
I think the answer here is to just use the letsencrypt node.js NPM package. No need to develop ACME protocol from scratch, as this library seems to do it.

Resources