Add crypto module: encrypt, decrypt, generateEncryptionKey, EncryptedDataSchema
This commit is contained in:
@@ -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
103
src/graphs/crypto.ts
Normal 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);
|
||||||
|
}
|
||||||
@@ -4,3 +4,10 @@ export {
|
|||||||
GRAPH_STATUS,
|
GRAPH_STATUS,
|
||||||
GraphStatus,
|
GraphStatus,
|
||||||
} from "./modules/metagraph.ts";
|
} from "./modules/metagraph.ts";
|
||||||
|
export {
|
||||||
|
encrypt,
|
||||||
|
decrypt,
|
||||||
|
generateEncryptionKey,
|
||||||
|
EncryptedDataSchema,
|
||||||
|
type EncryptedData,
|
||||||
|
} from "./crypto.ts";
|
||||||
Reference in New Issue
Block a user