Add crypto module: encrypt, decrypt, generateEncryptionKey, EncryptedDataSchema

This commit is contained in:
2026-05-29 10:57:02 +00:00
parent c999e24242
commit 09f8f7ef51
3 changed files with 112 additions and 1 deletions

View File

@@ -16,6 +16,7 @@
"drizzle-orm/pg-core": "npm:drizzle-orm/pg-core", "drizzle-orm/pg-core": "npm:drizzle-orm/pg-core",
"@libsql/client": "npm:@libsql/client", "@libsql/client": "npm:@libsql/client",
"postgres": "npm:postgres", "postgres": "npm:postgres",
"@std/encoding": "jsr:@std/encoding",
"@std/assert": "jsr:@std/assert", "@std/assert": "jsr:@std/assert",
"@std/flags": "jsr:@std/flags", "@std/flags": "jsr:@std/flags",
"@std/path": "jsr:@std/path" "@std/path": "jsr:@std/path"

103
src/graphs/crypto.ts Normal file
View File

@@ -0,0 +1,103 @@
import { type Static, Type } from "@alkdev/typebox";
import { decodeBase64, encodeBase64 } from "@std/encoding";
export const EncryptedDataSchema = Type.Object({
keyVersion: Type.Integer({ minimum: 1 }),
salt: Type.String(),
iv: Type.String(),
data: Type.String(),
});
export type EncryptedData = Static<typeof EncryptedDataSchema>;
function generateSalt(): Uint8Array {
return crypto.getRandomValues(new Uint8Array(16));
}
function generateIV(): Uint8Array {
return crypto.getRandomValues(new Uint8Array(12));
}
async function deriveKey(
password: string,
salt: Uint8Array,
keyVersion: number = 1,
): Promise<CryptoKey> {
const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
"raw",
encoder.encode(password),
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"],
);
const iterations = keyVersion === 1 ? 100000 : 200000;
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: new Uint8Array(salt),
iterations,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"],
);
}
export async function encrypt(
plaintext: string,
password: string,
keyVersion: number = 1,
): Promise<EncryptedData> {
const encoder = new TextEncoder();
const salt = generateSalt();
const iv = generateIV();
const key = await deriveKey(password, salt, keyVersion);
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: new Uint8Array(iv) },
key,
encoder.encode(plaintext),
);
return {
keyVersion,
salt: encodeBase64(salt),
iv: encodeBase64(iv),
data: encodeBase64(new Uint8Array(encrypted)),
};
}
export async function decrypt(
encryptedData: EncryptedData,
password: string,
): Promise<string> {
const decoder = new TextDecoder();
const salt = decodeBase64(encryptedData.salt);
const iv = decodeBase64(encryptedData.iv);
const data = decodeBase64(encryptedData.data);
const key = await deriveKey(password, salt, encryptedData.keyVersion);
try {
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: new Uint8Array(iv) },
key,
data,
);
return decoder.decode(decrypted);
} catch (_error) {
throw new Error("Decryption failed: Invalid data or key");
}
}
export function generateEncryptionKey(): string {
const bytes = crypto.getRandomValues(new Uint8Array(32));
return encodeBase64(bytes);
}

View File

@@ -3,4 +3,11 @@ export {
Metagraph, Metagraph,
GRAPH_STATUS, GRAPH_STATUS,
GraphStatus, GraphStatus,
} from "./modules/metagraph.ts"; } from "./modules/metagraph.ts";
export {
encrypt,
decrypt,
generateEncryptionKey,
EncryptedDataSchema,
type EncryptedData,
} from "./crypto.ts";