Skip to content

Commit

Permalink
Store init vector in DB
Browse files Browse the repository at this point in the history
  • Loading branch information
Yureien committed Jun 9, 2023
1 parent cc9cbbc commit 4d1b992
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 46 deletions.
12 changes: 6 additions & 6 deletions src/lib/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export async function encryptWithPassword(plaintext: string, password: string) {
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: await crypto.subtle.digest('SHA-256', encoder.encode(password)),
iterations: 100000,
hash: 'SHA-256'
salt: await crypto.subtle.digest('SHA-512', encoder.encode(password)),
iterations: 310000,
hash: 'SHA-512'
},
keyMaterial,
alg,
Expand Down Expand Up @@ -79,9 +79,9 @@ export async function decryptWithPassword(ciphertext: string, iv: string, passwo
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: await crypto.subtle.digest('SHA-256', encoder.encode(password)),
iterations: 100000,
hash: 'SHA-256'
salt: await crypto.subtle.digest('SHA-512', encoder.encode(password)),
iterations: 310000,
hash: 'SHA-512'
},
keyMaterial,
alg,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
Warnings:
- You are about to alter the column `key` on the `Paste` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(20)`.
- You are about to alter the column `language` on the `Paste` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(64)`.
*/
-- AlterTable
ALTER TABLE "Paste" ADD COLUMN "initVector" VARCHAR(64),
ALTER COLUMN "key" SET DATA TYPE VARCHAR(20),
ALTER COLUMN "language" SET DATA TYPE VARCHAR(64);

-- CreateIndex
CREATE INDEX "Paste_key_idx" ON "Paste"("key");
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[initVector]` on the table `Paste` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "Paste_initVector_key" ON "Paste"("initVector");
7 changes: 5 additions & 2 deletions src/lib/server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ datasource db {
model Paste {
id BigInt @id @default(autoincrement())
createdAt DateTime @default(now())
key String @unique
key String @unique @db.VarChar(20)
content String
encrypted Boolean @default(false)
passwordProtected Boolean @default(false)
language String @default("plaintext")
initVector String? @unique @db.VarChar(64)
language String @default("plaintext") @db.VarChar(64)
expiresAt DateTime?
expiresCount Int?
readCount Int @default(0)
@@index([key])
}
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Paste {
content: string;
config: PasteConfig;
passwordProtected: boolean;
initVector?: string;
}

export interface PasteCreateResponse {
Expand Down
17 changes: 8 additions & 9 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,27 @@
let finalContent = content;
let urlParams = '';
let passwordProtected = false;
let initVector: string | undefined;
if (config.encrypted) {
if (password) {
passwordProtected = true;
const { ciphertext, iv } = await encryptWithPassword(content, password);
finalContent = ciphertext;
urlParams = `i=${encodeURIComponent(iv)}`;
initVector = iv;
} else {
const { ciphertext, iv, key } = await encrypt(content);
finalContent = ciphertext;
urlParams = `i=${encodeURIComponent(iv)}#${encodeURIComponent(key)}`;
initVector = iv;
urlParams = `#${encodeURIComponent(key)}`;
}
}
const data: Paste = {
content: finalContent,
config,
passwordProtected
passwordProtected,
initVector
};
try {
Expand All @@ -106,7 +109,7 @@
const json: PasteCreateResponse = await response.json();
if (json.success) {
_sessionStorage?.removeItem('contentBackup');
await goto(`/${json.data?.key}?${urlParams}`);
await goto(`/${json.data?.key}${urlParams}`);
} else {
console.log(json);
}
Expand Down Expand Up @@ -162,11 +165,7 @@
</button>

<div class="flex flex-row gap-4 mb-4 justify-center">
<button
class="underline underline-offset-4 py-1"
title="{cmdKey}+N"
on:click={newPaste}
>
<button class="underline underline-offset-4 py-1" title="{cmdKey}+N" on:click={newPaste}>
New
</button>
<button
Expand Down
5 changes: 3 additions & 2 deletions src/routes/[key]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export async function load({ params }) {
const { key } = params;

const data = await getPaste(key);
let { content, language, encrypted, passwordProtected, expiresCount, readCount } = data;
let { content, language, encrypted, passwordProtected, initVector } = data;

let contentHtml: string;

Expand All @@ -28,6 +28,7 @@ export async function load({ params }) {
contentHtml,
encrypted,
language,
passwordProtected
passwordProtected,
initVector
};
}
23 changes: 9 additions & 14 deletions src/routes/[key]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
let Prism: any;
export let data: PageData;
let { content, contentHtml, language, encrypted, passwordProtected } = data;
let { content, contentHtml, language, encrypted, passwordProtected, initVector } = data;
let password = '';
let isDecrypted = false;
let codeRef: HTMLElement;
Expand Down Expand Up @@ -60,13 +60,11 @@
contentHtml = 'Decrypting...';
(async () => {
try {
const ivStr = $page.url.searchParams.get('i');
const keyStr = $page.url.hash.slice(1);
console.log(keyStr);
if (!ivStr || !keyStr) throw new Error('Missing key');
if (!initVector || !keyStr) throw new Error('Missing key');
const { decrypt } = await import('$lib/crypto');
content = await decrypt(content, decodeURIComponent(ivStr), decodeURIComponent(keyStr));
content = await decrypt(content, initVector, decodeURIComponent(keyStr));
} catch (e) {
error = 'Failed to decrypt';
} finally {
Expand All @@ -78,11 +76,10 @@
async function decryptPassword() {
try {
const ivStr = $page.url.searchParams.get('i');
if (!ivStr) throw new Error('Missing key');
if (!initVector) throw new Error('Missing key');
const { decryptWithPassword } = await import('$lib/crypto');
content = await decryptWithPassword(content, ivStr, password);
content = await decryptWithPassword(content, initVector, password);
} catch (e) {
error = 'Failed to decrypt';
} finally {
Expand All @@ -97,13 +94,11 @@
function openRaw() {
const url = new URL($page.url.toString());
url.searchParams.set('r', '');
if (
!confirm(
"WARNING: Getting the raw will decrypt the content on the server. It's not recommended to get the raw if the content is encrypted. Continue?"
)
)
return;
const confirmMsg =
"WARNING: Getting the raw will decrypt the content on the server. It's not recommended to get the raw if the content is encrypted. Continue?";
if (encrypted && !passwordProtected) {
if (!confirm(confirmMsg)) return;
url.searchParams.set('k', decodeURIComponent(url.hash.slice(1)));
url.hash = '';
}
Expand Down
15 changes: 5 additions & 10 deletions src/routes/[key]/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,23 @@ export async function GET({ url, params }) {
return text('An error occurred', { status: 500 });
}

const { encrypted, passwordProtected } = data;
const { encrypted, passwordProtected, initVector } = data;
let { content } = data;
const searchParams = url.searchParams;
const ivStr = searchParams.get('i');
const keyStr = searchParams.get('k');
const password = searchParams.get('p');

if (encrypted && ivStr && keyStr && !passwordProtected) {
if (encrypted && initVector && keyStr && !passwordProtected) {
try {
content = await decrypt(content, decodeURIComponent(ivStr), decodeURIComponent(keyStr));
content = await decrypt(content, initVector, decodeURIComponent(keyStr));
} catch (e) {
return text('Failed to decrypt', { status: 403 });
}
}

if (encrypted && ivStr && passwordProtected && password) {
if (encrypted && initVector && passwordProtected && password) {
try {
content = await decryptWithPassword(
content,
decodeURIComponent(ivStr),
decodeURIComponent(password)
);
content = await decryptWithPassword(content, initVector, decodeURIComponent(password));
} catch (e) {
return text('Failed to decrypt', { status: 403 });
}
Expand Down
5 changes: 3 additions & 2 deletions src/routes/api/create/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Paste, PasteCreateResponse } from '$lib/types';
import prisma from '@db';

export async function POST({ request }) {
const { content, config, passwordProtected }: Paste = await request.json();
const { content, config, passwordProtected, initVector }: Paste = await request.json();

let attempts = 0;
let keyLength = 5;
Expand All @@ -24,7 +24,8 @@ export async function POST({ request }) {
language: config.language,
encrypted: config.encrypted,
passwordProtected,
expiresCount: config.burnAfterRead ? 1 : null
expiresCount: config.burnAfterRead ? 1 : null,
initVector
}
});

Expand Down
3 changes: 2 additions & 1 deletion src/routes/info/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
</li>
<li>
<b>Optional</b> end-to-end encryption (we're using AES-256-GCM) with optional password protection
(using PBKDF2).
(using PBKDF2-SHA512). The initialization vector is stored on the server, since it can be public
and it results in a shorter URL while having the same end-to-end security.
</li>
<li>
Syntax highlighting (using <a
Expand Down

0 comments on commit 4d1b992

Please sign in to comment.