forked from TrueCloudLab/restic
parent
9afec53c55
commit
f0d7f3f1bd
3 changed files with 117 additions and 23 deletions
|
@ -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
|
||||
}
|
||||
|
|
14
src/restic/crypto/kdf_test.go
Normal file
14
src/restic/crypto/kdf_test.go
Normal 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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue