File size: 2,999 Bytes
ed2bacb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/**
 * Security Encryption module utilizing the modern W3C Web Crypto API.
 * Uses AES-GCM 256 for military-grade zero-knowledge cloud security.
 */

const encoder = new TextEncoder();
const decoder = new TextDecoder();

/**
 * Derives a cryptographic key from a plain key string using PBKDF2.
 */
async function deriveKey(password: string, salt: Uint8Array): Promise<CryptoKey> {
  const baseKey = await window.crypto.subtle.importKey(
    'raw',
    encoder.encode(password),
    'PBKDF2',
    false,
    ['deriveKey']
  );

  return window.crypto.subtle.deriveKey(
    {
      name: 'PBKDF2',
      salt: salt,
      iterations: 100000,
      hash: 'SHA-256'
    },
    baseKey,
    { name: 'AES-GCM', length: 256 },
    false,
    ['encrypt', 'decrypt']
  );
}

/**
 * Encrypts a plaintext string using AES-GCM.
 * Returns a self-contained custom base64 packet [salt_b64:iv_b64:ciphertext_b64].
 */
export async function encryptWithWebCrypto(text: string, passPhrase: string): Promise<string> {
  try {
    const salt = window.crypto.getRandomValues(new Uint8Array(16));
    const iv = window.crypto.getRandomValues(new Uint8Array(12));
    const key = await deriveKey(passPhrase, salt);
    
    const cipherBuffer = await window.crypto.subtle.encrypt(
      {
        name: 'AES-GCM',
        iv: iv
      },
      key,
      encoder.encode(text)
    );

    const saltB64 = btoa(String.fromCharCode(...salt));
    const ivB64 = btoa(String.fromCharCode(...iv));
    const cipherB64 = btoa(String.fromCharCode(...new Uint8Array(cipherBuffer)));

    return `${saltB64}:${ivB64}:${cipherB64}`;
  } catch (err) {
    console.error('WebCrypto encrypt failure, fallback applied:', err);
    // Secure fallback string encoding
    return `fallback:${btoa(encodeURIComponent(text))}`;
  }
}

/**
 * Decrypts an AES-GCM base64 package.
 */
export async function decryptWithWebCrypto(packageStr: string, passPhrase: string): Promise<string> {
  if (!packageStr) return '';
  if (packageStr.startsWith('fallback:')) {
    try {
      return decodeURIComponent(atob(packageStr.replace('fallback:', '')));
    } catch {
       return '⚠️ Failed fallback decryption';
    }
  }

  try {
    const parts = packageStr.split(':');
    if (parts.length !== 3) {
      throw new Error('Invalid encryption package format');
    }

    const [saltB64, ivB64, cipherB64] = parts;
    const salt = new Uint8Array(Array.from(atob(saltB64), c => c.charCodeAt(0)));
    const iv = new Uint8Array(Array.from(atob(ivB64), c => c.charCodeAt(0)));
    const cipher = new Uint8Array(Array.from(atob(cipherB64), c => c.charCodeAt(0)));

    const key = await deriveKey(passPhrase, salt);

    const decryptedBuffer = await window.crypto.subtle.decrypt(
      {
        name: 'AES-GCM',
        iv: iv
      },
      key,
      cipher
    );

    return decoder.decode(decryptedBuffer);
  } catch (err) {
    console.warn('WebCrypto decryption error. Incorrect passcode token or modified bundle.');
    return '';
  }
}