Skip to content

Instantly share code, notes, and snippets.

@fhefh2015
Forked from banks/ecdh.go
Created April 23, 2020 02:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fhefh2015/b5c58525e630e23dcb59a5e505536ca5 to your computer and use it in GitHub Desktop.
Save fhefh2015/b5c58525e630e23dcb59a5e505536ca5 to your computer and use it in GitHub Desktop.
// ecdh implements a simple way to perform Diffie-Hellman Key Exchange using
// Curve25519 on the command line.
//
// NOTE: this is a toy for fun. Don't use it.
//
// See https://godoc.org/golang.org/x/crypto/curve25519 and
// https://cr.yp.to/ecdh.html for more info.
//
// The final shared secret given is the raw shared secret bytes from DH and is
// not typically suitable for direct use as an encryption key as it can leak
// information about the private key used in the exchange. Use it as input to
// your favourite secure key derivation function.
//
// Also note that there is no validation of the other party's public key so it
// assume you got it from a trusted medium etc.
//
// Example usage:
// $ ecdh keygen -out priv1.key
// $ ecdh keygen -out priv2.key
// $ cat priv1.key
// wCwU9JOb0zjBMfOuhGRO2/9OmDY2H0hjxFJkEZrZ404= ~/Downloads cat priv2.key
// qC135wW3HfEsTq4066P3rF1ilOk+MamlW8aPX0XHxFk= ~/Downloads ./ecdh public -k priv1.key > pub1.key
// $ ecdh public -k priv2.key > pub2.key
// $ cat pub1.key
// Q0tqbVI6trOpz84tYJo9+j3TRfXUn7h0o+N3wIDqPQA=
// $ cat pub2.key
// 3BRuRdDXk2bLJM2JVviZ//+8vif9Y0W1M9UdDrBGKiw=
// $ ecdh shared -k priv1.key -public-key-file pub2.key
// Wnh6YlW/1avYAf22H5aehR3FfrKNxjuu1lzWjosyiSw=
// $ ecdh shared -k priv2.key -public-key-file pub1.key
// Wnh6YlW/1avYAf22H5aehR3FfrKNxjuu1lzWjosyiSw=
package main
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"github.com/urfave/cli"
"golang.org/x/crypto/curve25519"
)
func generatePrivateKey(c *cli.Context) error {
secret := make([]byte, 32)
_, err := rand.Read(secret)
if err != nil {
fmt.Printf("ERR: failed to read random bytes: %s", err)
return err
}
// Clamp: https://cr.yp.to/ecdh.html
secret[0] &= 248
secret[31] &= 127
secret[31] |= 64
// b64
encoded := make([]byte, base64.StdEncoding.EncodedLen(32))
base64.StdEncoding.Encode(encoded, secret)
// Write out
filePath := c.String("out")
if filePath == "" {
fmt.Printf("%s\n", encoded)
return nil
}
err = ioutil.WriteFile(filePath, encoded, 0600)
if err != nil {
fmt.Printf("ERR: failed to write file '%s': %s", filePath, err)
return err
}
return nil
}
func generatePublicKey(c *cli.Context) error {
pkSlice, err := readB64File(c.String("priv-key-file"))
if err != nil {
fmt.Printf("ERR: failed to read private key: %s", err)
return err
}
var privKey, pubKey [32]byte
copy(privKey[:], pkSlice)
curve25519.ScalarBaseMult(&pubKey, &privKey)
fmt.Println(base64.StdEncoding.EncodeToString(pubKey[:]))
return nil
}
func readB64File(path string) ([]byte, error) {
encoded, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
encoded = bytes.TrimSpace(encoded)
// 33 because base64 of 32 has padding which _might_ decode to other bits
decode := make([]byte, 33)
if base64.StdEncoding.DecodedLen(len(encoded)) != 33 {
return nil, fmt.Errorf("key is invalid - must be exactly 32 bytes")
}
n, err := base64.StdEncoding.Decode(decode, encoded)
if err != nil {
return nil, err
}
if n != 32 {
return nil, fmt.Errorf("key is invalid - must be exactly 32 bytes")
}
return decode[:32], nil
}
func generateSharedSecret(c *cli.Context) error {
privKeySlice, err := readB64File(c.String("priv-key-file"))
if err != nil {
fmt.Printf("ERR: failed to read private key: %s", err)
return err
}
pubKeySlice, err := readB64File(c.String("public-key-file"))
if err != nil {
fmt.Printf("ERR: failed to read public key: %s", err)
return err
}
var privKey, pubKey, shared [32]byte
copy(privKey[:], privKeySlice)
copy(pubKey[:], pubKeySlice)
curve25519.ScalarMult(&shared, &privKey, &pubKey)
fmt.Println(base64.StdEncoding.EncodeToString(shared[:]))
return nil
}
func main() {
app := cli.NewApp()
app.Name = "ecdh.go"
app.Usage = "simple ECDH on the command line using Curve25519, for funsies"
app.Commands = []cli.Command{
{
Name: "keygen",
Usage: "generate a new Curve25519 private key as raw base64",
Flags: []cli.Flag{
cli.StringFlag{
Name: "out, o",
Value: "",
Usage: "The `FILE` to write private key to. Leave empty for STDOUT",
},
},
Action: generatePrivateKey,
},
{
Name: "public",
Flags: []cli.Flag{
cli.StringFlag{
Name: "priv-key-file, k",
Value: "",
Usage: "The private key `FILE` to generate from",
},
},
Usage: "derive a Curve25519 public key from the provided private key base64",
Action: generatePublicKey,
},
{
Name: "shared",
Flags: []cli.Flag{
cli.StringFlag{
Name: "priv-key-file, k",
Value: "",
Usage: "The private key `FILE` to generate from",
},
cli.StringFlag{
Name: "public-key-file",
Value: "",
Usage: "The `FILE` containing the peer's public key to use.",
},
},
Usage: "derive a Curve25519 shared secret from the provided private key and the other party's public key as base64",
Action: generateSharedSecret,
},
}
app.Run(os.Args)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment