Unable to decrypt JWE with NPM Jose package - javascript

I'm able to decrypt a JWE with an older version of jose but I'm struggling to use the latest version API.
My token headers are the following:
{
"alg": "A128KW",
"enc": "A128CBC-HS256",
"typ": "JWT"
}
With jose 2.0.3 this code (copied from another stackoverflow post) is working to decrypt the payload:
const { JWK, JWE } = require('jose');
const privateKey = JWK.asKey("my key");
const jwe = "my JWE"
const jwt = JWE.decrypt(jwe, privateKey);
const payload = Buffer.from(jwt.toString().split('.')[1], 'base64');
const data = JSON.parse(payload);
Obviously I cannot share the the private key. It's a string with 16 characters.
I tried to used a later version of jose (4.9.2) but the API changed totally. I tried this but it does not end well:
const jose = require('jose');
const jwe = "my token";
const key = await jose.importJWK({ kty: 'oct', k: 'my key', alg: 'A128CBC-HS256' })
const { plaintext, protectedHeader } = await jose.compactDecrypt(jwe, key);
console.log(protectedHeader)
console.log(new TextDecoder().decode(plaintext))
I get this error :
TypeError: Invalid key size for alg: A128KW
I confirm my key is 16 characters long (so 128 bits normally) so I don't get this error. Any hint?

The issue is your secret key. You're using 16 character string and passing it in as a JWK. JWK k is meant to be base64url encoded, so your 16 characters "encoded" becomes a 12 byte secret when "decoded".
Here's what will work, don't bother with a key import for symmetric secrets, just pass a Buffer instance instead.
const { plaintext, protectedHeader } = await jose.compactDecrypt(jwe, Buffer.from('my 16 bytes secret'));
console.log(protectedHeader)
console.log(new TextDecoder().decode(plaintext))
Additionally, if your payload is a JWT claims set, you might as well do a proper JWT validation combined with the decrypt operation. docs
const { plaintext, protectedHeader } = await jose.jwtDecrypt(jwe, Buffer.from('my 16 bytes secret'));
console.log(protectedHeader);
console.log(payload);

This is full example about jwt and jwe.
Full project -- Generation -- Validation -- How to use
Generation jwt and jwe
let jwt = require('jsonwebtoken'),
{
JWK,
parse
} = require('node-jose');
async function getJwtEncrypt(raw, format = 'compact', contentAlg = 'A256GCM', alg = 'RSA-OAEP') {
let publicKey = await JWK.asKey(process.env.JWT_PUBLIC_KEY, 'pem');
const buffer = Buffer.from(JSON.stringify(raw));
return await JWE.createEncrypt(
{format: format, contentAlg: contentAlg, fields: {alg: alg}}, publicKey)
.update(buffer).final();
}
function getJwtSign(payload, subject) {
const SIGN_OPTIONS = {
subject: `${subject}`,
expiresIn: payload.expiresIn,
algorithm: 'RS256'
};
return jwt.sign(payload, process.env.PRAIVATE_KEY, SIGN_OPTIONS);
}
Validation jwt and jwe
let jwt ,
{
TokenExpiredError,
JsonWebTokenError
} = require('jsonwebtoken'),
{
JWK,
parse
} = require('node-jose');
function getJwtVerify(token, cb) {
try {
jwt.verify(token, process.env.PUBLIC_KEY, {}, (err, decoded) => {
if (err !== null && err instanceof TokenExpiredError) {
cb('TOKEN_EXP');
return Json.builder(Response.HTTP_UNAUTHORIZED_TOKEN_EXP);
}
if (err instanceof JsonWebTokenError) {
cb('IN_VALID_TOKEN');
return Json.builder(Response.HTTP_UNAUTHORIZED_INVALID_TOKEN);
}
cb(decoded);
});
} catch (e) {
ValidationException(e);
}
}
async function getJwtDecrypt(encryptedBody) {
try {
let keystore = JWK.createKeyStore();
await keystore.add(await JWK.asKey(process.env.JWE_PRAIVATE_KEY, 'pem'));
let outPut = parse.compact(encryptedBody);
let decryptedVal = await outPut.perform(keystore);
let token = Buffer.from(decryptedVal.plaintext).toString();
if (typeof decryptedVal.plaintext === ('undefined' || null))
return Json.builder(Response.HTTP_UNAUTHORIZED_INVALID_TOKEN);
return token.replace(/["]+/g, '');
} catch (e) {
ValidationException(e);
}
}
.env file
PRAIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDHRyVyzQTw5XkTaM3bgfKE5Ry+CqqmijpvHprM1Dvkr3q7lR2A
ZTzKGnyHVbnVpKPga8AHs3E7l/MVci2BhWLbHGOn4GgrrqDmItvCvFcfGZM9USZE
2tp5zLj3aBoqImk+niFHQipSPus9JhNU9HkLNTUJGwuuxfWuXwtnMhWcpwIDAQAB
AoGAMs9bJwhLSDjaRC6mvl9FvMjGKVaC6G+6Mnb1NWCv3ME5Y/bDTOeDNmzGb6NV
/Lk7547RqaUBLBa0LjWskKe36n6hfWExEaRe9ikqRiz6y0QXyfO5h6qZD0YXSsIu
5VY8LBCz45Z330I296jLbgkZ2OhZbEj8lk8rf99JtzGzRBECQQD1W0cBRIKt6e8g
KJNLp8A0dLId6bbmgG5xnOscTMfxzZAvMAz6eN4ur8vkJKDVr1YjRCHETrfdaX/d
kfY/29TtAkEAz+wokyzPERFA+XCdLLW9d9b3nPQhTUodBIq9EXfnhPAXRWzjObLl
zItS3bDzWpGqfwP7nc9zjpcqY0zKdJ+5YwJASnyDeecKpTG33tNypC0xNLuYt2wU
krW60dMJrXXB3a7CbxDvX7sB+Lp187UK/tRUGjC875PWTemRX/rH/2sFoQJBAIN8
MFyB5aBBbPlRAdQYSezTAFs89yJNT/RjWBUH4lzrB4xbw4XlX/Tt1kVjdUE9BLi1
6BRv7/+oEKIjGZSOvUkCQEdb/CPaqXPfn7zA/SKL3hgJbbEo8aowOM4VfV+ZVojp
vzvj/ZjQpRKDEJ7iiJCXBUT59BwPuirq8v6fjWMRneU=
-----END RSA PRIVATE KEY-----"
PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHRyVyzQTw5XkTaM3bgfKE5Ry+
CqqmijpvHprM1Dvkr3q7lR2AZTzKGnyHVbnVpKPga8AHs3E7l/MVci2BhWLbHGOn
4GgrrqDmItvCvFcfGZM9USZE2tp5zLj3aBoqImk+niFHQipSPus9JhNU9HkLNTUJ
GwuuxfWuXwtnMhWcpwIDAQAB
-----END PUBLIC KEY-----"
JWE_PRAIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----
MIICWgIBAAKBgFsiRgcjHE4S0uoNvOEz/YdmPRRBo+APWpWpM8+B2JSXNZdQCoin
7psz8Pup+4C95Qz/R5Zhw/IZBfvOpD6Hj9v9cMgnnMjPYW6eecByJ6S6lynetRLT
Igy4ICRynhx4rHvRwXpr6dEeaazArDJlwutMv9cW7DdSPiqRSTl9YAffAgMBAAEC
gYAsZ1qvh4/3CnzxxZfOMsLJAiuofwMV3OVKHpM7/AxG+hYGj91SEGDWBkzYkk4U
wHGmD4wV3bTXdRHRSzIDtZGGDZbneWN58TRUPuSP1XiNVIb8Doaj91cprRiBxlmM
ZHJsix/OLN/Sm3UjuZolSjG3K0QucrrBUtPBfhxJ5GXmYQJBAKOwYZHyhz9gGrW7
zkkF9Fivu9P/H0/eS4HaTQLYEUG475KIj6xQLrjTRtxZ139DV3jrGoLEapFvAaOk
fETiBwMCQQCOhy7Aa5muKcELFJqxzY1CWLCaayX43ZPVYKCkLaqDYRWtufLjZ3vj
hWiQScULEGLiM+axZFgTmbtWI6oy4cb1AkBT+UeEzQvvSklJlChWs/RPjw/nyQjy
O1M3MZuyatAnjE1zOhWiy5u8e77tijWQdyanxMzb6xHUvEL2BYsu91mrAkAbq/tT
uJBZ1Bl6wUFXjAUFAJspH+x7aOmu39fQiF02rL68wAF8TTcscVZfzTLIdyH7sP/1
KPpAs/Q/QSVmQ5eRAkBHWQgmTC5MsmLmJO6UdUjOp5a9rJzxnMej8zUiR/lZ7ssc
4jAfqi8ukBv+5THumh3TKDNPAu9+r5nWOahb/jrJ
-----END RSA PRIVATE KEY-----"
JWT_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgFsiRgcjHE4S0uoNvOEz/YdmPRRB
o+APWpWpM8+B2JSXNZdQCoin7psz8Pup+4C95Qz/R5Zhw/IZBfvOpD6Hj9v9cMgn
nMjPYW6eecByJ6S6lynetRLTIgy4ICRynhx4rHvRwXpr6dEeaazArDJlwutMv9cW
7DdSPiqRSTl9YAffAgMBAAE=
-----END PUBLIC KEY-----"

Related

Pycryptodome incorrect decryption

I am generating a RSA key pair in a django view, storing the keys in db, sending the public key to the client template, encrypting a message, then sending the message to a different view where I am retrieving the private key from the db and trying to decrypt the message. When I do the decryption, I get ValueError: Incorrect decryption..
Server: Generate keys, store in db, send public key to client:
def generateKeys(request):
store = Keys.objects.filter(pk=request.GET.get('pk'))
serverkeypair = Crypto.PublicKey.RSA.generate(4096)
spk = serverkeypair.publickey().exportKey(format='PEM', passphrase=None, pkcs=8, protection=None, randfunc=None)
sprk = serverkeypair.exportKey(format='PEM', passphrase=None, pkcs=8, protection=None, randfunc=None)
spk = spk.decode()
store.update(spk = spk)
store.update(sprk = sprk.decode())
return JsonResponse(['success', spk], safe=False)
Client: Import public key, encrypt message:
fetch('../api/endpoint1/?pk='+pk, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}).then(response => response.json())
.then(async data => {
var pbk = data[1];
pbk = pbk.substring(27, pbk.length - 25);
var plaintext = window.crypto.getRandomValues(new Uint8Array(64));
console.log(btoa(ab2str(plaintext)));
window.crypto.subtle.importKey(
"spki",
str2ab(atob(pbk)),
{
name: "RSA-OAEP",
hash: "SHA-256"
},
true,
["encrypt"]
).then((imp_key) => {
window.crypto.subtle.encrypt({name: 'RSA-OAEP'}, imp_key, plaintext).then((ciphertext) => {
ciphertext = btoa(ab2str(ciphertext));
console.log(ciphertext);
ciphertext = b64_to_b64url(ciphertext);
fetch('/api/endpoint2/?pk='+pk+'&ciph='+ciphertext);
});
});
});
/*
Convert an ArrayBuffer into a string
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
/*
Convert a string into an ArrayBuffer
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function b64_to_b64url(s) {
return s.replace(/\+/g, '-').replace(/\//g, '_');
}
Server: Retrieve key, decrypt message:
def decrypt(request):
store = Keys.objects.get(pk=request.GET.get('pk'))
sprk = getattr(store, 'sprk')
key = RSA.importKey(sprk.encode())
cipher = PKCS1_OAEP.new(key)
ciphertext = request.GET.get('ciph')
ciphertext = base64.b64decode(b64url_to_b64(ciphertext))
plaintext = cipher.decrypt(ciphertext)
print(plaintext)
return JsonResponse(['success'], safe=False)
def b64url_to_b64(str):
return str.replace('-', '+').replace('_', '/')
Test data:
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoxvH93Qpcbg75fMMkBxp
gWNaIamVo4dvwc7xi8o2JQTYOU+wzCYE5Hs7qN2YoPMJdDQHNqvgIfHpfpLwcIaA
F2qrojRLA8YvJoKpamlg/ui0xyu8UNtYgkfhatwihHOGl/eYkzqgeu1JQ6maF/PO
MfVv6ulioTDPEZaREx4Gj6iVgLRhW7eRu0oYG4tGoTmzmze1sIbxZvTkZWDdp9Y2
S6dsUHllsROUFUH2d/RxKJXoG7jEMjki8l+CaxkqNxjffQGBkg060Pj+Os6QbICZ
Ql4KYp+KNsRhQduaJkYsYuA5k/bl+BIWXh9E5o2eFNPQ0d6qm72Zl+ryxBwno2Sj
JYlYN9foRYxJB+2E759eAnLASK3amZaeQRrzMdbsWLzRDJDTSBzvqqmK2b5tFe7W
GM7JyVg/sHwLlY+hVoIQ8+WbuV/knxm3IV6XXUhg8VtFtlFYdcY4yVwO1K7yFzQN
h7mZjC8AhOGsw7oqzzExEasMdIsJ8DdkdtRUYJjcQaQiPU9luyVNvMFIZ4wkUTvm
FHyB5kHhwT7uWkFfkj9HXle9CQxhdlQIM2kfc3H7XBH+l4qf90pjg0cP7iG1semc
jyvv0WcFmatsAEBQ+XbIxqw76XtWh8UNGgypkI0PlVVRjKauywH6YofsjTATsE7H
asL5I39I2/XxJ9gg+0rqD3kCAwEAAQ==
-----END PUBLIC KEY-----
-----BEGIN PRIVATE KEY-----
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\nh78PtsliPE9KF/6EGS6Ysagxa2HuOQ==
-----END PRIVATE KEY-----
Plaintext (converted to string, then base64 encoded):
2w+c4ONLd50j1uipIXJ4yJlMnD1UtYuYRK6fTchr2RM4XMTz6vpB21Qu4qFIjJJGavub/OhV29c/8PB82L4www==
Ciphertext:
fIi24T8TXlYWyl+SfTVIkmF/aBc1nYPbsdtaMGQwtrOxDWjwEW7rK4b5i1dgGq3dpH2nxTxx0HwunFsD51I5I+XiGjeODGubSbM4+YNplFnu+cQ6YcN9M4jNVKkYrkDvEQUtyrM/G3oYIjZSVSh30Yb2ZyR5Tw5Tx+PAzXadUi/aUpyk0Yo92saBFGPRZMFxKuE4mozQJuNYug/qmk8+VTUd4djsWx+MFgEb+99/dTvupHu25mXA/NOFqw6t3SF4J3PW8nwlrztMnyrMKaLVJAWjNRYRDbroM3PkRG2dPbNVtg0d5e+GeVQvGFoaV2xukkP+QS2EqlQcWE1UdMvSHOx9jKp01EFrYoBjPgu/LlU1gpcqoA8yHIjiNaZ1mEpck0znKPeZmxPMh3n3XBGTO4U6t1/aPgBmn5FD8r7tPw2HJQmDEDpoGRsD+9T9CjrgGoB8Cpt2VQBqA1BPBfhrIKUdY/HHj5zCF5RHYujdMNn/qmLleN2fH/Ee3kFZ83s27fGpkwJIZD6Z9NMvIGfwJ9JKLM8xnm26KcXBKgml0Qw6py/9tE0WasEhWlv4KQOdfVfdjP6yp+wzNH8SgjBAMy6ANhCLLiY6MzgenDKPJBPZK/z+QiVakSOJ/boSpckjxDxnxo4LA+yZkWYwsR9UjWH9IC/Uj3ud23yiDawqfdk=
Encryption and decryption apply RSA with OAEP as padding. However, in the WebCrypto code SHA-256 is explicitly specified for content and MGF1 digests, while in the PyCryptodome code the default value SHA-1 is used for both digests when instantiating the cipher object, see PKCS1_OAEP and also this post of yours.
To fix the issue, pass SHA256 as second parameter in the PyCryptodome code when instantiating the cipher object:
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
...
cipher = PKCS1_OAEP.new(key, SHA256)
then decryption is successful.

JavaScript Handling Promise Response and Saving the response to MYSQL (PHP)

So I have this pre-built Javascript from Vendor which only returns Promise (https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt). Now that I managed to get the response into Console.log. I wish to save this Response to mysql using PHP.
My function looks like below which basically takes rawdata and using public Key via windows crypto and generates a Encryption sent to the vendor for verification:
function encryptfunction(dataPB, pKey, cb) {
var crypto = window.crypto || window.msCrypto;
if(crypto.subtle)
{
// fetch the part of the PEM string between header and footer
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
const pemContents = pKey.substring(pemHeader.length, pKey.length - pemFooter.length);
// base64 decode the string to get the binary data
const binaryDerString = window.atob(pemContents);
// convert from a binary string to an ArrayBuffer
const binaryDer = str2ab(binaryDerString);
crypto.subtle.importKey(
"spki",
binaryDer,
{
name: "RSA-OAEP",
hash: {
name: "SHA-512"
}
},
false, ["encrypt"]).then(
key => {
crypto.subtle.encrypt("RSA-OAEP", dataPB, pinBlock).then(
result => cb(btoa(ab2str(result))),
reason => console.log('encrypt failed', reason)
);
},
reason => console.log('import failed', reason));
}
else
{
alert("Cryptography API not Supported");
}
}
and my HTML
<textarea id="output" rows="20" cols="80"></textarea>';
so when I call my Function:
encryptfunction(pbdata, pKey,r => document.getElementById("output").value = r);
The reponse is shows in the ID (Textarea) properly, but I m having difficulty in storing this value to mysql using PHP due to two reasons.
The value "Response" is a promise which can not be accessed outside the function
The PHP page which runs these code is called into the application via CURL. (which means i just need to return or echo the RESPONSE.
Any tips or suggestion would be highly appreciated.

how to show error when wrong password is given NodeJS crypto - createDecipheriv and pbkdf2

Created a simple app that encrypts text, but how do i show error when wrong password or salt is given. Hosted it on replit.
But when i give wrong password or salt it just decrypts it. There isn't a callback or function in crypto for crypto.createDecipheriv()
const app = {
encrypt(text, password, salt) {
password = password.repeat(32).substr(0, 32);
salt = salt.repeat(16).substr(0, 16);
crypto.pbkdf2(password, salt, 10, 16, 'sha512', (err, key) => {
if (err) {
console.log(err);
} else {
key = key.toString('hex');
const cipher = crypto.createCipheriv('aes-256-gcm', key, salt);
let encrypted = cipher.update(text, 'utf8', 'hex');
console.log(encrypted);
}
});
},
decrypt(text, password, salt) {
password = password.repeat(32).substr(0, 32);
salt = salt.repeat(16).substr(0, 16);
crypto.pbkdf2(password, salt, 10, 16, 'sha512', (err, key) => {
if (err) {
console.log(err);
} else {
key = key.toString('hex');
const cipher = crypto.createDecipheriv('aes-256-gcm', key, salt);
let decrypted = cipher.update(text, 'hex', 'utf8');
console.log(decrypted);
}
});
}
}
const message = 'Hello World';
app.encrypt(message, 'password', 'salt');
const cipherText = 'a0a4e0ad97133494856502';
app.decrypt(cipherText, 'password', 'salt');
GCM generates an authentication tag (16 bytes by default) during encryption, which is used for authentication during decryption.
Some libraries (e.g. Java) implicitly concatenate ciphertext and tag (ciphertext|tag) during encryption and implicitly separate both during decryption (this is not critical since the tag is not secret).
The crypto module of NodeJS, on the other hand, handles ciphertext and tag independently, so the tag must be considered explicitly. It can be determined during encryption with getAuthTag() and must be set during decryption with setAuthTag(). Both is missing in the posted code.
Also missing are the final() calls that generate the tag on encryption and perform authentication on decryption.
To pass the tag to the decryption in this example, the following fix concatenates the tag with the ciphertext during encryption and separates it during decryption (following the Java pattern).
If these problems are fixed, the decryption works for correct data and displays a corresponding message for incorrect data: Error: Unsupported state or unable to authenticate data.
Fixed code, see the comments for details:
const crypto = require('crypto');
const app = {
encrypt(text, password, salt) {
password = password.repeat(32).substr(0, 32);
salt = salt.repeat(16).substr(0, 16);
crypto.pbkdf2(password, salt, 10, 16, 'sha512', (err, key) => {
if (err) {
console.log(err);
} else {
key = key.toString('hex');
const cipher = crypto.createCipheriv('aes-256-gcm', key, salt);
let encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex'); // Fix 1a: call final(): create tag
let tag = cipher.getAuthTag(); // Fix 2a: get tag
console.log(encrypted + tag.toString('hex')); // Fix 3a: concat ciphertext and tag
}
});
},
decrypt(text, password, salt) {
var tag = Buffer.from(text.substr(-32, 32), 'hex'); // Fix 3b: Separate ciphertext and tag
var ciphertext = text.substr(0, text.length - 32);
password = password.repeat(32).substr(0, 32);
salt = salt.repeat(16).substr(0, 16);
crypto.pbkdf2(password, salt, 10, 16, 'sha512', (err, key) => {
if (err) {
console.log(err);
} else {
key = key.toString('hex');
const cipher = crypto.createDecipheriv('aes-256-gcm', key, salt);
cipher.setAuthTag(tag); // Fix 2b: set tag
try {
let decrypted = cipher.update(ciphertext, 'hex', 'utf8') + cipher.final('utf8'); // Fix 1b: call final(): authenticate
console.log(decrypted);
} catch (e) {
console.log("Authentication failed!");
}
}
});
}
}
const message = 'Hello World';
app.encrypt(message, 'password', 'salt');
const cipherText = 'a0a4e0ad971334948565023568ae285d45b9cefc80abe3afcf9155';
app.decrypt(cipherText, 'password', 'salt');
app.decrypt(cipherText, 'password123', 'salt');

NodeJS validate that a private key matches a X.509 certificate

Given a private key and a X.509 certificate (no RSA), I want to confirm that:
The private key is "valid".
It matches the certificate.
This validation would run in nodejs.
Example of valid private key (don't worry, testing purposes):
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgSVkfyOqQ4E6No+v6
h/wotfYuGqiqierJ2YXx2v3rP1GhRANCAASMlEMAwv9jf8FAKDAxrnPGWVGBBzbD
wt3VQrrM5i/DOwCzF1XH7v6iYbvpYe9P0Qvf5ndqYYBklqLkXHAR37Vz
-----END PRIVATE KEY-----
Example of matching certificate:
-----BEGIN CERTIFICATE-----
MIICkDCCAjegAwIBAgIUCL+kBzVdqMGzurpuYwIxkuLbYrgwCgYIKoZIzj0EAwIw
czELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh
biBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT
E2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTkwMjA1MDgyMjAwWhcNMjAwMjA1MDgy
NzAwWjBDMTAwDQYDVQQLEwZjbGllbnQwCwYDVQQLEwRvcmcxMBIGA1UECxMLZGVw
YXJ0bWVudDExDzANBgNVBAMTBm5hdGhhbjBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABIyUQwDC/2N/wUAoMDGuc8ZZUYEHNsPC3dVCuszmL8M7ALMXVcfu/qJhu+lh
70/RC9/md2phgGSWouRccBHftXOjgdgwgdUwDgYDVR0PAQH/BAQDAgeAMAwGA1Ud
EwEB/wQCMAAwHQYDVR0OBBYEFJYSgUGno6j2eYUKjLs9BRzreUY1MCsGA1UdIwQk
MCKAIEI5qg3NdtruuLoM2nAYUdFFBNMarRst3dusalc2Xkl8MGkGCCoDBAUGBwgB
BF17ImF0dHJzIjp7ImhmLkFmZmlsaWF0aW9uIjoib3JnMS5kZXBhcnRtZW50MSIs
ImhmLkVucm9sbG1lbnRJRCI6Im5hdGhhbiIsImhmLlR5cGUiOiJjbGllbnQifX0w
CgYIKoZIzj0EAwIDRwAwRAIgbYQ4UscWT5rgqLwrhcj8kRNN0kfA5n12Zpl1Fclw
+7QCIAlTx9oMsGBAeaNxJ3PV6mo9Zng5aMNnAmwW2PVcDlXt
-----END CERTIFICATE-----
You could attempt to create a secure context, using the built in node api. It will throw if the certificates don't match:
import tls from 'tls';
function testCertAndKeyMatch (cert, key) {
try {
tls.createSecureContext({ cert, key });
return true;
} catch (error) {
if (error.code === 'ERR_OSSL_X509_KEY_VALUES_MISMATCH') {
return false;
}
throw error;
}
}
You can use it like:
const cert = fs.readFileSync('./certs/cert.pem', 'utf8');
const key = fs.readFileSync('./certs/key.pem', 'utf8');
const isMatch = testCertAndKeyMatch(cert, key);

Generating ECDH keys in the browser through WebCryptoAPI instead of the browserified node crypto module

I have a very tiny node script to create a public/private key
Is there any way to do it on the client side without having to browserify hole crypto module?
var crypto = require('crypto');
var userCurve = crypto.createECDH('prime256v1');
var userPublicKey = userCurve.generateKeys()
var userPrivateKey = userCurve.getPrivateKey();
I have tried this so far:
// https://github.com/diafygi/webcrypto-examples#ecdh---generatekey
window.crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256", //can be "P-256", "P-384", or "P-521"
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["deriveKey", "deriveBits"] //can be any combination of "deriveKey" and "deriveBits"
)
.then(function(key){
//returns a keypair object
console.log(key);
console.log(key.publicKey);
console.log(key.privateKey);
})
.catch(function(err){
console.error(err);
});
But it looks nothing like the node version when i log it
Let's do a complete elliptic curve Diffie-Hellman (ECDH) exchange to establish a shared secret between two parties. Alice uses Node.js and Bob sits at his browser (a recent version of Chrome or Firefox). (No need to browserify anything.)
(1) Alice generates a private and public key.
const crypto = require('crypto');
const alice = crypto.createECDH('prime256v1');
alice.generateKeys()
const alicePublicKey = alice.getPublicKey('hex')
const alicePrivateKey = alice.getPrivateKey('hex')
console.log(`publicKey: ${alicePublicKey}`)
console.log(`privateKey: ${alicePrivateKey}`)
Example output:
publicKey: 043a3770a8068738ded16c9409e1a6fbf6dde2360ac5b3fd3e5bb8d9fd6adaed6ea83ff5153f58ae13098e86da89df1beb14ef46388d3df76e8fe2ee0ff9e926d5
privateKey: 03ce9cb317c8761699f174943dc9b2d2b7991515b48216a4c677fcf5ee879f2c
(2) Alice sends her public key to Bob (043a3770...). Bob has written some helpers to convert hex strings to Uint8Arrays and buffers to hex strings.
const hex2Arr = str => {
if (!str) {
return new Uint8Array()
}
const arr = []
for (let i = 0, len = str.length; i < len; i+=2) {
arr.push(parseInt(str.substr(i, 2), 16))
}
return new Uint8Array(arr)
}
const buf2Hex = buf => {
return Array.from(new Uint8Array(buf))
.map(x => ('00' + x.toString(16)).slice(-2))
.join('')
}
(3) Bob receives Alices's key and computes the shared secret
He generates his own private and public key
He exports his public key and sends it to Alice
He imports Alice's public key
He computes the shared secret using his private and Alice's public key
// Alice's public key (received over an [insecure] connection)
const alicePublicKeyHex = '043a3770a8068738ded16c9409e1a6fbf6dde2360ac5b3fd3e5bb8d9fd6adaed6ea83ff5153f58ae13098e86da89df1beb14ef46388d3df76e8fe2ee0ff9e926d5'
const alicePublicKey = hex2Arr(alicePublicKeyHex)
console.log(`Alice's publicKey: ${alicePublicKeyHex}`)
let bob = null
// generate Bob's private and public key
window.crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256'
},
false, // no need to make Bob's private key exportable
['deriveKey', 'deriveBits'])
.then(bobKey => {
bob = bobKey
// export Bob's public key
return window.crypto.subtle.exportKey(
'raw', bobKey.publicKey
)
})
.then(bobPublicKeyExported => {
const bobPublicKeyHex = buf2Hex(bobPublicKeyExported)
// display and send Bob's public key to Alice
console.log(`Bob's publicKey: ${bobPublicKeyHex}`)
// import Alice's public key
return window.crypto.subtle.importKey(
'raw',
alicePublicKey,
{
name: 'ECDH',
namedCurve: 'P-256'
},
true,
[])
})
.then(aliceKeyImported => {
// use Alice's imported public key and
// Bob's private key to compute the shared secret
return window.crypto.subtle.deriveBits(
{
name: 'ECDH',
namedCurve: 'P-256',
public: aliceKeyImported
},
bob.privateKey,
256)
})
.then(sharedSecret => {
const sharedSecretHex = buf2Hex(sharedSecret)
console.log(`sharedSecret: ${sharedSecretHex}`)
})
.catch(err => {
console.log(err)
})
Example output:
Alice's publicKey: 043a3770a8068738ded16c9409e1a6fbf6dde2360ac5b3fd3e5bb8d9fd6adaed6ea83ff5153f58ae13098e86da89df1beb14ef46388d3df76e8fe2ee0ff9e926d5
Bob's publicKey: 04aeceba6ae783c9b705833c2fa8822281f47f6f36bc867e4d398fa7a744d4fc63a010cbce1e6c9ac8858ad376a24ee8551615560f01c8bb63c86335c046b18962
sharedSecret: c26c9f370f001a947d7fec4dc9282d3e9ea718e1de487eb4f6fa7d6f0a311b97
(4) Alice receives Bob's public key (04aece...). She computes the shared secret as well.
const crypto = require('crypto')
const alice = crypto.createECDH('prime256v1')
// Alice's privateKey (generated previously)
const alicePrivateKey = '937cdd11062b612ff3cb3e4a3c183254b9728b4c8c3a64de799ed196b672734b'
// Bob's publicKey transmitted to Alice
const bobPublicKey = '04aeceba6ae783c9b705833c2fa8822281f47f6f36bc867e4d398fa7a744d4fc63a010cbce1e6c9ac8858ad376a24ee8551615560f01c8bb63c86335c046b18962'
// set Alice's private key (not needed if continuing from (1))
alice.setPrivateKey(alicePrivateKey, 'hex')
const sharedSecret = alice.computeSecret(bobPublicKey, 'hex', 'hex')
console.log(`sharedSecret: ${sharedSecret}`)
Examle output:
sharedSecret: c26c9f370f001a947d7fec4dc9282d3e9ea718e1de487eb4f6fa7d6f0a311b97
The secret is shared (the same).
(5) The shared secret is typically used to derive a symmetric key for encrypting messages between Alice and Bob (and they communicated happily ever after).
Remarks:
Usually there is no need to display or export the private key. Alice would typically continue the computation of the shared secret from step (1) (and omit alice.setPrivateKey(alicePrivateKey, 'hex')).
As the shared secret is most often used to derive a symmetric key, there is window.crypto.subtle.deriveKey and deriveBits can be ommitted. deriveBits was used here to illustrate that Alice and Bob indeed agreed on a shared secret.

Categories