Calibrate scrypt for the current hardware

Closes #17
This commit is contained in:
Alexander Neumann 2016-08-21 12:32:38 +02:00
parent 9afec53c55
commit f0d7f3f1bd
3 changed files with 117 additions and 23 deletions

View file

@ -1,22 +1,76 @@
package crypto
import (
"crypto/rand"
"fmt"
"time"
sscrypt "github.com/elithrar/simple-scrypt"
"golang.org/x/crypto/scrypt"
)
const saltLength = 64
// KDFParams are the default parameters used for the key derivation function KDF().
type KDFParams struct {
N int
R int
P int
}
// DefaultKDFParams are the default parameters used for Calibrate and KDF().
var DefaultKDFParams = KDFParams{
N: sscrypt.DefaultParams.N,
R: sscrypt.DefaultParams.R,
P: sscrypt.DefaultParams.P,
}
// Calibrate determines new KDF parameters for the current hardware.
func Calibrate(timeout time.Duration, memory int) (KDFParams, error) {
defaultParams := sscrypt.Params{
N: DefaultKDFParams.N,
R: DefaultKDFParams.R,
P: DefaultKDFParams.P,
DKLen: sscrypt.DefaultParams.DKLen,
SaltLen: sscrypt.DefaultParams.SaltLen,
}
params, err := sscrypt.Calibrate(timeout, memory, defaultParams)
if err != nil {
return DefaultKDFParams, err
}
return KDFParams{
N: params.N,
R: params.R,
P: params.P,
}, nil
}
// KDF derives encryption and message authentication keys from the password
// using the supplied parameters N, R and P and the Salt.
func KDF(N, R, P int, salt []byte, password string) (*Key, error) {
if len(salt) == 0 {
return nil, fmt.Errorf("scrypt() called with empty salt")
func KDF(p KDFParams, salt []byte, password string) (*Key, error) {
if len(salt) != saltLength {
return nil, fmt.Errorf("scrypt() called with invalid salt bytes (len %d)", len(salt))
}
// make sure we have valid parameters
params := sscrypt.Params{
N: p.N,
R: p.R,
P: p.P,
DKLen: sscrypt.DefaultParams.DKLen,
SaltLen: len(salt),
}
if err := params.Check(); err != nil {
return nil, err
}
derKeys := &Key{}
keybytes := macKeySize + aesKeySize
scryptKeys, err := scrypt.Key([]byte(password), salt, N, R, P, keybytes)
scryptKeys, err := scrypt.Key([]byte(password), salt, p.N, p.R, p.P, keybytes)
if err != nil {
return nil, fmt.Errorf("error deriving keys from password: %v", err)
}
@ -33,3 +87,15 @@ func KDF(N, R, P int, salt []byte, password string) (*Key, error) {
return derKeys, nil
}
// NewSalt returns new random salt bytes to use with KDF(). If NewSalt returns
// an error, this is a grave situation and the program must abort and terminate.
func NewSalt() ([]byte, error) {
buf := make([]byte, saltLength)
n, err := rand.Read(buf)
if n != saltLength || err != nil {
panic("unable to read enough random bytes for new salt")
}
return buf, nil
}

View file

@ -0,0 +1,14 @@
package crypto
import (
"testing"
"time"
)
func TestCalibrate(t *testing.T) {
params, err := Calibrate(100*time.Millisecond, 50)
if err != nil {
t.Fatal(err)
}
t.Logf("testing calibrate, params after: %v", params)
}

View file

@ -1,7 +1,6 @@
package repository
import (
"crypto/rand"
"encoding/json"
"errors"
"fmt"
@ -19,15 +18,6 @@ var (
ErrNoKeyFound = errors.New("wrong password or no key found")
)
// TODO: figure out scrypt values on the fly depending on the current
// hardware.
const (
scryptN = 65536
scryptR = 8
scryptP = 1
scryptSaltsize = 64
)
// Key represents an encrypted master key for a repository.
type Key struct {
Created time.Time `json:"created"`
@ -47,6 +37,15 @@ type Key struct {
name string
}
// KDFParams tracks the parameters used for the KDF. If not set, it will be
// calibrated on the first run of AddKey().
var KDFParams *crypto.KDFParams
var (
KDFTimeout = 500 * time.Millisecond // timeout for KDF
KDFMemory = 60 // max memory for KDF, in MiB
)
// createMasterKey creates a new master key in the given backend and encrypts
// it with the password.
func createMasterKey(s *Repository, password string) (*Key, error) {
@ -67,7 +66,12 @@ func OpenKey(s *Repository, name string, password string) (*Key, error) {
}
// derive user key
k.user, err = crypto.KDF(k.N, k.R, k.P, k.Salt, password)
params := crypto.KDFParams{
N: k.N,
R: k.R,
P: k.P,
}
k.user, err = crypto.KDF(params, k.Salt, password)
if err != nil {
return nil, err
}
@ -134,13 +138,24 @@ func LoadKey(s *Repository, name string) (k *Key, err error) {
// AddKey adds a new key to an already existing repository.
func AddKey(s *Repository, password string, template *crypto.Key) (*Key, error) {
// make sure we have valid KDF parameters
if KDFParams == nil {
p, err := crypto.Calibrate(KDFTimeout, KDFMemory)
if err != nil {
return nil, err
}
KDFParams = &p
debug.Log("repository.AddKey", "calibrated KDF parameters are %v", p)
}
// fill meta data about key
newkey := &Key{
Created: time.Now(),
KDF: "scrypt",
N: scryptN,
R: scryptR,
P: scryptP,
N: KDFParams.N,
R: KDFParams.R,
P: KDFParams.P,
}
hn, err := os.Hostname()
@ -154,14 +169,13 @@ func AddKey(s *Repository, password string, template *crypto.Key) (*Key, error)
}
// generate random salt
newkey.Salt = make([]byte, scryptSaltsize)
n, err := rand.Read(newkey.Salt)
if n != scryptSaltsize || err != nil {
panic("unable to read enough random bytes for salt")
newkey.Salt, err = crypto.NewSalt()
if err != nil {
panic("unable to read enough random bytes for salt: " + err.Error())
}
// call KDF to derive user key
newkey.user, err = crypto.KDF(newkey.N, newkey.R, newkey.P, newkey.Salt, password)
newkey.user, err = crypto.KDF(*KDFParams, newkey.Salt, password)
if err != nil {
return nil, err
}