From fa09eed5565755a1ebf0ded307f80f4ca0e7d828 Mon Sep 17 00:00:00 2001
From: "M.M.Azizi" <121211931+TheGreatAzizi@users.noreply.github.com>
Date: Thu, 19 Feb 2026 14:30:48 +0330
Subject: [PATCH] Add encryption and decryption features in script.js
Implement encryption and decryption functionality with password support, including UI updates for error handling and content display.
---
script.js | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 255 insertions(+)
create mode 100644 script.js
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..7d67953
--- /dev/null
+++ b/script.js
@@ -0,0 +1,255 @@
+let pendingKey = null;
+let pendingData = null;
+
+function showError(msg) {
+ document.getElementById('errorDisplay').style.display = 'flex';
+ document.getElementById('errorMessage').textContent = msg;
+}
+
+function clearError() {
+ document.getElementById('errorDisplay').style.display = 'none';
+}
+
+function isRTL(text) {
+ const rtlRegex = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF\uFB50-\uFDFF\uFE70-\uFEFF]/;
+ return rtlRegex.test(text);
+}
+
+const contentTextarea = document.getElementById('content');
+if (contentTextarea) {
+ contentTextarea.addEventListener('input', function(e) {
+ const text = e.target.value;
+ if (isRTL(text)) {
+ e.target.style.direction = 'rtl';
+ e.target.style.fontFamily = "'Vazirmatn', sans-serif";
+ } else {
+ e.target.style.direction = 'ltr';
+ e.target.style.fontFamily = "'JetBrains Mono', monospace";
+ }
+ });
+}
+
+function togglePassword() {
+ const enabled = document.getElementById('enablePassword').checked;
+ const wrapper = document.getElementById('passwordWrapper');
+ wrapper.classList.toggle('show', enabled);
+}
+
+const Base64 = {
+ encode(buf) {
+ const bytes = new Uint8Array(buf);
+ let bin = '';
+ for (let b of bytes) bin += String.fromCharCode(b);
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
+ },
+ decode(str) {
+ str = str.replace(/-/g, '+').replace(/_/g, '/');
+ while (str.length % 4) str += '=';
+ const bin = atob(str);
+ const bytes = new Uint8Array(bin.length);
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
+ return bytes;
+ }
+};
+
+async function deriveKeyFromPassword(password, salt) {
+ const encoder = new TextEncoder();
+ const keyMaterial = await crypto.subtle.importKey(
+ 'raw', encoder.encode(password), { name: 'PBKDF2' }, false, ['deriveKey']
+ );
+ return crypto.subtle.deriveKey(
+ { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
+ keyMaterial,
+ { name: 'AES-GCM', length: 256 },
+ true,
+ ['encrypt', 'decrypt']
+ );
+}
+
+function genId() {
+ const arr = new Uint8Array(16);
+ crypto.getRandomValues(arr);
+ return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');
+}
+
+async function createPaste() {
+ clearError();
+ const content = document.getElementById('content').value.trim();
+ if (!content) return showError('Please enter content to encrypt');
+
+ const hasPassword = document.getElementById('enablePassword').checked;
+ const password = document.getElementById('passwordInput').value;
+
+ if (hasPassword && password.length < 4) {
+ return showError('Password must be at least 4 characters');
+ }
+
+ const btn = document.getElementById('createBtn');
+ const btnText = document.getElementById('btnText');
+ btn.disabled = true;
+ btnText.innerHTML = ' Encrypting...';
+
+ try {
+ let key, keyToExport;
+
+ if (hasPassword) {
+ const salt = crypto.getRandomValues(new Uint8Array(16));
+ key = await deriveKeyFromPassword(password, salt);
+ keyToExport = salt;
+ } else {
+ key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
+ keyToExport = await crypto.subtle.exportKey('raw', key);
+ }
+
+ const iv = crypto.getRandomValues(new Uint8Array(12));
+ const encrypted = await crypto.subtle.encrypt(
+ { name: 'AES-GCM', iv },
+ key,
+ new TextEncoder().encode(content)
+ );
+
+ const id = genId();
+ const payload = {
+ iv: Array.from(iv),
+ data: Array.from(new Uint8Array(encrypted))
+ };
+
+ const res = await fetch('/api/create', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ id,
+ encryptedData: payload,
+ expiresIn: parseInt(document.getElementById('expiresIn').value),
+ burnAfterRead: document.getElementById('burnAfterRead').checked,
+ hasPassword: hasPassword
+ })
+ });
+
+ const result = await res.json();
+ if (!res.ok) throw new Error(result.error || 'Server error');
+
+ let url;
+ if (hasPassword) {
+ url = `${location.origin}/#${id}:${Base64.encode(keyToExport)}:pwd`;
+ } else {
+ url = `${location.origin}/#${id}:${Base64.encode(keyToExport)}`;
+ }
+
+ document.getElementById('shareUrl').value = url;
+ document.getElementById('expiryTime').textContent = new Date(Date.now() + parseInt(document.getElementById('expiresIn').value) * 1000).toLocaleString();
+ document.getElementById('passwordNotice').style.display = hasPassword ? 'flex' : 'none';
+ document.getElementById('resultBox').classList.add('show');
+ document.getElementById('content').value = '';
+
+ } catch (err) {
+ showError(err.message);
+ } finally {
+ btn.disabled = false;
+ btnText.textContent = '🔐 Encrypt & Save';
+ }
+}
+
+async function decryptPaste() {
+ const hash = location.hash.slice(1);
+ if (!hash.includes(':')) return;
+
+ const parts = hash.split(':');
+ const id = parts[0];
+ const keyData = parts[1];
+ const isPasswordProtected = parts[2] === 'pwd';
+
+ try {
+ const res = await fetch(`/api/get/${id}`);
+ const data = await res.json();
+ if (!res.ok) throw new Error(data.error);
+
+ if (data.hasPassword || isPasswordProtected) {
+ pendingKey = keyData;
+ pendingData = data;
+ document.getElementById('createView').style.display = 'none';
+ document.getElementById('passwordPrompt').classList.add('show');
+ return;
+ }
+
+ await performDecryption(keyData, data);
+
+ } catch (err) {
+ showError('Failed: ' + err.message);
+ setTimeout(() => location.href = '/', 3000);
+ }
+}
+
+async function decryptWithPassword() {
+ const password = document.getElementById('decryptPassword').value;
+ if (!password) return;
+
+ const btn = document.querySelector('#passwordPrompt .btn');
+ const btnText = document.getElementById('decryptBtnText');
+ btn.disabled = true;
+ btnText.innerHTML = ' Decrypting...';
+
+ try {
+ const salt = Base64.decode(pendingKey);
+ const key = await deriveKeyFromPassword(password, salt);
+ await performDecryption(key, pendingData, true);
+ } catch (err) {
+ document.getElementById('passwordError').style.display = 'block';
+ btn.disabled = false;
+ btnText.textContent = '🔓 Decrypt';
+ }
+}
+
+async function performDecryption(keyOrData, data, isKeyObject = false) {
+ let key;
+
+ if (isKeyObject) {
+ key = keyOrData;
+ } else {
+ const keyRaw = Base64.decode(keyOrData);
+ key = await crypto.subtle.importKey(
+ 'raw', keyRaw, { name: 'AES-GCM', length: 256 }, false, ['decrypt']
+ );
+ }
+
+ const decrypted = await crypto.subtle.decrypt(
+ { name: 'AES-GCM', iv: new Uint8Array(data.data.iv) },
+ key,
+ new Uint8Array(data.data.data)
+ );
+
+ const text = new TextDecoder().decode(decrypted);
+
+ document.getElementById('passwordPrompt').classList.remove('show');
+ document.getElementById('decryptView').classList.add('show');
+
+ if (data.burnAfterRead) {
+ document.getElementById('burnNotice').style.display = 'flex';
+ }
+
+ const contentBox = document.getElementById('decryptedContent');
+ contentBox.textContent = text;
+
+ if (isRTL(text)) {
+ contentBox.style.direction = 'rtl';
+ contentBox.style.fontFamily = "'Vazirmatn', sans-serif";
+ } else {
+ contentBox.style.direction = 'ltr';
+ contentBox.style.fontFamily = "'JetBrains Mono', monospace";
+ }
+
+ history.replaceState(null, null, ' ');
+}
+
+function copyUrl() {
+ const inp = document.getElementById('shareUrl');
+ inp.select();
+ document.execCommand('copy');
+ const btn = document.querySelector('.btn-copy');
+ btn.textContent = '✅ Copied!';
+ setTimeout(() => btn.textContent = '📋 Copy', 2000);
+}
+
+window.addEventListener('load', () => {
+ if (location.hash.length > 1) decryptPaste();
+});