aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/envshare/pkg
diff options
context:
space:
mode:
author简律纯 <hsiangnianian@outlook.com>2023-04-18 03:02:17 +0800
committer简律纯 <hsiangnianian@outlook.com>2023-04-18 03:02:17 +0800
commit4919f028c884a041da7ff098abb02389b4eac598 (patch)
treeb0f482568c4b8c8a680ce6e2e70a7b7ca87dc190 /envshare/pkg
parentb135aac8531c1e1488147ad8c6f98eddbdbe0c99 (diff)
downloadHydroRoll-4919f028c884a041da7ff098abb02389b4eac598.tar.gz
HydroRoll-4919f028c884a041da7ff098abb02389b4eac598.zip
✨add envshare docs
Diffstat (limited to 'envshare/pkg')
-rw-r--r--envshare/pkg/constants.ts3
-rw-r--r--envshare/pkg/encoding.test.ts23
-rw-r--r--envshare/pkg/encoding.ts31
-rw-r--r--envshare/pkg/encryption.test.ts24
-rw-r--r--envshare/pkg/encryption.ts51
-rw-r--r--envshare/pkg/id.ts8
6 files changed, 140 insertions, 0 deletions
diff --git a/envshare/pkg/constants.ts b/envshare/pkg/constants.ts
new file mode 100644
index 0000000..09cc451
--- /dev/null
+++ b/envshare/pkg/constants.ts
@@ -0,0 +1,3 @@
+export const ID_LENGTH = 16;
+export const ENCRYPTION_KEY_LENGTH = 128;
+export const LATEST_KEY_VERSION = 2;
diff --git a/envshare/pkg/encoding.test.ts b/envshare/pkg/encoding.test.ts
new file mode 100644
index 0000000..be0a7f8
--- /dev/null
+++ b/envshare/pkg/encoding.test.ts
@@ -0,0 +1,23 @@
+import { describe, it, expect, beforeAll } from "@jest/globals";
+import { decodeCompositeKey, encodeCompositeKey } from "./encoding";
+import { generateKey } from "./encryption";
+import { generateId } from "./id";
+import crypto from "node:crypto";
+
+beforeAll(() => {
+ global.crypto = crypto.webcrypto;
+});
+describe("composite key encoding", () => {
+ it("encodes and decodes composite keys", async () => {
+ for (let i = 0; i < 10000; i++) {
+ const id = generateId();
+ const key = new Uint8Array(await crypto.subtle.exportKey("raw", await generateKey()));
+
+ const encoded = encodeCompositeKey(1, id, key);
+
+ const decoded = decodeCompositeKey(encoded);
+ expect(decoded.id).toEqual(id);
+ expect(decoded.encryptionKey).toEqual(key);
+ }
+ });
+});
diff --git a/envshare/pkg/encoding.ts b/envshare/pkg/encoding.ts
new file mode 100644
index 0000000..2025133
--- /dev/null
+++ b/envshare/pkg/encoding.ts
@@ -0,0 +1,31 @@
+import { fromBase58, toBase58 } from "../util/base58";
+import { ID_LENGTH, ENCRYPTION_KEY_LENGTH } from "./constants";
+/**
+ * To share links easily, we encode the id, where the data is stored in redis, together with the secret encryption key.
+ */
+export function encodeCompositeKey(version: number, id: string, encryptionKey: Uint8Array): string {
+ if (version < 0 || version > 255) {
+ throw new Error("Version must fit in a byte");
+ }
+ const compositeKey = new Uint8Array([version, ...fromBase58(id), ...encryptionKey]);
+
+ return toBase58(compositeKey);
+}
+
+/**
+ * To share links easily, we encode the id, where the data is stored in redis, together with the secret encryption key.
+ */
+export function decodeCompositeKey(compositeKey: string): { id: string; encryptionKey: Uint8Array; version: number } {
+ const decoded = fromBase58(compositeKey);
+ const version = decoded.at(0);
+
+ if (version === 1 || version === 2) {
+ return {
+ id: toBase58(decoded.slice(1, 1 + ID_LENGTH)),
+ encryptionKey: decoded.slice(1 + ID_LENGTH, 1 + ID_LENGTH + ENCRYPTION_KEY_LENGTH),
+ version,
+ };
+ }
+
+ throw new Error(`Unsupported composite key version: ${version}`);
+}
diff --git a/envshare/pkg/encryption.test.ts b/envshare/pkg/encryption.test.ts
new file mode 100644
index 0000000..5d4cf15
--- /dev/null
+++ b/envshare/pkg/encryption.test.ts
@@ -0,0 +1,24 @@
+import { describe, it, expect, beforeAll } from "@jest/globals";
+import { decrypt, encrypt } from "./encryption";
+import crypto from "node:crypto";
+import { toBase58 } from "../util/base58";
+
+beforeAll(() => {
+ global.crypto = crypto.webcrypto;
+});
+describe("aes", () => {
+ it("encrypts and decrypts correctly", async () => {
+ for (let i = 0; i < 500; i++) {
+ const buf = new Uint8Array(Math.ceil(Math.random() * 10 * i));
+ crypto.getRandomValues(buf);
+
+ const text = toBase58(buf);
+
+ const { encrypted, key, iv } = await encrypt(text);
+
+ const decrypted = await decrypt(toBase58(encrypted), key, toBase58(iv), 2);
+
+ expect(decrypted).toEqual(text);
+ }
+ }, 30_000);
+});
diff --git a/envshare/pkg/encryption.ts b/envshare/pkg/encryption.ts
new file mode 100644
index 0000000..c9f0e9d
--- /dev/null
+++ b/envshare/pkg/encryption.ts
@@ -0,0 +1,51 @@
+import { fromBase58 } from "../util/base58";
+
+export async function generateKey() {
+ return await crypto.subtle.generateKey(
+ {
+ name: "AES-GCM",
+ length: 128,
+ },
+ true,
+ ["encrypt", "decrypt"],
+ );
+}
+
+export async function encrypt(text: string): Promise<{ encrypted: Uint8Array; iv: Uint8Array; key: Uint8Array }> {
+ const key = await generateKey();
+
+ const iv = crypto.getRandomValues(new Uint8Array(16));
+
+ const encryptedBuffer = await crypto.subtle.encrypt(
+ {
+ name: "AES-GCM",
+ iv,
+ },
+ key,
+ new TextEncoder().encode(text),
+ );
+
+ const exportedKey = await crypto.subtle.exportKey("raw", key);
+ return {
+ encrypted: new Uint8Array(encryptedBuffer),
+ key: new Uint8Array(exportedKey),
+ iv,
+ };
+}
+
+export async function decrypt(encrypted: string, keyData: Uint8Array, iv: string, keyVersion: number): Promise<string> {
+ const algorithm = keyVersion === 1 ? "AES-CBC" : "AES-GCM";
+
+ const key = await crypto.subtle.importKey("raw", keyData, { name: algorithm, length: 128 }, false, ["decrypt"]);
+
+ const decrypted = await crypto.subtle.decrypt(
+ {
+ name: algorithm,
+ iv: fromBase58(iv),
+ },
+ key,
+ fromBase58(encrypted),
+ );
+
+ return new TextDecoder().decode(decrypted);
+}
diff --git a/envshare/pkg/id.ts b/envshare/pkg/id.ts
new file mode 100644
index 0000000..efdfb5e
--- /dev/null
+++ b/envshare/pkg/id.ts
@@ -0,0 +1,8 @@
+import { toBase58 } from "../util/base58";
+import { ID_LENGTH } from "./constants";
+
+export function generateId(): string {
+ const bytes = new Uint8Array(ID_LENGTH);
+ crypto.getRandomValues(bytes);
+ return toBase58(bytes);
+}