From 7e6fc15ece2e73590f984b2a850feb3afa16949e Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Aug 2016 10:50:02 +0200 Subject: [PATCH] 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) +}