I'm generating the Public and Private key by using the Native Browser crypto API as below:
export const generateKeyPair = async (): Promise<CryptoKeyPair> => {
return await window.crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-384",
},
true,
["deriveKey", "deriveBits"],
);
};
Then I'll export the publicKey by using the exportKey function under the window.crypto.subtle as below:
const keyPair: CryptoKeyPair = yield generateKeyPair();
const publicKeyArrayBuffer: ArrayBuffer = yield window.crypto.subtle.exportKey("raw", keyPair.publicKey);
const publicKeyAsBase64 = arrayBufferToBase64(publicKeyArrayBuffer);
If you have any suggestions, please let me know and help me to fix this issue.
Both codes use different curves, the Java code secp256r1 (aka P-256), the JavaScript code P-384. To make both codes compatible, the JavaScript code must apply the same curve as the Java code, i.e. P-256 (s. also here).
The Java code exports a public EC key in X.509/SPKI format, which is Base64 encoded. The JavaScript code exports the public key in uncompressed format 0x04|<x>|<y>. The export in X.509/SPKI format is possible with spki as 1st parameter, s. here and here.
Related
I have an asymmetric RSA key pair stored in two separate files. I want to generate a new symmetric key and encrypt it with public RSA key in my postbuild.js GULP script, so the user cannot access it. Then I want to send it to the C# server, where it would be decrypted and used.
I use the following JavaScript code in Node.js for encryption:
const generateAndEncryptKey = () => {
const symmetricKey = crypto.randomBytes(32);
const publicKey = fs.readFileSync("pubkey.pem", "utf8");
const encryptedSymmetricKey = crypto.publicEncrypt({
key: publicKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: "sha256",
}, Buffer.from(symmetricKey)).toString("base64");
return encryptedSymmetricKey;
}
The above code somehow works and generates a base64 string that I later send to the server. I'm not sure if this is the correct way to do this.
But I'm unable to find a way to decrypt this string in C#. I tried to use the BouncyCastle library and the following code:
public string DecryptKey(string encryptedKey) {
var privateKey = #"-----BEGIN RSA PRIVATE KEY-----
...shortened...
-----END RSA PRIVATE KEY-----";
var bytesToDecrypt = Convert.FromBase64String(encryptedKey);
var decryptEngine = new Pkcs1Encoding(new RsaEngine());
using (var txtreader = new StringReader(privateKey)) {
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)new PemReader(txtreader).ReadObject();
decryptEngine.Init(false, keyPair.Private);
}
var decrypted = Encoding.UTF8.GetString(decryptEngine.ProcessBlock(bytesToDecrypt, 0, bytesToDecrypt.Length));
return decrypted;
}
But the ProcessBlock method always throws an InvalidCipherTextException "unknown block type".
Can someone help me to find out what am I doing wrong or point me to another better way of achieving this?
Decryption with the C# code fails because in the NodeJS code OAEP/SHA256 is used as padding and in the C# code PKCS#1 v1.5 padding. For decryption to work, both paddings must be identical. The padding in the C# code can be adapted to that of the NodeJS code as follows:
var decryptEngine = new OaepEncoding(new RsaEngine(), new Sha256Digest());
Also, the decrypted key must not be UTF-8 decoded as this corrupts the data. Either it is returned as byte[], or if conversion to a string is desired, a suitable binary-to-text encoding such as Base64 or hex must be used.
With these changes decryption works in the C# code.
I'm trying to convert this code from Javascript to Python3:
import crypto from 'crypto';
const secretKey = 'NgTriSCalcUltAbLoGResOnOuSeAKeSTraLryOuR'
function verifySignature(rawBody) {
const calculatedSignature = crypto
.createHmac('sha256', secretKey)
.update(rawBody, 'utf8')
.digest('base64');
return calculatedSignature;
}
console.log(verifySignature('a'));
With that code I get this output: vC8XBte0duRLElGZ4jCsplsbXnVTwBW4BJsUV1qgZbo=
So I'm trying to convert the same function to Python using this code:
UPDATED
import hmac
import hashlib
message = "a"
key= "NgTriSCalcUltAbLoGResOnOuSeAKeSTraLryOuR"
hmac1 = hmac.new(key=key.encode(), msg=message.encode(), digestmod=hashlib.sha256)
message_digest1 = hmac1.hexdigest()
print(message_digest1)
But I get this error: AttributeError: 'hash' object has no attribute 'digest_size'
Can someone tell me what I am missing to achieve the same output in Python?
Thanks you! :)
You're not getting base64 from the digest() on your return statement. In your javascript code it was also encoded in UTF-8 which should be specified below. The key was also not supplied in your python code.
This code snippet I just tested should generate the same result as your javascript code:
def newhashab(msg, key):
digest = hmac.new(key.encode('UTF-8'), msg.encode('UTF-8'), hashlib.sha256)
return base64.b64encode(digest.digest()).decode('UTF-8')
print(newhashab("a", 'NgTriSCalcUltAbLoGResOnOuSeAKeSTraLryOuR'))
It should return:
'vC8XBte0duRLElGZ4jCsplsbXnVTwBW4BJsUV1qgZbo='
I'm trying to use Elliptical Curve Diffie-Hellman keys to create a shared secret between a Browser and NodeJS. If I export the browser public key as raw, everything works, but I'm required to export the key as spki and then NodeJS gets mad about it.
In the Browser I do this:
async function generateDHKeys() {
const key_ECDH = await window.crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
['deriveKey'],
);
const publicKeyData = await window.crypto.subtle.exportKey(
'spki',
key_ECDH.publicKey,
);
const publicKeyBytes = new Uint8Array(publicKeyData);
publicKeyB64 = btoa(String.fromCharCode.apply(null, publicKeyBytes));
const privateKeyData = await window.crypto.subtle.exportKey(
'pkcs8',
key_ECDH.privateKey,
);
const privateKeyBytes = new Uint8Array(privateKeyData);
privateKeyB64 = btoa(String.fromCharCode.apply(null, privateKeyBytes));
privateKeyBytes.fill(0);
return { publicKeyB64, privateKeyB64 };
}
const {publicKeyB64} = await generateDHKeys();
So, now I've exported the Public Key and converted it to Base64. I then send it to the NodeJS server, and I try to create a shared secret:
In NodeJS, I do this:
export function generateDHKeys(foreignPublicKeyB64) {
const ecdh = crypto.createECDH("prime256v1");
ecdh.generateKeys();
const publicKeyB64 = ecdh.getPublicKey("base64");
const privateKeyB64 = ecdh.getPrivateKey("base64");
const sharedSecretB64 = ecdh.computeSecret(foreignPublicKeyB64, "base64", "base64");
const sharedSecretHashB64 = crypto
.createHash("sha256")
.update(sharedSecretB64, "base64")
.digest("base64");
return { publicKeyB64, privateKeyB64, sharedSecretB64, sharedSecretHashB64 };
}
And I get an error saying "Public key is not valid for specified curve."
However, if in the Browser code I export the key as raw (instead of spki) it works....
How can I export the public key as spki in the browser, and then use it to generate a shared secret in NodeJS? Or, how can I convert a Base64 SPKI public key into a raw key in Node?
EDIT
It has been discovered that the Browser Crypto APIs are indeed supported in Node v15.0.0+, meaning my browser JS can be simply copied and run in a Node context. Rather than access window.crypto.subtle as I would in a browser, in a Node application I can import the subtle module like so:
const { subtle } = require("crypto").webcrypto;
HOWEVER... as #Topaco pointed out, as of Node v16.2.0, this API is still experimental and subject to change. See #Topaco's answer for additional information and documentation links.
As far as I know, the NodeJS crypto module does not support the X.509/SPKI format for the public key in ECDH context, but only the raw key. However, it is possible to derive the raw key from the X.509/SPKI key.
The X.509/SPKI key generated with the WebCrypto code encapsulates the raw (more precisely uncompressed) key, 0x04 + <x> + <y>, which is localized at the end. For P-256 aka prime256v1 the last 65 bytes correspond to the raw key. The front part is identical for different P-256 keys.
This way, in the NodeJS code, the raw key for P-256 can be determined as the last 65 bytes from the X.509/SPKI key.
Similarly, the front part of the X.509/SPKI key can be concatenated with the raw key generated with the NodeJS code, thus converting the raw key to the X.509/SPKI format.
The NodeJS code for this is:
// Convert the SPKI key of the WebCrypto side into the raw format
var webcryptoSpkiB64 = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPF2r2yyMp/PykPZEt6v8WFAvnrf5FsI3UnpEYsbKo7UKVKB8k2hfxxhjKw8p9nulNaRo472hTcEqsSbsGcr5Dg==';
var webcryptoRawB64 = Buffer.from(webcryptoSpkiB64, 'base64').slice(-65).toString('base64'); // the last 65 bytes
// Calculate the shared secret for the NodeJS side
var { publicKeyB64, privateKeyB64, sharedSecretB64, sharedSecretHashB64 } = generateDHKeys(webcryptoRawB64);
// Convert the raw key of the NodeJS side into the SPKI format
var nodejsSpkiB64 = Buffer.concat([
Buffer.from(webcryptoSpkiB64, 'base64').slice(0, -65), // all bytes except the last 65
Buffer.from(publicKeyB64, 'base64')]
).toString('base64');
console.log("Shared secret:", sharedSecretB64);
console.log("SPKI:", nodejsSpkiB64); // will be sent to the WebCrypto side and used there to calculate the shared secret
where generateDHKeys() is the function posted in the question.
Edit: As noted in the comment from the OP, the WebCrypto API is now part of NodeJS, so X.509/SPKI keys are also supported in the context of ECDH via the WebCrypto API within NodeJS. However, it should be mentioned that the WebCrypto API in the current NodeJS version v16.0.2 has stability 1 level (Experimental). This means that non-backward compatible changes or removals are possible. Also, the current LTS version (v14.17.0) does not include the WebCrypto API.
I've been having a really hard time figuring out how to store a Secp256k1 privateKey from multiple libraries (currently on this one for ECIES encryption: https://npm.io/package/#toruslabs/eccrypto).
I have tried encoding and decoding with base64, many implementations of functions that copy array buffer for input encoded string to localStoarge and corresponding output Uint8Array from localStorage, I tried it with IndexedDB, JSON.stringify and parse do not work with binary data, and so many more variations.
When I go through the array buffer elements individually to copy it into a new Uint8Array, I get a similar private key, but with two missing key/field's (parent and offset) which I believe is why every library I have tried so far returns something a long the lines of "bad private key" when I try generating the public key from them.
I am exhausted and I would like some professional insight for my lack of skill in this particular subject. So how can I store (in any way as long as it's client/local) a Secp256k1 private key in a way that if I call it from that persistent client sided data base, they can be used to generate the public keys?
Apparently, the library that uses the private/public key (in this case being #toruslabs/eccrypto) requires a buffer parameter for the keys.
A simple solution would be to make the NodeJS Buffer available in the browser, through browserify. You will only need to include the NodeJS Buffer class to the window object when creating the browserify file, as shown:
const eccrypto = require('./index');
window.eccrypto = eccrypto;
window.Buffer = Buffer;
Then, generate the bundle file using browserify: browserify main.js -o bundle.js
After this, you will be able to use the Buffer class in your browser, which will make loading the private/public key possible. Sample code here:
<script src="bundle.js"></script>
<script>
const eccrypto = window.eccrypto;
const privateKey = eccrypto.generatePrivate();
const publicKey = eccrypto.getPublic(privateKey);
// hex string output of private key
const hexPrivateKey = privateKey.toString('hex')
console.log(hexPrivateKey); // we can do this as privateKey is a Buffer
// load private key again
const newPrivateKey = Buffer.from(hexPrivateKey, 'hex');
const enc = new TextEncoder();
// code referenced from #toruslabs/eccrypto README
// Encrypting the message.
eccrypto.encrypt(publicKey, enc.encode("my testing msg")).then(function (encrypted) {
// Decrypting the message.
eccrypto.decrypt(newPrivateKey, encrypted).then(function (plaintext) {
console.log("Message:", plaintext.toString());
});
});
</script>
This should be sufficient to store the hex string of the private key in the localStorage or any client-side database/storage that you will be using.
I'm trying to parse and validate a JWT token in node.js based on this sample (authored in .NET): https://github.com/liveservices/LiveSDK/blob/master/Samples/Asp.net/AuthenticationTokenSample/JsonWebToken.cs
Here is my node js javascript that validates the token:
var validateSignature = function(key, claims, envelope, signature) {
var hasher = crypto.createHash('sha256');
hasher.update(key + "JWTSig");
var key = hasher.digest('binary');
var hmac = crypto.createHmac('sha256', key);
hmac.update(envelope + '.' + claims);
var out = hmac.digest('base64');
console.log(out);
console.log(signature);
console.log(out === signature);
}
Now, the very weird thing is - it almost works. Here's the output of the three console.log statements:
pEwNPJ+LUHBdvNx631UzdyVhPFUOvFY8jG3x/cP81FE=
pEwNPJ-LUHBdvNx631UzdyVhPFUOvFY8jG3x_cP81FE
false
It seems suspicious to me that the hashes are both the same except for the +-/_=
Anybody spot my mistake? Something to do with my base64 encoding.
UPDATE
I played some more and there seems to be something funky going on with base64 encoding here. The following code in node js:
console.log(signature);
var b = new Buffer(signature, 'base64');
console.log(b.toString('base64'));
yields:
pEwNPJ-LUHBdvNx631UzdyVhPFUOvFY8jG3x_cP81FE
pEwNPJLUHBdvNx631UzdyVhPFUOvFY8jG3xcP81F
Which seems very odd, right?
Thanks to Timothy Meade for commenting and pushing me in the right direction.
Node's Buffer type generates standard Base64 with +, / and =
There is a URL safe base64 encoding as mentioned here: http://en.wikipedia.org/wiki/Base64
It replaces + with -, / with _ and = is optional. The token that is passed on the QueryString (d'uh) is a URL safe version. Hence the difference.
Code was fixed by a simple:
out = out.replace('+','-').replace('/','_').replace('=','');
I wrote this library a while ago, I guess you can use some of the code. It is supposed to run in both node.js and in a modern browser.
JWT library for javascript
This is not the exact method that you were trying to use, but I believe it is the preferred way to validate a JWT in NodeJS. Note that I am using the NPM base64url library to convert between base64Url (the default encoding for a JWT) and base64 (what NodeJS expects for the verification function).
Also note, you need a public and private keypair to sign and verify respectively. I have included the private and public keys that were used to sign and verify this JWT at the bottom of this post.
const base64 = require('base64url');
const crypto = require('crypto');
const verifyFunction = crypto.createVerify('RSA-SHA256');
const fs = require('fs');
// The sample JWT from https://jwt.io/
const JWT = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA';
// This just gets the value of the public key (same as the one at bottom of this post)
const PUB_KEY = fs.readFileSync(__dirname + '/id_rsa_pub.pem', 'utf8');
// Split the JWT by `.` to get each part
const jwtHeader = JWT.split('.')[0];
const jwtPayload = JWT.split('.')[1];
const jwtSignature = JWT.split('.')[2];
// We only need the first two pieces to verify
verifyFunction.write(jwtHeader + '.' + jwtPayload);
verifyFunction.end();
// IMPORTANT: NodeJS expects base64 format, not base64url format!
const jwtSignatureBase64 = base64.toBase64(jwtSignature);
// IMPORTANT: You need to specify that the `jwtSignatureBase64` data is base64 format,
// otherwise, it will default to Buffer format and return false
const signatureIsValid = verifyFunction.verify(PUB_KEY, jwtSignatureBase64, 'base64');
console.log(signatureIsValid); // true
The keys below are from the example JWT mentioned here.
Private Key:
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw
kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr
m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi
NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV
3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2
QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs
kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go
amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM
+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9
D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC
0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y
lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+
hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp
bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X
+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B
BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC
2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx
QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz
5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9
Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0
NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j
8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma
3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K
y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB
jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=
-----END RSA PRIVATE KEY-----
Public Key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----