How to: use the Elliptic Curve Diffie Hellman library in Go

Go introduced a new library called Elliptic Curve Diffie Hellman(crypto/ecdh) in v1.20.

Let's see how to use this library to exchange encrypted data between two entities without sharing the secret that was used to encrypt the data.

Generate public key on the client

clientCurve := ecdh.P256()
clientPrivKey, err := clientCurve.GenerateKey(rand.Reader)
if err != nil {
    t.Fatalf("Error: %v", err)
}
clientPubKey := clientPrivKey.PublicKey()

Generate public key on the server

serverCurve := ecdh.P256()
serverPrivKey, err := serverCurve.GenerateKey(rand.Reader)
if err != nil {
    t.Fatalf("Error: %v", err)
}
serverPubKey := serverPrivKey.PublicKey()

The clientPubkey and serverPubKey can be shared over the network as plain text.

Generate Secret using the public key

Client generates the clientSecret using the server's public key.

clientSecret, err := clientPrivKey.ECDH(serverPubKey)
if err != nil {
    t.Fatalf("Error: %v", err)
}

Server generates the serverSecret using the client's public key

serverSecret, err := serverPrivKey.ECDH(clientPubKey)
if err != nil {
    t.Fatalf("Error: %v", err)
}

Sharing Encrypted Data

Using the clientSecret the client can encrypt data and share over the network while the server will be able to decrypt the data using the serverSecret.

What was wonderful about this is the fact that the secret in itself was not shared over the network.

Conclusion

Diffie-Hellman key exchange algorithm is used more often than we realize. It is a very nice addition to the Go standard library.

Full Code

package main

import (
    "bytes"
    "crypto/ecdh"
    "crypto/rand"
    "log"
)

func main() {

    clientCurve := ecdh.P256()
    clientPrivKey, err := clientCurve.GenerateKey(rand.Reader)
    if err != nil {
        log.Fatalf("Error: %v", err)
    }
    clientPubKey := clientPrivKey.PublicKey()

    serverCurve := ecdh.P256()
    serverPrivKey, err := serverCurve.GenerateKey(rand.Reader)
    if err != nil {
        log.Fatalf("Error: %v", err)
    }
    serverPubKey := serverPrivKey.PublicKey()

    clientSecret, err := clientPrivKey.ECDH(serverPubKey)
    if err != nil {
        log.Fatalf("Error: %v", err)
    }
    serverSecret, err := serverPrivKey.ECDH(clientPubKey)
    if err != nil {
        log.Fatalf("Error: %v", err)
    }
    if !bytes.Equal(clientSecret, serverSecret) {
        log.Fatalf("The secrets do not match")
    }
    log.Printf("The secrets match")
}