Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example for crypto publicKey signing in Browser and privateKey decryption in node #25589

Closed
MasterJames opened this issue Jan 20, 2019 · 3 comments
Labels
crypto Issues and PRs related to the crypto subsystem. doc Issues and PRs related to the documentations.

Comments

@MasterJames
Copy link

MasterJames commented Jan 20, 2019

I have been hard pressed to discover an example of how to use a publicKey sent to a Browser to the be able to decrypt in node what was encrypted in the browser with the nodejs crypto generate KeyPair's PublicKey.

It has been difficult being 'subtle' in the nuances of which type of encryption to use and the impossibility of seeing what it is as it's encrypted and doesn't seem to carry probable information about how it was done etc.

@bnoordhuis bnoordhuis added crypto Issues and PRs related to the crypto subsystem. doc Issues and PRs related to the documentations. labels Jan 22, 2019
@bnoordhuis
Copy link
Member

It's unclear to me what API you are referring to. crypto.generateKeyPair()?

Can you be more specific as to what is and isn't clear to you in the existing examples?

@MasterJames
Copy link
Author

MasterJames commented Jan 23, 2019

I had a bigger write up I removed when I made some progress, but I will attempt to document some help for others and ultimately a documentation update I'm suggestion.
On this I realized in varied amounts and different stages is that pbkdf2 is only for hashing and was not applicable for my purposes (although apparently better of that's is in itself the purpose).

This is about getting a public key to the browser so it can send a message back that can be undone with the private key so as a-typical as it gets for cryptography, and yes generateKeyPairSync is the first step on server side. I took the documented example and have been able to change a few settings but I think it should eventually support 384 and 512 in the future [ not just aes-256-*] (it doesn't seem to currently and looking for that info was also elusive, "what ciphers are allowed and compatible how with the browsers side?").

genKeyPod: async function ( ciphers ) {
	if( ciphers === und ) ciphers = {
		//modulusLength: 2048,
		modulusLength: 4096,
		publicKeyEncoding: {
			type: 'spki',
			format: 'pem'
		},
		privateKeyEncoding: {
			type: 'pkcs8',
			format: 'pem',
			//cipher: 'aes-256-cbc',
			cipher: 'aes-256-ecb',
			passphrase: 'somethingofyourchoosingbeitgeneratedetc"
		}
	};
	let keys = lib.crypto.generateKeyPairSync( 'rsa', ciphers );
	return {
		ciphers : ciphers,
		privateKey: keys.privateKey,
		publicKey: keys.publicKey
	};
},

I ultimately send only th publicKey via a websocket connect or what-have-you. It is in the normal form of a openssl generate PEM file at this point so you could alternatively write and load it as a file.
The tricky bit undocumented really anywhere with current settings is both sides of the coin, and knowing something more about ArrayBuffers and differences between base64 vs hex also potential pitfalls, which desire more clarity.
Anyway on the browser side I am doing this to import that public key generated by nodejs 11.7
[importRsaKey could be more synchronously done as well I suppose.]

let importRsaKey = function( pem ) {
	let binStr = pemToBinary( pem );
	let keyBuf = str2ab( binStr );
	return crypto.importKey(
		"spki", keyBuf,
		{
			name: "RSA-OAEP",
			hash: "SHA-1"
		},
		true, ["encrypt"]
	);
};

let pemToBinary = function( pem ) {
	let prtTrim, prt, prts = pem.split('\n');
	let encd = '', idx = 0, len = prts.length;
	while( idx < len ){
		prt = prts[ idx ];
		prtTrim = prt.trim();
		if( prtTrim.length > 0 && prt.indexOf('-BEGIN ') < 0 && prt.indexOf('-END ') < 0 ) encd = ( encd + prtTrim );
		idx = ( idx + 1 );
	}
	return atob( encd );
};

let str2ab = function( str ) {
	let buf = new ArrayBuffer(str.length);
	let bufView = new Uint8Array(buf);
	for (let i = 0, strLen = str.length; i < strLen; i++) {
		bufView[i] = str.charCodeAt(i);
	}
	return buf;
};

After getting it working I cleaned these bits and bobbins to my liking from the ones I posted here before that are often not so much to my liking.

[I was concerned about the SHA-1 but it's a public key so no worries there I suppose.]

Next on the client side via SubtleCrypto as crypto I can encrypt and send it back.

let te = new TextEncoder( 'utf-8' );
let msgEncoded = te.encode( msg );
let msgBuf = await crypto.encrypt( { name: "RSA-OAEP" }, aKeyFromImport, msgEncoded );
let msgHex = btoa(String.fromCharCode(...new Uint8Array( msgBuf )));

Finally back on the server I can decrypt with the private key the encoded message.

let buffer = Buffer.from( rMsg.msg, 'base64' );
let msgDec = lib.crypto.privateDecrypt( {
			key: stored.privateKey,
			passphrase: stored.passphrase
		}, buffer );
rMsg.readableMsg = msgDec.toString('utf8');

So I think that's enough to show what's needed to get one way encryption working.
Bi-directional, signing and much more one can do does get complicated but with this foundation one can start without a week of frustration I spent pulling hair and losing my voice via profanities.

It should be possible to wrap these steps on both sides into something more user friendly and easier.
There's probably an easier route to get the PEM imported and ready for encryption. For instance maybe if the encryption text is not an ArrayBuffer it could be encoded as such. This is on the SubtleCrypto Browser side, and maybe I should cross post something about my experiences there too. The thing is that the Browser is less inclined to be talking to nodejs exclusively. Still some server side examples there would have also helped greatly,
Again I am suggesting some example of nodejs to browser/chrome and back again is an important thing to provide as that is ultimately it's purpose and it's not clear looking at the two sides independently.

https://nodejs.org/api/crypto.html
https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt

I am left wondering is there a better cipher that's compatible and other questions about how to tighten the nuts up. I know Chrome won't go 512 but it can do 384 (that might be an Elliptic only issue).

@bnoordhuis
Copy link
Member

So, documentation pull requests are always welcome but do keep in mind that the API documentation is reference documentation; it's not supposed to veer off into specific use cases.

A short list of ciphers that can be expected to work with modern browsers is probably okay, a tutorial on how to interoperate between Node.js and a browser likely isn't.

It might be material for a guide on the website (probably here) but I can't speak to that; you'd have to ask over at https://github.com/nodejs/nodejs.org.

I'm going to tentatively close this but let me know if I should reopen. And if you want to send a PR, please do!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crypto Issues and PRs related to the crypto subsystem. doc Issues and PRs related to the documentations.
Projects
None yet
Development

No branches or pull requests

2 participants