@lakutata/security 3.0.0-beta.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 +23 -0
- package/README.md +143 -0
- package/THIRD_PARTY_NOTICES.md +36 -0
- package/dist/cjs/exceptions/DecryptionFailedException.d.ts +13 -0
- package/dist/cjs/exceptions/DecryptionFailedException.js +20 -0
- package/dist/cjs/exceptions/InvalidSignatureException.d.ts +13 -0
- package/dist/cjs/exceptions/InvalidSignatureException.js +20 -0
- package/dist/cjs/exceptions/PasswordTooLongException.d.ts +12 -0
- package/dist/cjs/exceptions/PasswordTooLongException.js +19 -0
- package/dist/cjs/exports/Security.d.ts +19 -0
- package/dist/cjs/exports/Security.js +38 -0
- package/dist/cjs/functions/Encryption.d.ts +95 -0
- package/dist/cjs/functions/Encryption.js +208 -0
- package/dist/cjs/functions/GenerateSecret.d.ts +20 -0
- package/dist/cjs/functions/GenerateSecret.js +26 -0
- package/dist/cjs/functions/HMAC.d.ts +50 -0
- package/dist/cjs/functions/HMAC.js +58 -0
- package/dist/cjs/functions/TimingSafeEqual.d.ts +18 -0
- package/dist/cjs/functions/TimingSafeEqual.js +30 -0
- package/dist/cjs/lib/AssertKey.d.ts +5 -0
- package/dist/cjs/lib/AssertKey.js +11 -0
- package/dist/cjs/lib/bcrypt/constants.d.ts +13 -0
- package/dist/cjs/lib/bcrypt/constants.js +213 -0
- package/dist/cjs/lib/bcrypt/index.d.ts +84 -0
- package/dist/cjs/lib/bcrypt/index.js +658 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/providers/Crypt.d.ts +71 -0
- package/dist/cjs/providers/Crypt.js +88 -0
- package/dist/cjs/providers/Mac.d.ts +86 -0
- package/dist/cjs/providers/Mac.js +114 -0
- package/dist/cjs/providers/PasswordHash.d.ts +78 -0
- package/dist/cjs/providers/PasswordHash.js +146 -0
- package/dist/esm/exceptions/DecryptionFailedException.js +16 -0
- package/dist/esm/exceptions/InvalidSignatureException.js +16 -0
- package/dist/esm/exceptions/PasswordTooLongException.js +15 -0
- package/dist/esm/exports/Security.js +19 -0
- package/dist/esm/functions/Encryption.js +202 -0
- package/dist/esm/functions/GenerateSecret.js +23 -0
- package/dist/esm/functions/HMAC.js +54 -0
- package/dist/esm/functions/TimingSafeEqual.js +27 -0
- package/dist/esm/lib/AssertKey.js +8 -0
- package/dist/esm/lib/bcrypt/constants.js +202 -0
- package/dist/esm/lib/bcrypt/index.js +645 -0
- package/dist/esm/providers/Crypt.js +84 -0
- package/dist/esm/providers/Mac.js +110 -0
- package/dist/esm/providers/PasswordHash.js +109 -0
- package/dist/types/exceptions/DecryptionFailedException.d.ts +13 -0
- package/dist/types/exceptions/InvalidSignatureException.d.ts +13 -0
- package/dist/types/exceptions/PasswordTooLongException.d.ts +12 -0
- package/dist/types/exports/Security.d.ts +19 -0
- package/dist/types/functions/Encryption.d.ts +95 -0
- package/dist/types/functions/GenerateSecret.d.ts +20 -0
- package/dist/types/functions/HMAC.d.ts +50 -0
- package/dist/types/functions/TimingSafeEqual.d.ts +18 -0
- package/dist/types/lib/AssertKey.d.ts +5 -0
- package/dist/types/lib/bcrypt/constants.d.ts +13 -0
- package/dist/types/lib/bcrypt/index.d.ts +84 -0
- package/dist/types/providers/Crypt.d.ts +71 -0
- package/dist/types/providers/Mac.d.ts +86 -0
- package/dist/types/providers/PasswordHash.d.ts +78 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present Lakutata
|
|
4
|
+
|
|
5
|
+
All rights reserved
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @lakutata/security
|
|
2
|
+
|
|
3
|
+
The security toolkit of the lakutata framework, built on `node:crypto`: password hashing with bcrypt (`PasswordHash`),
|
|
4
|
+
message signatures with HMAC (`Mac`), authenticated encryption with AES-256-GCM (`Crypt`), with key rotation, and the
|
|
5
|
+
functions under them (random keys, constant-time comparison, encryption with a key or a password). The umbrella package
|
|
6
|
+
`lakutata` re-exports it as `lakutata/security`, and `PasswordHash` alone as `lakutata/provider/passwordHash`.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```shell
|
|
11
|
+
npm install lakutata
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
It has no optional dependencies: bcrypt is implemented in JavaScript, the rest uses `node:crypto`.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### The providers in an application
|
|
19
|
+
|
|
20
|
+
Declare the providers in the `providers` of the application, with their keys read from the environment, then get them
|
|
21
|
+
with `getObject()` or `@Inject`. They are scoped, like every provider.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import {Application} from 'lakutata'
|
|
25
|
+
import {Crypt, generateSecret, Mac, PasswordHash} from 'lakutata/security'
|
|
26
|
+
|
|
27
|
+
Application
|
|
28
|
+
.run(() => ({
|
|
29
|
+
id: 'security.app',
|
|
30
|
+
name: 'Security',
|
|
31
|
+
providers: {
|
|
32
|
+
passwordHash: {class: PasswordHash, saltRounds: 12},
|
|
33
|
+
//The first key signs and encrypts, every key verifies and decrypts
|
|
34
|
+
mac: {class: Mac, keys: [process.env.MAC_KEY ?? generateSecret()], algorithm: 'sha256'},
|
|
35
|
+
crypt: {class: Crypt, keys: [process.env.CRYPT_KEY ?? generateSecret(), process.env.CRYPT_PREVIOUS_KEY ?? generateSecret()]}
|
|
36
|
+
}
|
|
37
|
+
}))
|
|
38
|
+
.onLaunched(async (app: Application): Promise<void> => {
|
|
39
|
+
const passwordHash: PasswordHash = await app.getObject('passwordHash')
|
|
40
|
+
const hash: string = await passwordHash.hash('correct horse battery staple')
|
|
41
|
+
console.log(await passwordHash.validate('correct horse battery staple', hash))
|
|
42
|
+
|
|
43
|
+
const mac: Mac = await app.getObject('mac')
|
|
44
|
+
const token: string = mac.signMessage('user:42')
|
|
45
|
+
console.log(mac.unsignMessage(token))
|
|
46
|
+
|
|
47
|
+
const crypt: Crypt = await app.getObject('crypt')
|
|
48
|
+
console.log(crypt.decryptString(crypt.encrypt('api-token-123')))
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Which tool for which secret
|
|
53
|
+
|
|
54
|
+
| Need | Use | Fit for passwords | Output |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| Store a user password, check it at login | `PasswordHash` (bcrypt) | yes: slow and salted | 60-character bcrypt hash (`$2b$10$...`) |
|
|
57
|
+
| Detect tampering (cookies, links, webhooks) | `Mac`, `hmac()` / `verifyHMAC()` | no: fast, needs a random key | base64url HMAC (43 characters with SHA-256) |
|
|
58
|
+
| Hide and protect data, read it back | `Crypt`, `encryptByKey()` / `decryptByKey()` | no: needs a random key | base64url: version, salt, IV, tag, ciphertext |
|
|
59
|
+
| Encrypt with a passphrase typed by a person | `encryptByPassword()` / `decryptByPassword()` | yes, for encryption (scrypt) | same format, another version |
|
|
60
|
+
| Generate a key or a token | `generateSecret()` | — | base64url random bytes (32 by default) |
|
|
61
|
+
| Compare a secret received with the expected one | `constantTimeEqual()` | no: use `PasswordHash.validate()` | boolean |
|
|
62
|
+
|
|
63
|
+
- **Passwords**: `PasswordHash.hash()` rejects passwords longer than 72 bytes in UTF-8 (bcrypt ignores the following
|
|
64
|
+
bytes) with `PasswordTooLongException`, and `validate()` gives `false` for them. Each hash has its own random salt and
|
|
65
|
+
its cost (`saltRounds`, 4 to 31, 10 by default: each extra round doubles the time); store the hash as is and check it
|
|
66
|
+
with `validate()`, never with `===`. `validate()` gives `false` for a value which is not a bcrypt hash.
|
|
67
|
+
- **Keys**: the keys of `Mac` and `Crypt` are strings of at least 16 characters, used as their UTF-8 bytes. Generate
|
|
68
|
+
them with `generateSecret()` (32 random bytes): the key derivations of `Mac`, `Crypt` and `encryptByKey()` are fast, so a
|
|
69
|
+
password used as a key is open to brute force. The functions refuse an empty key with a `RangeError`, but do not
|
|
70
|
+
check the key's length otherwise.
|
|
71
|
+
- **Key rotation**: put the new key first and keep the previous ones while the data they signed or encrypted is read;
|
|
72
|
+
the first key signs and encrypts, every key verifies and decrypts.
|
|
73
|
+
- **Formats**: the signatures and the encrypted data are base64url strings without padding, safe in URLs and cookies.
|
|
74
|
+
The encrypted data is authenticated: any change makes the decryption fail rather than return altered data.
|
|
75
|
+
|
|
76
|
+
### The functions
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import {
|
|
80
|
+
decryptByKey,
|
|
81
|
+
decryptByPassword,
|
|
82
|
+
encryptByKey,
|
|
83
|
+
encryptByPassword,
|
|
84
|
+
generateSecret,
|
|
85
|
+
hmac,
|
|
86
|
+
constantTimeEqual,
|
|
87
|
+
verifyHMAC
|
|
88
|
+
} from 'lakutata/security'
|
|
89
|
+
|
|
90
|
+
const key: string = generateSecret()
|
|
91
|
+
|
|
92
|
+
//Signatures: the raw body and its signature, sent in a header
|
|
93
|
+
const body: string = JSON.stringify({order: 42})
|
|
94
|
+
const signature: string = hmac(body, key, 'sha512')
|
|
95
|
+
console.log(verifyHMAC(body, signature, key, 'sha512'))
|
|
96
|
+
|
|
97
|
+
//Encryption with a random key, decrypted with the current or a previous key
|
|
98
|
+
const encrypted: string = encryptByKey('card ending 4242', key)
|
|
99
|
+
console.log(decryptByKey(encrypted, [key, generateSecret()]).toString('utf8'))
|
|
100
|
+
|
|
101
|
+
//Encryption with a passphrase (scrypt, asynchronous)
|
|
102
|
+
const backup: string = await encryptByPassword('backup contents', 'a long passphrase')
|
|
103
|
+
console.log((await decryptByPassword(backup, 'a long passphrase')).toString('utf8'))
|
|
104
|
+
|
|
105
|
+
//Constant-time comparison of a presented token
|
|
106
|
+
console.log(constantTimeEqual('presented-token', process.env.API_TOKEN ?? ''))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API
|
|
110
|
+
|
|
111
|
+
| Export | What it is |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `PasswordHash` | Provider hashing passwords with bcrypt: `hash()`, `validate()`; the option `saltRounds` (default 10) |
|
|
114
|
+
| `Mac` | Provider signing with HMAC and key rotation: `sign()`, `verify()`, `signMessage()`, `unsignMessage()`; the options `keys`, `algorithm` (default `'sha256'`) |
|
|
115
|
+
| `Crypt` | Provider encrypting with AES-256-GCM and key rotation: `encrypt()`, `decrypt()`, `decryptString()`; the option `keys` |
|
|
116
|
+
| `hmac`, `verifyHMAC` | Sign data with a key, check a signature in constant time |
|
|
117
|
+
| `HMACAlgorithm` | The hash functions of the signatures: `'sha256'`, `'sha384'`, `'sha512'` |
|
|
118
|
+
| `encryptByKey`, `decryptByKey` | Authenticated encryption with a random key (HKDF-SHA256 per message) |
|
|
119
|
+
| `encryptByPassword`, `decryptByPassword` | Authenticated encryption with a password (scrypt) |
|
|
120
|
+
| `generateSecret` | A random key or token, base64url |
|
|
121
|
+
| `constantTimeEqual` | Compare two secrets in constant time, whatever their lengths |
|
|
122
|
+
| `PasswordTooLongException`, `InvalidSignatureException`, `DecryptionFailedException` | The exceptions |
|
|
123
|
+
|
|
124
|
+
All are exported by `lakutata/security` and `@lakutata/security`. The typings document each declaration with examples.
|
|
125
|
+
|
|
126
|
+
## Errors
|
|
127
|
+
|
|
128
|
+
| Exception | When |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `PasswordTooLongException` (`E_PASSWORD_TOO_LONG`) | `PasswordHash.hash()` with a password longer than 72 bytes in UTF-8 |
|
|
131
|
+
| `InvalidSignatureException` (`E_INVALID_SIGNATURE`) | `Mac.unsignMessage()` with a message changed, unsigned or signed by an unknown key |
|
|
132
|
+
| `DecryptionFailedException` (`E_DECRYPTION_FAILED`) | `Crypt.decrypt()`, `decryptString()`, `decryptByKey()`, `decryptByPassword()`: a wrong key or password, changed or truncated data |
|
|
133
|
+
| `InvalidObjectOptionsException` (from `lakutata`) | The application starts with invalid provider options: no key, a key shorter than 16 characters, an unknown algorithm, `saltRounds` outside 4 to 31 (`InvalidValueException` when the provider is created outside the checked options, as in a bare `Container`) |
|
|
134
|
+
| `RangeError` | `generateSecret()` with a negative or too large number of bytes; `hmac()`, `verifyHMAC()`, `encryptByKey()`, `decryptByKey()` with an empty key |
|
|
135
|
+
|
|
136
|
+
The exceptions have the HTTP status 500 unless the application maps them; `verify()`, `verifyHMAC()` (given a key),
|
|
137
|
+
`validate()` and `constantTimeEqual()` return `false` for a wrong or malformed signature, hash or secret instead of
|
|
138
|
+
throwing.
|
|
139
|
+
|
|
140
|
+
## See also
|
|
141
|
+
|
|
142
|
+
- `doc/en/Security.md` of the package `lakutata` (Chinese in `doc/zh`): the guide of the security tools.
|
|
143
|
+
- `@lakutata/core` (`lakutata`): the providers, `Application` and the dependency injection.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
Parts of `@lakutata/security` are derived from the third-party projects listed below. They are rewritten in
|
|
4
|
+
TypeScript and modified; the original copyright notices and license terms are reproduced here as required.
|
|
5
|
+
The files derived from these projects refer to "the upstream" in their header; the projects, versions and directories
|
|
6
|
+
are listed in `src/lib/bcrypt/UPSTREAM.md`.
|
|
7
|
+
|
|
8
|
+
## bcrypt.js (bcryptjs) 3.0.3
|
|
9
|
+
|
|
10
|
+
Used in `src/lib/bcrypt` (and its ported tests).
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2012 Nevins Bartolomeo <nevins.bartolomeo@gmail.com>
|
|
13
|
+
Copyright (c) 2012 Shane Girish <shaneGirish@gmail.com>
|
|
14
|
+
Copyright (c) 2025 Daniel Wirtz <dcode@dcode.io>
|
|
15
|
+
|
|
16
|
+
Redistribution and use in source and binary forms, with or without
|
|
17
|
+
modification, are permitted provided that the following conditions
|
|
18
|
+
are met:
|
|
19
|
+
1. Redistributions of source code must retain the above copyright
|
|
20
|
+
notice, this list of conditions and the following disclaimer.
|
|
21
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
22
|
+
notice, this list of conditions and the following disclaimer in the
|
|
23
|
+
documentation and/or other materials provided with the distribution.
|
|
24
|
+
3. The name of the author may not be used to endorse or promote products
|
|
25
|
+
derived from this software without specific prior written permission.
|
|
26
|
+
|
|
27
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
28
|
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
29
|
+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
30
|
+
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
31
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
32
|
+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
33
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
34
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
35
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
36
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Exception } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by {@link Crypt.decrypt}, {@link Crypt.decryptString}, {@link decryptByKey} and {@link decryptByPassword}
|
|
4
|
+
* when the data cannot be decrypted: a wrong key or password, data changed or truncated, or data not produced by the
|
|
5
|
+
* matching encryption function. The message does not tell these cases apart, on purpose. Its `errno` is
|
|
6
|
+
* `'E_DECRYPTION_FAILED'`; its HTTP status is 500 unless the application handles it.
|
|
7
|
+
*/
|
|
8
|
+
export declare class DecryptionFailedException extends Exception {
|
|
9
|
+
/**
|
|
10
|
+
* `'E_DECRYPTION_FAILED'`.
|
|
11
|
+
*/
|
|
12
|
+
errno: string | number;
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DecryptionFailedException = void 0;
|
|
4
|
+
const core_1 = require("@lakutata/core");
|
|
5
|
+
/**
|
|
6
|
+
* Thrown by {@link Crypt.decrypt}, {@link Crypt.decryptString}, {@link decryptByKey} and {@link decryptByPassword}
|
|
7
|
+
* when the data cannot be decrypted: a wrong key or password, data changed or truncated, or data not produced by the
|
|
8
|
+
* matching encryption function. The message does not tell these cases apart, on purpose. Its `errno` is
|
|
9
|
+
* `'E_DECRYPTION_FAILED'`; its HTTP status is 500 unless the application handles it.
|
|
10
|
+
*/
|
|
11
|
+
class DecryptionFailedException extends core_1.Exception {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
/**
|
|
15
|
+
* `'E_DECRYPTION_FAILED'`.
|
|
16
|
+
*/
|
|
17
|
+
this.errno = 'E_DECRYPTION_FAILED';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.DecryptionFailedException = DecryptionFailedException;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Exception } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by {@link Mac.unsignMessage} when the signed message has no signature or its signature is not valid (the
|
|
4
|
+
* message or the signature was changed, or none of the configured keys signed it); applications may throw it too
|
|
5
|
+
* after {@link verifyHMAC} fails. Its `errno` is `'E_INVALID_SIGNATURE'`; its HTTP status is 500 unless the application
|
|
6
|
+
* handles it.
|
|
7
|
+
*/
|
|
8
|
+
export declare class InvalidSignatureException extends Exception {
|
|
9
|
+
/**
|
|
10
|
+
* `'E_INVALID_SIGNATURE'`.
|
|
11
|
+
*/
|
|
12
|
+
errno: string | number;
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InvalidSignatureException = void 0;
|
|
4
|
+
const core_1 = require("@lakutata/core");
|
|
5
|
+
/**
|
|
6
|
+
* Thrown by {@link Mac.unsignMessage} when the signed message has no signature or its signature is not valid (the
|
|
7
|
+
* message or the signature was changed, or none of the configured keys signed it); applications may throw it too
|
|
8
|
+
* after {@link verifyHMAC} fails. Its `errno` is `'E_INVALID_SIGNATURE'`; its HTTP status is 500 unless the application
|
|
9
|
+
* handles it.
|
|
10
|
+
*/
|
|
11
|
+
class InvalidSignatureException extends core_1.Exception {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
/**
|
|
15
|
+
* `'E_INVALID_SIGNATURE'`.
|
|
16
|
+
*/
|
|
17
|
+
this.errno = 'E_INVALID_SIGNATURE';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.InvalidSignatureException = InvalidSignatureException;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Exception } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by {@link PasswordHash.hash} when the password is longer than 72 bytes once encoded in UTF-8, the most bcrypt
|
|
4
|
+
* reads: hashing only its prefix would let any password sharing those 72 bytes match. Ask the user for a shorter
|
|
5
|
+
* password. Its `errno` is `'E_PASSWORD_TOO_LONG'`; its HTTP status is 500 unless the application handles it.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PasswordTooLongException extends Exception {
|
|
8
|
+
/**
|
|
9
|
+
* `'E_PASSWORD_TOO_LONG'`.
|
|
10
|
+
*/
|
|
11
|
+
errno: string | number;
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PasswordTooLongException = void 0;
|
|
4
|
+
const core_1 = require("@lakutata/core");
|
|
5
|
+
/**
|
|
6
|
+
* Thrown by {@link PasswordHash.hash} when the password is longer than 72 bytes once encoded in UTF-8, the most bcrypt
|
|
7
|
+
* reads: hashing only its prefix would let any password sharing those 72 bytes match. Ask the user for a shorter
|
|
8
|
+
* password. Its `errno` is `'E_PASSWORD_TOO_LONG'`; its HTTP status is 500 unless the application handles it.
|
|
9
|
+
*/
|
|
10
|
+
class PasswordTooLongException extends core_1.Exception {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
/**
|
|
14
|
+
* `'E_PASSWORD_TOO_LONG'`.
|
|
15
|
+
*/
|
|
16
|
+
this.errno = 'E_PASSWORD_TOO_LONG';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.PasswordTooLongException = PasswordTooLongException;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Providers
|
|
3
|
+
*/
|
|
4
|
+
export * from '../providers/PasswordHash.js';
|
|
5
|
+
export * from '../providers/Mac.js';
|
|
6
|
+
export * from '../providers/Crypt.js';
|
|
7
|
+
/**
|
|
8
|
+
* Functions
|
|
9
|
+
*/
|
|
10
|
+
export * from '../functions/TimingSafeEqual.js';
|
|
11
|
+
export * from '../functions/GenerateSecret.js';
|
|
12
|
+
export * from '../functions/HMAC.js';
|
|
13
|
+
export * from '../functions/Encryption.js';
|
|
14
|
+
/**
|
|
15
|
+
* Exceptions
|
|
16
|
+
*/
|
|
17
|
+
export * from '../exceptions/PasswordTooLongException.js';
|
|
18
|
+
export * from '../exceptions/InvalidSignatureException.js';
|
|
19
|
+
export * from '../exceptions/DecryptionFailedException.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* Providers
|
|
19
|
+
*/
|
|
20
|
+
__exportStar(require("../providers/PasswordHash.js"), exports);
|
|
21
|
+
__exportStar(require("../providers/Mac.js"), exports);
|
|
22
|
+
__exportStar(require("../providers/Crypt.js"), exports);
|
|
23
|
+
/**
|
|
24
|
+
* Functions
|
|
25
|
+
*/
|
|
26
|
+
__exportStar(require("../functions/TimingSafeEqual.js"), exports);
|
|
27
|
+
__exportStar(require("../functions/GenerateSecret.js"), exports);
|
|
28
|
+
__exportStar(require("../functions/HMAC.js"), exports);
|
|
29
|
+
__exportStar(require("../functions/Encryption.js"), exports);
|
|
30
|
+
/**
|
|
31
|
+
* Exceptions
|
|
32
|
+
*/
|
|
33
|
+
__exportStar(require("../exceptions/PasswordTooLongException.js"), exports);
|
|
34
|
+
__exportStar(require("../exceptions/InvalidSignatureException.js"), exports);
|
|
35
|
+
__exportStar(require("../exceptions/DecryptionFailedException.js"), exports);
|
|
36
|
+
|
|
37
|
+
//The default of the entry: the entry itself (scripts/package-build/cjs-package.mjs)
|
|
38
|
+
if (!Object.prototype.hasOwnProperty.call(exports, 'default')) Object.defineProperty(exports, 'default', {value: exports})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypt data with a secret key, using AES-256-GCM (authenticated encryption): the output is confidential and
|
|
3
|
+
* tamper-proof. Each call derives a fresh key from the key and a random 16-byte salt (HKDF-sha256) and uses a random
|
|
4
|
+
* 12-byte IV, so encrypting the same data twice gives different outputs. The {@link Crypt} provider does the same with
|
|
5
|
+
* configured keys and key rotation.
|
|
6
|
+
*
|
|
7
|
+
* The key must be random (see {@link generateSecret}): its derivation is fast, so a password used as a key is open to
|
|
8
|
+
* brute force; use {@link encryptByPassword} for passwords. The function refuses an empty key, but does not check
|
|
9
|
+
* the key's length or strength otherwise.
|
|
10
|
+
* @param data The data to encrypt: a string (encrypted as its UTF-8 bytes) or bytes.
|
|
11
|
+
* @param key The secret key: a string (used as its UTF-8 bytes, not decoded from base64) or bytes, not empty; 32
|
|
12
|
+
* random bytes recommended.
|
|
13
|
+
* @returns The encrypted data, base64url encoded without padding: 1 version byte, the salt, the IV, a 16-byte
|
|
14
|
+
* authentication tag and the ciphertext (45 bytes more than the data, before base64url).
|
|
15
|
+
* @throws {RangeError} When the key is empty.
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import {decryptByKey, encryptByKey, generateSecret} from 'lakutata/security'
|
|
19
|
+
*
|
|
20
|
+
* const key: string = process.env.CRYPT_KEY ?? generateSecret()
|
|
21
|
+
* const encrypted: string = encryptByKey('card ending 4242', key)
|
|
22
|
+
* console.log(decryptByKey(encrypted, key).toString('utf8'))
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function encryptByKey(data: string | Uint8Array, key: string | Uint8Array): string;
|
|
26
|
+
/**
|
|
27
|
+
* Decrypt data encrypted by {@link encryptByKey} (or {@link Crypt.encrypt}) with one of the keys, tried in order: list
|
|
28
|
+
* the current key and the previous ones during a key rotation. The authentication tag is checked before anything is
|
|
29
|
+
* returned, so changed data is never decrypted.
|
|
30
|
+
* @param encrypted The encrypted data, base64url as {@link encryptByKey} returns it.
|
|
31
|
+
* @param keys The key, or the keys to try: strings (used as their UTF-8 bytes) or bytes, none empty.
|
|
32
|
+
* @returns The decrypted bytes (call `.toString('utf8')` for a string).
|
|
33
|
+
* @throws {DecryptionFailedException} When no key decrypts the data, or it was changed, truncated or not produced by
|
|
34
|
+
* {@link encryptByKey} (data encrypted by {@link encryptByPassword} included).
|
|
35
|
+
* @throws {RangeError} When a key is empty.
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import {decryptByKey, DecryptionFailedException} from 'lakutata/security'
|
|
39
|
+
*
|
|
40
|
+
* declare const encrypted: string
|
|
41
|
+
* declare const currentKey: string
|
|
42
|
+
* declare const previousKey: string
|
|
43
|
+
*
|
|
44
|
+
* try {
|
|
45
|
+
* //The new key first, the previous one while old data remains
|
|
46
|
+
* const secret: string = decryptByKey(encrypted, [currentKey, previousKey]).toString('utf8')
|
|
47
|
+
* console.log(secret)
|
|
48
|
+
* } catch (error) {
|
|
49
|
+
* if (error instanceof DecryptionFailedException) console.log('Changed, or encrypted with an unknown key')
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function decryptByKey(encrypted: string, keys: string | Uint8Array | (string | Uint8Array)[]): Buffer;
|
|
54
|
+
/**
|
|
55
|
+
* Encrypt data with a password chosen by a person, using AES-256-GCM (authenticated encryption) with a key derived
|
|
56
|
+
* from the password and a random 16-byte salt by scrypt (N = 16384, r = 8, p = 1: about 16 MB of memory and a few
|
|
57
|
+
* tens of milliseconds per call, off the main thread). The slow derivation makes guessing the password costly; a
|
|
58
|
+
* strong password still matters. For a random key, {@link encryptByKey} is faster. To check a login password, use
|
|
59
|
+
* {@link PasswordHash} instead.
|
|
60
|
+
* @param data The data to encrypt: a string (encrypted as its UTF-8 bytes) or bytes.
|
|
61
|
+
* @param password The password: a string (used as its UTF-8 bytes) or bytes. Its length is not checked.
|
|
62
|
+
* @returns The encrypted data, base64url encoded without padding, in the format of {@link encryptByKey} with another
|
|
63
|
+
* version byte: the two are not interchangeable.
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* import {decryptByPassword, encryptByPassword} from 'lakutata/security'
|
|
67
|
+
*
|
|
68
|
+
* const encrypted: string = await encryptByPassword('backup contents', 'a long passphrase of the user')
|
|
69
|
+
* console.log((await decryptByPassword(encrypted, 'a long passphrase of the user')).toString('utf8'))
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function encryptByPassword(data: string | Uint8Array, password: string | Uint8Array): Promise<string>;
|
|
73
|
+
/**
|
|
74
|
+
* Decrypt data encrypted by {@link encryptByPassword}, deriving the key from the password and the salt stored in the
|
|
75
|
+
* data (the same scrypt cost).
|
|
76
|
+
* @param encrypted The encrypted data, base64url as {@link encryptByPassword} returns it.
|
|
77
|
+
* @param password The password it was encrypted with: a string (used as its UTF-8 bytes) or bytes.
|
|
78
|
+
* @returns The decrypted bytes (call `.toString('utf8')` for a string).
|
|
79
|
+
* @throws {DecryptionFailedException} When the password is wrong, or the data was changed, truncated or not produced
|
|
80
|
+
* by {@link encryptByPassword} (data encrypted by {@link encryptByKey} included).
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import {decryptByPassword, DecryptionFailedException} from 'lakutata/security'
|
|
84
|
+
*
|
|
85
|
+
* declare const encrypted: string
|
|
86
|
+
* declare const passphrase: string
|
|
87
|
+
*
|
|
88
|
+
* try {
|
|
89
|
+
* console.log((await decryptByPassword(encrypted, passphrase)).toString('utf8'))
|
|
90
|
+
* } catch (error) {
|
|
91
|
+
* if (error instanceof DecryptionFailedException) console.log('Wrong passphrase, or changed data')
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function decryptByPassword(encrypted: string, password: string | Uint8Array): Promise<Buffer>;
|