I am looking for cryptography libraries that can yield consistent results in both Python and Javascript with regards to generating public-key from private-key, and, deriving shared-key.
Both javascript side and python sides would communicating their public keys with one another (one being the client and other being the server), and they should arrive at the identical shared keys independently without communicating anything else.
I initially wrote the entire code to perform the key-exchange and observed different results being observed in python and js side. I further debugged and found that the public-key calculated in for a given private-key is very different in the two sides. For instance, have a look at the following python and javascript codes and their results:
JavaScript:
var EC = require('elliptic').ec;
var ec = new EC('curve25519');
const privateKeyString1 = '04aeab1acdf96520bb07ab3ef43f5600f3cbfab5ccceb7fae5f93d711b439981';
var key1 = ec.keyFromPrivate(privateKeyString1, 'hex');
console.log('Public key: ' + key1.getPublic('hex')); // Use 'hex' encoding
(base) zenin@ZeninMac tempECDSA % node tempECDSA.js
Public key: 5ff02d90308504eaff5eb30dec4d507503b2c5671dd774fcaaa5d8748a7a82cb
Python:
privateKeyString1 = '04aeab1acdf96520bb07ab3ef43f5600f3cbfab5ccceb7fae5f93d711b439981'
key1 = x25519.X25519PrivateKey.from_private_bytes(bytes.fromhex(privateKeyString1))
public_key_bytes = key1.public_key().public_bytes(encoding = serialization.Encoding.Raw, format = serialization.PublicFormat.Raw)
print(f'Public key: {public_key_bytes.hex()}') # Use 'hex' encoding
(base) zenin@ZeninMac tempECDSA % python tempECDSA.py
Public key: 73844232281fe099d71dc914b21b9d04564e6c980cc8fa6cd7acc6648d489803
Does anyone know of a library that provides consistent results at both ends?
It appears to be that the following JS-library appears to work consistently with the python side. https://github.com/CryptoEsel/js-x25519
The following are test-cases written in JS and Python, respectively, that showcases the same.
const fromHexString = (hexString) =>
Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
const toHexString = (bytes) =>
bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
const getPublicKey = (privateKey) =>
toHexString(X25519.getPublic(fromHexString(privateKey)));
const getSharedKey = (privateKey, publicKey) =>
toHexString(X25519.getSharedKey(
fromHexString(privateKey), fromHexString(publicKey)
));
const alicePrivateKey = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
const alicePublicKey = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
const bobPrivateKey = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
const bobPublicKey = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
const sharedSecretKey = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
// Test1: Verify that the public key is generated as expected
{
const calcAlicePublicKey = getPublicKey(alicePrivateKey);
console.assert(calcAlicePublicKey === alicePublicKey);
const calcBobPublicKey = getPublicKey(bobPrivateKey);
console.assert(calcBobPublicKey === bobPublicKey);
console.log('Test1 is OK');
}
// Test2: Verify that the shared key is generated as expected
{
const calcSharedKey1 = getSharedKey(alicePrivateKey, bobPublicKey);
const calcSharedKey2 = getSharedKey(bobPrivateKey, alicePublicKey);
console.assert(calcSharedKey1 === calcSharedKey2);
console.assert(calcSharedKey1 === sharedSecretKey);
console.log('Test2 is OK');
}
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives import serialization, padding
from cryptography.hazmat.backends import default_backend
def getPublicKey(privateKey):
privateKeyObj = x25519.X25519PrivateKey.from_private_bytes(bytes.fromhex(privateKey))
publicKey = privateKeyObj.public_key().public_bytes(
encoding = serialization.Encoding.Raw,
format = serialization.PublicFormat.Raw
).hex()
return publicKey
def getSharedKey(privateKey, publicKey):
privateKeyObj = x25519.X25519PrivateKey.from_private_bytes(bytes.fromhex(privateKey))
publicKeyObj = x25519.X25519PublicKey.from_public_bytes(bytes.fromhex(publicKey))
sharedKey = privateKeyObj.exchange(publicKeyObj).hex()
return sharedKey
alicePrivateKey = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a'
alicePublicKey = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a'
bobPrivateKey = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb'
bobPublicKey = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f'
sharedSecretKey = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742'
# Test1: Verify that the public key is generated as expected
def test1():
calcAlicePublicKey = getPublicKey(alicePrivateKey)
assert(calcAlicePublicKey == alicePublicKey)
calcBobPublicKey = getPublicKey(bobPrivateKey)
assert(calcBobPublicKey == bobPublicKey)
print('Test1 is OK')
test1()
# Test2: Verify that the shared key is generated as expected
def test2():
calcSharedKey1 = getSharedKey(alicePrivateKey, bobPublicKey)
calcSharedKey2 = getSharedKey(bobPrivateKey, alicePublicKey)
assert(calcSharedKey1 == calcSharedKey2)
assert(calcSharedKey1 == sharedSecretKey)
print('Test2 is OK')
test2()