From 29268777b907394c3ef9088b7ed8971b682341a7 Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Sat, 13 Jan 2018 15:01:03 +0000 Subject: [PATCH] Add encryption for user keys, and a command-line tool for testing --- cmd/userenckey-tool/main.go | 64 +++++++++++++++++++++++++++++++++++++ userenckey/decrypt.go | 17 +++++++--- userenckey/encrypt.go | 32 +++++++++++++++++++ userenckey/encrypt_test.go | 24 ++++++++++++++ 4 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 cmd/userenckey-tool/main.go create mode 100644 userenckey/encrypt.go create mode 100644 userenckey/encrypt_test.go diff --git a/cmd/userenckey-tool/main.go b/cmd/userenckey-tool/main.go new file mode 100644 index 00000000..13b021eb --- /dev/null +++ b/cmd/userenckey-tool/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "bytes" + "encoding/base64" + "flag" + "fmt" + "log" + "os/exec" + + "git.autistici.org/id/keystore/userenckey" +) + +var ( + doGenKeys = flag.Bool("gen-keys", false, "generate user encryption keys with the specified curve") + curve = flag.String("curve", "secp224r1", "EC curve to use") + password = flag.String("password", "", "password") +) + +func genKeys() ([]byte, []byte, error) { + priv, err := exec.Command("sh", "-c", fmt.Sprintf("openssl ecparam -name %s -genkey | openssl pkey", *curve)).Output() + if err != nil { + return nil, nil, err + } + + cmd := exec.Command("sh", "-c", "openssl ec -pubout") + cmd.Stdin = bytes.NewReader(priv) + pub, err := cmd.Output() + if err != nil { + return nil, nil, err + } + + priv, err = userenckey.Encrypt(priv, []byte(*password)) + if err != nil { + return nil, nil, err + } + + return priv, pub, err +} + +func main() { + log.SetFlags(0) + flag.Parse() + + switch { + case *doGenKeys: + if *password == "" { + log.Fatal("must specify --password") + } + + priv, pub, err := genKeys() + if err != nil { + log.Fatal(err) + } + fmt.Printf( + "public key: %s\nprivate key (encrypted): %s\n", + base64.StdEncoding.EncodeToString(pub), + base64.StdEncoding.EncodeToString(priv), + ) + + default: + log.Fatal("no actions specified") + } +} diff --git a/userenckey/decrypt.go b/userenckey/decrypt.go index 3f59eca6..99d7f711 100644 --- a/userenckey/decrypt.go +++ b/userenckey/decrypt.go @@ -7,8 +7,11 @@ import ( "golang.org/x/crypto/scrypt" ) +// ErrBadPassword is returned on decryption failure. var ErrBadPassword = errors.New("could not decrypt key with password") +const aeadAlgo = "AES-SIV" + const ( scryptN = 32768 scryptR = 8 @@ -23,13 +26,17 @@ const ( func Decrypt(encKeys [][]byte, pw []byte) ([]byte, error) { for _, key := range encKeys { dec, err := decryptData(key, pw) - if err != nil { - return dec, err + if err == nil { + return dec, nil } } return nil, ErrBadPassword } +func kdf(pw, salt []byte) ([]byte, error) { + return scrypt.Key(pw, salt, scryptN, scryptR, scryptP, keyLen) +} + func decryptData(data, pw []byte) ([]byte, error) { // The KDF salt is prepended to the encrypted key. if len(data) < saltLen { @@ -40,16 +47,16 @@ func decryptData(data, pw []byte) ([]byte, error) { // Apply the key derivation function to the password to obtain // a 64 byte key. - dk, err := scrypt.Key(pw, salt, scryptN, scryptR, scryptP, keyLen) + dk, err := kdf(pw, salt) if err != nil { return nil, err } // Set up the AES-SIV secret box. - cipher, err := miscreant.NewAESCMACSIV(dk) + cipher, err := miscreant.NewAEAD(aeadAlgo, dk, 0) if err != nil { return nil, err } - return cipher.Open(nil, data) + return cipher.Open(nil, nil, data, nil) } diff --git a/userenckey/encrypt.go b/userenckey/encrypt.go new file mode 100644 index 00000000..877b0381 --- /dev/null +++ b/userenckey/encrypt.go @@ -0,0 +1,32 @@ +package userenckey + +import ( + "crypto/rand" + "io" + + "github.com/miscreant/miscreant/go" +) + +// Encrypt a key with a password and a random salt. +func Encrypt(key, pw []byte) ([]byte, error) { + salt := genRandomSalt() + dk, err := kdf(pw, salt) + if err != nil { + return nil, err + } + + cipher, err := miscreant.NewAEAD(aeadAlgo, dk, 0) + if err != nil { + return nil, err + } + + return cipher.Seal(salt, nil, key, nil), nil +} + +func genRandomSalt() []byte { + var b [saltLen]byte + if _, err := io.ReadFull(rand.Reader, b[:]); err != nil { + panic(err) + } + return b[:] +} diff --git a/userenckey/encrypt_test.go b/userenckey/encrypt_test.go new file mode 100644 index 00000000..36220c26 --- /dev/null +++ b/userenckey/encrypt_test.go @@ -0,0 +1,24 @@ +package userenckey + +import ( + "bytes" + "testing" +) + +func TestEncrypt(t *testing.T) { + pw := []byte("stracchino") + key := []byte("this is a very secret key") + + enc, err := Encrypt(key, pw) + if err != nil { + t.Fatal("Encrypt():", err) + } + + dec, err := Decrypt([][]byte{enc}, pw) + if err != nil { + t.Fatal("Decrypt():", err) + } + if !bytes.Equal(key, dec) { + t.Fatalf("bad decrypted ciphertext: %v", dec) + } +} -- GitLab