Last active
April 23, 2020 02:12
-
-
Save banks/f3f0fd297e7a3606b1e40847cc861b5e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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