@libp2p/crypto 0.0.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/dist/src/aes/cipher-mode.d.ts +2 -0
- package/dist/src/aes/cipher-mode.d.ts.map +1 -0
- package/dist/src/aes/cipher-mode.js +13 -0
- package/dist/src/aes/cipher-mode.js.map +1 -0
- package/dist/src/aes/ciphers-browser.d.ts +8 -0
- package/dist/src/aes/ciphers-browser.d.ts.map +1 -0
- package/dist/src/aes/ciphers-browser.js +26 -0
- package/dist/src/aes/ciphers-browser.js.map +1 -0
- package/dist/src/aes/ciphers.d.ts +5 -0
- package/dist/src/aes/ciphers.d.ts.map +1 -0
- package/dist/src/aes/ciphers.js +4 -0
- package/dist/src/aes/ciphers.js.map +1 -0
- package/dist/src/aes/index.d.ts +6 -0
- package/dist/src/aes/index.d.ts.map +1 -0
- package/dist/src/aes/index.js +17 -0
- package/dist/src/aes/index.js.map +1 -0
- package/dist/src/ciphers/aes-gcm.browser.d.ts +3 -0
- package/dist/src/ciphers/aes-gcm.browser.d.ts.map +1 -0
- package/dist/src/ciphers/aes-gcm.browser.js +61 -0
- package/dist/src/ciphers/aes-gcm.browser.js.map +1 -0
- package/dist/src/ciphers/aes-gcm.d.ts +3 -0
- package/dist/src/ciphers/aes-gcm.d.ts.map +1 -0
- package/dist/src/ciphers/aes-gcm.js +83 -0
- package/dist/src/ciphers/aes-gcm.js.map +1 -0
- package/dist/src/ciphers/interface.d.ts +14 -0
- package/dist/src/ciphers/interface.d.ts.map +1 -0
- package/dist/src/ciphers/interface.js +2 -0
- package/dist/src/ciphers/interface.js.map +1 -0
- package/dist/src/hmac/index-browser.d.ts +5 -0
- package/dist/src/hmac/index-browser.d.ts.map +1 -0
- package/dist/src/hmac/index-browser.js +25 -0
- package/dist/src/hmac/index-browser.js.map +1 -0
- package/dist/src/hmac/index.d.ts +5 -0
- package/dist/src/hmac/index.d.ts.map +1 -0
- package/dist/src/hmac/index.js +14 -0
- package/dist/src/hmac/index.js.map +1 -0
- package/dist/src/hmac/lengths.d.ts +7 -0
- package/dist/src/hmac/lengths.d.ts.map +1 -0
- package/dist/src/hmac/lengths.js +6 -0
- package/dist/src/hmac/lengths.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/keys/ecdh-browser.d.ts +3 -0
- package/dist/src/keys/ecdh-browser.d.ts.map +1 -0
- package/dist/src/keys/ecdh-browser.js +97 -0
- package/dist/src/keys/ecdh-browser.js.map +1 -0
- package/dist/src/keys/ecdh.d.ts +3 -0
- package/dist/src/keys/ecdh.d.ts.map +1 -0
- package/dist/src/keys/ecdh.js +26 -0
- package/dist/src/keys/ecdh.js.map +1 -0
- package/dist/src/keys/ed25519-class.d.ts +39 -0
- package/dist/src/keys/ed25519-class.d.ts.map +1 -0
- package/dist/src/keys/ed25519-class.js +119 -0
- package/dist/src/keys/ed25519-class.js.map +1 -0
- package/dist/src/keys/ed25519.d.ts +18 -0
- package/dist/src/keys/ed25519.d.ts.map +1 -0
- package/dist/src/keys/ed25519.js +52 -0
- package/dist/src/keys/ed25519.js.map +1 -0
- package/dist/src/keys/ephemeral-keys.d.ts +9 -0
- package/dist/src/keys/ephemeral-keys.d.ts.map +1 -0
- package/dist/src/keys/ephemeral-keys.js +9 -0
- package/dist/src/keys/ephemeral-keys.js.map +1 -0
- package/dist/src/keys/exporter.d.ts +7 -0
- package/dist/src/keys/exporter.d.ts.map +1 -0
- package/dist/src/keys/exporter.js +13 -0
- package/dist/src/keys/exporter.js.map +1 -0
- package/dist/src/keys/importer.d.ts +7 -0
- package/dist/src/keys/importer.d.ts.map +1 -0
- package/dist/src/keys/importer.js +13 -0
- package/dist/src/keys/importer.js.map +1 -0
- package/dist/src/keys/index.d.ts +33 -0
- package/dist/src/keys/index.d.ts.map +1 -0
- package/dist/src/keys/index.js +111 -0
- package/dist/src/keys/index.js.map +1 -0
- package/dist/src/keys/interface.d.ts +17 -0
- package/dist/src/keys/interface.d.ts.map +1 -0
- package/dist/src/keys/interface.js +2 -0
- package/dist/src/keys/interface.js.map +1 -0
- package/dist/src/keys/jwk2pem.d.ts +4 -0
- package/dist/src/keys/jwk2pem.d.ts.map +1 -0
- package/dist/src/keys/jwk2pem.js +14 -0
- package/dist/src/keys/jwk2pem.js.map +1 -0
- package/dist/src/keys/key-stretcher.d.ts +17 -0
- package/dist/src/keys/key-stretcher.d.ts.map +1 -0
- package/dist/src/keys/key-stretcher.js +65 -0
- package/dist/src/keys/key-stretcher.js.map +1 -0
- package/dist/src/keys/keys.d.ts +225 -0
- package/dist/src/keys/keys.d.ts.map +1 -0
- package/dist/src/keys/keys.js +345 -0
- package/dist/src/keys/keys.js.map +1 -0
- package/dist/src/keys/rsa-browser.d.ts +17 -0
- package/dist/src/keys/rsa-browser.d.ts.map +1 -0
- package/dist/src/keys/rsa-browser.js +99 -0
- package/dist/src/keys/rsa-browser.js.map +1 -0
- package/dist/src/keys/rsa-class.d.ts +42 -0
- package/dist/src/keys/rsa-class.d.ts.map +1 -0
- package/dist/src/keys/rsa-class.js +126 -0
- package/dist/src/keys/rsa-class.js.map +1 -0
- package/dist/src/keys/rsa-utils.d.ts +7 -0
- package/dist/src/keys/rsa-utils.d.ts.map +1 -0
- package/dist/src/keys/rsa-utils.js +65 -0
- package/dist/src/keys/rsa-utils.js.map +1 -0
- package/dist/src/keys/rsa.d.ts +13 -0
- package/dist/src/keys/rsa.d.ts.map +1 -0
- package/dist/src/keys/rsa.js +58 -0
- package/dist/src/keys/rsa.js.map +1 -0
- package/dist/src/keys/secp256k1-class.d.ts +36 -0
- package/dist/src/keys/secp256k1-class.d.ts.map +1 -0
- package/dist/src/keys/secp256k1-class.js +95 -0
- package/dist/src/keys/secp256k1-class.js.map +1 -0
- package/dist/src/keys/secp256k1.d.ts +17 -0
- package/dist/src/keys/secp256k1.d.ts.map +1 -0
- package/dist/src/keys/secp256k1.js +65 -0
- package/dist/src/keys/secp256k1.js.map +1 -0
- package/dist/src/pbkdf2.d.ts +5 -0
- package/dist/src/pbkdf2.d.ts.map +1 -0
- package/dist/src/pbkdf2.js +30 -0
- package/dist/src/pbkdf2.js.map +1 -0
- package/dist/src/random-bytes.d.ts +2 -0
- package/dist/src/random-bytes.d.ts.map +1 -0
- package/dist/src/random-bytes.js +9 -0
- package/dist/src/random-bytes.js.map +1 -0
- package/dist/src/util.d.ts +9 -0
- package/dist/src/util.d.ts.map +1 -0
- package/dist/src/util.js +37 -0
- package/dist/src/util.js.map +1 -0
- package/dist/src/webcrypto.d.ts +5 -0
- package/dist/src/webcrypto.d.ts.map +1 -0
- package/dist/src/webcrypto.js +17 -0
- package/dist/src/webcrypto.js.map +1 -0
- package/package.json +123 -4
- package/src/aes/cipher-mode.ts +15 -0
- package/src/aes/ciphers-browser.ts +28 -0
- package/src/aes/ciphers.ts +4 -0
- package/src/aes/index.ts +25 -0
- package/src/ciphers/aes-gcm.browser.ts +74 -0
- package/src/ciphers/aes-gcm.ts +102 -0
- package/src/ciphers/interface.ts +15 -0
- package/src/hmac/index-browser.ts +35 -0
- package/src/hmac/index.ts +15 -0
- package/src/hmac/lengths.ts +6 -0
- package/src/index.ts +11 -0
- package/src/keys/ecdh-browser.ts +138 -0
- package/src/keys/ecdh.ts +33 -0
- package/src/keys/ed25519-class.ts +145 -0
- package/src/keys/ed25519.ts +63 -0
- package/src/keys/ephemeral-keys.ts +9 -0
- package/src/keys/exporter.ts +13 -0
- package/src/keys/importer.ts +13 -0
- package/src/keys/index.ts +126 -0
- package/src/keys/interface.ts +20 -0
- package/src/keys/jwk2pem.ts +16 -0
- package/src/keys/key-stretcher.ts +77 -0
- package/src/keys/keys.d.ts +146 -0
- package/src/keys/keys.js +366 -0
- package/src/keys/keys.proto +15 -0
- package/src/keys/rsa-browser.ts +156 -0
- package/src/keys/rsa-class.ts +155 -0
- package/src/keys/rsa-utils.ts +74 -0
- package/src/keys/rsa.ts +69 -0
- package/src/keys/secp256k1-class.ts +118 -0
- package/src/keys/secp256k1.ts +69 -0
- package/src/pbkdf2.ts +39 -0
- package/src/random-bytes.ts +9 -0
- package/src/util.ts +42 -0
- package/src/webcrypto.ts +24 -0
package/package.json
CHANGED
|
@@ -1,7 +1,126 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libp2p/crypto",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "0.22.0",
|
|
4
|
+
"description": "Crypto primitives for libp2p",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./dist/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./aes": {
|
|
12
|
+
"import": "./dist/src/aes/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./ciphers": {
|
|
15
|
+
"import": "./dist/src/ciphers/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./hmac": {
|
|
18
|
+
"import": "./dist/src/hmac/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./keys": {
|
|
21
|
+
"import": "./dist/src/keys/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"src",
|
|
26
|
+
"dist/src",
|
|
27
|
+
"!dist/test",
|
|
28
|
+
"!**/*.tsbuildinfo"
|
|
29
|
+
],
|
|
30
|
+
"eslintConfig": {
|
|
31
|
+
"extends": "ipfs",
|
|
32
|
+
"parserOptions": {
|
|
33
|
+
"sourceType": "module"
|
|
34
|
+
},
|
|
35
|
+
"ignorePatterns": [
|
|
36
|
+
"src/*.d.ts"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"pretest": "npm run build",
|
|
41
|
+
"test": "aegir test -f ./dist/test/**/*.js",
|
|
42
|
+
"test:browser": "aegir test -t browser -f ./dist/test/**/*.js",
|
|
43
|
+
"test:node": "aegir test -t node -f ./dist/test/**/*.js",
|
|
44
|
+
"test:electron": "aegir test -t electron-main -f ./dist/test/**/*.js",
|
|
45
|
+
"lint": "aegir ts -p check && aegir lint",
|
|
46
|
+
"release": "aegir release --docs",
|
|
47
|
+
"release-minor": "aegir release --target node --type minor --docs",
|
|
48
|
+
"release-major": "aegir release --type major --docs",
|
|
49
|
+
"build": "tsc",
|
|
50
|
+
"build:proto": "npm run build:proto:js && npm run build:proto:types",
|
|
51
|
+
"build:proto:js": "pbjs -t static-module -w es6 --es6 -r libp2p-crypto-keys --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/keys/keys.js ./src/keys/keys.proto",
|
|
52
|
+
"build:proto:types": "pbts -o src/keys/keys.d.ts src/keys/keys.js",
|
|
53
|
+
"dep-check": "aegir dep-check dist/src/**/*.js dist/test/**/*.js"
|
|
54
|
+
},
|
|
55
|
+
"browser": {
|
|
56
|
+
"./dist/src/aes/ciphers.js": "./dist/src/aes/ciphers-browser.js",
|
|
57
|
+
"./dist/src/ciphers/aes-gcm.js": "./dist/src/ciphers/aes-gcm.browser.js",
|
|
58
|
+
"./dist/src/hmac/index.js": "./dist/src/hmac/index-browser.js",
|
|
59
|
+
"./dist/src/keys/ecdh.js": "./dist/src/keys/ecdh-browser.js",
|
|
60
|
+
"./dist/src/keys/rsa.js": "./dist/src/keys/rsa-browser.js"
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"IPFS",
|
|
64
|
+
"libp2p",
|
|
65
|
+
"crypto",
|
|
66
|
+
"rsa",
|
|
67
|
+
"secp256k1"
|
|
68
|
+
],
|
|
69
|
+
"license": "MIT",
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@noble/ed25519": "^1.3.3",
|
|
72
|
+
"@noble/secp256k1": "^1.3.4",
|
|
73
|
+
"err-code": "^3.0.1",
|
|
74
|
+
"iso-random-stream": "^2.0.0",
|
|
75
|
+
"multiformats": "^9.4.5",
|
|
76
|
+
"node-forge": "^0.10.0",
|
|
77
|
+
"protobufjs": "^6.11.2",
|
|
78
|
+
"uint8arrays": "^3.0.0"
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@types/mocha": "^9.0.0",
|
|
82
|
+
"aegir": "^36.0.2",
|
|
83
|
+
"benchmark": "^2.1.4",
|
|
84
|
+
"sinon": "^12.0.1",
|
|
85
|
+
"util": "^0.12.3",
|
|
86
|
+
"wherearewe": "^1.0.0"
|
|
87
|
+
},
|
|
88
|
+
"engines": {
|
|
89
|
+
"node": ">=15.0.0"
|
|
90
|
+
},
|
|
91
|
+
"repository": {
|
|
92
|
+
"type": "git",
|
|
93
|
+
"url": "https://github.com/libp2p/js-libp2p-crypto.git"
|
|
94
|
+
},
|
|
95
|
+
"bugs": {
|
|
96
|
+
"url": "https://github.com/libp2p/js-libp2p-crypto/issues"
|
|
97
|
+
},
|
|
98
|
+
"homepage": "https://github.com/libp2p/js-libp2p-crypto",
|
|
99
|
+
"contributors": [
|
|
100
|
+
"David Dias <daviddias.p@gmail.com>",
|
|
101
|
+
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
|
102
|
+
"Jacob Heun <jacobheun@gmail.com>",
|
|
103
|
+
"Vasco Santos <vasco.santos@moxy.studio>",
|
|
104
|
+
"Maciej Krüger <mkg20001@gmail.com>",
|
|
105
|
+
"Alex Potsides <alex@achingbrain.net>",
|
|
106
|
+
"dignifiedquire <dignifiedquire@users.noreply.github.com>",
|
|
107
|
+
"dryajov <dryajov@gmail.com>",
|
|
108
|
+
"Hugo Dias <hugomrdias@gmail.com>",
|
|
109
|
+
"Alan Shaw <alan.shaw@protocol.ai>",
|
|
110
|
+
"Cayman <caymannava@gmail.com>",
|
|
111
|
+
"Yusef Napora <yusef@napora.org>",
|
|
112
|
+
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
|
|
113
|
+
"Arve Knudsen <arve.knudsen@gmail.com>",
|
|
114
|
+
"Alberto Elias <hi@albertoelias.me>",
|
|
115
|
+
"Jack Kleeman <jackkleeman@gmail.com>",
|
|
116
|
+
"Nadim Kobeissi <nadim@symbolic.software>",
|
|
117
|
+
"Richard Littauer <richard.littauer@gmail.com>",
|
|
118
|
+
"Richard Schneider <makaretu@gmail.com>",
|
|
119
|
+
"dirkmc <dirkmdev@gmail.com>",
|
|
120
|
+
"nikuda <nikuda@gmail.com>",
|
|
121
|
+
"Jimmy Wärting <jimmy@warting.se>",
|
|
122
|
+
"Carson Farmer <carson.farmer@gmail.com>",
|
|
123
|
+
"Tom Swindell <t.swindell@rubyx.co.uk>",
|
|
124
|
+
"Joao Santos <jrmsantos15@gmail.com>"
|
|
125
|
+
]
|
|
7
126
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import errcode from 'err-code'
|
|
2
|
+
|
|
3
|
+
const CIPHER_MODES = {
|
|
4
|
+
16: 'aes-128-ctr',
|
|
5
|
+
32: 'aes-256-ctr'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function cipherMode (key: Uint8Array) {
|
|
9
|
+
if (key.length === 16 || key.length === 32) {
|
|
10
|
+
return CIPHER_MODES[key.length]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const modes = Object.entries(CIPHER_MODES).map(([k, v]) => `${k} (${v})`).join(' / ')
|
|
14
|
+
throw errcode(new Error(`Invalid key length ${key.length} bytes. Must be ${modes}`), 'ERR_INVALID_KEY_LENGTH')
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
import 'node-forge/lib/aes.js'
|
|
3
|
+
// @ts-expect-error types are missing
|
|
4
|
+
import forge from 'node-forge/lib/forge.js'
|
|
5
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
6
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
7
|
+
|
|
8
|
+
export function createCipheriv (mode: any, key: Uint8Array, iv: Uint8Array) {
|
|
9
|
+
const cipher2 = forge.cipher.createCipher('AES-CTR', uint8ArrayToString(key, 'ascii'))
|
|
10
|
+
cipher2.start({ iv: uint8ArrayToString(iv, 'ascii') })
|
|
11
|
+
return {
|
|
12
|
+
update: (data: Uint8Array) => {
|
|
13
|
+
cipher2.update(forge.util.createBuffer(uint8ArrayToString(data, 'ascii')))
|
|
14
|
+
return uint8ArrayFromString(cipher2.output.getBytes(), 'ascii')
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createDecipheriv (mode: any, key: Uint8Array, iv: Uint8Array) {
|
|
20
|
+
const cipher2 = forge.cipher.createDecipher('AES-CTR', uint8ArrayToString(key, 'ascii'))
|
|
21
|
+
cipher2.start({ iv: uint8ArrayToString(iv, 'ascii') })
|
|
22
|
+
return {
|
|
23
|
+
update: (data: Uint8Array) => {
|
|
24
|
+
cipher2.update(forge.util.createBuffer(uint8ArrayToString(data, 'ascii')))
|
|
25
|
+
return uint8ArrayFromString(cipher2.output.getBytes(), 'ascii')
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/aes/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as ciphers from './ciphers.js'
|
|
2
|
+
import { cipherMode } from './cipher-mode.js'
|
|
3
|
+
|
|
4
|
+
export interface AESCipher {
|
|
5
|
+
encrypt: (data: Uint8Array) => Promise<Uint8Array>
|
|
6
|
+
decrypt: (data: Uint8Array) => Promise<Uint8Array>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function create (key: Uint8Array, iv: Uint8Array) { // eslint-disable-line require-await
|
|
10
|
+
const mode = cipherMode(key)
|
|
11
|
+
const cipher = ciphers.createCipheriv(mode, key, iv)
|
|
12
|
+
const decipher = ciphers.createDecipheriv(mode, key, iv)
|
|
13
|
+
|
|
14
|
+
const res: AESCipher = {
|
|
15
|
+
async encrypt (data) { // eslint-disable-line require-await
|
|
16
|
+
return cipher.update(data)
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async decrypt (data) { // eslint-disable-line require-await
|
|
20
|
+
return decipher.update(data)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return res
|
|
25
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { concat } from 'uint8arrays/concat'
|
|
2
|
+
import { fromString } from 'uint8arrays/from-string'
|
|
3
|
+
import webcrypto from '../webcrypto.js'
|
|
4
|
+
import type { CreateOptions, AESCipher } from './interface.js'
|
|
5
|
+
|
|
6
|
+
// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples
|
|
7
|
+
|
|
8
|
+
export function create (opts?: CreateOptions) {
|
|
9
|
+
const algorithm = opts?.algorithm ?? 'AES-GCM'
|
|
10
|
+
let keyLength = opts?.keyLength ?? 16
|
|
11
|
+
const nonceLength = opts?.nonceLength ?? 12
|
|
12
|
+
const digest = opts?.digest ?? 'SHA-256'
|
|
13
|
+
const saltLength = opts?.saltLength ?? 16
|
|
14
|
+
const iterations = opts?.iterations ?? 32767
|
|
15
|
+
|
|
16
|
+
const crypto = webcrypto.get()
|
|
17
|
+
keyLength *= 8 // Browser crypto uses bits instead of bytes
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Uses the provided password to derive a pbkdf2 key. The key
|
|
21
|
+
* will then be used to encrypt the data.
|
|
22
|
+
*/
|
|
23
|
+
async function encrypt (data: Uint8Array, password: string | Uint8Array) { // eslint-disable-line require-await
|
|
24
|
+
const salt = crypto.getRandomValues(new Uint8Array(saltLength))
|
|
25
|
+
const nonce = crypto.getRandomValues(new Uint8Array(nonceLength))
|
|
26
|
+
const aesGcm = { name: algorithm, iv: nonce }
|
|
27
|
+
|
|
28
|
+
if (typeof password === 'string') {
|
|
29
|
+
password = fromString(password)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Derive a key using PBKDF2.
|
|
33
|
+
const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
|
|
34
|
+
const rawKey = await crypto.subtle.importKey('raw', password, { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits'])
|
|
35
|
+
const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['encrypt'])
|
|
36
|
+
|
|
37
|
+
// Encrypt the string.
|
|
38
|
+
const ciphertext = await crypto.subtle.encrypt(aesGcm, cryptoKey, data)
|
|
39
|
+
return concat([salt, aesGcm.iv, new Uint8Array(ciphertext)])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Uses the provided password to derive a pbkdf2 key. The key
|
|
44
|
+
* will then be used to decrypt the data. The options used to create
|
|
45
|
+
* this decryption cipher must be the same as those used to create
|
|
46
|
+
* the encryption cipher.
|
|
47
|
+
*/
|
|
48
|
+
async function decrypt (data: Uint8Array, password: string | Uint8Array) {
|
|
49
|
+
const salt = data.slice(0, saltLength)
|
|
50
|
+
const nonce = data.slice(saltLength, saltLength + nonceLength)
|
|
51
|
+
const ciphertext = data.slice(saltLength + nonceLength)
|
|
52
|
+
const aesGcm = { name: algorithm, iv: nonce }
|
|
53
|
+
|
|
54
|
+
if (typeof password === 'string') {
|
|
55
|
+
password = fromString(password)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Derive the key using PBKDF2.
|
|
59
|
+
const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
|
|
60
|
+
const rawKey = await crypto.subtle.importKey('raw', password, { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits'])
|
|
61
|
+
const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['decrypt'])
|
|
62
|
+
|
|
63
|
+
// Decrypt the string.
|
|
64
|
+
const plaintext = await crypto.subtle.decrypt(aesGcm, cryptoKey, ciphertext)
|
|
65
|
+
return new Uint8Array(plaintext)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const cipher: AESCipher = {
|
|
69
|
+
encrypt,
|
|
70
|
+
decrypt
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return cipher
|
|
74
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
|
|
3
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
4
|
+
import type { CreateOptions, AESCipher } from './interface.js'
|
|
5
|
+
|
|
6
|
+
// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples
|
|
7
|
+
|
|
8
|
+
export function create (opts?: CreateOptions) {
|
|
9
|
+
const algorithm = opts?.algorithm ?? 'aes-128-gcm'
|
|
10
|
+
const keyLength = opts?.keyLength ?? 16
|
|
11
|
+
const nonceLength = opts?.nonceLength ?? 12
|
|
12
|
+
const digest = opts?.digest ?? 'sha256'
|
|
13
|
+
const saltLength = opts?.saltLength ?? 16
|
|
14
|
+
const iterations = opts?.iterations ?? 32767
|
|
15
|
+
const algorithmTagLength = opts?.algorithmTagLength ?? 16
|
|
16
|
+
|
|
17
|
+
async function encryptWithKey (data: Uint8Array, key: Uint8Array) { // eslint-disable-line require-await
|
|
18
|
+
const nonce = crypto.randomBytes(nonceLength)
|
|
19
|
+
|
|
20
|
+
// Create the cipher instance.
|
|
21
|
+
const cipher = crypto.createCipheriv(algorithm, key, nonce)
|
|
22
|
+
|
|
23
|
+
// Encrypt and prepend nonce.
|
|
24
|
+
const ciphertext = uint8ArrayConcat([cipher.update(data), cipher.final()])
|
|
25
|
+
|
|
26
|
+
// @ts-expect-error getAuthTag is not a function
|
|
27
|
+
return uint8ArrayConcat([nonce, ciphertext, cipher.getAuthTag()])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Uses the provided password to derive a pbkdf2 key. The key
|
|
32
|
+
* will then be used to encrypt the data.
|
|
33
|
+
*/
|
|
34
|
+
async function encrypt (data: Uint8Array, password: string | Uint8Array) { // eslint-disable-line require-await
|
|
35
|
+
// Generate a 128-bit salt using a CSPRNG.
|
|
36
|
+
const salt = crypto.randomBytes(saltLength)
|
|
37
|
+
|
|
38
|
+
if (typeof password === 'string') {
|
|
39
|
+
password = uint8ArrayFromString(password)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Derive a key using PBKDF2.
|
|
43
|
+
const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
|
|
44
|
+
|
|
45
|
+
// Encrypt and prepend salt.
|
|
46
|
+
return uint8ArrayConcat([salt, await encryptWithKey(Uint8Array.from(data), key)])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Decrypts the given cipher text with the provided key. The `key` should
|
|
51
|
+
* be a cryptographically safe key and not a plaintext password. To use
|
|
52
|
+
* a plaintext password, use `decrypt`. The options used to create
|
|
53
|
+
* this decryption cipher must be the same as those used to create
|
|
54
|
+
* the encryption cipher.
|
|
55
|
+
*/
|
|
56
|
+
async function decryptWithKey (ciphertextAndNonce: Uint8Array, key: Uint8Array) { // eslint-disable-line require-await
|
|
57
|
+
// Create Uint8Arrays of nonce, ciphertext and tag.
|
|
58
|
+
const nonce = ciphertextAndNonce.slice(0, nonceLength)
|
|
59
|
+
const ciphertext = ciphertextAndNonce.slice(nonceLength, ciphertextAndNonce.length - algorithmTagLength)
|
|
60
|
+
const tag = ciphertextAndNonce.slice(ciphertext.length + nonceLength)
|
|
61
|
+
|
|
62
|
+
// Create the cipher instance.
|
|
63
|
+
const cipher = crypto.createDecipheriv(algorithm, key, nonce)
|
|
64
|
+
|
|
65
|
+
// Decrypt and return result.
|
|
66
|
+
// @ts-expect-error getAuthTag is not a function
|
|
67
|
+
cipher.setAuthTag(tag)
|
|
68
|
+
return uint8ArrayConcat([cipher.update(ciphertext), cipher.final()])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Uses the provided password to derive a pbkdf2 key. The key
|
|
73
|
+
* will then be used to decrypt the data. The options used to create
|
|
74
|
+
* this decryption cipher must be the same as those used to create
|
|
75
|
+
* the encryption cipher.
|
|
76
|
+
*
|
|
77
|
+
* @param {Uint8Array} data - The data to decrypt
|
|
78
|
+
* @param {string|Uint8Array} password - A plain password
|
|
79
|
+
*/
|
|
80
|
+
async function decrypt (data: Uint8Array, password: string | Uint8Array) { // eslint-disable-line require-await
|
|
81
|
+
// Create Uint8Arrays of salt and ciphertextAndNonce.
|
|
82
|
+
const salt = data.slice(0, saltLength)
|
|
83
|
+
const ciphertextAndNonce = data.slice(saltLength)
|
|
84
|
+
|
|
85
|
+
if (typeof password === 'string') {
|
|
86
|
+
password = uint8ArrayFromString(password)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Derive the key using PBKDF2.
|
|
90
|
+
const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
|
|
91
|
+
|
|
92
|
+
// Decrypt and return result.
|
|
93
|
+
return await decryptWithKey(ciphertextAndNonce, key)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cipher: AESCipher = {
|
|
97
|
+
encrypt,
|
|
98
|
+
decrypt
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return cipher
|
|
102
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
export interface CreateOptions {
|
|
3
|
+
algorithm?: string
|
|
4
|
+
nonceLength?: number
|
|
5
|
+
keyLength?: number
|
|
6
|
+
digest?: string
|
|
7
|
+
saltLength?: number
|
|
8
|
+
iterations?: number
|
|
9
|
+
algorithmTagLength?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AESCipher {
|
|
13
|
+
encrypt: (data: Uint8Array, password: string | Uint8Array) => Promise<Uint8Array>
|
|
14
|
+
decrypt: (data: Uint8Array, password: string | Uint8Array) => Promise<Uint8Array>
|
|
15
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import webcrypto from '../webcrypto.js'
|
|
2
|
+
import lengths from './lengths.js'
|
|
3
|
+
|
|
4
|
+
const hashTypes = {
|
|
5
|
+
SHA1: 'SHA-1',
|
|
6
|
+
SHA256: 'SHA-256',
|
|
7
|
+
SHA512: 'SHA-512'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const sign = async (key: CryptoKey, data: Uint8Array) => {
|
|
11
|
+
const buf = await webcrypto.get().subtle.sign({ name: 'HMAC' }, key, data)
|
|
12
|
+
return new Uint8Array(buf, 0, buf.byteLength)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function create (hashType: 'SHA1' | 'SHA256' | 'SHA512', secret: Uint8Array) {
|
|
16
|
+
const hash = hashTypes[hashType]
|
|
17
|
+
|
|
18
|
+
const key = await webcrypto.get().subtle.importKey(
|
|
19
|
+
'raw',
|
|
20
|
+
secret,
|
|
21
|
+
{
|
|
22
|
+
name: 'HMAC',
|
|
23
|
+
hash: { name: hash }
|
|
24
|
+
},
|
|
25
|
+
false,
|
|
26
|
+
['sign']
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
async digest (data: Uint8Array) { // eslint-disable-line require-await
|
|
31
|
+
return await sign(key, data)
|
|
32
|
+
},
|
|
33
|
+
length: lengths[hashType]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import lengths from './lengths.js'
|
|
3
|
+
|
|
4
|
+
export async function create (hash: 'SHA1' | 'SHA256' | 'SHA512', secret: Uint8Array) { // eslint-disable-line require-await
|
|
5
|
+
const res = {
|
|
6
|
+
async digest (data: Uint8Array) { // eslint-disable-line require-await
|
|
7
|
+
const hmac = crypto.createHmac(hash.toLowerCase(), secret)
|
|
8
|
+
hmac.update(data)
|
|
9
|
+
return hmac.digest()
|
|
10
|
+
},
|
|
11
|
+
length: lengths[hash]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return res
|
|
15
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as hmac from './hmac/index.js'
|
|
2
|
+
import * as aes from './aes/index.js'
|
|
3
|
+
import * as keys from './keys/index.js'
|
|
4
|
+
import randomBytes from './random-bytes.js'
|
|
5
|
+
import pbkdf2 from './pbkdf2.js'
|
|
6
|
+
|
|
7
|
+
export { aes }
|
|
8
|
+
export { hmac }
|
|
9
|
+
export { keys }
|
|
10
|
+
export { randomBytes }
|
|
11
|
+
export { pbkdf2 }
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import errcode from 'err-code'
|
|
2
|
+
import webcrypto from '../webcrypto.js'
|
|
3
|
+
import { base64urlToBuffer } from '../util.js'
|
|
4
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
5
|
+
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
|
|
6
|
+
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
|
7
|
+
import type { ECDHKey, ECDHKeyPair } from './interface.js'
|
|
8
|
+
|
|
9
|
+
const bits = {
|
|
10
|
+
'P-256': 256,
|
|
11
|
+
'P-384': 384,
|
|
12
|
+
'P-521': 521
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const curveTypes = Object.keys(bits)
|
|
16
|
+
const names = curveTypes.join(' / ')
|
|
17
|
+
|
|
18
|
+
export async function generateEphmeralKeyPair (curve: string) {
|
|
19
|
+
if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') {
|
|
20
|
+
throw errcode(new Error(`Unknown curve: ${curve}. Must be ${names}`), 'ERR_INVALID_CURVE')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const pair = await webcrypto.get().subtle.generateKey(
|
|
24
|
+
{
|
|
25
|
+
name: 'ECDH',
|
|
26
|
+
namedCurve: curve
|
|
27
|
+
},
|
|
28
|
+
true,
|
|
29
|
+
['deriveBits']
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// forcePrivate is used for testing only
|
|
33
|
+
const genSharedKey = async (theirPub: Uint8Array, forcePrivate?: ECDHKeyPair) => {
|
|
34
|
+
let privateKey
|
|
35
|
+
|
|
36
|
+
if (forcePrivate != null) {
|
|
37
|
+
privateKey = await webcrypto.get().subtle.importKey(
|
|
38
|
+
'jwk',
|
|
39
|
+
unmarshalPrivateKey(curve, forcePrivate),
|
|
40
|
+
{
|
|
41
|
+
name: 'ECDH',
|
|
42
|
+
namedCurve: curve
|
|
43
|
+
},
|
|
44
|
+
false,
|
|
45
|
+
['deriveBits']
|
|
46
|
+
)
|
|
47
|
+
} else {
|
|
48
|
+
privateKey = pair.privateKey
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const key = await webcrypto.get().subtle.importKey(
|
|
52
|
+
'jwk',
|
|
53
|
+
unmarshalPublicKey(curve, theirPub),
|
|
54
|
+
{
|
|
55
|
+
name: 'ECDH',
|
|
56
|
+
namedCurve: curve
|
|
57
|
+
},
|
|
58
|
+
false,
|
|
59
|
+
[]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const buffer = await webcrypto.get().subtle.deriveBits(
|
|
63
|
+
{
|
|
64
|
+
name: 'ECDH',
|
|
65
|
+
// @ts-expect-error namedCurve is missing from the types
|
|
66
|
+
namedCurve: curve,
|
|
67
|
+
public: key
|
|
68
|
+
},
|
|
69
|
+
privateKey,
|
|
70
|
+
bits[curve]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return new Uint8Array(buffer, 0, buffer.byteLength)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// @ts-expect-error jwk is missing from the types
|
|
77
|
+
const publicKey = await webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
|
|
78
|
+
|
|
79
|
+
const ecdhKey: ECDHKey = {
|
|
80
|
+
key: marshalPublicKey(publicKey),
|
|
81
|
+
genSharedKey
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return ecdhKey
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const curveLengths = {
|
|
88
|
+
'P-256': 32,
|
|
89
|
+
'P-384': 48,
|
|
90
|
+
'P-521': 66
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Marshal converts a jwk encoded ECDH public key into the
|
|
94
|
+
// form specified in section 4.3.6 of ANSI X9.62. (This is the format
|
|
95
|
+
// go-ipfs uses)
|
|
96
|
+
function marshalPublicKey (jwk: JsonWebKey) {
|
|
97
|
+
if (jwk.crv == null || jwk.x == null || jwk.y == null) {
|
|
98
|
+
throw errcode(new Error('JWK was missing components'), 'ERR_INVALID_PARAMETERS')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (jwk.crv !== 'P-256' && jwk.crv !== 'P-384' && jwk.crv !== 'P-521') {
|
|
102
|
+
throw errcode(new Error(`Unknown curve: ${jwk.crv}. Must be ${names}`), 'ERR_INVALID_CURVE')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const byteLen = curveLengths[jwk.crv]
|
|
106
|
+
|
|
107
|
+
return uint8ArrayConcat([
|
|
108
|
+
Uint8Array.from([4]), // uncompressed point
|
|
109
|
+
base64urlToBuffer(jwk.x, byteLen),
|
|
110
|
+
base64urlToBuffer(jwk.y, byteLen)
|
|
111
|
+
], 1 + byteLen * 2)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Unmarshal converts a point, serialized by Marshal, into an jwk encoded key
|
|
115
|
+
function unmarshalPublicKey (curve: string, key: Uint8Array) {
|
|
116
|
+
if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') {
|
|
117
|
+
throw errcode(new Error(`Unknown curve: ${curve}. Must be ${names}`), 'ERR_INVALID_CURVE')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const byteLen = curveLengths[curve]
|
|
121
|
+
|
|
122
|
+
if (!uint8ArrayEquals(key.slice(0, 1), Uint8Array.from([4]))) {
|
|
123
|
+
throw errcode(new Error('Cannot unmarshal public key - invalid key format'), 'ERR_INVALID_KEY_FORMAT')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
kty: 'EC',
|
|
128
|
+
crv: curve,
|
|
129
|
+
x: uint8ArrayToString(key.slice(1, byteLen + 1), 'base64url'),
|
|
130
|
+
y: uint8ArrayToString(key.slice(1 + byteLen), 'base64url'),
|
|
131
|
+
ext: true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const unmarshalPrivateKey = (curve: string, key: ECDHKeyPair) => ({
|
|
136
|
+
...unmarshalPublicKey(curve, key.public),
|
|
137
|
+
d: uint8ArrayToString(key.private, 'base64url')
|
|
138
|
+
})
|
package/src/keys/ecdh.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import errcode from 'err-code'
|
|
3
|
+
import type { ECDHKey, ECDHKeyPair } from './interface.js'
|
|
4
|
+
|
|
5
|
+
const curves = {
|
|
6
|
+
'P-256': 'prime256v1',
|
|
7
|
+
'P-384': 'secp384r1',
|
|
8
|
+
'P-521': 'secp521r1'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const curveTypes = Object.keys(curves)
|
|
12
|
+
const names = curveTypes.join(' / ')
|
|
13
|
+
|
|
14
|
+
export async function generateEphmeralKeyPair (curve: string): Promise<ECDHKey> { // eslint-disable-line require-await
|
|
15
|
+
if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') {
|
|
16
|
+
throw errcode(new Error(`Unknown curve: ${curve}. Must be ${names}`), 'ERR_INVALID_CURVE')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ecdh = crypto.createECDH(curves[curve])
|
|
20
|
+
ecdh.generateKeys()
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
key: ecdh.getPublicKey() as Uint8Array,
|
|
24
|
+
|
|
25
|
+
async genSharedKey (theirPub: Uint8Array, forcePrivate?: ECDHKeyPair): Promise<Uint8Array> { // eslint-disable-line require-await
|
|
26
|
+
if (forcePrivate != null) {
|
|
27
|
+
ecdh.setPrivateKey(forcePrivate.private)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return ecdh.computeSecret(theirPub)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|