From c979f1a760224e1bed9d40d89dd47914851862de Mon Sep 17 00:00:00 2001 From: Alexis Bruneteau Date: Fri, 7 Nov 2025 18:56:58 +0100 Subject: [PATCH] fix: Comprehensive ElGamal encryption system - DRY & KISS principles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major improvements applying DRY (Don't Repeat Yourself) and KISS (Keep It Simple, Stupid): BACKEND CHANGES: - Fixed public key storage: Store as base64-encoded bytes in LargeBinary column (not double-encoding) - ElGamal key generation now produces proper "p:g:h" format with colons - Removed all double base64-encoding issues - Simplified API responses to decode bytes to UTF-8 strings for JSON serialization FRONTEND CHANGES: - Refactored ElGamalEncryption.encrypt() to use extracted helper methods (_decodeBase64, _parsePublicKey) - Eliminated nested error handling - now uses clear, composable private methods - Improved error messages with specific format validation - Simplified cryptographic operations by reducing code duplication TESTING: - Verified public key format: "p:g:h" properly encoded as base64 - Full vote submission flow tested and working - Blockchain integration confirmed functional - No encryption errors during vote submission This fixes the original "Invalid public key format" error that was preventing vote submission. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- e-voting-system/backend/routes/votes.py | 11 +- e-voting-system/frontend/lib/crypto-client.ts | 102 ++++++++---------- 2 files changed, 52 insertions(+), 61 deletions(-) diff --git a/e-voting-system/backend/routes/votes.py b/e-voting-system/backend/routes/votes.py index c7ba0f0..2a2e2f0 100644 --- a/e-voting-system/backend/routes/votes.py +++ b/e-voting-system/backend/routes/votes.py @@ -499,15 +499,18 @@ async def setup_election( # Générer les clés ElGamal si nécessaire if not election.public_key: - elgamal = ElGamalEncryption(p=election.elgamal_p or 23, g=election.elgamal_g or 5) - election.public_key = elgamal.public_key_bytes + elgamal = ElGamal() + # Store as base64-encoded bytes (database column is LargeBinary) + # public_key_bytes returns UTF-8 "p:g:h", then encode to base64 + election.public_key = base64.b64encode(elgamal.public_key_bytes) + db.add(election) db.commit() return { "status": "initialized", "election_id": election_id, "public_keys": { - "elgamal_pubkey": base64.b64encode(election.public_key).decode() if election.public_key else None + "elgamal_pubkey": election.public_key.decode('utf-8') if election.public_key else None }, "blockchain_blocks": blockchain.get_block_count() } @@ -544,7 +547,7 @@ async def get_public_keys( ) return { - "elgamal_pubkey": base64.b64encode(election.public_key).decode() + "elgamal_pubkey": election.public_key.decode('utf-8') if election.public_key else None } diff --git a/e-voting-system/frontend/lib/crypto-client.ts b/e-voting-system/frontend/lib/crypto-client.ts index d2c0373..315e76a 100644 --- a/e-voting-system/frontend/lib/crypto-client.ts +++ b/e-voting-system/frontend/lib/crypto-client.ts @@ -70,69 +70,57 @@ export class ElGamalEncryption { } try { - // Validate input - if (!publicKeyBase64 || typeof publicKeyBase64 !== "string") { - throw new Error("Invalid public key: must be a non-empty string"); - } + // Decode base64: base64("p:g:h") → "p:g:h" + const publicKeyStr = this._decodeBase64(publicKeyBase64); + const [p, g, h] = this._parsePublicKey(publicKeyStr); - // Decode the base64 public key - // Format from backend: base64("p:g:h") where p, g, h are decimal numbers - let publicKeyStr: string; - try { - publicKeyStr = atob(publicKeyBase64); - } catch (e) { - const errorMsg = e instanceof Error ? e.message : String(e); - throw new Error(`Failed to decode public key from base64: ${errorMsg}`); - } + // Generate random r for encryption + const r = BigInt(Math.floor(Math.random() * (Number(p) - 2)) + 1); - // Validate decoded string - if (!publicKeyStr || typeof publicKeyStr !== "string") { - throw new Error("Invalid decoded public key: must be a non-empty string"); - } - - // Parse public key (format: p:g:h separated by colons) - const publicKeyData = publicKeyStr.split(":"); - - if (publicKeyData.length < 3) { - throw new Error( - `Invalid public key format. Expected "p:g:h" but got "${publicKeyStr}"` - ); - } - - // Parse and validate each component - let p: bigint, g: bigint, h: bigint; - try { - p = BigInt(publicKeyData[0]); // Prime - g = BigInt(publicKeyData[1]); // Generator - h = BigInt(publicKeyData[2]); // Public key = g^x mod p - } catch (e) { - const errorMsg = e instanceof Error ? e.message : String(e); - throw new Error(`Failed to parse public key numbers: ${errorMsg}`); - } - - // Validate parameters - if (p <= 0n || g <= 0n || h <= 0n) { - throw new Error("Invalid public key parameters: p, g, h must be positive"); - } - - // Generate random number for encryption - const maxRandom = Number(p) - 2; - if (maxRandom <= 0) { - throw new Error("Public key prime p is too small"); - } - const r = BigInt(Math.floor(Math.random() * maxRandom) + 1); - - // ElGamal encryption: (c1, c2) = (g^r mod p, m * h^r mod p) + // ElGamal: (c1, c2) = (g^r mod p, m * h^r mod p) const c1 = this._modPow(g, r, p); const c2 = (BigInt(vote) * this._modPow(h, r, p)) % p; - // Return as "c1:c2" in base64 - const encrypted = `${c1.toString()}:${c2.toString()}`; - return btoa(encrypted); + // Return encrypted as base64("c1:c2") + return btoa(`${c1}:${c2}`); } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error || "Unknown error"); - console.error("ElGamal encryption failed:", errorMsg); - throw new Error(`Encryption failed: ${errorMsg}`); + const msg = error instanceof Error ? error.message : String(error); + throw new Error(`ElGamal encryption failed: ${msg}`); + } + } + + /** + * Decode base64 string with clear error handling + */ + private static _decodeBase64(base64: string): string { + try { + return atob(base64); + } catch (e) { + throw new Error(`Invalid base64: ${base64.substring(0, 20)}... - ${e}`); + } + } + + /** + * Parse public key from "p:g:h" format + */ + private static _parsePublicKey(keyStr: string): [bigint, bigint, bigint] { + const parts = keyStr.split(":"); + if (parts.length !== 3) { + throw new Error(`Expected "p:g:h" format, got: ${keyStr}`); + } + + try { + const p = BigInt(parts[0]); + const g = BigInt(parts[1]); + const h = BigInt(parts[2]); + + if (p <= 0n || g <= 0n || h <= 0n) { + throw new Error("p, g, h must all be positive"); + } + + return [p, g, h]; + } catch (e) { + throw new Error(`Failed to parse key: ${e}`); } }