This repository has been archived by the owner on Jul 21, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 52
feat: add exporting/importing of non rsa keys in libp2p-key format #179
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
926b74d
feat: add exporting/importing of ed25519 keys in libp2p-key format
jacobheun 1c7fff8
feat: add libp2p-key export/import support for rsa and secp keys
jacobheun d7cb0bd
chore: dep bumps
jacobheun 8cd671b
chore: update aegir
jacobheun 2ae1d2b
refactor: import and export base64 strings
jacobheun be4b372
refactor: simplify api for now
jacobheun ad26c60
chore: fix lint
jacobheun d02ab5c
refactor: remove extraneous param
jacobheun d391509
refactor: clean up
jacobheun 7fa1a16
fix: review patches
jacobheun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,10 @@ | |
"types": "src/index.d.ts", | ||
"leadMaintainer": "Jacob Heun <[email protected]>", | ||
"browser": { | ||
"./src/aes/ciphers.js": "./src/aes/ciphers-browser.js", | ||
"./src/ciphers/aes-gcm.js": "./src/ciphers/aes-gcm.browser.js", | ||
"./src/hmac/index.js": "./src/hmac/index-browser.js", | ||
"./src/keys/ecdh.js": "./src/keys/ecdh-browser.js", | ||
"./src/aes/ciphers.js": "./src/aes/ciphers-browser.js", | ||
"./src/keys/rsa.js": "./src/keys/rsa-browser.js" | ||
}, | ||
"files": [ | ||
|
@@ -43,21 +44,22 @@ | |
"is-typedarray": "^1.0.0", | ||
"iso-random-stream": "^1.1.0", | ||
"keypair": "^1.0.1", | ||
"multibase": "^0.7.0", | ||
"multibase": "^1.0.1", | ||
"multicodec": "^1.0.4", | ||
"multihashing-async": "^0.8.1", | ||
"node-forge": "^0.9.1", | ||
"pem-jwk": "^2.0.0", | ||
"protons": "^1.0.1", | ||
"protons": "^1.2.1", | ||
"secp256k1": "^4.0.0", | ||
"ursa-optional": "~0.10.1" | ||
"uint8arrays": "^1.0.0", | ||
"ursa-optional": "^0.10.1" | ||
}, | ||
"devDependencies": { | ||
"@types/chai": "^4.2.11", | ||
"@types/chai": "^4.2.12", | ||
"@types/chai-string": "^1.4.2", | ||
"@types/dirty-chai": "^2.0.2", | ||
"@types/mocha": "^7.0.1", | ||
"@types/sinon": "^9.0.0", | ||
"aegir": "^22.0.0", | ||
"@types/mocha": "^8.0.1", | ||
"aegir": "^25.0.0", | ||
"benchmark": "^2.1.4", | ||
"chai": "^4.2.0", | ||
"chai-string": "^1.5.0", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
'use strict' | ||
|
||
const concat = require('uint8arrays/concat') | ||
const fromString = require('uint8arrays/from-string') | ||
|
||
const webcrypto = require('../webcrypto') | ||
|
||
// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples | ||
|
||
/** | ||
* | ||
* @param {object} [options] | ||
* @param {string} [options.algorithm=AES-GCM] | ||
* @param {Number} [options.nonceLength=12] | ||
* @param {Number} [options.keyLength=16] | ||
* @param {string} [options.digest=sha256] | ||
* @param {Number} [options.saltLength=16] | ||
* @param {Number} [options.iterations=32767] | ||
* @returns {*} | ||
*/ | ||
function create ({ | ||
algorithm = 'AES-GCM', | ||
nonceLength = 12, | ||
keyLength = 16, | ||
digest = 'SHA-256', | ||
saltLength = 16, | ||
iterations = 32767 | ||
} = {}) { | ||
const crypto = webcrypto.get() | ||
keyLength *= 8 // Browser crypto uses bits instead of bytes | ||
|
||
/** | ||
* Uses the provided password to derive a pbkdf2 key. The key | ||
* will then be used to encrypt the data. | ||
* | ||
* @param {Uint8Array} data The data to decrypt | ||
* @param {string} password A plain password | ||
* @returns {Promise<Uint8Array>} | ||
*/ | ||
async function encrypt (data, password) { // eslint-disable-line require-await | ||
const salt = crypto.getRandomValues(new Uint8Array(saltLength)) | ||
const nonce = crypto.getRandomValues(new Uint8Array(nonceLength)) | ||
const aesGcm = { name: algorithm, iv: nonce } | ||
|
||
// Derive a key using PBKDF2. | ||
const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } } | ||
const rawKey = await crypto.subtle.importKey('raw', fromString(password), { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits']) | ||
const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['encrypt']) | ||
|
||
// Encrypt the string. | ||
const ciphertext = await crypto.subtle.encrypt(aesGcm, cryptoKey, data) | ||
return concat([salt, aesGcm.iv, new Uint8Array(ciphertext)]) | ||
} | ||
|
||
/** | ||
* Uses the provided password to derive a pbkdf2 key. The key | ||
* will then be used to decrypt the data. The options used to create | ||
* this decryption cipher must be the same as those used to create | ||
* the encryption cipher. | ||
* | ||
* @param {Uint8Array} data The data to decrypt | ||
* @param {string} password A plain password | ||
* @returns {Promise<Uint8Array>} | ||
*/ | ||
async function decrypt (data, password) { | ||
const salt = data.slice(0, saltLength) | ||
const nonce = data.slice(saltLength, saltLength + nonceLength) | ||
const ciphertext = data.slice(saltLength + nonceLength) | ||
const aesGcm = { name: algorithm, iv: nonce } | ||
|
||
// Derive the key using PBKDF2. | ||
const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } } | ||
const rawKey = await crypto.subtle.importKey('raw', fromString(password), { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits']) | ||
const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['decrypt']) | ||
|
||
// Decrypt the string. | ||
const plaintext = await crypto.subtle.decrypt(aesGcm, cryptoKey, ciphertext) | ||
return new Uint8Array(plaintext) | ||
} | ||
|
||
return { | ||
encrypt, | ||
decrypt | ||
} | ||
} | ||
|
||
module.exports = { | ||
create | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
'use strict' | ||
|
||
const crypto = require('crypto') | ||
|
||
// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples | ||
|
||
/** | ||
* | ||
* @param {object} [options] | ||
* @param {Number} [options.algorithmTagLength=16] | ||
* @param {Number} [options.nonceLength=12] | ||
* @param {Number} [options.keyLength=16] | ||
* @param {string} [options.digest=sha256] | ||
* @param {Number} [options.saltLength=16] | ||
* @param {Number} [options.iterations=32767] | ||
* @returns {*} | ||
*/ | ||
function create ({ | ||
algorithmTagLength = 16, | ||
nonceLength = 12, | ||
keyLength = 16, | ||
digest = 'sha256', | ||
saltLength = 16, | ||
iterations = 32767 | ||
} = {}) { | ||
const algorithm = 'aes-128-gcm' | ||
/** | ||
* | ||
* @private | ||
* @param {Buffer} data | ||
* @param {Buffer} key | ||
* @returns {Promise<Buffer>} | ||
*/ | ||
async function encryptWithKey (data, key) { // eslint-disable-line require-await | ||
const nonce = crypto.randomBytes(nonceLength) | ||
|
||
// Create the cipher instance. | ||
const cipher = crypto.createCipheriv(algorithm, key, nonce) | ||
|
||
// Encrypt and prepend nonce. | ||
const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]) | ||
|
||
return Buffer.concat([nonce, ciphertext, cipher.getAuthTag()]) | ||
} | ||
|
||
/** | ||
* Uses the provided password to derive a pbkdf2 key. The key | ||
* will then be used to encrypt the data. | ||
* | ||
* @param {Buffer} data The data to decrypt | ||
* @param {string|Buffer} password A plain password | ||
* @returns {Promise<Buffer>} | ||
*/ | ||
async function encrypt (data, password) { // eslint-disable-line require-await | ||
// Generate a 128-bit salt using a CSPRNG. | ||
const salt = crypto.randomBytes(saltLength) | ||
|
||
// Derive a key using PBKDF2. | ||
const key = crypto.pbkdf2Sync(Buffer.from(password), salt, iterations, keyLength, digest) | ||
|
||
// Encrypt and prepend salt. | ||
return Buffer.concat([salt, await encryptWithKey(Buffer.from(data), key)]) | ||
} | ||
|
||
/** | ||
* Decrypts the given cipher text with the provided key. The `key` should | ||
* be a cryptographically safe key and not a plaintext password. To use | ||
* a plaintext password, use `decrypt`. The options used to create | ||
* this decryption cipher must be the same as those used to create | ||
* the encryption cipher. | ||
* | ||
* @private | ||
* @param {Buffer} ciphertextAndNonce The data to decrypt | ||
* @param {Buffer} key | ||
* @returns {Promise<Buffer>} | ||
*/ | ||
async function decryptWithKey (ciphertextAndNonce, key) { // eslint-disable-line require-await | ||
// Create buffers of nonce, ciphertext and tag. | ||
const nonce = ciphertextAndNonce.slice(0, nonceLength) | ||
const ciphertext = ciphertextAndNonce.slice(nonceLength, ciphertextAndNonce.length - algorithmTagLength) | ||
const tag = ciphertextAndNonce.slice(ciphertext.length + nonceLength) | ||
|
||
// Create the cipher instance. | ||
const cipher = crypto.createDecipheriv(algorithm, key, nonce) | ||
|
||
// Decrypt and return result. | ||
cipher.setAuthTag(tag) | ||
return Buffer.concat([cipher.update(ciphertext), cipher.final()]) | ||
} | ||
|
||
/** | ||
* Uses the provided password to derive a pbkdf2 key. The key | ||
* will then be used to decrypt the data. The options used to create | ||
* this decryption cipher must be the same as those used to create | ||
* the encryption cipher. | ||
* | ||
* @param {Buffer} data The data to decrypt | ||
* @param {string|Buffer} password A plain password | ||
*/ | ||
async function decrypt (data, password) { // eslint-disable-line require-await | ||
// Create buffers of salt and ciphertextAndNonce. | ||
const salt = data.slice(0, saltLength) | ||
const ciphertextAndNonce = data.slice(saltLength) | ||
|
||
// Derive the key using PBKDF2. | ||
const key = crypto.pbkdf2Sync(Buffer.from(password), salt, iterations, keyLength, digest) | ||
|
||
// Decrypt and return result. | ||
return decryptWithKey(ciphertextAndNonce, key) | ||
} | ||
|
||
return { | ||
encrypt, | ||
decrypt | ||
} | ||
} | ||
|
||
module.exports = { | ||
create | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a notice for this in the README
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It only has the import