README

simple-scrypt

GoDoc Build Status

simple-scrypt provides a convenience wrapper around Go's existing 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 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:

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:

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:

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

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.

Expand ▾ Collapse ▴

Documentation

Overview

    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.

    Index

    Examples

    Constants

    This section is empty.

    Variables

    View Source
    var DefaultParams = Params{N: 16384, R: 8, P: 1, SaltLen: 16, DKLen: 32}

      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.

      View Source
      var ErrInvalidHash = errors.New("scrypt: the provided hash is not in the correct format")

        ErrInvalidHash is returned when failing to parse a provided scrypt hash and/or parameters.

        View Source
        var ErrInvalidParams = errors.New("scrypt: the parameters provided are invalid")

          ErrInvalidParams is returned when the cost parameters (N, r, p), salt length or derived key length are invalid.

          View Source
          var ErrMismatchedHashAndPassword = errors.New("scrypt: the hashed password does not match the hash of the given password")

            ErrMismatchedHashAndPassword is returned when a password (hashed) and given hash do not match.

            Functions

            func CompareHashAndPassword

            func CompareHashAndPassword(hash []byte, password []byte) error

              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 GenerateFromPassword

              func GenerateFromPassword(password []byte, params Params) ([]byte, error)

                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 GenerateRandomBytes

                func GenerateRandomBytes(n int) ([]byte, error)

                  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.

                  Types

                  type Params

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

                    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

                    func Calibrate

                    func Calibrate(timeout time.Duration, memMiBytes int, params Params) (Params, error)

                      Calibrate returns the hardest parameters, 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.
                      
                      Example
                      Output:
                      
                      

                      func Cost

                      func Cost(hash []byte) (Params, error)

                        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 (*Params) Check

                        func (p *Params) Check() error

                          Check checks that the parameters are valid for input into the scrypt key derivation function.

                          Source Files