@enbox/crypto 0.0.2 → 0.0.4
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/README.md +34 -102
- package/dist/browser.mjs +6 -10
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/algorithms/aes-ctr.js +1 -1
- package/dist/esm/algorithms/aes-gcm.js +35 -2
- package/dist/esm/algorithms/aes-gcm.js.map +1 -1
- package/dist/esm/algorithms/aes-kw.js +154 -0
- package/dist/esm/algorithms/aes-kw.js.map +1 -0
- package/dist/esm/algorithms/ecdsa.js +119 -6
- package/dist/esm/algorithms/ecdsa.js.map +1 -1
- package/dist/esm/algorithms/eddsa.js +99 -6
- package/dist/esm/algorithms/eddsa.js.map +1 -1
- package/dist/esm/algorithms/hkdf.js +53 -0
- package/dist/esm/algorithms/hkdf.js.map +1 -0
- package/dist/esm/algorithms/pbkdf2.js +55 -0
- package/dist/esm/algorithms/pbkdf2.js.map +1 -0
- package/dist/esm/algorithms/sha-2.js +2 -2
- package/dist/esm/algorithms/sha-2.js.map +1 -1
- package/dist/esm/algorithms/x25519.js +125 -0
- package/dist/esm/algorithms/x25519.js.map +1 -0
- package/dist/esm/crypto-error.js +41 -0
- package/dist/esm/crypto-error.js.map +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/jose/jwk.js +52 -26
- package/dist/esm/jose/jwk.js.map +1 -1
- package/dist/esm/local-key-manager.js +9 -3
- package/dist/esm/local-key-manager.js.map +1 -1
- package/dist/esm/primitives/aes-ctr.js.map +1 -1
- package/dist/esm/primitives/aes-gcm.js.map +1 -1
- package/dist/esm/primitives/aes-kw.js +246 -0
- package/dist/esm/primitives/aes-kw.js.map +1 -0
- package/dist/esm/primitives/concat-kdf.js +1 -1
- package/dist/esm/primitives/concat-kdf.js.map +1 -1
- package/dist/esm/primitives/ecies-secp256k1.js +79 -0
- package/dist/esm/primitives/ecies-secp256k1.js.map +1 -0
- package/dist/esm/primitives/ed25519.js +3 -3
- package/dist/esm/primitives/ed25519.js.map +1 -1
- package/dist/esm/primitives/hkdf.js +79 -0
- package/dist/esm/primitives/hkdf.js.map +1 -0
- package/dist/esm/primitives/pbkdf2.js +49 -0
- package/dist/esm/primitives/pbkdf2.js.map +1 -1
- package/dist/esm/primitives/secp256k1.js +4 -4
- package/dist/esm/primitives/secp256k1.js.map +1 -1
- package/dist/esm/primitives/secp256r1.js +4 -4
- package/dist/esm/primitives/secp256r1.js.map +1 -1
- package/dist/esm/primitives/x25519.js +10 -17
- package/dist/esm/primitives/x25519.js.map +1 -1
- package/dist/esm/primitives/xchacha20-poly1305.js +48 -3
- package/dist/esm/primitives/xchacha20-poly1305.js.map +1 -1
- package/dist/esm/primitives/xchacha20.js +1 -1
- package/dist/esm/primitives/xchacha20.js.map +1 -1
- package/dist/esm/utils.js +30 -0
- package/dist/esm/utils.js.map +1 -1
- package/dist/types/algorithms/aes-ctr.d.ts +2 -2
- package/dist/types/algorithms/aes-ctr.d.ts.map +1 -1
- package/dist/types/algorithms/aes-gcm.d.ts +25 -5
- package/dist/types/algorithms/aes-gcm.d.ts.map +1 -1
- package/dist/types/algorithms/aes-kw.d.ts +129 -0
- package/dist/types/algorithms/aes-kw.d.ts.map +1 -0
- package/dist/types/algorithms/ecdsa.d.ts +49 -4
- package/dist/types/algorithms/ecdsa.d.ts.map +1 -1
- package/dist/types/algorithms/eddsa.d.ts +49 -4
- package/dist/types/algorithms/eddsa.d.ts.map +1 -1
- package/dist/types/algorithms/hkdf.d.ts +35 -0
- package/dist/types/algorithms/hkdf.d.ts.map +1 -0
- package/dist/types/algorithms/pbkdf2.d.ts +35 -0
- package/dist/types/algorithms/pbkdf2.d.ts.map +1 -0
- package/dist/types/algorithms/sha-2.d.ts +2 -2
- package/dist/types/algorithms/sha-2.d.ts.map +1 -1
- package/dist/types/algorithms/x25519.d.ts +76 -0
- package/dist/types/algorithms/x25519.d.ts.map +1 -0
- package/dist/types/crypto-error.d.ts +29 -0
- package/dist/types/crypto-error.d.ts.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/jose/jwk.d.ts.map +1 -1
- package/dist/types/local-key-manager.d.ts +6 -6
- package/dist/types/local-key-manager.d.ts.map +1 -1
- package/dist/types/primitives/aes-kw.d.ts +103 -0
- package/dist/types/primitives/aes-kw.d.ts.map +1 -0
- package/dist/types/primitives/concat-kdf.d.ts +1 -1
- package/dist/types/primitives/concat-kdf.d.ts.map +1 -1
- package/dist/types/primitives/ecies-secp256k1.d.ts +53 -0
- package/dist/types/primitives/ecies-secp256k1.d.ts.map +1 -0
- package/dist/types/primitives/hkdf.d.ts +90 -0
- package/dist/types/primitives/hkdf.d.ts.map +1 -0
- package/dist/types/primitives/pbkdf2.d.ts +58 -0
- package/dist/types/primitives/pbkdf2.d.ts.map +1 -1
- package/dist/types/primitives/x25519.d.ts +9 -16
- package/dist/types/primitives/x25519.d.ts.map +1 -1
- package/dist/types/primitives/xchacha20-poly1305.d.ts +47 -0
- package/dist/types/primitives/xchacha20-poly1305.d.ts.map +1 -1
- package/dist/types/types/cipher.d.ts +1 -1
- package/dist/types/types/crypto-api.d.ts +54 -6
- package/dist/types/types/crypto-api.d.ts.map +1 -1
- package/dist/types/types/key-converter.d.ts +37 -15
- package/dist/types/types/key-converter.d.ts.map +1 -1
- package/dist/types/types/key-deriver.d.ts +41 -0
- package/dist/types/types/key-deriver.d.ts.map +1 -1
- package/dist/types/types/key-io.d.ts +37 -0
- package/dist/types/types/key-io.d.ts.map +1 -1
- package/dist/types/types/params-direct.d.ts +96 -1
- package/dist/types/types/params-direct.d.ts.map +1 -1
- package/dist/types/types/params-kms.d.ts +55 -0
- package/dist/types/types/params-kms.d.ts.map +1 -1
- package/dist/types/utils.d.ts +19 -0
- package/dist/types/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +4 -4
- package/package.json +29 -45
- package/src/algorithms/aes-ctr.ts +2 -2
- package/src/algorithms/aes-gcm.ts +41 -4
- package/src/algorithms/aes-kw.ts +182 -0
- package/src/algorithms/ecdsa.ts +145 -8
- package/src/algorithms/eddsa.ts +117 -10
- package/src/algorithms/hkdf.ts +54 -0
- package/src/algorithms/pbkdf2.ts +57 -0
- package/src/algorithms/sha-2.ts +3 -3
- package/src/algorithms/x25519.ts +153 -0
- package/src/crypto-error.ts +45 -0
- package/src/index.ts +8 -0
- package/src/jose/jwk.ts +32 -32
- package/src/local-key-manager.ts +22 -16
- package/src/primitives/aes-ctr.ts +1 -1
- package/src/primitives/aes-gcm.ts +5 -5
- package/src/primitives/aes-kw.ts +269 -0
- package/src/primitives/concat-kdf.ts +4 -2
- package/src/primitives/ecies-secp256k1.ts +113 -0
- package/src/primitives/ed25519.ts +6 -6
- package/src/primitives/hkdf.ts +121 -0
- package/src/primitives/pbkdf2.ts +91 -0
- package/src/primitives/secp256k1.ts +6 -6
- package/src/primitives/secp256r1.ts +6 -6
- package/src/primitives/x25519.ts +12 -19
- package/src/primitives/xchacha20-poly1305.ts +57 -4
- package/src/primitives/xchacha20.ts +1 -1
- package/src/types/cipher.ts +1 -1
- package/src/types/crypto-api.ts +129 -11
- package/src/types/key-converter.ts +33 -7
- package/src/types/key-deriver.ts +49 -0
- package/src/types/key-io.ts +40 -0
- package/src/types/params-direct.ts +118 -1
- package/src/types/params-kms.ts +67 -0
- package/src/utils.ts +55 -2
- package/dist/browser.js +0 -64
- package/dist/browser.js.map +0 -7
- package/dist/cjs/algorithms/aes-ctr.js +0 -188
- package/dist/cjs/algorithms/aes-ctr.js.map +0 -1
- package/dist/cjs/algorithms/aes-gcm.js +0 -196
- package/dist/cjs/algorithms/aes-gcm.js.map +0 -1
- package/dist/cjs/algorithms/crypto-algorithm.js +0 -13
- package/dist/cjs/algorithms/crypto-algorithm.js.map +0 -1
- package/dist/cjs/algorithms/ecdsa.js +0 -352
- package/dist/cjs/algorithms/ecdsa.js.map +0 -1
- package/dist/cjs/algorithms/eddsa.js +0 -325
- package/dist/cjs/algorithms/eddsa.js.map +0 -1
- package/dist/cjs/algorithms/sha-2.js +0 -119
- package/dist/cjs/algorithms/sha-2.js.map +0 -1
- package/dist/cjs/index.js +0 -41
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/jose/jwe.js +0 -3
- package/dist/cjs/jose/jwe.js.map +0 -1
- package/dist/cjs/jose/jwk.js +0 -278
- package/dist/cjs/jose/jwk.js.map +0 -1
- package/dist/cjs/jose/jws.js +0 -3
- package/dist/cjs/jose/jws.js.map +0 -1
- package/dist/cjs/jose/jwt.js +0 -3
- package/dist/cjs/jose/jwt.js.map +0 -1
- package/dist/cjs/jose/utils.js +0 -60
- package/dist/cjs/jose/utils.js.map +0 -1
- package/dist/cjs/local-key-manager.js +0 -521
- package/dist/cjs/local-key-manager.js.map +0 -1
- package/dist/cjs/package.json +0 -1
- package/dist/cjs/primitives/aes-ctr.js +0 -398
- package/dist/cjs/primitives/aes-ctr.js.map +0 -1
- package/dist/cjs/primitives/aes-gcm.js +0 -425
- package/dist/cjs/primitives/aes-gcm.js.map +0 -1
- package/dist/cjs/primitives/concat-kdf.js +0 -215
- package/dist/cjs/primitives/concat-kdf.js.map +0 -1
- package/dist/cjs/primitives/ed25519.js +0 -651
- package/dist/cjs/primitives/ed25519.js.map +0 -1
- package/dist/cjs/primitives/pbkdf2.js +0 -120
- package/dist/cjs/primitives/pbkdf2.js.map +0 -1
- package/dist/cjs/primitives/secp256k1.js +0 -958
- package/dist/cjs/primitives/secp256k1.js.map +0 -1
- package/dist/cjs/primitives/secp256r1.js +0 -959
- package/dist/cjs/primitives/secp256r1.js.map +0 -1
- package/dist/cjs/primitives/sha256.js +0 -93
- package/dist/cjs/primitives/sha256.js.map +0 -1
- package/dist/cjs/primitives/x25519.js +0 -498
- package/dist/cjs/primitives/x25519.js.map +0 -1
- package/dist/cjs/primitives/xchacha20-poly1305.js +0 -340
- package/dist/cjs/primitives/xchacha20-poly1305.js.map +0 -1
- package/dist/cjs/primitives/xchacha20.js +0 -316
- package/dist/cjs/primitives/xchacha20.js.map +0 -1
- package/dist/cjs/types/cipher.js +0 -3
- package/dist/cjs/types/cipher.js.map +0 -1
- package/dist/cjs/types/crypto-api.js +0 -3
- package/dist/cjs/types/crypto-api.js.map +0 -1
- package/dist/cjs/types/hasher.js +0 -3
- package/dist/cjs/types/hasher.js.map +0 -1
- package/dist/cjs/types/identifier.js +0 -3
- package/dist/cjs/types/identifier.js.map +0 -1
- package/dist/cjs/types/key-compressor.js +0 -3
- package/dist/cjs/types/key-compressor.js.map +0 -1
- package/dist/cjs/types/key-converter.js +0 -3
- package/dist/cjs/types/key-converter.js.map +0 -1
- package/dist/cjs/types/key-deriver.js +0 -3
- package/dist/cjs/types/key-deriver.js.map +0 -1
- package/dist/cjs/types/key-generator.js +0 -3
- package/dist/cjs/types/key-generator.js.map +0 -1
- package/dist/cjs/types/key-io.js +0 -3
- package/dist/cjs/types/key-io.js.map +0 -1
- package/dist/cjs/types/key-wrapper.js +0 -3
- package/dist/cjs/types/key-wrapper.js.map +0 -1
- package/dist/cjs/types/params-direct.js +0 -3
- package/dist/cjs/types/params-direct.js.map +0 -1
- package/dist/cjs/types/params-enclosed.js +0 -3
- package/dist/cjs/types/params-enclosed.js.map +0 -1
- package/dist/cjs/types/params-kms.js +0 -3
- package/dist/cjs/types/params-kms.js.map +0 -1
- package/dist/cjs/types/signer.js +0 -3
- package/dist/cjs/types/signer.js.map +0 -1
- package/dist/cjs/utils.js +0 -173
- package/dist/cjs/utils.js.map +0 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { Jwk } from '../jose/jwk.js';
|
|
2
|
+
import type { UnwrapKeyParams, WrapKeyParams } from '../types/params-direct.js';
|
|
3
|
+
|
|
4
|
+
import { getWebcryptoSubtle } from '@noble/ciphers/webcrypto';
|
|
5
|
+
|
|
6
|
+
import { Convert } from '@enbox/common';
|
|
7
|
+
import { computeJwkThumbprint, isOctPrivateJwk } from '../jose/jwk.js';
|
|
8
|
+
import { CryptoError, CryptoErrorCode } from '../crypto-error.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Constant defining the AES key length values in bits.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* NIST publication FIPS 197 states:
|
|
15
|
+
* > The AES algorithm is capable of using cryptographic keys of 128, 192, and 256 bits to encrypt
|
|
16
|
+
* > and decrypt data in blocks of 128 bits.
|
|
17
|
+
*
|
|
18
|
+
* This implementation does not support key lengths that are different from the three values
|
|
19
|
+
* defined by this constant.
|
|
20
|
+
*
|
|
21
|
+
* @see {@link https://doi.org/10.6028/NIST.FIPS.197-upd1 | NIST FIPS 197}
|
|
22
|
+
*/
|
|
23
|
+
const AES_KEY_LENGTHS = [128, 192, 256] as const;
|
|
24
|
+
|
|
25
|
+
export class AesKw {
|
|
26
|
+
/**
|
|
27
|
+
* Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* This method takes a symmetric key represented as a byte array (Uint8Array) and
|
|
31
|
+
* converts it into a JWK object for use with AES (Advanced Encryption Standard)
|
|
32
|
+
* for key wrapping. The conversion process involves encoding the key into
|
|
33
|
+
* base64url format and setting the appropriate JWK parameters.
|
|
34
|
+
*
|
|
35
|
+
* The resulting JWK object includes the following properties:
|
|
36
|
+
* - `kty`: Key Type, set to 'oct' for Octet Sequence (representing a symmetric key).
|
|
37
|
+
* - `k`: The symmetric key, base64url-encoded.
|
|
38
|
+
* - `kid`: Key ID, generated based on the JWK thumbprint.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const privateKeyBytes = new Uint8Array([...]); // Replace with actual symmetric key bytes
|
|
43
|
+
* const privateKey = await AesKw.bytesToPrivateKey({ privateKeyBytes });
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @param params - The parameters for the symmetric key conversion.
|
|
47
|
+
* @param params.privateKeyBytes - The raw symmetric key as a Uint8Array.
|
|
48
|
+
*
|
|
49
|
+
* @returns A Promise that resolves to the symmetric key in JWK format.
|
|
50
|
+
*/
|
|
51
|
+
public static async bytesToPrivateKey({ privateKeyBytes }: {
|
|
52
|
+
privateKeyBytes: Uint8Array;
|
|
53
|
+
}): Promise<Jwk> {
|
|
54
|
+
// Construct the private key in JWK format.
|
|
55
|
+
const privateKey: Jwk = {
|
|
56
|
+
k : Convert.uint8Array(privateKeyBytes).toBase64Url(),
|
|
57
|
+
kty : 'oct'
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Compute the JWK thumbprint and set as the key ID.
|
|
61
|
+
privateKey.kid = await computeJwkThumbprint({ jwk: privateKey });
|
|
62
|
+
|
|
63
|
+
// Add algorithm identifier based on key length.
|
|
64
|
+
const lengthInBits = privateKeyBytes.length * 8;
|
|
65
|
+
privateKey.alg = { 128: 'A128KW', 192: 'A192KW', 256: 'A256KW' }[lengthInBits];
|
|
66
|
+
|
|
67
|
+
return privateKey;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generates a symmetric key for AES for key wrapping in JSON Web Key (JWK) format.
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* This method creates a new symmetric key of a specified length suitable for use with
|
|
75
|
+
* AES key wrapping. It uses cryptographically secure random number generation to
|
|
76
|
+
* ensure the uniqueness and security of the key. The generated key adheres to the JWK
|
|
77
|
+
* format, making it compatible with common cryptographic standards and easy to use in
|
|
78
|
+
* various cryptographic processes.
|
|
79
|
+
*
|
|
80
|
+
* The generated key includes the following components:
|
|
81
|
+
* - `kty`: Key Type, set to 'oct' for Octet Sequence.
|
|
82
|
+
* - `k`: The symmetric key component, base64url-encoded.
|
|
83
|
+
* - `kid`: Key ID, generated based on the JWK thumbprint.
|
|
84
|
+
* - `alg`: Algorithm, set to 'A128KW', 'A192KW', or 'A256KW' for AES Key Wrap with the
|
|
85
|
+
* specified key length.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const length = 256; // Length of the key in bits (e.g., 128, 192, 256)
|
|
90
|
+
* const privateKey = await AesKw.generateKey({ length });
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @param params - The parameters for the key generation.
|
|
94
|
+
* @param params.length - The length of the key in bits. Common lengths are 128, 192, and 256 bits.
|
|
95
|
+
*
|
|
96
|
+
* @returns A Promise that resolves to the generated symmetric key in JWK format.
|
|
97
|
+
*/
|
|
98
|
+
public static async generateKey({ length }: {
|
|
99
|
+
length: typeof AES_KEY_LENGTHS[number];
|
|
100
|
+
}): Promise<Jwk> {
|
|
101
|
+
// Validate the key length.
|
|
102
|
+
if (!(AES_KEY_LENGTHS as readonly number[]).includes(length)) {
|
|
103
|
+
throw new RangeError(`The key length is invalid: Must be ${AES_KEY_LENGTHS.join(', ')} bits`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Get the Web Crypto API interface.
|
|
107
|
+
const webCrypto = getWebcryptoSubtle() as SubtleCrypto;
|
|
108
|
+
|
|
109
|
+
// Generate a random private key.
|
|
110
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#usage_notes for
|
|
111
|
+
// an explanation for why Web Crypto generateKey() is used instead of getRandomValues().
|
|
112
|
+
const webCryptoKey = await webCrypto.generateKey( { name: 'AES-KW', length }, true, ['wrapKey', 'unwrapKey']);
|
|
113
|
+
|
|
114
|
+
// Export the private key in JWK format.
|
|
115
|
+
const { ext, key_ops, ...privateKey } = await webCrypto.exportKey('jwk', webCryptoKey) as Jwk;
|
|
116
|
+
|
|
117
|
+
// Compute the JWK thumbprint and set as the key ID.
|
|
118
|
+
privateKey.kid = await computeJwkThumbprint({ jwk: privateKey });
|
|
119
|
+
|
|
120
|
+
return privateKey;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array).
|
|
125
|
+
*
|
|
126
|
+
* @remarks
|
|
127
|
+
* This method takes a symmetric key in JWK format and extracts its raw byte representation.
|
|
128
|
+
* It decodes the 'k' parameter of the JWK value, which represents the symmetric key in base64url
|
|
129
|
+
* encoding, into a byte array.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* const privateKey = { ... }; // A symmetric key in JWK format
|
|
134
|
+
* const privateKeyBytes = await AesKw.privateKeyToBytes({ privateKey });
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @param params - The parameters for the symmetric key conversion.
|
|
138
|
+
* @param params.privateKey - The symmetric key in JWK format.
|
|
139
|
+
*
|
|
140
|
+
* @returns A Promise that resolves to the symmetric key as a Uint8Array.
|
|
141
|
+
*/
|
|
142
|
+
public static async privateKeyToBytes({ privateKey }: {
|
|
143
|
+
privateKey: Jwk;
|
|
144
|
+
}): Promise<Uint8Array> {
|
|
145
|
+
// Verify the provided JWK represents a valid oct private key.
|
|
146
|
+
if (!isOctPrivateJwk(privateKey)) {
|
|
147
|
+
throw new Error(`AesKw: The provided key is not a valid oct private key.`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Decode the provided private key to bytes.
|
|
151
|
+
const privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array();
|
|
152
|
+
|
|
153
|
+
return privateKeyBytes;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public static async unwrapKey({ wrappedKeyBytes, wrappedKeyAlgorithm, decryptionKey }:
|
|
157
|
+
UnwrapKeyParams
|
|
158
|
+
): Promise<Jwk> {
|
|
159
|
+
if (!('alg' in decryptionKey && decryptionKey.alg)) {
|
|
160
|
+
throw new CryptoError(CryptoErrorCode.InvalidJwk, `The decryption key is missing the 'alg' property.`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!['A128KW', 'A192KW', 'A256KW'].includes(decryptionKey.alg)) {
|
|
164
|
+
throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `The 'decryptionKey' algorithm is not supported: ${decryptionKey.alg}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Get the Web Crypto API interface.
|
|
168
|
+
const webCrypto = getWebcryptoSubtle() as SubtleCrypto;
|
|
169
|
+
|
|
170
|
+
// Import the decryption key for use with the Web Crypto API.
|
|
171
|
+
const decryptionCryptoKey = await webCrypto.importKey(
|
|
172
|
+
'jwk', // key format
|
|
173
|
+
decryptionKey as JsonWebKey, // key data
|
|
174
|
+
{ name: 'AES-KW' }, // algorithm identifier
|
|
175
|
+
true, // key is extractable
|
|
176
|
+
['unwrapKey'] // key usages
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Map the private key's JOSE algorithm name to the Web Crypto API algorithm identifier.
|
|
180
|
+
const webCryptoAlgorithm = {
|
|
181
|
+
A128KW : 'AES-KW', A192KW : 'AES-KW', A256KW : 'AES-KW',
|
|
182
|
+
A128GCM : 'AES-GCM', A192GCM : 'AES-GCM', A256GCM : 'AES-GCM',
|
|
183
|
+
}[wrappedKeyAlgorithm];
|
|
184
|
+
|
|
185
|
+
if (!webCryptoAlgorithm) {
|
|
186
|
+
throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `The 'wrappedKeyAlgorithm' is not supported: ${wrappedKeyAlgorithm}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Unwrap the key using the Web Crypto API.
|
|
190
|
+
const unwrappedCryptoKey = await webCrypto.unwrapKey(
|
|
191
|
+
'raw', // output format
|
|
192
|
+
wrappedKeyBytes.buffer, // key to unwrap
|
|
193
|
+
decryptionCryptoKey, // unwrapping key
|
|
194
|
+
'AES-KW', // algorithm identifier
|
|
195
|
+
{ name: webCryptoAlgorithm }, // unwrapped key algorithm identifier
|
|
196
|
+
true, // key is extractable
|
|
197
|
+
['unwrapKey'] // key usages
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// Export the unwrapped key in JWK format.
|
|
201
|
+
const { ext, key_ops, ...unwrappedJsonWebKey } = await webCrypto.exportKey('jwk', unwrappedCryptoKey);
|
|
202
|
+
const unwrappedKey = unwrappedJsonWebKey as Jwk;
|
|
203
|
+
|
|
204
|
+
// Compute the JWK thumbprint and set as the key ID.
|
|
205
|
+
unwrappedKey.kid = await computeJwkThumbprint({ jwk: unwrappedKey });
|
|
206
|
+
|
|
207
|
+
return unwrappedKey;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public static async wrapKey({ unwrappedKey, encryptionKey }:
|
|
211
|
+
WrapKeyParams
|
|
212
|
+
): Promise<Uint8Array> {
|
|
213
|
+
if (!('alg' in encryptionKey && encryptionKey.alg)) {
|
|
214
|
+
throw new CryptoError(CryptoErrorCode.InvalidJwk, `The encryption key is missing the 'alg' property.`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!['A128KW', 'A192KW', 'A256KW'].includes(encryptionKey.alg)) {
|
|
218
|
+
throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `The 'encryptionKey' algorithm is not supported: ${encryptionKey.alg}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!('alg' in unwrappedKey && unwrappedKey.alg)) {
|
|
222
|
+
throw new CryptoError(CryptoErrorCode.InvalidJwk, `The private key to wrap is missing the 'alg' property.`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Get the Web Crypto API interface.
|
|
226
|
+
const webCrypto = getWebcryptoSubtle() as SubtleCrypto;
|
|
227
|
+
|
|
228
|
+
// Import the encryption key for use with the Web Crypto API.
|
|
229
|
+
const encryptionCryptoKey = await webCrypto.importKey(
|
|
230
|
+
'jwk', // key format
|
|
231
|
+
encryptionKey as JsonWebKey, // key data
|
|
232
|
+
{ name: 'AES-KW' }, // algorithm identifier
|
|
233
|
+
true, // key is extractable
|
|
234
|
+
['wrapKey'] // key usages
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Map the private key's JOSE algorithm name to the Web Crypto API algorithm identifier.
|
|
238
|
+
const webCryptoAlgorithm = {
|
|
239
|
+
A128KW : 'AES-KW', A192KW : 'AES-KW', A256KW : 'AES-KW',
|
|
240
|
+
A128GCM : 'AES-GCM', A192GCM : 'AES-GCM', A256GCM : 'AES-GCM',
|
|
241
|
+
}[unwrappedKey.alg];
|
|
242
|
+
|
|
243
|
+
if (!webCryptoAlgorithm) {
|
|
244
|
+
throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `The 'unwrappedKey' algorithm is not supported: ${unwrappedKey.alg}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Import the private key to wrap for use with the Web Crypto API.
|
|
248
|
+
const unwrappedCryptoKey = await webCrypto.importKey(
|
|
249
|
+
'jwk', // key format
|
|
250
|
+
unwrappedKey as JsonWebKey, // key data
|
|
251
|
+
{ name: webCryptoAlgorithm }, // algorithm identifier
|
|
252
|
+
true, // key is extractable
|
|
253
|
+
['unwrapKey'] // key usages
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Wrap the key using the Web Crypto API.
|
|
257
|
+
const wrappedKeyBuffer = await webCrypto.wrapKey(
|
|
258
|
+
'raw', // output format
|
|
259
|
+
unwrappedCryptoKey, // key to wrap
|
|
260
|
+
encryptionCryptoKey, // wrapping key
|
|
261
|
+
'AES-KW' // algorithm identifier
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Convert from ArrayBuffer to Uint8Array.
|
|
265
|
+
const wrappedKeyBytes = new Uint8Array(wrappedKeyBuffer);
|
|
266
|
+
|
|
267
|
+
return wrappedKeyBytes;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { TypedArray } from '@noble/hashes/utils';
|
|
2
|
+
|
|
3
|
+
import { concatBytes } from '@noble/hashes/utils';
|
|
1
4
|
import { sha256 } from '@noble/hashes/sha256';
|
|
2
5
|
import { Convert, universalTypeOf } from '@enbox/common';
|
|
3
|
-
import { TypedArray, concatBytes } from '@noble/hashes/utils';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* ConcatKDF FixedInfo Parameters.
|
|
@@ -45,7 +47,7 @@ export type ConcatKdfFixedInfo = {
|
|
|
45
47
|
* to the key agreement.
|
|
46
48
|
*/
|
|
47
49
|
suppPrivInfo?: string | TypedArray;
|
|
48
|
-
}
|
|
50
|
+
};
|
|
49
51
|
|
|
50
52
|
/**
|
|
51
53
|
* An implementation of the Concatenation Key Derivation Function (ConcatKDF)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { concatBytes } from '@noble/ciphers/utils';
|
|
2
|
+
import { gcm } from '@noble/ciphers/aes';
|
|
3
|
+
import { hkdf } from '@noble/hashes/hkdf';
|
|
4
|
+
import { randomBytes } from '@noble/ciphers/webcrypto';
|
|
5
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
6
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AEAD tag length for AES-256-GCM (16 bytes / 128 bits).
|
|
10
|
+
*/
|
|
11
|
+
const AEAD_TAG_LENGTH = 16;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Nonce length for AES-256-GCM encryption.
|
|
15
|
+
*/
|
|
16
|
+
const NONCE_LENGTH = 16;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Output of an ECIES-SECP256K1 encryption operation.
|
|
20
|
+
*/
|
|
21
|
+
export type EciesSecp256k1EncryptionOutput = {
|
|
22
|
+
/** The AES-GCM initialization vector. */
|
|
23
|
+
initializationVector: Uint8Array;
|
|
24
|
+
/** The ephemeral secp256k1 public key (compressed, 33 bytes). */
|
|
25
|
+
ephemeralPublicKey: Uint8Array;
|
|
26
|
+
/** The encrypted data. */
|
|
27
|
+
ciphertext: Uint8Array;
|
|
28
|
+
/** The AES-GCM authentication tag (16 bytes). */
|
|
29
|
+
messageAuthenticationCode: Uint8Array;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Input required for ECIES-SECP256K1 decryption.
|
|
34
|
+
*/
|
|
35
|
+
export type EciesSecp256k1EncryptionInput = EciesSecp256k1EncryptionOutput & {
|
|
36
|
+
/** The recipient's secp256k1 private key (raw 32 bytes). */
|
|
37
|
+
privateKey: Uint8Array;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Browser-compatible ECIES (Elliptic Curve Integrated Encryption Scheme) using secp256k1.
|
|
42
|
+
*
|
|
43
|
+
* Wire-format compatible with `eciesjs` v0.4.x configured with
|
|
44
|
+
* `isEphemeralKeyCompressed: true, isHkdfKeyCompressed: false` (the default).
|
|
45
|
+
*
|
|
46
|
+
* Protocol:
|
|
47
|
+
* 1. Generate an ephemeral secp256k1 key pair.
|
|
48
|
+
* 2. ECDH shared secret (uncompressed point).
|
|
49
|
+
* 3. HKDF-SHA-256 key derivation: `hkdf(sha256, ephemeralPubUncompressed || sharedPointUncompressed)`.
|
|
50
|
+
* 4. AES-256-GCM encryption with random 16-byte nonce.
|
|
51
|
+
*
|
|
52
|
+
* All underlying primitives (`@noble/ciphers`, `@noble/curves`, `@noble/hashes`)
|
|
53
|
+
* are pure JavaScript and work in Node, Bun, and browsers.
|
|
54
|
+
*/
|
|
55
|
+
export class EciesSecp256k1 {
|
|
56
|
+
/**
|
|
57
|
+
* Encrypt plaintext for a given secp256k1 public key.
|
|
58
|
+
* @param publicKeyBytes - Recipient's public key (compressed 33 bytes or uncompressed 65 bytes).
|
|
59
|
+
* @param plaintext - The data to encrypt.
|
|
60
|
+
*/
|
|
61
|
+
public static encrypt(publicKeyBytes: Uint8Array, plaintext: Uint8Array): EciesSecp256k1EncryptionOutput {
|
|
62
|
+
// Generate ephemeral key pair.
|
|
63
|
+
const ephemeralPrivateKey = secp256k1.utils.randomPrivateKey();
|
|
64
|
+
const ephemeralPubCompressed = secp256k1.getPublicKey(ephemeralPrivateKey, true);
|
|
65
|
+
const ephemeralPubUncompressed = secp256k1.getPublicKey(ephemeralPrivateKey, false);
|
|
66
|
+
|
|
67
|
+
// ECDH: shared point (uncompressed).
|
|
68
|
+
const sharedPointUncompressed = secp256k1.getSharedSecret(ephemeralPrivateKey, publicKeyBytes, false);
|
|
69
|
+
|
|
70
|
+
// HKDF-SHA-256: derive 32-byte symmetric key.
|
|
71
|
+
// eciesjs (isHkdfKeyCompressed=false): master = senderPubUncompressed || sharedPointUncompressed
|
|
72
|
+
const symmetricKey = hkdf(sha256, concatBytes(ephemeralPubUncompressed, sharedPointUncompressed), undefined, undefined, 32);
|
|
73
|
+
|
|
74
|
+
// AES-256-GCM encrypt.
|
|
75
|
+
const nonce = randomBytes(NONCE_LENGTH);
|
|
76
|
+
const ciphered = gcm(symmetricKey, nonce).encrypt(plaintext); // ciphertext || tag
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
ephemeralPublicKey : ephemeralPubCompressed,
|
|
80
|
+
initializationVector : nonce,
|
|
81
|
+
messageAuthenticationCode : ciphered.subarray(ciphered.length - AEAD_TAG_LENGTH),
|
|
82
|
+
ciphertext : ciphered.subarray(0, ciphered.length - AEAD_TAG_LENGTH),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Decrypt ciphertext produced by {@link EciesSecp256k1.encrypt}.
|
|
88
|
+
* @param input - The encryption output plus the recipient's private key.
|
|
89
|
+
*/
|
|
90
|
+
public static decrypt(input: EciesSecp256k1EncryptionInput): Uint8Array {
|
|
91
|
+
const { privateKey, ephemeralPublicKey, initializationVector, messageAuthenticationCode, ciphertext } = input;
|
|
92
|
+
|
|
93
|
+
// Decompress ephemeral public key (HKDF needs uncompressed form).
|
|
94
|
+
const ephemeralPubUncompressed = secp256k1.ProjectivePoint.fromHex(ephemeralPublicKey).toRawBytes(false);
|
|
95
|
+
|
|
96
|
+
// ECDH: shared point (uncompressed).
|
|
97
|
+
const sharedPointUncompressed = secp256k1.getSharedSecret(privateKey, ephemeralPublicKey, false);
|
|
98
|
+
|
|
99
|
+
// HKDF-SHA-256: derive 32-byte symmetric key (same derivation as encrypt).
|
|
100
|
+
const symmetricKey = hkdf(sha256, concatBytes(ephemeralPubUncompressed, sharedPointUncompressed), undefined, undefined, 32);
|
|
101
|
+
|
|
102
|
+
// AES-256-GCM decrypt: reconstruct the wire format (ciphertext || tag).
|
|
103
|
+
const ciphered = concatBytes(ciphertext, messageAuthenticationCode);
|
|
104
|
+
return gcm(symmetricKey, Uint8Array.from(initializationVector)).decrypt(ciphered);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Whether the ephemeral public key is compressed (always true for this implementation).
|
|
109
|
+
*/
|
|
110
|
+
public static get isEphemeralKeyCompressed(): boolean {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Convert } from '@enbox/common';
|
|
2
|
-
import { ed25519,
|
|
2
|
+
import { ed25519, edwardsToMontgomeryPriv, edwardsToMontgomeryPub, x25519 } from '@noble/curves/ed25519';
|
|
3
3
|
|
|
4
4
|
import type { Jwk } from '../jose/jwk.js';
|
|
5
5
|
import type { ComputePublicKeyParams, GetPublicKeyParams, SignParams, VerifyParams } from '../types/params-direct.js';
|
|
@@ -86,7 +86,7 @@ export class Ed25519 {
|
|
|
86
86
|
privateKeyBytes: Uint8Array;
|
|
87
87
|
}): Promise<Jwk> {
|
|
88
88
|
// Derive the public key from the private key.
|
|
89
|
-
const publicKeyBytes
|
|
89
|
+
const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes);
|
|
90
90
|
|
|
91
91
|
// Construct the private key in JWK format.
|
|
92
92
|
const privateKey: Jwk = {
|
|
@@ -167,10 +167,10 @@ export class Ed25519 {
|
|
|
167
167
|
ComputePublicKeyParams
|
|
168
168
|
): Promise<Jwk> {
|
|
169
169
|
// Convert the provided private key to a byte array.
|
|
170
|
-
const privateKeyBytes
|
|
170
|
+
const privateKeyBytes = await Ed25519.privateKeyToBytes({ privateKey: key });
|
|
171
171
|
|
|
172
172
|
// Derive the public key from the private key.
|
|
173
|
-
const publicKeyBytes
|
|
173
|
+
const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes);
|
|
174
174
|
|
|
175
175
|
// Construct the public key in JWK format.
|
|
176
176
|
const publicKey: Jwk = {
|
|
@@ -355,7 +355,7 @@ export class Ed25519 {
|
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
// Remove the private key property ('d') and make a shallow copy of the provided key.
|
|
358
|
-
|
|
358
|
+
const { d, ...publicKey } = key;
|
|
359
359
|
|
|
360
360
|
// If the key ID is undefined, set it to the JWK thumbprint.
|
|
361
361
|
publicKey.kid ??= await computeJwkThumbprint({ jwk: publicKey });
|
|
@@ -502,7 +502,7 @@ export class Ed25519 {
|
|
|
502
502
|
// Check if points are on the Twisted Edwards curve.
|
|
503
503
|
point.assertValidity();
|
|
504
504
|
|
|
505
|
-
} catch
|
|
505
|
+
} catch {
|
|
506
506
|
return false;
|
|
507
507
|
}
|
|
508
508
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { DeriveKeyBytesParams } from '../types/params-direct.js';
|
|
2
|
+
|
|
3
|
+
import { getWebcryptoSubtle } from '@noble/ciphers/webcrypto';
|
|
4
|
+
|
|
5
|
+
import { Convert } from '@enbox/common';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The object that should be passed into `Hkdf.deriveKeyBytes()`, when using the HKDF algorithm.
|
|
9
|
+
*/
|
|
10
|
+
export type HkdfParams = {
|
|
11
|
+
/**
|
|
12
|
+
* A string representing the digest algorithm to use. This may be one of:
|
|
13
|
+
* - 'SHA-256'
|
|
14
|
+
* - 'SHA-384'
|
|
15
|
+
* - 'SHA-512'
|
|
16
|
+
*/
|
|
17
|
+
hash: 'SHA-256' | 'SHA-384' | 'SHA-512';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The salt value to use in the derivation process.
|
|
21
|
+
*
|
|
22
|
+
* Ideally, the salt is a random or pseudo-random value with the same length as the output of the
|
|
23
|
+
* digest function. Unlike the input key material passed into deriveKey(), salt does not need to
|
|
24
|
+
* be kept secret.
|
|
25
|
+
*
|
|
26
|
+
* Note: The {@link https://datatracker.ietf.org/doc/html/rfc5869 | HKDF specification} states
|
|
27
|
+
* that adding salt "adds significantly to the strength of HKDF".
|
|
28
|
+
*/
|
|
29
|
+
salt: string | Uint8Array;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Optional application-specific information to use in the HKDF.
|
|
33
|
+
*
|
|
34
|
+
* If given, this value is used to bind the derived key to application-specific contextual
|
|
35
|
+
* information. This makes it possible to derive different keys for different contexts while using
|
|
36
|
+
* the same input key material.
|
|
37
|
+
*
|
|
38
|
+
* If not provided, the `info` value is set to an empty array.
|
|
39
|
+
*
|
|
40
|
+
* Note: It is important that the `info` value be independent and unrelated to the input key
|
|
41
|
+
* material.
|
|
42
|
+
*/
|
|
43
|
+
info?: string | Uint8Array,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The `Hkdf` class provides an interface for HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
|
|
48
|
+
* as defined in RFC 5869.
|
|
49
|
+
*
|
|
50
|
+
* Note: The `baseKeyBytes` that will be the input key material for HKDF should be a high-entropy secret
|
|
51
|
+
* value, such as a cryptographic key. It should be kept confidential and not be derived from a
|
|
52
|
+
* low-entropy value, such as a password.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const info = new Uint8Array([...]);
|
|
57
|
+
* const derivedKeyBytes = await Hkdf.deriveKeyBytes({
|
|
58
|
+
* baseKeyBytes: new Uint8Array([...]), // Input keying material
|
|
59
|
+
* hash: 'SHA-256', // The hash function to use ('SHA-256', 'SHA-384', 'SHA-512')
|
|
60
|
+
* salt: new Uint8Array([...]), // The salt value
|
|
61
|
+
* info: new Uint8Array([...]), // Optional application-specific information
|
|
62
|
+
* length: 256 // The length of the derived key in bits
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export class Hkdf {
|
|
67
|
+
/**
|
|
68
|
+
* Derives a key using the HMAC-based Extract-and-Expand Key Derivation Function (HKDF).
|
|
69
|
+
*
|
|
70
|
+
* This method generates a derived key using a hash function from input keying material given as
|
|
71
|
+
* `baseKeyBytes`. The length of the derived key can be specified. Optionally, it can also use a salt
|
|
72
|
+
* and info for the derivation process.
|
|
73
|
+
*
|
|
74
|
+
* HKDF is useful in various cryptographic applications and protocols, especially when
|
|
75
|
+
* there's a need to derive multiple keys from a single source of key material.
|
|
76
|
+
*
|
|
77
|
+
* Note: The `baseKeyBytes` that will be the input key material for HKDF should be a high-entropy
|
|
78
|
+
* secret value, such as a cryptographic key. It should be kept confidential and not be derived
|
|
79
|
+
* from a low-entropy value, such as a password.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* const info = new Uint8Array([...]);
|
|
84
|
+
* const derivedKeyBytes = await Hkdf.deriveKeyBytes({
|
|
85
|
+
* baseKeyBytes: new Uint8Array([...]), // Input keying material
|
|
86
|
+
* hash: 'SHA-256', // The hash function to use ('SHA-256', 'SHA-384', 'SHA-512')
|
|
87
|
+
* salt: new Uint8Array([...]), // The salt value
|
|
88
|
+
* info: new Uint8Array([...]), // Optional application-specific information
|
|
89
|
+
* length: 256 // The length of the derived key in bits
|
|
90
|
+
* });
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @param params - The parameters for key derivation.
|
|
94
|
+
* @returns A Promise that resolves to the derived key as a byte array.
|
|
95
|
+
*/
|
|
96
|
+
public static async deriveKeyBytes({ baseKeyBytes, length, hash, salt, info = new Uint8Array() }:
|
|
97
|
+
DeriveKeyBytesParams & HkdfParams
|
|
98
|
+
): Promise<Uint8Array> {
|
|
99
|
+
// Get the Web Crypto API interface.
|
|
100
|
+
const webCrypto = getWebcryptoSubtle() as SubtleCrypto;
|
|
101
|
+
|
|
102
|
+
// Import the baseKeyBytes into the Web Crypto API to use for the key derivation operation.
|
|
103
|
+
const webCryptoKey = await webCrypto.importKey('raw', baseKeyBytes, { name: 'HKDF' }, false, ['deriveBits']);
|
|
104
|
+
|
|
105
|
+
// Convert the salt and info to Uint8Array if they are provided as strings.
|
|
106
|
+
const saltBytes = typeof salt === 'string' ? Convert.string(salt).toUint8Array() : salt;
|
|
107
|
+
const infoBytes = typeof info === 'string' ? Convert.string(info).toUint8Array() : info;
|
|
108
|
+
|
|
109
|
+
// Derive the bytes using the Web Crypto API.
|
|
110
|
+
const derivedKeyBuffer = await webCrypto.deriveBits(
|
|
111
|
+
{ name: 'HKDF', hash, salt: saltBytes, info: infoBytes },
|
|
112
|
+
webCryptoKey,
|
|
113
|
+
length
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Convert from ArrayBuffer to Uint8Array.
|
|
117
|
+
const derivedKeyBytes = new Uint8Array(derivedKeyBuffer);
|
|
118
|
+
|
|
119
|
+
return derivedKeyBytes;
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/primitives/pbkdf2.ts
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
|
+
import type { DeriveKeyBytesParams } from '../types/params-direct.js';
|
|
2
|
+
|
|
3
|
+
import { getWebcryptoSubtle } from '@noble/ciphers/webcrypto';
|
|
4
|
+
|
|
1
5
|
import { crypto } from '@noble/hashes/crypto';
|
|
2
6
|
|
|
7
|
+
/**
|
|
8
|
+
* The object that should be passed into `Pbkdf2.deriveKeyBytes()`, when using the PBKDF2 algorithm.
|
|
9
|
+
*/
|
|
10
|
+
export interface Pbkdf2Params {
|
|
11
|
+
/**
|
|
12
|
+
* A string representing the digest algorithm to use. This may be one of:
|
|
13
|
+
* - 'SHA-256'
|
|
14
|
+
* - 'SHA-384'
|
|
15
|
+
* - 'SHA-512'
|
|
16
|
+
*/
|
|
17
|
+
hash: 'SHA-256' | 'SHA-384' | 'SHA-512';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The salt value to use in the derivation process, as a Uint8Array. This should be a random or
|
|
21
|
+
* pseudo-random value of at least 16 bytes. Unlike the `password`, `salt` does not need to be
|
|
22
|
+
* kept secret.
|
|
23
|
+
*/
|
|
24
|
+
salt: Uint8Array;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A `Number` representing the number of iterations the hash function will be executed in
|
|
28
|
+
* `deriveKey()`. This impacts the computational cost of the `deriveKey()` operation, making it
|
|
29
|
+
* more resistant to dictionary attacks. The higher the number, the more secure, but also slower,
|
|
30
|
+
* the operation. Choose a value that balances security needs and performance for your
|
|
31
|
+
* application.
|
|
32
|
+
*/
|
|
33
|
+
iterations: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
3
36
|
/**
|
|
4
37
|
* The object that should be passed into `Pbkdf2.deriveKey()`, when using the PBKDF2 algorithm.
|
|
5
38
|
*/
|
|
@@ -119,4 +152,62 @@ export class Pbkdf2 {
|
|
|
119
152
|
|
|
120
153
|
return derivedKey;
|
|
121
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Derives cryptographic key bytes from base key material using the PBKDF2 algorithm.
|
|
158
|
+
*
|
|
159
|
+
* @remarks
|
|
160
|
+
* This method is similar to {@link Pbkdf2.deriveKey | `deriveKey()`} but accepts
|
|
161
|
+
* raw key bytes (`baseKeyBytes`) instead of a password. It is intended for use cases
|
|
162
|
+
* where the input key material is already available as a byte array.
|
|
163
|
+
*
|
|
164
|
+
* Notes:
|
|
165
|
+
* - The `baseKeyBytes` that will be the input key material for PBKDF2 is expected to be a
|
|
166
|
+
* low-entropy value, such as a password or passphrase. It should be kept confidential.
|
|
167
|
+
* - In 2023,
|
|
168
|
+
* {@link https://web.archive.org/web/20230123232056/https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
|
|
169
|
+
* | OWASP recommended}
|
|
170
|
+
* a minimum of 600,000 iterations for PBKDF2-HMAC-SHA256 and 210,000 for PBKDF2-HMAC-SHA512.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* const derivedKeyBytes = await Pbkdf2.deriveKeyBytes({
|
|
175
|
+
* baseKeyBytes: new TextEncoder().encode('password'),
|
|
176
|
+
* hash: 'SHA-256',
|
|
177
|
+
* salt: new Uint8Array([...]),
|
|
178
|
+
* iterations: 600_000,
|
|
179
|
+
* length: 256
|
|
180
|
+
* });
|
|
181
|
+
* ```
|
|
182
|
+
*
|
|
183
|
+
* @param params - The parameters for key derivation.
|
|
184
|
+
* @returns A Promise that resolves to the derived key as a byte array.
|
|
185
|
+
*/
|
|
186
|
+
public static async deriveKeyBytes({ baseKeyBytes, hash, salt, iterations, length }:
|
|
187
|
+
DeriveKeyBytesParams & Pbkdf2Params
|
|
188
|
+
): Promise<Uint8Array> {
|
|
189
|
+
// Get the Web Crypto API interface.
|
|
190
|
+
const webCrypto = getWebcryptoSubtle() as SubtleCrypto;
|
|
191
|
+
|
|
192
|
+
// Import the password as a raw key for use with the Web Crypto API.
|
|
193
|
+
const webCryptoKey = await webCrypto.importKey(
|
|
194
|
+
'raw', // key format is raw bytes
|
|
195
|
+
baseKeyBytes, // key data to import
|
|
196
|
+
{ name: 'PBKDF2' }, // algorithm identifier
|
|
197
|
+
false, // key is not extractable
|
|
198
|
+
['deriveBits'] // key usages
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Derive the bytes using the Web Crypto API.
|
|
202
|
+
const derivedKeyBuffer = await webCrypto.deriveBits(
|
|
203
|
+
{ name: 'PBKDF2', hash, salt, iterations },
|
|
204
|
+
webCryptoKey,
|
|
205
|
+
length
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Convert from ArrayBuffer to Uint8Array.
|
|
209
|
+
const derivedKeyBytes = new Uint8Array(derivedKeyBuffer);
|
|
210
|
+
|
|
211
|
+
return derivedKeyBytes;
|
|
212
|
+
}
|
|
122
213
|
}
|