Elliptic Curve Diffie Hellman (ECDH) is used to create a shared key. In this we use the elliptic curve defined as secp256k1 to generate points on the curve. Bob will generate a public key (\(B\)) and private key (\(b\)), as will Alice (\(A\) and \(a\)). They then exchange their public keys, and the shared key will then be \(a \times b \times G\), and where \(G\) is the generator point on the elliptic curve. In this case we will use the Bouncy Castle library and C#.
ECDH using Bouncy Castle and C# |
Outline
The Internet was created with very little concept of security. Basically, we just need to get data from one place to another in a reliable way. The foundation protocols we created, such as IP, TCP, HTTP and FTP had no real integration of security. Luckily Whitfield Diffie and Marty Hellman came along and showed us it was possible for Bob and Alice to openly communicate, and for them to share a secret key. This key could then be used with symmetric key encryption, and thus secure their communications. It’s almost like Newton’s Law of Physics, applied in cybersecurity, and is known as the Diffie-Helman (DH) method. Its concept is based on discrete logs.
With DH, Bob and Alice generate their private keys (b and a), and then compute their public keys (B and A). These are exchanged, and then we can compute a shared secret, and from this, we can generate a shared key using HKDF (HMAC Key Derivation Function):
But, discrete logs have become a little cumbersome as computing power has improved. These improvements have meant it has become easier to crack the discrete log method for relatively small prime numbers, and so, the prime numbers involved have become fairly large (up to around 4,096 bits long). To solve this problem, elliptic curve cryptography (ECC) has provided a ‘drop-in’ solution, and where we can use elliptic curve methods rather than discrete logs. These use prime numbers that are much smaller, such as for just 256-bits for the Bitcoin curve (secp256k1). With this, we have a base point on the curve (G), and then multiply this base point with the private key value to be a public key point:
Theory
Elliptic Curve Diffie Hellman (ECDH) is used to create a shared key. In this example we use secp256k1 (as used in Bitcoin) to generate points on the curve. Its format is:
\(y^2 = x^3+7 \pmod p\)
with a prime number (\(p\)) of \( 2^{256} - 2^{32} - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 \)
Bob will generate a public key and a private key by taking a point on the curve. The private key is a random number (\(b\)) and the Bob's public key (\(B\)) is a point on the curve, and will be:
\(B = b \times G\)
Alice will do the same and generate her public key point (\(A\)) from her private key (\(a\)):
\(A = a \times G\)
They then exchange their public keys. Alice will then use Bob's public key and her private key to calculate:
SharekeyAlice\( = a \times B\)
This will be the same as:
SharekeyAlice\( = a \times b \times G\)
Bob will then use Alice's public key and his private key to determine:
SharekeyBob \(= b \times A\)
This will be the same as:
SharekeyBob\( = b \times a \times G\)
And the shared secret should match. All we have to do now, is to feed it into a Key Derivation Function to produce the shared key.
Code
First we create a folder named "bc_ecdh", and then go into that folder.We can create a Dotnet console project for .NET 8.0 with:
dotnet new console --framework net8.0
Next some code:
namespace ECDH { using Org.BouncyCastle.Security; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Digests; using System.Text; using System.Security.Cryptography; class Program { static void Main(string[] args) { try { var curvename="secp256k1"; var size=128; if (args.Length >0) curvename=args[0]; if (args.Length >1) size=Convert.ToInt32(args[1]); X9ECParameters ecParams = ECNamedCurveTable.GetByName(curvename); var n= new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed() ); Org.BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters keygenParams = new Org.BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters (n, new SecureRandom()); Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator generator = new Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator(); generator.Init(keygenParams); var keyPair = generator.GenerateKeyPair(); var BobprivateKey = (ECPrivateKeyParameters) keyPair.Private; var BobpublicKey = (ECPublicKeyParameters) keyPair.Public; keyPair = generator.GenerateKeyPair(); var AliceprivateKey = (ECPrivateKeyParameters) keyPair.Private; var AlicepublicKey = (ECPublicKeyParameters) keyPair.Public; var exch = new Org.BouncyCastle.Crypto.Agreement.ECDHBasicAgreement(); exch.Init(AliceprivateKey); var secretAlice= exch.CalculateAgreement(BobpublicKey).ToByteArray(); exch = new Org.BouncyCastle.Crypto.Agreement.ECDHBasicAgreement(); exch.Init(BobprivateKey); var secretBob= exch.CalculateAgreement(AlicepublicKey).ToByteArray(); // Use HKDF to derive final key - ignore salt and extra info var hkdf = new HkdfBytesGenerator(new Sha256Digest()); hkdf.Init(new HkdfParameters (secretAlice, null, null)); byte[] derivedKey = new byte[size / 8]; hkdf.GenerateBytes(derivedKey, 0, derivedKey.Length); Console.WriteLine("Secret Alice:\t{0}",Convert.ToHexString(secretAlice)); Console.WriteLine("Secret Bob:\t{0}",Convert.ToHexString(secretBob)); Console.WriteLine("\nDerived Key (using secret and HKDF):\t{0}", Convert.ToHexString(derivedKey)); Console.WriteLine("\n=== Static keys ==="); Console.WriteLine("Bob Private key {0}",BobprivateKey.D); Console.WriteLine("Bob Public key {0}, {1}",BobpublicKey.Q.AffineXCoord,BobpublicKey.Q.AffineYCoord); Console.WriteLine("\nAlice Private key {0}",AliceprivateKey.D); Console.WriteLine("Alice Public key {0}, {1}",AlicepublicKey.Q.AffineXCoord,AlicepublicKey.Q.AffineYCoord); Console.WriteLine("Type: {0}",curvename); Console.WriteLine("\nG={0},{1}",ecParams.G.AffineXCoord,ecParams.G.AffineYCoord); Console.WriteLine("N (order)={0}",ecParams.N); Console.WriteLine("H ={0}",ecParams.H); Console.WriteLine("A ={0}\nB={1}\nField size={2}",ecParams.Curve.A,ecParams.Curve.B,ecParams.Curve.FieldSize); } catch (Exception e) { Console.WriteLine("Error: {0}",e.Message); } } } }
Key Alice: 00921EDABAF33C4BBF29FD968E1480A5B52DC78EAFC5E3EE5A4F61359B12B5B11F Key Bob: 00921EDABAF33C4BBF29FD968E1480A5B52DC78EAFC5E3EE5A4F61359B12B5B11F === Static keys === Bob Private key 91032952456795657614412812070040312238852539823539786432396440172102909903649 Bob Public key 9393e63ae967d9b8e8509f0be3a170e77ed47e175ebed11aa26e5accbb9e7de, 5838a9678805439d4e677b99de9224386f4198e90c28d3434e2d70b6f0d3a32d Alice Private key 44848236077155516339826559897719436768002306152610056029784453249616455213778 Alice Public key 5392d68179117f76f9b487e5bb53f822a05926a6e0553520ca876d8c7435bf8a, ebcca15a6d402a4dfec7362e7e1868afe0c126905f611e895e1964815f9782c2 Type: P-256 G=6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 N (order)=115792089210356248762697446949407573529996955224135760342422259061068512044369 H =1 A =ffffffff00000001000000000000000000000000fffffffffffffffffffffffc B=5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b Field size=256