/** * Pure TypeScript implementation of Diffie-Hellman key exchange * * Uses MODP Group 14 (RFC 3526) parameters: * - 2048-bit prime modulus * - Generator = 2 */ // MODP Group 14 parameters (RFC 3526) const PRIME_2048 = BigInt( '0x' + 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' + '29024E088A67CC74020BBEA63B139B22514A08798E3404DD' + 'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' + 'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D' + 'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F' + '83655D23DCA3AD961C62F356208552BB9ED529077096966D' + '670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF', ); const GENERATOR = BigInt(2); /** * Convert Uint8Array to BigInt */ function uint8ArrayToBigInt(bytes: Uint8Array): bigint { let result = BigInt(0); for (let i = 0; i < bytes.length; i++) { result = (result << BigInt(8)) + BigInt(bytes[i]); } return result; } /** * Convert BigInt to Uint8Array of specified length */ function bigIntToUint8Array(value: bigint, byteLength: number): Uint8Array { const result = new Uint8Array(byteLength); let temp = value; for (let i = byteLength - 1; i >= 0; i--) { result[i] = Number(temp & BigInt(0xff)); temp = temp >> BigInt(8); } return result; } /** * Modular exponentiation: (base^exponent) mod modulus * Uses binary exponentiation for efficiency */ function modPow(base: bigint, exponent: bigint, modulus: bigint): bigint { if (modulus === BigInt(1)) return BigInt(0); let result = BigInt(1); base = base % modulus; while (exponent > BigInt(0)) { if (exponent % BigInt(2) === BigInt(1)) { result = (result * base) % modulus; } exponent = exponent >> BigInt(1); base = (base * base) % modulus; } return result; } /** * Generate a Diffie-Hellman public key from a private key * * @param privateKey - Private key as Uint8Array (should be random and less than the prime) * @returns Public key as Uint8Array */ export function generatePublicKey(privateKey: Uint8Array): Uint8Array { const privateKeyBigInt = uint8ArrayToBigInt(privateKey); const publicKeyBigInt = modPow(GENERATOR, privateKeyBigInt, PRIME_2048); return bigIntToUint8Array(publicKeyBigInt, 256); // 2048 bits = 256 bytes } /** * Compute Diffie-Hellman shared secret * * @param privateKey - Your private key as Uint8Array * @param publicKey - Other party's public key as Uint8Array * @returns Shared secret as Uint8Array */ export function diffieHellman( privateKey: Uint8Array, publicKey: Uint8Array, ): Uint8Array { const privateKeyBigInt = uint8ArrayToBigInt(privateKey); const publicKeyBigInt = uint8ArrayToBigInt(publicKey); // Validate that the public key is valid (1 < publicKey < prime-1) if ( publicKeyBigInt <= BigInt(1) || publicKeyBigInt >= PRIME_2048 - BigInt(1) ) { throw new Error('Invalid public key: must be between 1 and prime-1'); } // Compute shared secret: (publicKey^privateKey) mod prime const sharedSecretBigInt = modPow( publicKeyBigInt, privateKeyBigInt, PRIME_2048, ); return bigIntToUint8Array(sharedSecretBigInt, 256); // 2048 bits = 256 bytes } /** * Generate a random private key suitable for Diffie-Hellman * * @returns Random private key as Uint8Array */ export function generatePrivateKey(): Uint8Array { // Generate a random 256-byte private key // In practice, you'd use crypto.getRandomValues() but since this is pure TS: const privateKey = new Uint8Array(256); // Simple pseudo-random generation (NOT cryptographically secure) // In real applications, use crypto.getRandomValues() or similar for (let i = 0; i < privateKey.length; i++) { privateKey[i] = Math.floor(Math.random() * 256); } // Ensure the private key is less than the prime by clearing the most significant bit privateKey[0] &= 0x7f; return privateKey; } /** * Complete Diffie-Hellman key exchange example * * @returns Object with generated keys and shared secret */ export function demonstrateDiffieHellman() { // Alice generates her key pair const alicePrivateKey = generatePrivateKey(); const alicePublicKey = generatePublicKey(alicePrivateKey); // Bob generates his key pair const bobPrivateKey = generatePrivateKey(); const bobPublicKey = generatePublicKey(bobPrivateKey); // Both parties compute the same shared secret const aliceSharedSecret = diffieHellman(alicePrivateKey, bobPublicKey); const bobSharedSecret = diffieHellman(bobPrivateKey, alicePublicKey); // Verify they computed the same secret const secretsMatch = aliceSharedSecret.every( (byte, index) => byte === bobSharedSecret[index], ); return { alicePrivateKey, alicePublicKey, bobPrivateKey, bobPublicKey, aliceSharedSecret, bobSharedSecret, secretsMatch, }; } // Example usage and test if (require.main === module) { console.log('Diffie-Hellman Key Exchange Demo'); console.log('================================'); const demo = demonstrateDiffieHellman(); console.log( 'Alice Public Key (first 8 bytes):', Array.from(demo.alicePublicKey.slice(0, 8)), ); console.log( 'Bob Public Key (first 8 bytes):', Array.from(demo.bobPublicKey.slice(0, 8)), ); console.log( 'Shared Secret (first 8 bytes):', Array.from(demo.aliceSharedSecret.slice(0, 8)), ); console.log('Secrets Match:', demo.secretsMatch); }