Vendor github.com/elithrar/simple-scrypt

This commit is contained in:
Alexander Neumann 2016-08-21 10:50:02 +02:00
parent 84c14e623d
commit 7e6fc15ece
5 changed files with 634 additions and 0 deletions

6
vendor/manifest vendored
View file

@ -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",

View file

@ -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.

View file

@ -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.

View file

@ -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()
}

View file

@ -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)
}