From 7e6fc15ece2e73590f984b2a850feb3afa16949e Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Aug 2016 10:50:02 +0200 Subject: [PATCH 1/8] Vendor github.com/elithrar/simple-scrypt --- vendor/manifest | 6 + .../github.com/elithrar/simple-scrypt/LICENSE | 22 ++ .../elithrar/simple-scrypt/README.md | 155 +++++++++ .../elithrar/simple-scrypt/scrypt.go | 295 ++++++++++++++++++ .../elithrar/simple-scrypt/scrypt_test.go | 156 +++++++++ 5 files changed, 634 insertions(+) create mode 100644 vendor/src/github.com/elithrar/simple-scrypt/LICENSE create mode 100644 vendor/src/github.com/elithrar/simple-scrypt/README.md create mode 100644 vendor/src/github.com/elithrar/simple-scrypt/scrypt.go create mode 100644 vendor/src/github.com/elithrar/simple-scrypt/scrypt_test.go diff --git a/vendor/manifest b/vendor/manifest index 1fc43f174..9b570c3bc 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -7,6 +7,12 @@ "revision": "18419ee53958df28fcfc9490fe6123bd59e237bb", "branch": "HEAD" }, + { + "importpath": "github.com/elithrar/simple-scrypt", + "repository": "https://github.com/elithrar/simple-scrypt", + "revision": "cbb1ebac08e2ca5495a43f4ef5555e61a7ec7677", + "branch": "master" + }, { "importpath": "github.com/jessevdk/go-flags", "repository": "https://github.com/jessevdk/go-flags", diff --git a/vendor/src/github.com/elithrar/simple-scrypt/LICENSE b/vendor/src/github.com/elithrar/simple-scrypt/LICENSE new file mode 100644 index 000000000..e8988e7b4 --- /dev/null +++ b/vendor/src/github.com/elithrar/simple-scrypt/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Matthew Silverlock (matt@eatsleeprepeat.net) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/src/github.com/elithrar/simple-scrypt/README.md b/vendor/src/github.com/elithrar/simple-scrypt/README.md new file mode 100644 index 000000000..4a093e186 --- /dev/null +++ b/vendor/src/github.com/elithrar/simple-scrypt/README.md @@ -0,0 +1,155 @@ +# simple-scrypt +[![GoDoc](https://godoc.org/github.com/elithrar/simple-scrypt?status.svg)](https://godoc.org/github.com/elithrar/simple-scrypt) [![Build Status](https://travis-ci.org/elithrar/simple-scrypt.svg?branch=master)](https://travis-ci.org/elithrar/simple-scrypt) + +simple-scrypt provides a convenience wrapper around Go's existing +[scrypt](http://golang.org/x/crypto/scrypt) package that makes it easier to +securely derive strong keys ("hash user passwords"). This library allows you to: + +* Generate a scrypt derived key with a crytographically secure salt and sane + default parameters for N, r and p. +* Upgrade the parameters used to generate keys as hardware improves by storing + them with the derived key (the scrypt spec. doesn't allow for this by + default). +* Provide your own parameters (if you wish to). + +The API closely mirrors Go's [bcrypt](https://golang.org/x/crypto/bcrypt) +library in an effort to make it easy to migrate—and because it's an easy to grok +API. + +## Installation + +With a [working Go toolchain](https://golang.org/doc/code.html): + +```sh +go get -u github.com/elithrar/simple-scrypt +``` + +## Example + +simple-scrypt doesn't try to re-invent the wheel or do anything "special". It +wraps the `scrypt.Key` function as thinly as possible, generates a +crytographically secure salt for you using Go's `crypto/rand` package, and +returns the derived key with the parameters prepended: + +```go +package main + +import( + "fmt" + "log" + + "github.com/elithrar/simple-scrypt" +) + +func main() { + // e.g. r.PostFormValue("password") + passwordFromForm := "prew8fid9hick6c" + + // Generates a derived key of the form "N$r$p$salt$dk" where N, r and p are defined as per + // Colin Percival's scrypt paper: http://www.tarsnap.com/scrypt/scrypt.pdf + // scrypt.Defaults (N=16384, r=8, p=1) makes it easy to provide these parameters, and + // (should you wish) provide your own values via the scrypt.Params type. + hash, err := scrypt.GenerateFromPassword([]byte(passwordFromForm), scrypt.DefaultParams) + if err != nil { + log.Fatal(err) + } + + // Print the derived key with its parameters prepended. + fmt.Printf("%s\n", hash) + + // Uses the parameters from the existing derived key. Return an error if they don't match. + err := scrypt.CompareHashAndPassword(hash, []byte(passwordFromForm)) + if err != nil { + log.Fatal(err) + } +} +``` + +## Upgrading Parameters + +Upgrading derived keys from a set of parameters to a "stronger" set of parameters +as hardware improves, or as you scale (and move your auth process to separate +hardware), can be pretty useful. Here's how to do it with simple-scrypt: + +```go +func main() { + // SCENE: We've successfully authenticated a user, compared their submitted + // (cleartext) password against the derived key stored in our database, and + // now want to upgrade the parameters (more rounds, more parallelism) to + // reflect some shiny new hardware we just purchased. As the user is logging + // in, we can retrieve the parameters used to generate their key, and if + // they don't match our "new" parameters, we can re-generate the key while + // we still have the cleartext password in memory + // (e.g. before the HTTP request ends). + current, err := scrypt.Cost(hash) + if err != nil { + log.Fatal(err) + } + + // Now to check them against our own Params struct (e.g. using reflect.DeepEquals) + // and determine whether we want to generate a new key with our "upgraded" parameters. + slower := scrypt.Params{ + N: 32768, + R: 8, + P: 2, + SaltLen: 16, + DKLen: 32, + } + + if !reflect.DeepEqual(current, slower) { + // Re-generate the key with the slower parameters + // here using scrypt.GenerateFromPassword + } +} +``` + +## Automatically Determining Parameters + +Thanks to the work by [tgulacsi](https://github.com/tgulacsi), you can have simple-scrypt +automatically determine the optimal parameters for you (time vs. memory). You should run this once +on program startup, as calibrating parameters can be an expensive operation. + +```go +var params scrypt.Params + +func main() { + var err error + // 500ms, 64MB of RAM per hash. + params, err = scrypt.Calibrate(500*time.Millisecond, 64, Params{}) + if err != nil { + return nil, err + } + + ... +} + +func RegisterUserHandler(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Make sure you validate: not empty, not too long, etc. + email := r.PostFormValue("email") + pass := r.PostFormValue("password") + + // Use our calibrated parameters + hash, err := scrypt.GenerateFromPassword([]byte(pass), params) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Save to DB, etc. +} +``` + +Be aware that increasing these, whilst making it harder to brute-force the resulting hash, also +increases the risk of a denial-of-service attack against your server. A surge in authenticate +attempts (even if legitimate!) could consume all available resources. + +## License + +MIT Licensed. See LICENSE file for details. + diff --git a/vendor/src/github.com/elithrar/simple-scrypt/scrypt.go b/vendor/src/github.com/elithrar/simple-scrypt/scrypt.go new file mode 100644 index 000000000..1f1c085b6 --- /dev/null +++ b/vendor/src/github.com/elithrar/simple-scrypt/scrypt.go @@ -0,0 +1,295 @@ +// Package scrypt provides a convenience wrapper around Go's existing scrypt package +// that makes it easier to securely derive strong keys from weak +// inputs (i.e. user passwords). +// The package provides password generation, constant-time comparison and +// parameter upgrading for scrypt derived keys. +package scrypt + +import ( + "crypto/rand" + "crypto/subtle" + "encoding/hex" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "golang.org/x/crypto/scrypt" +) + +// Constants +const ( + maxInt = 1<<31 - 1 + minDKLen = 16 // the minimum derived key length in bytes. + minSaltLen = 8 // the minimum allowed salt length in bytes. +) + +// Params describes the input parameters to the scrypt +// key derivation function as per Colin Percival's scrypt +// paper: http://www.tarsnap.com/scrypt/scrypt.pdf +type Params struct { + N int // CPU/memory cost parameter (logN) + R int // block size parameter (octets) + P int // parallelisation parameter (positive int) + SaltLen int // bytes to use as salt (octets) + DKLen int // length of the derived key (octets) +} + +// DefaultParams provides sensible default inputs into the scrypt function +// for interactive use (i.e. web applications). +// These defaults will consume approxmiately 16MB of memory (128 * r * N). +// The default key length is 256 bits. +var DefaultParams = Params{N: 16384, R: 8, P: 1, SaltLen: 16, DKLen: 32} + +// ErrInvalidHash is returned when failing to parse a provided scrypt +// hash and/or parameters. +var ErrInvalidHash = errors.New("scrypt: the provided hash is not in the correct format") + +// ErrInvalidParams is returned when the cost parameters (N, r, p), salt length +// or derived key length are invalid. +var ErrInvalidParams = errors.New("scrypt: the parameters provided are invalid") + +// ErrMismatchedHashAndPassword is returned when a password (hashed) and +// given hash do not match. +var ErrMismatchedHashAndPassword = errors.New("scrypt: the hashed password does not match the hash of the given password") + +// GenerateRandomBytes returns securely generated random bytes. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + // err == nil only if len(b) == n + if err != nil { + return nil, err + } + + return b, nil +} + +// GenerateFromPassword returns the derived key of the password using the +// parameters provided. The parameters are prepended to the derived key and +// separated by the "$" character (0x24). +// If the parameters provided are less than the minimum acceptable values, +// an error will be returned. +func GenerateFromPassword(password []byte, params Params) ([]byte, error) { + salt, err := GenerateRandomBytes(params.SaltLen) + if err != nil { + return nil, err + } + + if err := params.Check(); err != nil { + return nil, err + } + + // scrypt.Key returns the raw scrypt derived key. + dk, err := scrypt.Key(password, salt, params.N, params.R, params.P, params.DKLen) + if err != nil { + return nil, err + } + + // Prepend the params and the salt to the derived key, each separated + // by a "$" character. The salt and the derived key are hex encoded. + return []byte(fmt.Sprintf("%d$%d$%d$%x$%x", params.N, params.R, params.P, salt, dk)), nil +} + +// CompareHashAndPassword compares a derived key with the possible cleartext +// equivalent. The parameters used in the provided derived key are used. +// The comparison performed by this function is constant-time. It returns nil +// on success, and an error if the derived keys do not match. +func CompareHashAndPassword(hash []byte, password []byte) error { + // Decode existing hash, retrieve params and salt. + params, salt, dk, err := decodeHash(hash) + if err != nil { + return err + } + + // scrypt the cleartext password with the same parameters and salt + other, err := scrypt.Key(password, salt, params.N, params.R, params.P, params.DKLen) + if err != nil { + return err + } + + // Constant time comparison + if subtle.ConstantTimeCompare(dk, other) == 1 { + return nil + } + + return ErrMismatchedHashAndPassword +} + +// Check checks that the parameters are valid for input into the +// scrypt key derivation function. +func (p *Params) Check() error { + // Validate N + if p.N > maxInt || p.N <= 1 || p.N%2 != 0 { + return ErrInvalidParams + } + + // Validate r + if p.R < 1 || p.R > maxInt { + return ErrInvalidParams + } + + // Validate p + if p.P < 1 || p.P > maxInt { + return ErrInvalidParams + } + + // Validate that r & p don't exceed 2^30 and that N, r, p values don't + // exceed the limits defined by the scrypt algorithm. + if uint64(p.R)*uint64(p.P) >= 1<<30 || p.R > maxInt/128/p.P || p.R > maxInt/256 || p.N > maxInt/128/p.R { + return ErrInvalidParams + } + + // Validate the salt length + if p.SaltLen < minSaltLen || p.SaltLen > maxInt { + return ErrInvalidParams + } + + // Validate the derived key length + if p.DKLen < minDKLen || p.DKLen > maxInt { + return ErrInvalidParams + } + + return nil +} + +// decodeHash extracts the parameters, salt and derived key from the +// provided hash. It returns an error if the hash format is invalid and/or +// the parameters are invalid. +func decodeHash(hash []byte) (Params, []byte, []byte, error) { + vals := strings.Split(string(hash), "$") + + // P, N, R, salt, scrypt derived key + if len(vals) != 5 { + return Params{}, nil, nil, ErrInvalidHash + } + + var params Params + var err error + + params.N, err = strconv.Atoi(vals[0]) + if err != nil { + return params, nil, nil, ErrInvalidHash + } + + params.R, err = strconv.Atoi(vals[1]) + if err != nil { + return params, nil, nil, ErrInvalidHash + } + + params.P, err = strconv.Atoi(vals[2]) + if err != nil { + return params, nil, nil, ErrInvalidHash + } + + salt, err := hex.DecodeString(vals[3]) + if err != nil { + return params, nil, nil, ErrInvalidHash + } + params.SaltLen = len(salt) + + dk, err := hex.DecodeString(vals[4]) + if err != nil { + return params, nil, nil, ErrInvalidHash + } + params.DKLen = len(dk) + + if err := params.Check(); err != nil { + return params, nil, nil, err + } + + return params, salt, dk, nil +} + +// Cost returns the scrypt parameters used to generate the derived key. This +// allows a package user to increase the cost (in time & resources) used as +// computational performance increases over time. +func Cost(hash []byte) (Params, error) { + params, _, _, err := decodeHash(hash) + + return params, err +} + +// Calibrate returns the hardest parameters (not weaker than the given params), +// allowed by the given limits. +// The returned params will not use more memory than the given (MiB); +// will not take more time than the given timeout, but more than timeout/2. +// +// +// The default timeout (when the timeout arg is zero) is 200ms. +// The default memMiBytes (when memMiBytes is zero) is 16MiB. +// The default parameters (when params == Params{}) is DefaultParams. +func Calibrate(timeout time.Duration, memMiBytes int, params Params) (Params, error) { + p := params + if p.N == 0 || p.R == 0 || p.P == 0 || p.SaltLen == 0 || p.DKLen == 0 { + p = DefaultParams + } else if err := p.Check(); err != nil { + return p, err + } + if timeout == 0 { + timeout = 200 * time.Millisecond + } + if memMiBytes == 0 { + memMiBytes = 16 + } + salt, err := GenerateRandomBytes(p.SaltLen) + if err != nil { + return p, err + } + password := []byte("weakpassword") + + // First, we calculate the minimal required time. + start := time.Now() + if _, err := scrypt.Key(password, salt, p.N, p.R, p.P, p.DKLen); err != nil { + return p, err + } + dur := time.Since(start) + + for dur < timeout && p.N < maxInt>>1 { + p.N <<= 1 + } + + // Memory usage is at least 128 * r * N, see + // http://blog.ircmaxell.com/2014/03/why-i-dont-recommend-scrypt.html + // or https://drupal.org/comment/4675994#comment-4675994 + + var again bool + memBytes := memMiBytes << 20 + // If we'd use more memory then the allowed, we can tune the memory usage + for 128*p.R*p.N > memBytes { + if p.R > 1 { + // by lowering r + p.R-- + } else if p.N > 16 { + again = true + p.N >>= 1 + } else { + break + } + } + if !again { + return p, p.Check() + } + + // We have to compensate the lowering of N, by increasing p. + for i := 0; i < 10 && p.P > 0; i++ { + start := time.Now() + if _, err := scrypt.Key(password, salt, p.N, p.R, p.P, p.DKLen); err != nil { + return p, err + } + dur := time.Since(start) + if dur < timeout/2 { + p.P = int(float64(p.P)*float64(timeout/dur) + 1) + } else if dur > timeout && p.P > 1 { + p.P-- + } else { + break + } + } + + return p, p.Check() +} diff --git a/vendor/src/github.com/elithrar/simple-scrypt/scrypt_test.go b/vendor/src/github.com/elithrar/simple-scrypt/scrypt_test.go new file mode 100644 index 000000000..bf438b190 --- /dev/null +++ b/vendor/src/github.com/elithrar/simple-scrypt/scrypt_test.go @@ -0,0 +1,156 @@ +package scrypt + +import ( + "fmt" + "reflect" + "testing" + "time" +) + +// Test cases +var ( + testLengths = []int{1, 8, 16, 32, 100, 500, 2500} + password = "super-secret-password" +) + +var testParams = []struct { + pass bool + params Params +}{ + {true, Params{16384, 8, 1, 32, 64}}, + {true, Params{16384, 8, 1, 16, 32}}, + {true, Params{65536, 8, 1, 16, 64}}, + {true, Params{1048576, 8, 2, 64, 128}}, + {false, Params{-1, 8, 1, 16, 32}}, // invalid N + {false, Params{0, 8, 1, 16, 32}}, // invalid N + {false, Params{1 << 31, 8, 1, 16, 32}}, // invalid N + {false, Params{16384, 0, 12, 16, 32}}, // invalid R + {false, Params{16384, 8, 0, 16, 32}}, // invalid R > maxInt/128/P + {false, Params{16384, 1 << 24, 1, 16, 32}}, // invalid R > maxInt/256 + {false, Params{1 << 31, 8, 0, 16, 32}}, // invalid p < 0 + {false, Params{4096, 8, 1, 5, 32}}, // invalid SaltLen + {false, Params{4096, 8, 1, 16, 2}}, // invalid DKLen +} + +var testHashes = []struct { + pass bool + hash string +}{ + {false, "1$8$1$9003d0e8e69482843e6bd560c2c9cd94$1976f233124e0ee32bb2678eb1b0ed668eb66cff6fa43279d1e33f6e81af893b"}, // N too small + {false, "$9003d0e8e69482843e6bd560c2c9cd94$1976f233124e0ee32bb2678eb1b0ed668eb66cff6fa43279d1e33f6e81af893b"}, // too short + {false, "16384#8#1#18fbc325efa37402d27c3c2172900cbf$d4e5e1b9eedc1a6a14aad6624ab57b7b42ae75b9c9845fde32de765835f2aaf9"}, // incorrect separators + {false, "16384$nogood$1$18fbc325efa37402d27c3c2172900cbf$d4e5e1b9eedc1a6a14aad6624ab57b7b42ae75b9c9845fde32de765835f2aaf9"}, // invalid R + {false, "16384$8$abc1$18fbc325efa37402d27c3c2172900cbf$d4e5e1b9eedc1a6a14aad6624ab57b7b42ae75b9c9845fde32de765835f2aaf9"}, // invalid P + {false, "16384$8$1$Tk9QRQ==$d4e5e1b9eedc1a6a14aad6624ab57b7b42ae75b9c9845fde32de765835f2aaf9"}, // invalid salt (not hex) + {false, "16384$8$1$18fbc325efa37402d27c3c2172900cbf$42ae====/75b9c9845fde32de765835f2aaf9"}, // invalid dk (not hex) +} + +func TestGenerateRandomBytes(t *testing.T) { + for _, v := range testLengths { + _, err := GenerateRandomBytes(v) + if err != nil { + t.Fatalf("failed to generate random bytes") + } + } +} + +func TestGenerateFromPassword(t *testing.T) { + for _, v := range testParams { + _, err := GenerateFromPassword([]byte(password), v.params) + if err != nil && v.pass == true { + t.Fatalf("no error was returned when expected for params: %+v", v.params) + } + } +} + +func TestCompareHashAndPassword(t *testing.T) { + hash, err := GenerateFromPassword([]byte(password), DefaultParams) + if err != nil { + t.Fatal(err) + } + + if err := CompareHashAndPassword(hash, []byte(password)); err != nil { + t.Fatal(err) + } + + if err := CompareHashAndPassword(hash, []byte("invalid-password")); err == nil { + t.Fatalf("mismatched passwords did not produce an error") + } + + invalidHash := []byte("$166$$11$a2ad56a415af5") + if err := CompareHashAndPassword(invalidHash, []byte(password)); err == nil { + t.Fatalf("did not identify an invalid hash") + } + +} + +func TestCost(t *testing.T) { + hash, err := GenerateFromPassword([]byte(password), DefaultParams) + if err != nil { + t.Fatal(err) + } + + params, err := Cost(hash) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(params, DefaultParams) { + t.Fatal("cost mismatch: parameters used did not match those retrieved") + } +} + +func TestDecodeHash(t *testing.T) { + for _, v := range testHashes { + _, err := Cost([]byte(v.hash)) + if err == nil && v.pass == false { + t.Fatal("invalid hash: did not correctly detect invalid password hash") + } + } +} + +func TestCalibrate(t *testing.T) { + timeout := 500 * time.Millisecond + for testNum, tc := range []struct { + MemMiB int + }{ + {64}, + {32}, + {16}, + {8}, + {1}, + } { + var ( + p Params + err error + ) + p, err = Calibrate(timeout, tc.MemMiB, p) + if err != nil { + t.Fatalf("%d. %#v: %v", testNum, p, err) + } + if (128*p.R*p.N)>>20 > tc.MemMiB { + t.Errorf("%d. wanted memory limit %d, got %d.", testNum, tc.MemMiB, (128*p.R*p.N)>>20) + } + start := time.Now() + _, err = GenerateFromPassword([]byte(password), p) + dur := time.Since(start) + t.Logf("GenerateFromPassword with %#v took %s (%v)", p, dur, err) + if err != nil { + t.Fatalf("%d. GenerateFromPassword with %#v: %v", testNum, p, err) + } + if dur < timeout/2 { + t.Errorf("%d. GenerateFromPassword was too fast (wanted around %s, got %s) with %#v.", testNum, timeout, dur, p) + } else if timeout*2 < dur { + t.Errorf("%d. GenerateFromPassword took too long (wanted around %s, got %s) with %#v.", testNum, timeout, dur, p) + } + } +} + +func ExampleCalibrate() { + p, err := Calibrate(1*time.Second, 128, Params{}) + if err != nil { + panic(err) + } + dk, err := GenerateFromPassword([]byte("super-secret-password"), p) + fmt.Printf("generated password is %q (%v)", dk, err) +} From 11098d6eb055650c09feace9141b01ea951aa1c7 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Aug 2016 11:33:46 +0200 Subject: [PATCH 2/8] Move KDF() to kdf.go --- src/restic/crypto/crypto.go | 29 ----------------------------- src/restic/crypto/kdf.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 29 deletions(-) create mode 100644 src/restic/crypto/kdf.go diff --git a/src/restic/crypto/crypto.go b/src/restic/crypto/crypto.go index 559569679..17cfa3097 100644 --- a/src/restic/crypto/crypto.go +++ b/src/restic/crypto/crypto.go @@ -9,7 +9,6 @@ import ( "fmt" "golang.org/x/crypto/poly1305" - "golang.org/x/crypto/scrypt" ) const ( @@ -315,34 +314,6 @@ func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) ([]byte, error return plaintext, 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") - } - - derKeys := &Key{} - - keybytes := macKeySize + aesKeySize - scryptKeys, err := scrypt.Key([]byte(password), salt, N, R, P, keybytes) - if err != nil { - return nil, fmt.Errorf("error deriving keys from password: %v", err) - } - - if len(scryptKeys) != keybytes { - return nil, fmt.Errorf("invalid numbers of bytes expanded from scrypt(): %d", len(scryptKeys)) - } - - // first 32 byte of scrypt output is the encryption key - copy(derKeys.Encrypt[:], scryptKeys[:aesKeySize]) - - // next 32 byte of scrypt output is the mac key, in the form k||r - macKeyFromSlice(&derKeys.MAC, scryptKeys[aesKeySize:]) - - return derKeys, nil -} - // Valid tests if the key is valid. func (k *Key) Valid() bool { return k.Encrypt.Valid() && k.MAC.Valid() diff --git a/src/restic/crypto/kdf.go b/src/restic/crypto/kdf.go new file mode 100644 index 000000000..3551fff60 --- /dev/null +++ b/src/restic/crypto/kdf.go @@ -0,0 +1,35 @@ +package crypto + +import ( + "fmt" + + "golang.org/x/crypto/scrypt" +) + +// 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") + } + + derKeys := &Key{} + + keybytes := macKeySize + aesKeySize + scryptKeys, err := scrypt.Key([]byte(password), salt, N, R, P, keybytes) + if err != nil { + return nil, fmt.Errorf("error deriving keys from password: %v", err) + } + + if len(scryptKeys) != keybytes { + return nil, fmt.Errorf("invalid numbers of bytes expanded from scrypt(): %d", len(scryptKeys)) + } + + // first 32 byte of scrypt output is the encryption key + copy(derKeys.Encrypt[:], scryptKeys[:aesKeySize]) + + // next 32 byte of scrypt output is the mac key, in the form k||r + macKeyFromSlice(&derKeys.MAC, scryptKeys[aesKeySize:]) + + return derKeys, nil +} From 9afec53c55d3b6f8bd1184e7d75993208e3a8b2b Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Aug 2016 11:33:53 +0200 Subject: [PATCH 3/8] Remove crypto reader/writer (unused) --- src/restic/crypto/crypto_test.go | 170 +------------------------------ src/restic/crypto/reader.go | 87 ---------------- src/restic/crypto/writer.go | 88 ---------------- 3 files changed, 2 insertions(+), 343 deletions(-) delete mode 100644 src/restic/crypto/reader.go delete mode 100644 src/restic/crypto/writer.go diff --git a/src/restic/crypto/crypto_test.go b/src/restic/crypto/crypto_test.go index f5e175f33..fe799da77 100644 --- a/src/restic/crypto/crypto_test.go +++ b/src/restic/crypto/crypto_test.go @@ -4,12 +4,12 @@ import ( "bytes" "crypto/rand" "io" - "io/ioutil" "testing" - "github.com/restic/chunker" "restic/crypto" . "restic/test" + + "github.com/restic/chunker" ) const testLargeCrypto = false @@ -128,25 +128,6 @@ func TestLargeEncrypt(t *testing.T) { } } -func BenchmarkEncryptWriter(b *testing.B) { - size := 8 << 20 // 8MiB - - k := crypto.NewRandomKey() - - b.ResetTimer() - b.SetBytes(int64(size)) - - for i := 0; i < b.N; i++ { - rd := RandomLimitReader(23, size) - wr := crypto.EncryptTo(k, ioutil.Discard) - n, err := io.Copy(wr, rd) - OK(b, err) - OK(b, wr.Close()) - Assert(b, n == int64(size), - "not enough bytes writter: want %d, got %d", size, n) - } -} - func BenchmarkEncrypt(b *testing.B) { size := 8 << 20 // 8MiB data := make([]byte, size) @@ -163,55 +144,6 @@ func BenchmarkEncrypt(b *testing.B) { } } -func BenchmarkDecryptReader(b *testing.B) { - size := 8 << 20 // 8MiB - buf := Random(23, size) - k := crypto.NewRandomKey() - - ciphertext := make([]byte, len(buf)+crypto.Extension) - _, err := crypto.Encrypt(k, ciphertext, buf) - OK(b, err) - - rd := bytes.NewReader(ciphertext) - - b.ResetTimer() - b.SetBytes(int64(size)) - - for i := 0; i < b.N; i++ { - rd.Seek(0, 0) - decRd, err := crypto.DecryptFrom(k, rd) - OK(b, err) - - _, err = io.Copy(ioutil.Discard, decRd) - OK(b, err) - } -} - -func BenchmarkEncryptDecryptReader(b *testing.B) { - k := crypto.NewRandomKey() - - size := 8 << 20 // 8MiB - - b.ResetTimer() - b.SetBytes(int64(size)) - - buf := bytes.NewBuffer(nil) - for i := 0; i < b.N; i++ { - rd := RandomLimitReader(23, size) - buf.Reset() - wr := crypto.EncryptTo(k, buf) - _, err := io.Copy(wr, rd) - OK(b, err) - OK(b, wr.Close()) - - r, err := crypto.DecryptFrom(k, buf) - OK(b, err) - - _, err = io.Copy(ioutil.Discard, r) - OK(b, err) - } -} - func BenchmarkDecrypt(b *testing.B) { size := 8 << 20 // 8MiB data := make([]byte, size) @@ -232,101 +164,3 @@ func BenchmarkDecrypt(b *testing.B) { OK(b, err) } } - -func TestEncryptStreamWriter(t *testing.T) { - k := crypto.NewRandomKey() - - tests := []int{5, 23, 2<<18 + 23, 1 << 20} - if testLargeCrypto { - tests = append(tests, 7<<20+123) - } - - for _, size := range tests { - data := Random(42, size) - - ciphertext := bytes.NewBuffer(nil) - wr := crypto.EncryptTo(k, ciphertext) - - _, err := io.Copy(wr, bytes.NewReader(data)) - OK(t, err) - OK(t, wr.Close()) - - l := len(data) + crypto.Extension - Assert(t, len(ciphertext.Bytes()) == l, - "wrong ciphertext length: expected %d, got %d", - l, len(ciphertext.Bytes())) - - // decrypt with default function - plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext.Bytes()) - OK(t, err) - Assert(t, bytes.Equal(data, plaintext), - "wrong plaintext after decryption: expected %02x, got %02x", - data, plaintext) - } -} - -func TestDecryptStreamReader(t *testing.T) { - k := crypto.NewRandomKey() - - tests := []int{5, 23, 2<<18 + 23, 1 << 20} - if testLargeCrypto { - tests = append(tests, 7<<20+123) - } - - for _, size := range tests { - data := Random(42, size) - var err error - ciphertext := make([]byte, size+crypto.Extension) - - // encrypt with default function - ciphertext, err = crypto.Encrypt(k, ciphertext, data) - OK(t, err) - Assert(t, len(ciphertext) == len(data)+crypto.Extension, - "wrong number of bytes returned after encryption: expected %d, got %d", - len(data)+crypto.Extension, len(ciphertext)) - - rd, err := crypto.DecryptFrom(k, bytes.NewReader(ciphertext)) - OK(t, err) - - plaintext, err := ioutil.ReadAll(rd) - OK(t, err) - - Assert(t, bytes.Equal(data, plaintext), - "wrong plaintext after decryption: expected %02x, got %02x", - data, plaintext) - } -} - -func TestEncryptWriter(t *testing.T) { - k := crypto.NewRandomKey() - - tests := []int{5, 23, 2<<18 + 23, 1 << 20} - if testLargeCrypto { - tests = append(tests, 7<<20+123) - } - - for _, size := range tests { - data := Random(42, size) - - buf := bytes.NewBuffer(nil) - wr := crypto.EncryptTo(k, buf) - - _, err := io.Copy(wr, bytes.NewReader(data)) - OK(t, err) - OK(t, wr.Close()) - - ciphertext := buf.Bytes() - - l := len(data) + crypto.Extension - Assert(t, len(ciphertext) == l, - "wrong ciphertext length: expected %d, got %d", - l, len(ciphertext)) - - // decrypt with default function - plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext) - OK(t, err) - Assert(t, bytes.Equal(data, plaintext), - "wrong plaintext after decryption: expected %02x, got %02x", - data, plaintext) - } -} diff --git a/src/restic/crypto/reader.go b/src/restic/crypto/reader.go deleted file mode 100644 index 20c451cf3..000000000 --- a/src/restic/crypto/reader.go +++ /dev/null @@ -1,87 +0,0 @@ -package crypto - -import ( - "bytes" - "errors" - "io" -) - -type decryptReader struct { - buf []byte - rd *bytes.Reader -} - -func (d *decryptReader) Read(dst []byte) (n int, err error) { - if d.buf == nil { - return 0, io.EOF - } - - n, err = d.rd.Read(dst) - if err == io.EOF { - d.free() - } - - return -} - -func (d *decryptReader) free() { - freeBuffer(d.buf) - d.buf = nil -} - -func (d *decryptReader) Close() error { - if d == nil || d.buf == nil { - return nil - } - - d.free() - return nil -} - -func (d *decryptReader) ReadByte() (c byte, err error) { - if d.buf == nil { - return 0, io.EOF - } - - c, err = d.rd.ReadByte() - if err == io.EOF { - d.free() - } - - return -} - -func (d *decryptReader) WriteTo(w io.Writer) (n int64, err error) { - if d.buf == nil { - return 0, errors.New("WriteTo() called on drained reader") - } - - n, err = d.rd.WriteTo(w) - d.free() - - return -} - -// DecryptFrom verifies and decrypts the ciphertext read from rd with ks and -// makes it available on the returned Reader. Ciphertext must be in the form IV -// || Ciphertext || MAC. In order to correctly verify the ciphertext, rd is -// drained, locally buffered and made available on the returned Reader -// afterwards. If a MAC verification failure is observed, it is returned -// immediately. -func DecryptFrom(ks *Key, rd io.Reader) (io.ReadCloser, error) { - buf := bytes.NewBuffer(getBuffer()[:0]) - _, err := buf.ReadFrom(rd) - if err != nil { - return (*decryptReader)(nil), err - } - - ciphertext := buf.Bytes() - - ciphertext, err = Decrypt(ks, ciphertext, ciphertext) - if err != nil { - freeBuffer(ciphertext) - return (*decryptReader)(nil), err - } - - return &decryptReader{buf: ciphertext, rd: bytes.NewReader(ciphertext)}, nil -} diff --git a/src/restic/crypto/writer.go b/src/restic/crypto/writer.go deleted file mode 100644 index 63a09438e..000000000 --- a/src/restic/crypto/writer.go +++ /dev/null @@ -1,88 +0,0 @@ -package crypto - -import ( - "crypto/aes" - "crypto/cipher" - "errors" - "fmt" - "io" -) - -type encryptWriter struct { - data []byte - key *Key - s cipher.Stream - w io.Writer - closed bool -} - -func (e *encryptWriter) Close() error { - if e == nil { - return nil - } - - if e.closed { - return errors.New("Close() called on already closed writer") - } - e.closed = true - - // encrypt everything - iv, c := e.data[:ivSize], e.data[ivSize:] - e.s.XORKeyStream(c, c) - - // compute mac - mac := poly1305MAC(c, iv, &e.key.MAC) - e.data = append(e.data, mac...) - - // write everything - n, err := e.w.Write(e.data) - if err != nil { - return err - } - - if n != len(e.data) { - return errors.New("not all bytes written") - } - - // return buffer to pool - freeBuffer(e.data) - - return nil -} - -func (e *encryptWriter) Write(p []byte) (int, error) { - // if e.data is too small, return it to the buffer and create new slice - if cap(e.data) < len(e.data)+len(p) { - b := make([]byte, len(e.data), len(e.data)*2) - copy(b, e.data) - freeBuffer(e.data) - e.data = b - } - - // copy new data to e.data - e.data = append(e.data, p...) - return len(p), nil -} - -// EncryptTo buffers data written to the returned io.WriteCloser. When Close() -// is called, the data is encrypted and written to the underlying writer. -func EncryptTo(ks *Key, wr io.Writer) io.WriteCloser { - ew := &encryptWriter{ - data: getBuffer(), - key: ks, - } - - // buffer iv for mac - ew.data = ew.data[:ivSize] - copy(ew.data, newIV()) - - c, err := aes.NewCipher(ks.Encrypt[:]) - if err != nil { - panic(fmt.Sprintf("unable to create cipher: %v", err)) - } - - ew.s = cipher.NewCTR(c, ew.data[:ivSize]) - ew.w = wr - - return ew -} From f0d7f3f1bd422255accb1c0ffcc21baaa7552355 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Aug 2016 12:32:38 +0200 Subject: [PATCH 4/8] Calibrate scrypt for the current hardware Closes #17 --- src/restic/crypto/kdf.go | 74 +++++++++++++++++++++++++++++++++-- src/restic/crypto/kdf_test.go | 14 +++++++ src/restic/repository/key.go | 52 +++++++++++++++--------- 3 files changed, 117 insertions(+), 23 deletions(-) create mode 100644 src/restic/crypto/kdf_test.go diff --git a/src/restic/crypto/kdf.go b/src/restic/crypto/kdf.go index 3551fff60..7fe124e44 100644 --- a/src/restic/crypto/kdf.go +++ b/src/restic/crypto/kdf.go @@ -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 +} diff --git a/src/restic/crypto/kdf_test.go b/src/restic/crypto/kdf_test.go new file mode 100644 index 000000000..5823eb889 --- /dev/null +++ b/src/restic/crypto/kdf_test.go @@ -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) +} diff --git a/src/restic/repository/key.go b/src/restic/repository/key.go index 0a3dfe58e..417ef615f 100644 --- a/src/restic/repository/key.go +++ b/src/restic/repository/key.go @@ -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 } From 79e950b71099c7d21fcab8fcefb65ac42f9702eb Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Aug 2016 13:07:34 +0200 Subject: [PATCH 5/8] Remove dead code --- src/restic/index/index_test.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/restic/index/index_test.go b/src/restic/index/index_test.go index c950608ab..559c3f08b 100644 --- a/src/restic/index/index_test.go +++ b/src/restic/index/index_test.go @@ -4,7 +4,6 @@ import ( "math/rand" "restic" "restic/backend" - "restic/backend/local" "restic/pack" "restic/repository" . "restic/test" @@ -124,21 +123,6 @@ func TestIndexLoad(t *testing.T) { } } -func openRepo(t testing.TB, dir, password string) *repository.Repository { - b, err := local.Open(dir) - if err != nil { - t.Fatalf("open backend %v failed: %v", dir, err) - } - - r := repository.New(b) - err = r.SearchKey(password) - if err != nil { - t.Fatalf("unable to open repo with password: %v", err) - } - - return r -} - func BenchmarkIndexNew(b *testing.B) { repo, cleanup := createFilledRepo(b, 3, 0) defer cleanup() From d8107f77aadf25233d0dc70027c1a3a53183f6c5 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Aug 2016 13:09:31 +0200 Subject: [PATCH 6/8] Limit the number of key files checked on SearchKey --- src/cmds/restic/global.go | 4 +++- src/restic/checker/checker_test.go | 2 +- src/restic/repository/key.go | 27 ++++++++++++++++++++++----- src/restic/repository/repository.go | 6 +++--- src/restic/test/helpers.go | 2 +- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/cmds/restic/global.go b/src/cmds/restic/global.go index 49a2ace30..7d0df2adf 100644 --- a/src/cmds/restic/global.go +++ b/src/cmds/restic/global.go @@ -197,6 +197,8 @@ func (o GlobalOptions) ReadPasswordTwice(prompt1, prompt2 string) string { return pw1 } +const maxKeys = 20 + // OpenRepository reads the password and opens the repository. func (o GlobalOptions) OpenRepository() (*repository.Repository, error) { if o.Repo == "" { @@ -214,7 +216,7 @@ func (o GlobalOptions) OpenRepository() (*repository.Repository, error) { o.password = o.ReadPassword("enter password for repository: ") } - err = s.SearchKey(o.password) + err = s.SearchKey(o.password, maxKeys) if err != nil { return nil, fmt.Errorf("unable to open repo: %v", err) } diff --git a/src/restic/checker/checker_test.go b/src/restic/checker/checker_test.go index 85135a98c..3448dafbc 100644 --- a/src/restic/checker/checker_test.go +++ b/src/restic/checker/checker_test.go @@ -249,7 +249,7 @@ func TestCheckerModifiedData(t *testing.T) { beError := &errorBackend{Backend: be} checkRepo := repository.New(beError) - OK(t, checkRepo.SearchKey(TestPassword)) + OK(t, checkRepo.SearchKey(TestPassword, 5)) chkr := checker.New(checkRepo) diff --git a/src/restic/repository/key.go b/src/restic/repository/key.go index 417ef615f..9e98aaa27 100644 --- a/src/restic/repository/key.go +++ b/src/restic/repository/key.go @@ -16,6 +16,9 @@ import ( var ( // ErrNoKeyFound is returned when no key for the repository could be decrypted. ErrNoKeyFound = errors.New("wrong password or no key found") + + // ErrMaxKeysReached is returned when the maximum number of keys was checked and no key could be found. + ErrMaxKeysReached = errors.New("maximum number of keys reached") ) // Key represents an encrypted master key for a repository. @@ -98,18 +101,32 @@ func OpenKey(s *Repository, name string, password string) (*Key, error) { return k, nil } -// SearchKey tries to decrypt all keys in the backend with the given password. -// If none could be found, ErrNoKeyFound is returned. -func SearchKey(s *Repository, password string) (*Key, error) { - // try all keys in repo +// SearchKey tries to decrypt at most maxKeys keys in the backend with the +// given password. If none could be found, ErrNoKeyFound is returned. When +// maxKeys is reached, ErrMaxKeysReached is returned. When setting maxKeys to +// zero, all keys in the repo are checked. +func SearchKey(s *Repository, password string, maxKeys int) (*Key, error) { + checked := 0 + + // try at most maxKeysForSearch keys in repo done := make(chan struct{}) defer close(done) for name := range s.Backend().List(backend.Key, done) { + if maxKeys > 0 && checked > maxKeys { + return nil, ErrMaxKeysReached + } + debug.Log("SearchKey", "trying key %v", name[:12]) key, err := OpenKey(s, name, password) if err != nil { debug.Log("SearchKey", "key %v returned error %v", name[:12], err) - continue + + // ErrUnauthenticated means the password is wrong, try the next key + if err == crypto.ErrUnauthenticated { + continue + } + + return nil, err } debug.Log("SearchKey", "successfully opened key %v", name[:12]) diff --git a/src/restic/repository/repository.go b/src/restic/repository/repository.go index ca53fb39d..9c8aea25b 100644 --- a/src/restic/repository/repository.go +++ b/src/restic/repository/repository.go @@ -405,9 +405,9 @@ func LoadIndex(repo *Repository, id backend.ID) (*Index, error) { } // SearchKey finds a key with the supplied password, afterwards the config is -// read and parsed. -func (r *Repository) SearchKey(password string) error { - key, err := SearchKey(r, password) +// read and parsed. It tries at most maxKeys key files in the repo. +func (r *Repository) SearchKey(password string, maxKeys int) error { + key, err := SearchKey(r, password, maxKeys) if err != nil { return err } diff --git a/src/restic/test/helpers.go b/src/restic/test/helpers.go index 4c3280fba..353c9b8ed 100644 --- a/src/restic/test/helpers.go +++ b/src/restic/test/helpers.go @@ -214,7 +214,7 @@ func OpenLocalRepo(t testing.TB, dir string) *repository.Repository { OK(t, err) repo := repository.New(be) - err = repo.SearchKey(TestPassword) + err = repo.SearchKey(TestPassword, 10) OK(t, err) return repo From 8e24c51233bf69e230bc92937e2829fd2835d5d1 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Aug 2016 13:13:05 +0200 Subject: [PATCH 7/8] Fix commets for constants --- src/restic/repository/key.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/restic/repository/key.go b/src/restic/repository/key.go index 9e98aaa27..2599f6e2e 100644 --- a/src/restic/repository/key.go +++ b/src/restic/repository/key.go @@ -45,8 +45,11 @@ type Key struct { var KDFParams *crypto.KDFParams var ( - KDFTimeout = 500 * time.Millisecond // timeout for KDF - KDFMemory = 60 // max memory for KDF, in MiB + // KDFTimeout specifies the maximum runtime for the KDF. + KDFTimeout = 500 * time.Millisecond + + // KDFMemory limits the memory the KDF is allowed to use. + KDFMemory = 60 ) // createMasterKey creates a new master key in the given backend and encrypts From a3492d69dd629953c5751895da682c98512a450e Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Aug 2016 13:42:04 +0200 Subject: [PATCH 8/8] Use low-security scrypt KDF parameters for testing --- src/cmds/restic/integration_test.go | 2 ++ src/restic/checker/checker_test.go | 2 ++ src/restic/repository/testing.go | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/cmds/restic/integration_test.go b/src/cmds/restic/integration_test.go index 94992104f..4b99d8a32 100644 --- a/src/cmds/restic/integration_test.go +++ b/src/cmds/restic/integration_test.go @@ -40,6 +40,8 @@ func parseIDsFromReader(t testing.TB, rd io.Reader) backend.IDs { } func cmdInit(t testing.TB, global GlobalOptions) { + repository.TestUseLowSecurityKDFParameters(t) + cmd := &CmdInit{global: &global} OK(t, cmd.Execute(nil)) diff --git a/src/restic/checker/checker_test.go b/src/restic/checker/checker_test.go index 3448dafbc..d06b2139b 100644 --- a/src/restic/checker/checker_test.go +++ b/src/restic/checker/checker_test.go @@ -239,6 +239,8 @@ func induceError(data []byte) { func TestCheckerModifiedData(t *testing.T) { be := mem.New() + repository.TestUseLowSecurityKDFParameters(t) + repo := repository.New(be) OK(t, repo.Init(TestPassword)) diff --git a/src/restic/repository/testing.go b/src/restic/repository/testing.go index f0a9913f1..904ee397c 100644 --- a/src/restic/repository/testing.go +++ b/src/restic/repository/testing.go @@ -5,11 +5,25 @@ import ( "restic/backend" "restic/backend/local" "restic/backend/mem" + "restic/crypto" "testing" "github.com/restic/chunker" ) +// testKDFParams are the parameters for the KDF to be used during testing. +var testKDFParams = crypto.KDFParams{ + N: 128, + R: 1, + P: 1, +} + +// TestUseLowSecurityKDFParameters configures low-security KDF parameters for testing. +func TestUseLowSecurityKDFParameters(t testing.TB) { + t.Logf("using low-security KDF parameters for test") + KDFParams = &testKDFParams +} + // TestBackend returns a fully configured in-memory backend. func TestBackend(t testing.TB) (be backend.Backend, cleanup func()) { return mem.New(), func() {} @@ -22,8 +36,10 @@ const testChunkerPol = chunker.Pol(0x3DA3358B4DC173) // TestRepositoryWithBackend returns a repository initialized with a test // password. If be is nil, an in-memory backend is used. A constant polynomial -// is used for the chunker. +// is used for the chunker and low-security test parameters. func TestRepositoryWithBackend(t testing.TB, be backend.Backend) (r *Repository, cleanup func()) { + TestUseLowSecurityKDFParameters(t) + var beCleanup func() if be == nil { be, beCleanup = TestBackend(t)