@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.
Files changed (226) hide show
  1. package/README.md +34 -102
  2. package/dist/browser.mjs +6 -10
  3. package/dist/browser.mjs.map +4 -4
  4. package/dist/esm/algorithms/aes-ctr.js +1 -1
  5. package/dist/esm/algorithms/aes-gcm.js +35 -2
  6. package/dist/esm/algorithms/aes-gcm.js.map +1 -1
  7. package/dist/esm/algorithms/aes-kw.js +154 -0
  8. package/dist/esm/algorithms/aes-kw.js.map +1 -0
  9. package/dist/esm/algorithms/ecdsa.js +119 -6
  10. package/dist/esm/algorithms/ecdsa.js.map +1 -1
  11. package/dist/esm/algorithms/eddsa.js +99 -6
  12. package/dist/esm/algorithms/eddsa.js.map +1 -1
  13. package/dist/esm/algorithms/hkdf.js +53 -0
  14. package/dist/esm/algorithms/hkdf.js.map +1 -0
  15. package/dist/esm/algorithms/pbkdf2.js +55 -0
  16. package/dist/esm/algorithms/pbkdf2.js.map +1 -0
  17. package/dist/esm/algorithms/sha-2.js +2 -2
  18. package/dist/esm/algorithms/sha-2.js.map +1 -1
  19. package/dist/esm/algorithms/x25519.js +125 -0
  20. package/dist/esm/algorithms/x25519.js.map +1 -0
  21. package/dist/esm/crypto-error.js +41 -0
  22. package/dist/esm/crypto-error.js.map +1 -0
  23. package/dist/esm/index.js +8 -0
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/jose/jwk.js +52 -26
  26. package/dist/esm/jose/jwk.js.map +1 -1
  27. package/dist/esm/local-key-manager.js +9 -3
  28. package/dist/esm/local-key-manager.js.map +1 -1
  29. package/dist/esm/primitives/aes-ctr.js.map +1 -1
  30. package/dist/esm/primitives/aes-gcm.js.map +1 -1
  31. package/dist/esm/primitives/aes-kw.js +246 -0
  32. package/dist/esm/primitives/aes-kw.js.map +1 -0
  33. package/dist/esm/primitives/concat-kdf.js +1 -1
  34. package/dist/esm/primitives/concat-kdf.js.map +1 -1
  35. package/dist/esm/primitives/ecies-secp256k1.js +79 -0
  36. package/dist/esm/primitives/ecies-secp256k1.js.map +1 -0
  37. package/dist/esm/primitives/ed25519.js +3 -3
  38. package/dist/esm/primitives/ed25519.js.map +1 -1
  39. package/dist/esm/primitives/hkdf.js +79 -0
  40. package/dist/esm/primitives/hkdf.js.map +1 -0
  41. package/dist/esm/primitives/pbkdf2.js +49 -0
  42. package/dist/esm/primitives/pbkdf2.js.map +1 -1
  43. package/dist/esm/primitives/secp256k1.js +4 -4
  44. package/dist/esm/primitives/secp256k1.js.map +1 -1
  45. package/dist/esm/primitives/secp256r1.js +4 -4
  46. package/dist/esm/primitives/secp256r1.js.map +1 -1
  47. package/dist/esm/primitives/x25519.js +10 -17
  48. package/dist/esm/primitives/x25519.js.map +1 -1
  49. package/dist/esm/primitives/xchacha20-poly1305.js +48 -3
  50. package/dist/esm/primitives/xchacha20-poly1305.js.map +1 -1
  51. package/dist/esm/primitives/xchacha20.js +1 -1
  52. package/dist/esm/primitives/xchacha20.js.map +1 -1
  53. package/dist/esm/utils.js +30 -0
  54. package/dist/esm/utils.js.map +1 -1
  55. package/dist/types/algorithms/aes-ctr.d.ts +2 -2
  56. package/dist/types/algorithms/aes-ctr.d.ts.map +1 -1
  57. package/dist/types/algorithms/aes-gcm.d.ts +25 -5
  58. package/dist/types/algorithms/aes-gcm.d.ts.map +1 -1
  59. package/dist/types/algorithms/aes-kw.d.ts +129 -0
  60. package/dist/types/algorithms/aes-kw.d.ts.map +1 -0
  61. package/dist/types/algorithms/ecdsa.d.ts +49 -4
  62. package/dist/types/algorithms/ecdsa.d.ts.map +1 -1
  63. package/dist/types/algorithms/eddsa.d.ts +49 -4
  64. package/dist/types/algorithms/eddsa.d.ts.map +1 -1
  65. package/dist/types/algorithms/hkdf.d.ts +35 -0
  66. package/dist/types/algorithms/hkdf.d.ts.map +1 -0
  67. package/dist/types/algorithms/pbkdf2.d.ts +35 -0
  68. package/dist/types/algorithms/pbkdf2.d.ts.map +1 -0
  69. package/dist/types/algorithms/sha-2.d.ts +2 -2
  70. package/dist/types/algorithms/sha-2.d.ts.map +1 -1
  71. package/dist/types/algorithms/x25519.d.ts +76 -0
  72. package/dist/types/algorithms/x25519.d.ts.map +1 -0
  73. package/dist/types/crypto-error.d.ts +29 -0
  74. package/dist/types/crypto-error.d.ts.map +1 -0
  75. package/dist/types/index.d.ts +8 -0
  76. package/dist/types/index.d.ts.map +1 -1
  77. package/dist/types/jose/jwk.d.ts.map +1 -1
  78. package/dist/types/local-key-manager.d.ts +6 -6
  79. package/dist/types/local-key-manager.d.ts.map +1 -1
  80. package/dist/types/primitives/aes-kw.d.ts +103 -0
  81. package/dist/types/primitives/aes-kw.d.ts.map +1 -0
  82. package/dist/types/primitives/concat-kdf.d.ts +1 -1
  83. package/dist/types/primitives/concat-kdf.d.ts.map +1 -1
  84. package/dist/types/primitives/ecies-secp256k1.d.ts +53 -0
  85. package/dist/types/primitives/ecies-secp256k1.d.ts.map +1 -0
  86. package/dist/types/primitives/hkdf.d.ts +90 -0
  87. package/dist/types/primitives/hkdf.d.ts.map +1 -0
  88. package/dist/types/primitives/pbkdf2.d.ts +58 -0
  89. package/dist/types/primitives/pbkdf2.d.ts.map +1 -1
  90. package/dist/types/primitives/x25519.d.ts +9 -16
  91. package/dist/types/primitives/x25519.d.ts.map +1 -1
  92. package/dist/types/primitives/xchacha20-poly1305.d.ts +47 -0
  93. package/dist/types/primitives/xchacha20-poly1305.d.ts.map +1 -1
  94. package/dist/types/types/cipher.d.ts +1 -1
  95. package/dist/types/types/crypto-api.d.ts +54 -6
  96. package/dist/types/types/crypto-api.d.ts.map +1 -1
  97. package/dist/types/types/key-converter.d.ts +37 -15
  98. package/dist/types/types/key-converter.d.ts.map +1 -1
  99. package/dist/types/types/key-deriver.d.ts +41 -0
  100. package/dist/types/types/key-deriver.d.ts.map +1 -1
  101. package/dist/types/types/key-io.d.ts +37 -0
  102. package/dist/types/types/key-io.d.ts.map +1 -1
  103. package/dist/types/types/params-direct.d.ts +96 -1
  104. package/dist/types/types/params-direct.d.ts.map +1 -1
  105. package/dist/types/types/params-kms.d.ts +55 -0
  106. package/dist/types/types/params-kms.d.ts.map +1 -1
  107. package/dist/types/utils.d.ts +19 -0
  108. package/dist/types/utils.d.ts.map +1 -1
  109. package/dist/utils.js +1 -1
  110. package/dist/utils.js.map +4 -4
  111. package/package.json +29 -45
  112. package/src/algorithms/aes-ctr.ts +2 -2
  113. package/src/algorithms/aes-gcm.ts +41 -4
  114. package/src/algorithms/aes-kw.ts +182 -0
  115. package/src/algorithms/ecdsa.ts +145 -8
  116. package/src/algorithms/eddsa.ts +117 -10
  117. package/src/algorithms/hkdf.ts +54 -0
  118. package/src/algorithms/pbkdf2.ts +57 -0
  119. package/src/algorithms/sha-2.ts +3 -3
  120. package/src/algorithms/x25519.ts +153 -0
  121. package/src/crypto-error.ts +45 -0
  122. package/src/index.ts +8 -0
  123. package/src/jose/jwk.ts +32 -32
  124. package/src/local-key-manager.ts +22 -16
  125. package/src/primitives/aes-ctr.ts +1 -1
  126. package/src/primitives/aes-gcm.ts +5 -5
  127. package/src/primitives/aes-kw.ts +269 -0
  128. package/src/primitives/concat-kdf.ts +4 -2
  129. package/src/primitives/ecies-secp256k1.ts +113 -0
  130. package/src/primitives/ed25519.ts +6 -6
  131. package/src/primitives/hkdf.ts +121 -0
  132. package/src/primitives/pbkdf2.ts +91 -0
  133. package/src/primitives/secp256k1.ts +6 -6
  134. package/src/primitives/secp256r1.ts +6 -6
  135. package/src/primitives/x25519.ts +12 -19
  136. package/src/primitives/xchacha20-poly1305.ts +57 -4
  137. package/src/primitives/xchacha20.ts +1 -1
  138. package/src/types/cipher.ts +1 -1
  139. package/src/types/crypto-api.ts +129 -11
  140. package/src/types/key-converter.ts +33 -7
  141. package/src/types/key-deriver.ts +49 -0
  142. package/src/types/key-io.ts +40 -0
  143. package/src/types/params-direct.ts +118 -1
  144. package/src/types/params-kms.ts +67 -0
  145. package/src/utils.ts +55 -2
  146. package/dist/browser.js +0 -64
  147. package/dist/browser.js.map +0 -7
  148. package/dist/cjs/algorithms/aes-ctr.js +0 -188
  149. package/dist/cjs/algorithms/aes-ctr.js.map +0 -1
  150. package/dist/cjs/algorithms/aes-gcm.js +0 -196
  151. package/dist/cjs/algorithms/aes-gcm.js.map +0 -1
  152. package/dist/cjs/algorithms/crypto-algorithm.js +0 -13
  153. package/dist/cjs/algorithms/crypto-algorithm.js.map +0 -1
  154. package/dist/cjs/algorithms/ecdsa.js +0 -352
  155. package/dist/cjs/algorithms/ecdsa.js.map +0 -1
  156. package/dist/cjs/algorithms/eddsa.js +0 -325
  157. package/dist/cjs/algorithms/eddsa.js.map +0 -1
  158. package/dist/cjs/algorithms/sha-2.js +0 -119
  159. package/dist/cjs/algorithms/sha-2.js.map +0 -1
  160. package/dist/cjs/index.js +0 -41
  161. package/dist/cjs/index.js.map +0 -1
  162. package/dist/cjs/jose/jwe.js +0 -3
  163. package/dist/cjs/jose/jwe.js.map +0 -1
  164. package/dist/cjs/jose/jwk.js +0 -278
  165. package/dist/cjs/jose/jwk.js.map +0 -1
  166. package/dist/cjs/jose/jws.js +0 -3
  167. package/dist/cjs/jose/jws.js.map +0 -1
  168. package/dist/cjs/jose/jwt.js +0 -3
  169. package/dist/cjs/jose/jwt.js.map +0 -1
  170. package/dist/cjs/jose/utils.js +0 -60
  171. package/dist/cjs/jose/utils.js.map +0 -1
  172. package/dist/cjs/local-key-manager.js +0 -521
  173. package/dist/cjs/local-key-manager.js.map +0 -1
  174. package/dist/cjs/package.json +0 -1
  175. package/dist/cjs/primitives/aes-ctr.js +0 -398
  176. package/dist/cjs/primitives/aes-ctr.js.map +0 -1
  177. package/dist/cjs/primitives/aes-gcm.js +0 -425
  178. package/dist/cjs/primitives/aes-gcm.js.map +0 -1
  179. package/dist/cjs/primitives/concat-kdf.js +0 -215
  180. package/dist/cjs/primitives/concat-kdf.js.map +0 -1
  181. package/dist/cjs/primitives/ed25519.js +0 -651
  182. package/dist/cjs/primitives/ed25519.js.map +0 -1
  183. package/dist/cjs/primitives/pbkdf2.js +0 -120
  184. package/dist/cjs/primitives/pbkdf2.js.map +0 -1
  185. package/dist/cjs/primitives/secp256k1.js +0 -958
  186. package/dist/cjs/primitives/secp256k1.js.map +0 -1
  187. package/dist/cjs/primitives/secp256r1.js +0 -959
  188. package/dist/cjs/primitives/secp256r1.js.map +0 -1
  189. package/dist/cjs/primitives/sha256.js +0 -93
  190. package/dist/cjs/primitives/sha256.js.map +0 -1
  191. package/dist/cjs/primitives/x25519.js +0 -498
  192. package/dist/cjs/primitives/x25519.js.map +0 -1
  193. package/dist/cjs/primitives/xchacha20-poly1305.js +0 -340
  194. package/dist/cjs/primitives/xchacha20-poly1305.js.map +0 -1
  195. package/dist/cjs/primitives/xchacha20.js +0 -316
  196. package/dist/cjs/primitives/xchacha20.js.map +0 -1
  197. package/dist/cjs/types/cipher.js +0 -3
  198. package/dist/cjs/types/cipher.js.map +0 -1
  199. package/dist/cjs/types/crypto-api.js +0 -3
  200. package/dist/cjs/types/crypto-api.js.map +0 -1
  201. package/dist/cjs/types/hasher.js +0 -3
  202. package/dist/cjs/types/hasher.js.map +0 -1
  203. package/dist/cjs/types/identifier.js +0 -3
  204. package/dist/cjs/types/identifier.js.map +0 -1
  205. package/dist/cjs/types/key-compressor.js +0 -3
  206. package/dist/cjs/types/key-compressor.js.map +0 -1
  207. package/dist/cjs/types/key-converter.js +0 -3
  208. package/dist/cjs/types/key-converter.js.map +0 -1
  209. package/dist/cjs/types/key-deriver.js +0 -3
  210. package/dist/cjs/types/key-deriver.js.map +0 -1
  211. package/dist/cjs/types/key-generator.js +0 -3
  212. package/dist/cjs/types/key-generator.js.map +0 -1
  213. package/dist/cjs/types/key-io.js +0 -3
  214. package/dist/cjs/types/key-io.js.map +0 -1
  215. package/dist/cjs/types/key-wrapper.js +0 -3
  216. package/dist/cjs/types/key-wrapper.js.map +0 -1
  217. package/dist/cjs/types/params-direct.js +0 -3
  218. package/dist/cjs/types/params-direct.js.map +0 -1
  219. package/dist/cjs/types/params-enclosed.js +0 -3
  220. package/dist/cjs/types/params-enclosed.js.map +0 -1
  221. package/dist/cjs/types/params-kms.js +0 -3
  222. package/dist/cjs/types/params-kms.js.map +0 -1
  223. package/dist/cjs/types/signer.js +0 -3
  224. package/dist/cjs/types/signer.js.map +0 -1
  225. package/dist/cjs/utils.js +0 -173
  226. 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, edwardsToMontgomeryPub, edwardsToMontgomeryPriv, x25519 } from '@noble/curves/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 = ed25519.getPublicKey(privateKeyBytes);
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 = await Ed25519.privateKeyToBytes({ privateKey: key });
170
+ const privateKeyBytes = await Ed25519.privateKeyToBytes({ privateKey: key });
171
171
 
172
172
  // Derive the public key from the private key.
173
- const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes);
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
- let { d, ...publicKey } = key;
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(error: any) {
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
+ }
@@ -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
  }