Skip to content

feat: Groq API key is encrypted with AES-GCM but the encryption key is derived from a fixed salt β€” rotating the key requires the user to manually re-enter their Groq key with no UI guidanceΒ #885

Description

@prince-pokharna

πŸ› Problem Statement

The README documents: "Encrypted Personal Groq Keys β€” Optional user-provided Groq keys are encrypted locally with AES-GCM and unlocked only for the current browser session."

The AES-GCM implementation in the frontend uses crypto.subtle to derive an encryption key from the user's Groq key. However, the key derivation likely uses a hardcoded or fixed salt value. This creates two concrete problems:

1. Fixed salt weakens the encryption: AES-GCM key derivation with a fixed salt means two users who enter the same Groq key get the same derived encryption key β€” the salt's entire purpose (uniqueness) is defeated. A fixed salt is effectively no salt.

2. No key rotation UX: The current UI has no "Remove my Groq key" or "Re-enter key" button. If a user's Groq key is compromised and they need to rotate it, the only option is to clear their browser's localStorage manually β€” a flow documented nowhere in the UI.

Proposed Fix

Fix 1 β€” Use crypto.getRandomValues() for a random, stored salt:

// In the key encryption utility (likely src/utils/ or src/hooks/useAI.js)

async function encryptGroqKey(rawKey) {
  // Generate a unique random salt per user (stored alongside the encrypted key)
  const salt = crypto.getRandomValues(new Uint8Array(16));

  const keyMaterial = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(rawKey),
    'PBKDF2',
    false,
    ['deriveKey']
  );

  const derivedKey = await crypto.subtle.deriveKey(
    { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
    keyMaterial,
    { name: 'AES-GCM', length: 256 },
    false,
    ['encrypt', 'decrypt']
  );

  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    derivedKey,
    new TextEncoder().encode(rawKey)
  );

  // Store: base64(salt) + base64(iv) + base64(ciphertext)
  localStorage.setItem('debugra_groq_encrypted', JSON.stringify({
    salt: btoa(String.fromCharCode(...salt)),
    iv: btoa(String.fromCharCode(...iv)),
    data: btoa(String.fromCharCode(...new Uint8Array(encrypted))),
  }));
}

Fix 2 β€” Add a "Remove API Key" button in the Settings/Header area:

// In the component where the Groq key input is rendered
<button
  onClick={() => {
    localStorage.removeItem('debugra_groq_encrypted');
    setHasGroqKey(false);
    setGroqKeyInput('');
  }}
  className="btn-remove-key"
  aria-label="Remove saved Groq API key"
>
  Remove API Key
</button>

Files to Modify

File Change
src/ (Groq key encryption utility) Use random salt via crypto.getRandomValues()
src/components/ (header/settings where key is entered) Add "Remove API Key" button

Suggested labels: security, enhancement, frontend, level: intermediate

I would like to work on this. Could you please assign it to me?

Metadata

Metadata

Assignees

No one assigned

    Labels

    gssocOfficial GSSoC '26 issue tagtype:bugVulnerability or logical bug fixestype:designTactile visual design and UI alignmentstype:docsDocumentation and guide upgradestype:featureNew functional feature additionstype:securitySecurity patches and threat fixes

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions