Commit 29268777 authored by ale's avatar ale

Add encryption for user keys, and a command-line tool for testing

parent f160f1cb
Pipeline #806 passed with stages
in 47 seconds
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")
}
}
......@@ -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)
}
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[:]
}
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)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment