Hazmat X448 key exchangeX448 is based on Curve 448, and is used for key exchange with ECDH (Elliptic Curve Diffie Hellman). It supports a 224-bit security level, and where we use a 448-bit (56-byte) prime number of \(P = 2^{448} - 2^{224} - 1\). It has improved security over Curve 25519, and which has a 255-bit prime number (\(P = 2^{255} - 19\)). As with X25519, in X448 we use a Montgomery curve (\(v^2 = u^3 + A \times u^2 + u\)) with scalar multiplication. In X448, we use 56-byte string values, rather than 32-byte values for X25519. Overall, X448 uses a little-endian method to store an array of bytes, and has a value of \(A = 39,081\). |
X448 and ECDH
We must thus create a unique encryption key for each routing host. This can be achieved by using X448, and uses ECDH (Elliptic Curve Diffie Hellman). With this we select a base x co-ordinate point of \(G\), and then Bob and Alice generate random values, and determine their public keys. Alice generates \(a\), and Bob generates \(b\). Alice’s public key will be:
\(A= aG \)
and Bob’s public key becomes:
\(B=bG \)
The exchange their values, and Alice multiplies by the value received from Bob by \(a\), and Bob multiplies the value he receives from Alice by \(b\). They should then end up with the same value, and which should be:
\(K= abG \)
For the shared key we only concentrate on the x-co-ordinate of \(K\), and then use a KDF (Key Derivation Function) to generate the derived key. Normally we use HKDF (HMAC KDF) to convert the shared key into a derived key.
Code
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives import serialization import binascii import sys size=32 if (len(sys.argv)>1): size=int(sys.argv[1]) alice_private_key = X448PrivateKey.generate() alice_public_key = alice_private_key.public_key() bob_private_key = X448PrivateKey.generate() bob_public_key = bob_private_key.public_key() shared_key = alice_private_key.exchange(bob_public_key) derived_key = HKDF(algorithm=hashes.SHA256(),length=size,salt=None,info=b'test data',).derive(shared_key) print ("Shared key: ",binascii.b2a_hex(shared_key)) print ("\nDerived key: ",binascii.b2a_hex(derived_key)) pem = alice_private_key.private_bytes(encoding=serialization.Encoding.PEM,format=serialization.PrivateFormat.PKCS8,encryption_algorithm=serialization.NoEncryption()) print ("\nAlice private key",pem.decode()) pem = alice_public_key.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo) print ("\nAlice Public key (PEM):\n",pem.decode()) pem = bob_private_key.private_bytes(encoding=serialization.Encoding.PEM,format=serialization.PrivateFormat.PKCS8,encryption_algorithm=serialization.NoEncryption()) print ("Bob private key",pem.decode()) pem = bob_public_key.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo) print ("\nBob Public key (PEM):\n",pem.decode())
A sample run is:
Shared key: b'80f8faea79066407636f4ab79e355e64c7e563e55862eaa4b134939e4785734fcc95d4eacb935a42de158f190ce8da4210a10dab00ec2afb' Derived key: b'08c26f14596755a38584551428376c5f166eac45602cc61a4f33cf3c2a415243ae4da8b3243343431c8884921cef4cb0d1f272ea76ff6baa2891e5cf8eed586f' Alice private key -----BEGIN PRIVATE KEY----- MEYCAQAwBQYDK2VvBDoEOFjVtZsR8MachaIi/wRNxgi6JtTSprMWnVxJeWOHR9Xb I7RGzM3jAu8KieyBlV7CoJ/klQUcULas -----END PRIVATE KEY----- Alice Public key (PEM): -----BEGIN PUBLIC KEY----- MEIwBQYDK2VvAzkAxwI9NJK82sSOs45lbOL4oNL6xE1rFc4W+5lnWqI9BMOs8ncu pXJ2eRCwpyFDOFBcClx/S2flPes= -----END PUBLIC KEY----- Bob private key -----BEGIN PRIVATE KEY----- MEYCAQAwBQYDK2VvBDoEOBxa+QBL2jAo3pjWyNfDGUagDl0ktc9KXw8F0xQEfauV uL5MONaT2yjRL0AG9H0/Vl8Pou7h4q6C -----END PRIVATE KEY----- Bob Public key (PEM): -----BEGIN PUBLIC KEY----- MEIwBQYDK2VvAzkAfptNmkmUopNmRtn17Jd+g4uzShviWxrpigKudmQcQF/0HJrs eGuKS4uUq4MhDn3HUwjEiWUD9EE= -----END PUBLIC KEY-----