192 lines
5.3 KiB
TypeScript
192 lines
5.3 KiB
TypeScript
/**
|
|
* 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);
|
|
}
|