forked from TrueCloudLab/restic
parent
9afec53c55
commit
f0d7f3f1bd
3 changed files with 117 additions and 23 deletions
|
@ -1,22 +1,76 @@
|
||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sscrypt "github.com/elithrar/simple-scrypt"
|
||||||
"golang.org/x/crypto/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
|
// KDF derives encryption and message authentication keys from the password
|
||||||
// using the supplied parameters N, R and P and the Salt.
|
// using the supplied parameters N, R and P and the Salt.
|
||||||
func KDF(N, R, P int, salt []byte, password string) (*Key, error) {
|
func KDF(p KDFParams, salt []byte, password string) (*Key, error) {
|
||||||
if len(salt) == 0 {
|
if len(salt) != saltLength {
|
||||||
return nil, fmt.Errorf("scrypt() called with empty salt")
|
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{}
|
derKeys := &Key{}
|
||||||
|
|
||||||
keybytes := macKeySize + aesKeySize
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error deriving keys from password: %v", err)
|
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
|
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
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -19,15 +18,6 @@ var (
|
||||||
ErrNoKeyFound = errors.New("wrong password or no key found")
|
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.
|
// Key represents an encrypted master key for a repository.
|
||||||
type Key struct {
|
type Key struct {
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
|
@ -47,6 +37,15 @@ type Key struct {
|
||||||
name string
|
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
|
// createMasterKey creates a new master key in the given backend and encrypts
|
||||||
// it with the password.
|
// it with the password.
|
||||||
func createMasterKey(s *Repository, password string) (*Key, error) {
|
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
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// AddKey adds a new key to an already existing repository.
|
||||||
func AddKey(s *Repository, password string, template *crypto.Key) (*Key, error) {
|
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
|
// fill meta data about key
|
||||||
newkey := &Key{
|
newkey := &Key{
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
KDF: "scrypt",
|
KDF: "scrypt",
|
||||||
N: scryptN,
|
N: KDFParams.N,
|
||||||
R: scryptR,
|
R: KDFParams.R,
|
||||||
P: scryptP,
|
P: KDFParams.P,
|
||||||
}
|
}
|
||||||
|
|
||||||
hn, err := os.Hostname()
|
hn, err := os.Hostname()
|
||||||
|
@ -154,14 +169,13 @@ func AddKey(s *Repository, password string, template *crypto.Key) (*Key, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate random salt
|
// generate random salt
|
||||||
newkey.Salt = make([]byte, scryptSaltsize)
|
newkey.Salt, err = crypto.NewSalt()
|
||||||
n, err := rand.Read(newkey.Salt)
|
if err != nil {
|
||||||
if n != scryptSaltsize || err != nil {
|
panic("unable to read enough random bytes for salt: " + err.Error())
|
||||||
panic("unable to read enough random bytes for salt")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// call KDF to derive user key
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue