Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

feat: Add (rsa)pubKey.encrypt and (rsa)privKey.decrypt #125

Merged
merged 11 commits into from
Oct 25, 2019
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"keypair": "^1.0.1",
"libp2p-crypto-secp256k1": "~0.4.0",
"multihashing-async": "~0.7.0",
"node-forge": "~0.8.5",
"node-forge": "~0.9.1",
"pem-jwk": "^2.0.0",
"protons": "^1.0.1",
"rsa-pem-to-jwk": "^1.1.3",
Expand Down
32 changes: 32 additions & 0 deletions src/keys/rsa-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,35 @@ function derivePublicFromPrivate (jwKey) {
['verify']
)
}

/*

RSA encryption/decryption for the browser with webcrypto workarround
"bloody dark magic. webcrypto's why."

Explanation:
- Convert JWK to PEM
- Load PEM with nodeForge
- Convert msg buffer to nodeForge buffer: ByteBuffer is a "binary-string backed buffer", so let's make our buffer a binary string
- Convert resulting nodeForge buffer to buffer: it returns a binary string, turn that into a uint8array(buffer)

*/

const forge = require('node-forge')
const pki = forge.pki
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing in the module as an extension sounds like a good approach, at least for now, so we can get this released.

Also, changing this to const pki = require('node-forge/lib/pki') will also shave off some kb, but the majority comes from pem-jwk.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just found this: https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/rsa.js#L28-L29

Also, applied that change and removed the PEM intermediary step for converting JWK to Forge

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const jwkToPem = require('pem-jwk').jwk2pem
function convertKey (key, pub, msg, handle) {
const pem = jwkToPem(key)
const fkey = pki[pub ? 'publicKeyFromPem' : 'privateKeyFromPem'](pem)
const fmsg = Buffer.from(msg).toString('binary')
const fomsg = handle(fmsg, fkey)
return Buffer.from(fomsg, 'binary')
}

exports.encrypt = function (key, msg) {
return convertKey(key, true, msg, (msg, key) => key.encrypt(msg))
}

exports.decrypt = function (key, msg) {
return convertKey(key, false, msg, (msg, key) => key.decrypt(msg))
}
6 changes: 5 additions & 1 deletion src/keys/rsa-class.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class RsaPublicKey {
}

encrypt (bytes) {
return this._key.encrypt(bytes, 'RSAES-PKCS1-V1_5')
return crypto.encrypt(this._key, bytes)
}

equals (key) {
Expand Down Expand Up @@ -68,6 +68,10 @@ class RsaPrivateKey {
return new RsaPublicKey(this._publicKey)
}

decrypt (bytes) {
return crypto.decrypt(this._key, bytes)
}

marshal () {
return crypto.utils.jwkToPkcs1(this._key)
}
Expand Down
10 changes: 10 additions & 0 deletions src/keys/rsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,13 @@ exports.hashAndVerify = async function (key, sig, msg) { // eslint-disable-line
const pem = jwkToPem(key)
return verify.verify(pem, sig)
}

const padding = crypto.constants.RSA_PKCS1_PADDING

exports.encrypt = function (key, bytes) {
return crypto.publicEncrypt({ key: jwkToPem(key), padding }, bytes)
}

exports.decrypt = function (key, bytes) {
return crypto.privateDecrypt({ key: jwkToPem(key), padding }, bytes)
}
20 changes: 20 additions & 0 deletions test/keys/rsa.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,26 @@ describe('RSA', function () {
expect(valid).to.be.eql(true)
})

it('encrypt and decrypt', async () => {
const data = Buffer.from('hello world')
const enc = await key.public.encrypt(data)
const dec = await key.decrypt(enc)
expect(dec).to.be.eql(data)
})

it('encrypt decrypt browser/node interop', async () => {
const id = await crypto.keys.unmarshalPrivateKey(Buffer.from('CAASqAkwggSkAgEAAoIBAQCk0O+6oNRxhcdZe2GxEDrFBkDV4TZFZnp2ly/dL1cGMBql/8oXPZgei6h7+P5zzfDq2YCfwbjbf0IVY1AshRl6B5VGE1WS+9p1y1OZxJf5os6V1ENnTi6FTcyuBl4BN8dmIKOif0hqgqflaT5OhfYZDXfbJyVQj4vb2+Stu2Xpph3nwqAnTw/7GC/7jrt2Cq6Tu1PoZi36wSwEPYW3eQ1HAYxZjTYYDXl2iyHygnTcbkGRwAQ7vjk+mW7u60zyoolCm9f6Y7c/orJ33DDUocbaGJLlHcfd8bioBwaZy/2m7q43X8pQs0Q1/iwUt0HHZj1YARmHKbh0zR31ciFiV37dAgMBAAECggEADtJBNKnA4QKURj47r0YT2uLwkqtBi6UnDyISalQXAdXyl4n0nPlrhBewC5H9I+HZr+zmTbeIjaiYgz7el1pSy7AB4v7bG7AtWZlyx6mvtwHGjR+8/f3AXjl8Vgv5iSeAdXUq8fJ7SyS7v3wi38HZOzCEXj9bci6ud5ODMYJgLE4gZD0+i1+/V9cpuYfGpS/gLTLEMQLiw/9o8NSZ7sAnxg0UlYhotqaQY23hvXPBOe+0oa95zl2n6XTxCafa3dQl/B6CD1tUq9dhbQew4bxqMq/mhRO9pREEqZ083Uh+u4PTc1BeHgIQaS864pHPb+AY1F7KDvPtHhdojnghp8d70QKBgQDeRYFxo6sd04ohY86Z/i9icVYIyCvfXAKnaMKeGUjK7ou6sDJwFX8W97+CzXpZ/vffsk/l5GGhC50KqrITxHAy/h5IjyDODfps7NMIp0Dm9sO4PWibbw3OOVBRc8w3b3i7I8MrUUA1nLHE1T1HA1rKOTz5jYhE0fi9XKiT1ciKOQKBgQC903w+n9y7M7eaMW7Z5/13kZ7PS3HlM681eaPrk8J4J+c6miFF40/8HOsmarS38v0fgTeKkriPz5A7aLzRHhSiOnp350JNM6c3sLwPEs2qx/CRuWWx1rMERatfDdUH6mvlK6QHu0QgSfQR27EO6a6XvVSJXbvFmimjmtIaz/IpxQKBgQDWJ9HYVAGC81abZTaiWK3/A4QJYhQjWNuVwPICsgnYvI4Uib+PDqcs0ffLZ38DRw48kek5bxpBuJbOuDhro1EXUJCNCJpq7jzixituovd9kTRyR3iKii2bDM2+LPwOTXDdnk9lZRugjCEbrPkleq33Ob7uEtfAty4aBTTHe6uEwQKBgQCB+2q8RyMSXNuADhFlzOFXGrOwJm0bEUUMTPrduRQUyt4e1qOqA3klnXe3mqGcxBpnlEe/76/JacvNom6Ikxx16a0qpYRU8OWz0KU1fR6vrrEgV98241k5t6sdL4+MGA1Bo5xyXtzLb1hdUh3vpDwVU2OrnC+To3iXus/b5EBiMQKBgEI1OaBcFiyjgLGEyFKoZbtzH1mdatTExfrAQqCjOVjQByoMpGhHTXwEaosvyYu63Pa8AJPT7juSGaiKYEJFcXO9BiNyVfmQiqSHJcYeuh+fmO9IlHRHgy5xaIIC00AHS2vC/gXwmXAdPis6BZqDJeiCuOLWJ94QXn8JBT8IgGAI', 'base64'))

const msg = Buffer.from('hello')

// browser
const dec1 = await id.decrypt(Buffer.from('YRFUDx8UjbWSfDS84cDA4WowaaOmd1qFNAv5QutodCKYb9uPtU/tDiAvJzOGu5DCJRo2J0l/35P2weiB4/C2Cb1aZgXKMx/QQC+2jSJiymhqcZaYerjTvkCFwkjCaqthoVo/YXxsaFZ1q7bdTZUDH1TaJR7hWfSyzyPcA8c0w43MIsw16pY8ZaPSclvnCwhoTg1JGjMk6te3we7+wR8QU7VrPhs54mZWxrpu3NQ8xZ6xQqIedsEiNhBUccrCSzYghgsP0Ae/8iKyGyl3U6IegsJNn8jcocvzOJrmU03rgIFPjvuBdaqB38xDSTjbA123KadB28jNoSZh18q/yH3ZIg==', 'base64'))
expect(dec1).to.be.eql(msg)
// node
const dec2 = await id.decrypt(Buffer.from('e6yxssqXsWc27ozDy0PGKtMkCS28KwFyES2Ijz89yiz+w6bSFkNOhHPKplpPzgQEuNoUGdbseKlJFyRYHjIT8FQFBHZM8UgSkgoimbY5on4xSxXs7E5/+twjqKdB7oNveTaTf7JCwaeUYnKSjbiYFEawtMiQE91F8sTT7TmSzOZ48tUhnddAAZ3Ac/O3Z9MSAKOCDipi+JdZtXRT8KimGt36/7hjjosYmPuHR1Xy/yMTL6SMbXtBM3yAuEgbQgP+q/7kHMHji3/JvTpYdIUU+LVtkMusXNasRA+UWG2zAht18vqjFMsm9JTiihZw9jRHD4vxAhf75M992tnC+0ZuQg==', 'base64'))
expect(dec2).to.be.eql(msg)
})

it('fails to verify for different data', async () => {
const data = Buffer.from('hello world')
const sig = await key.sign(data)
Expand Down