@lowerdeck/encryption 1.0.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/.turbo/turbo-test.log +11 -0
- package/package.json +36 -0
- package/src/base86.ts +11 -0
- package/src/crypto.ts +71 -0
- package/src/index.ts +28 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
[0m[2m[35m$[0m [2m[1mvitest run --passWithNoTests[0m
|
|
3
|
+
[?25l
|
|
4
|
+
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/Users/tobias/code/metorial/metorial-enterprise/oss/src/packages/backend/encryption[39m
|
|
5
|
+
|
|
6
|
+
No test files found, exiting with code 0
|
|
7
|
+
|
|
8
|
+
[2minclude: [22m[33m**/*.{test,spec}.?(c|m)[jt]s?(x)[39m
|
|
9
|
+
[2mexclude: [22m[33m**/node_modules/**[2m, [22m**/dist/**[2m, [22m**/cypress/**[2m, [22m**/.{idea,git,cache,output,temp}/**[2m, [22m**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*[39m
|
|
10
|
+
|
|
11
|
+
[?25h
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lowerdeck/encryption",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"author": "Tobias Herber",
|
|
8
|
+
"license": "Apache 2",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"source": "src/index.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"require": "./dist/index.cjs",
|
|
14
|
+
"import": "./dist/index.module.js",
|
|
15
|
+
"default": "./dist/index.modern.js"
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.module.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"unpkg": "./dist/index.umd.js",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "vitest run --passWithNoTests",
|
|
23
|
+
"lint": "prettier src/**/*.ts --check",
|
|
24
|
+
"build": "microbundle"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@lowerdeck/base62": "^1.0.0",
|
|
28
|
+
"@lowerdeck/id": "^1.0.0",
|
|
29
|
+
"base-x": "^5.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@lowerdeck/tsconfig": "^1.0.0",
|
|
33
|
+
"typescript": "5.8.2",
|
|
34
|
+
"vitest": "^3.1.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/base86.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import baseX from 'base-x';
|
|
2
|
+
|
|
3
|
+
let internal = baseX(
|
|
4
|
+
'!#$%()*+-.0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~§'
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
export let base86 = {
|
|
8
|
+
encode: (input: string | Uint8Array) =>
|
|
9
|
+
internal.encode(typeof input == 'string' ? new TextEncoder().encode(input) : input),
|
|
10
|
+
decode: (input: string) => internal.decode(input)
|
|
11
|
+
};
|
package/src/crypto.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { base62 } from '@metorial/base62';
|
|
2
|
+
import { base86 } from './base86';
|
|
3
|
+
|
|
4
|
+
let enc = new TextEncoder();
|
|
5
|
+
let dec = new TextDecoder();
|
|
6
|
+
|
|
7
|
+
let getPasswordKey = (password: string) =>
|
|
8
|
+
crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']);
|
|
9
|
+
|
|
10
|
+
let deriveKey = (passwordKey: CryptoKey, keyUsage: ('encrypt' | 'decrypt')[]) =>
|
|
11
|
+
crypto.subtle.deriveKey(
|
|
12
|
+
{
|
|
13
|
+
name: 'PBKDF2',
|
|
14
|
+
iterations: 250000,
|
|
15
|
+
hash: 'SHA-256'
|
|
16
|
+
},
|
|
17
|
+
passwordKey,
|
|
18
|
+
{ name: 'AES-GCM', length: 256 },
|
|
19
|
+
false,
|
|
20
|
+
keyUsage
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
let encryptData = async (secretData: string, password: string) => {
|
|
24
|
+
let iv = crypto.getRandomValues(new Uint8Array(12));
|
|
25
|
+
let passwordKey = await getPasswordKey(password);
|
|
26
|
+
let aesKey = await deriveKey(passwordKey, ['encrypt']);
|
|
27
|
+
let encryptedContent = await crypto.subtle.encrypt(
|
|
28
|
+
{
|
|
29
|
+
name: 'AES-GCM',
|
|
30
|
+
iv
|
|
31
|
+
},
|
|
32
|
+
aesKey,
|
|
33
|
+
enc.encode(secretData)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
let encryptedContentArr = new Uint8Array(encryptedContent);
|
|
37
|
+
let buff = new Uint8Array(iv.byteLength + encryptedContentArr.byteLength);
|
|
38
|
+
buff.set(iv, 0);
|
|
39
|
+
buff.set(encryptedContentArr, iv.byteLength);
|
|
40
|
+
|
|
41
|
+
return base86.encode(buff);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let decryptData = async (encryptedData: string, password: string) => {
|
|
45
|
+
let encryptedDataBuff = base86.decode(encryptedData);
|
|
46
|
+
let iv = encryptedDataBuff.slice(0, 16);
|
|
47
|
+
let data = encryptedDataBuff.slice(16);
|
|
48
|
+
let passwordKey = await getPasswordKey(password);
|
|
49
|
+
let aesKey = await deriveKey(passwordKey, ['decrypt']);
|
|
50
|
+
let decryptedContent = await crypto.subtle.decrypt(
|
|
51
|
+
{
|
|
52
|
+
name: 'AES-GCM',
|
|
53
|
+
iv
|
|
54
|
+
},
|
|
55
|
+
aesKey,
|
|
56
|
+
data
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return dec.decode(decryptedContent);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let sha512 = async (data: string) => {
|
|
63
|
+
let hashBuffer = await crypto.subtle.digest('SHA-512', enc.encode(data));
|
|
64
|
+
return base62.encode(new Uint8Array(hashBuffer));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export let secretsCrypto = {
|
|
68
|
+
encrypt: encryptData,
|
|
69
|
+
decrypt: decryptData,
|
|
70
|
+
sha512
|
|
71
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { generatePlainId } from '@metorial/id';
|
|
2
|
+
import { secretsCrypto } from './crypto';
|
|
3
|
+
|
|
4
|
+
export class Encryption {
|
|
5
|
+
constructor(private readonly password: string) {}
|
|
6
|
+
|
|
7
|
+
private async getPassword(entityId: string) {
|
|
8
|
+
return (await secretsCrypto.sha512(`${entityId}${this.password!}`)).slice(0, 50);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async encrypt(input: { secret: string; entityId: string }) {
|
|
12
|
+
return await secretsCrypto.encrypt(
|
|
13
|
+
JSON.stringify({
|
|
14
|
+
id: generatePlainId(10),
|
|
15
|
+
key: input.secret
|
|
16
|
+
}),
|
|
17
|
+
await this.getPassword(input.entityId)
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async decrypt(info: { encrypted: string; entityId: string }) {
|
|
22
|
+
let content = JSON.parse(
|
|
23
|
+
await secretsCrypto.decrypt(info.encrypted, await this.getPassword(info.entityId))
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return content.key;
|
|
27
|
+
}
|
|
28
|
+
}
|