pax_global_header00006660000000000000000000000064151426540650014522gustar00rootroot0000000000000052 comment=3b9f1a22960983f7009dbaa6efa0d39f953af8be elithrar-simple-scrypt-f165f15/000077500000000000000000000000001514265406500165045ustar00rootroot00000000000000elithrar-simple-scrypt-f165f15/.github/000077500000000000000000000000001514265406500200445ustar00rootroot00000000000000elithrar-simple-scrypt-f165f15/.github/workflows/000077500000000000000000000000001514265406500221015ustar00rootroot00000000000000elithrar-simple-scrypt-f165f15/.github/workflows/bonk.yml000066400000000000000000000015051514265406500235560ustar00rootroot00000000000000name: Bonk on: issue_comment: types: [created] pull_request_review_comment: types: [created] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number || github.ref }} cancel-in-progress: false jobs: bonk: if: github.event.sender.type != 'Bot' runs-on: ubuntu-latest permissions: id-token: write contents: write issues: write pull-requests: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: Run Bonk uses: ask-bonk/ask-bonk/github@main env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} with: model: opencode/claude-opus-4-6 mentions: "/bonk,@ask-bonk" permissions: write elithrar-simple-scrypt-f165f15/.github/workflows/test.yml000066400000000000000000000022121514265406500236000ustar00rootroot00000000000000name: Test on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest strategy: matrix: go-version: ['1.21', '1.22', '1.23', '1.24'] steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Verify formatting run: test -z "$(gofmt -l .)" - name: Run go vet run: go vet ./... - name: Run tests run: go test -v -race ./... test-tip: runs-on: ubuntu-latest continue-on-error: true steps: - uses: actions/checkout@v4 - name: Set up Go stable (for gotip bootstrap) uses: actions/setup-go@v5 with: go-version: stable - name: Install gotip run: | go install golang.org/dl/gotip@latest gotip download echo "$(gotip env GOROOT)/bin" >> $GITHUB_PATH - name: Verify formatting run: test -z "$(gofmt -l .)" - name: Run go vet run: gotip vet ./... - name: Run tests run: gotip test -v -race ./... elithrar-simple-scrypt-f165f15/.gitignore000066400000000000000000000004431514265406500204750ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof coverage.out *.DS_Store elithrar-simple-scrypt-f165f15/.opencode/000077500000000000000000000000001514265406500203565ustar00rootroot00000000000000elithrar-simple-scrypt-f165f15/.opencode/INSTRUCTIONS.md000066400000000000000000000024721514265406500226510ustar00rootroot00000000000000## INSTRUCTIONS ### code, git and GitHub * keep commit messages short: e.g. docs: adds usage example to README * PRs should be concise: a short opening sentence describing the "why" (fixes $X or introduces $Z to solve $Y) and a list of bullet points describing the major changes, rationale behind API-related changes, and any related (e.g. docs) changes. * prefer the `gh` CLI for creating PRs and issues. * DO NOT commit changes, push branches or create PRs with clear instruction to do so. * minimize introducing dependencies unless necessary and/or we agree. * install dependencies using the toolchain for the current project (e.g. npm i or cargo install) * comments should focus on the why. don't comment on single variables or short functions. save comments for logic that has I/O (e.g. calling an API), validates/rejects input, or handles edge cases. * don't cast things to circumvent type issues. fix them. ### general * DO NOT say "you're absolutely right" - just agree/disagree and then continue the conversation/response. * present options when prudent to do so, but don't overdo it. * bring opinions when presenting options - e.g. recommend Option B because reasons x, y + z. * be concise and avoid long walls of text. * link to sources when appropriate, and definitely do it when I ask you to provide sources/references. elithrar-simple-scrypt-f165f15/.opencode/opencode.jsonc000066400000000000000000000002031514265406500232030ustar00rootroot00000000000000{ "$schema": "https://opencode.ai/config.json", "theme": "orng", "autoupdate": true, "instructions": ["INSTRUCTIONS.md"] } elithrar-simple-scrypt-f165f15/AGENTS.md000066400000000000000000000105471514265406500200160ustar00rootroot00000000000000# AGENTS.md Guidelines for AI agents operating in the `simple-scrypt` repository. ## Project overview Single-package Go library wrapping `golang.org/x/crypto/scrypt` for password hashing. Module path: `github.com/elithrar/simple-scrypt`. One external dependency (`golang.org/x/crypto`). Two source files at the repo root: - `scrypt.go` — library implementation (public API mirrors Go's `bcrypt` package) - `scrypt_test.go` — tests (standard `testing` package, no third-party frameworks) Hash format: `N$R$P$hexsalt$hexdk` (dollar-separated, hex-encoded salt and derived key). Default branch is **`main`**. ## Build and test commands ```sh # build go build ./... # run full test suite (CI uses this exact command) go test -v -race ./... # run a single test go test -v -race -run TestCompareHashAndPassword ./... # run a single sub-test (table-driven) go test -v -race -run TestCalibrate/512 ./... # formatting check — CI rejects any diff gofmt -l . # apply formatting fixes gofmt -w . # static analysis — CI runs this go vet ./... ``` CI runs on push/PR to `main` across Go 1.21, 1.22, 1.23, 1.24 plus tip on Ubuntu. All three checks must pass: `gofmt`, `go vet`, `go test -v -race ./...`. Always run `go test -v -race ./...` before committing to catch data races and regressions. ## Code style ### Formatting `gofmt` is the only formatter. No custom config. CI enforces zero diff — run `gofmt -w .` before committing. ### Imports Standard library first, blank line, then external packages: ```go import ( "crypto/rand" "errors" "fmt" "golang.org/x/crypto/scrypt" ) ``` ### Naming - Exported types/functions: `PascalCase` — `Params`, `GenerateFromPassword`, `CompareHashAndPassword` - Unexported functions/constants: `camelCase` — `decodeHash`, `maxInt`, `minDKLen` - Sentinel errors: `Err` prefix — `ErrInvalidHash`, `ErrInvalidParams`, `ErrMismatchedHashAndPassword` - Receiver names: short, single letter — `p` for `*Params` ### Error handling - Sentinel errors as package-level `var` using `errors.New()` - Functions return `error` as the last return value - Early return on error (guard clauses) — no nested else blocks - No error wrapping in this codebase; return sentinel errors or upstream errors directly - `CompareHashAndPassword` returns `nil` on success (bcrypt convention) ```go var ErrInvalidHash = errors.New("scrypt: the provided hash is not in the correct format") func Cost(hash []byte) (Params, error) { params, _, _, err := decodeHash(hash) return params, err } ``` ### Comments - Godoc comments on all exported types, functions, and variables - Internal comments explain *why*, not *what* — save them for I/O, validation, and edge cases - Don't comment single variables or trivial functions ### Testing Standard `testing` package only. Tests are in `package scrypt` (white-box, same package). Patterns used in this codebase: - **Table-driven tests** with `pass bool` fields for expected outcomes: ```go var testParams = []struct { pass bool params Params }{ {true, Params{16384, 8, 1, 32, 64}}, {false, Params{-1, 8, 1, 16, 32}}, } ``` - `t.Fatal` / `t.Fatalf` for hard failures that should stop the test - `t.Errorf` for soft failures (test continues to check remaining cases) - `t.Logf` for informational output (e.g., timing data) - `Example*` functions for godoc examples Do not add third-party test frameworks or assertion libraries. Avoid tests that merely exercise language features — focus on validation, state, and error handling. ### Types - Do not use type casts to work around type issues; fix the underlying problem - Zero-value structs (e.g., `Params{}`) are used as signals for default behavior (see `Calibrate`) ## Dependencies Minimize new dependencies. The library has one external dependency by design. Install dependencies using `go get`. Ensure `go.sum` is committed alongside `go.mod` changes. ## Git and PR conventions - Short, imperative commit messages: `add calibration benchmarks` not `feat(scrypt): added calibration benchmarks` - Do not commit, push, or create PRs without explicit instruction - Use `gh` CLI for creating PRs and issues - Branch off `main` for new work; never commit directly to `main` - PRs: short opening sentence with the "why", bullet points for major changes, mention related docs/tests - Do not list changed files in PR descriptions — the diff shows that elithrar-simple-scrypt-f165f15/LICENSE000066400000000000000000000021521514265406500175110ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-2018 Matthew Silverlock (matt@eatsleeprepeat.net), Google LLC. 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. elithrar-simple-scrypt-f165f15/README.md000066400000000000000000000117711514265406500177720ustar00rootroot00000000000000# simple-scrypt [![GoDoc](https://godoc.org/github.com/elithrar/simple-scrypt?status.svg)](https://godoc.org/github.com/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 cryptographically 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 cryptographically 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.DefaultParams (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.DeepEqual) // 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, scrypt.Params{}) if err != nil { log.Fatal(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. elithrar-simple-scrypt-f165f15/go.mod000066400000000000000000000001271514265406500176120ustar00rootroot00000000000000module github.com/elithrar/simple-scrypt go 1.21 require golang.org/x/crypto v0.31.0 elithrar-simple-scrypt-f165f15/go.sum000066400000000000000000000002371514265406500176410ustar00rootroot00000000000000golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= elithrar-simple-scrypt-f165f15/scrypt.go000066400000000000000000000224641514265406500203670ustar00rootroot00000000000000// 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 approximately 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) { if err := params.Check(); err != nil { return nil, err } salt, err := GenerateRandomBytes(params.SaltLen) if 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), "$") // N, R, P, 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, 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. // // R is always set to 8 regardless of the input params, per // https://blog.filippo.io/the-scrypt-parameters/. // // 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 } if memMiBytes < 0 { return p, ErrInvalidParams } salt, err := GenerateRandomBytes(p.SaltLen) if err != nil { return p, err } password := []byte("weakpassword") // r is fixed to 8 and should not be used to tune the memory usage // if the cache lines of future processors are bigger, then r should be increased // see: https://blog.filippo.io/the-scrypt-parameters/ p.R = 8 // Scrypt runs p independent mixing functions with a memory requirement of roughly // 128 * r * N. Depending on the implementation these can be run sequentially or parallel. // The go implementation runs them sequentially, therefore p can be used to adjust the execution time of scrypt. // we start with p=1 and only increase it if we have to p.P = 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 // calculate N based on the desired memory usage memBytes := memMiBytes << 20 p.N = 1 for 128*int64(p.R)*int64(p.N) < int64(memBytes) { p.N <<= 1 } p.N >>= 1 // calculate the current execution 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) // reduce N if scrypt takes too long for dur > timeout { p.N >>= 1 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) } // try to reach desired timeout by increasing p // the further away we are from timeout the bigger the steps should be for dur < timeout { // the theoretical optimal p; can not be used because of inaccurate measuring durPerP := int64(dur) / int64(p.P) if durPerP == 0 { durPerP = 1 } optimalP := int(int64(timeout) / durPerP) if optimalP > p.P+1 { // use average between optimal p and current p p.P = (p.P + optimalP) / 2 } else { p.P++ } 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) } // lower by one to get shorter duration than timeout if p.P > 1 { p.P-- } return p, p.Check() } elithrar-simple-scrypt-f165f15/scrypt_test.go000066400000000000000000000206061514265406500214220ustar00rootroot00000000000000package scrypt import ( "encoding/hex" "fmt" "reflect" "strconv" "strings" "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 - 1, 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 - 1, 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 v.pass && err != nil { t.Fatalf("unexpected error for valid params %+v: %v", v.params, err) } if !v.pass && err == nil { t.Fatalf("expected error for invalid params %+v, got nil", v.params) } } } func TestHashFormat(t *testing.T) { hash, err := GenerateFromPassword([]byte(password), DefaultParams) if err != nil { t.Fatal(err) } parts := strings.Split(string(hash), "$") if len(parts) != 5 { t.Fatalf("expected 5 dollar-separated fields, got %d: %s", len(parts), hash) } n, err := strconv.Atoi(parts[0]) if err != nil || n != DefaultParams.N { t.Errorf("N field: got %q, want %d", parts[0], DefaultParams.N) } r, err := strconv.Atoi(parts[1]) if err != nil || r != DefaultParams.R { t.Errorf("R field: got %q, want %d", parts[1], DefaultParams.R) } p, err := strconv.Atoi(parts[2]) if err != nil || p != DefaultParams.P { t.Errorf("P field: got %q, want %d", parts[2], DefaultParams.P) } salt, err := hex.DecodeString(parts[3]) if err != nil { t.Fatalf("salt is not valid hex: %v", err) } if len(salt) != DefaultParams.SaltLen { t.Errorf("salt length: got %d, want %d", len(salt), DefaultParams.SaltLen) } dk, err := hex.DecodeString(parts[4]) if err != nil { t.Fatalf("dk is not valid hex: %v", err) } if len(dk) != DefaultParams.DKLen { t.Errorf("dk length: got %d, want %d", len(dk), DefaultParams.DKLen) } } 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") } if err := CompareHashAndPassword(nil, []byte(password)); err == nil { t.Fatal("expected error for nil hash") } if err := CompareHashAndPassword([]byte{}, []byte(password)); err == nil { t.Fatal("expected error for empty hash") } if err := CompareHashAndPassword(hash, nil); err == nil { t.Fatal("expected error for nil password, got match") } } func TestCost(t *testing.T) { for _, want := range []Params{ DefaultParams, {65536, 8, 1, 16, 64}, {4096, 8, 1, 32, 32}, } { hash, err := GenerateFromPassword([]byte(password), want) if err != nil { t.Fatalf("GenerateFromPassword(%+v): %v", want, err) } got, err := Cost(hash) if err != nil { t.Fatalf("Cost(%+v): %v", want, err) } if !reflect.DeepEqual(got, want) { t.Fatalf("cost mismatch: got %+v, want %+v", got, want) } } } 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") } } } // TestKnownHash verifies CompareHashAndPassword against a hash constructed from // the RFC 7914 Section 12 test vector (P="pleaseletmein", S="SodiumChloride", // N=16384, r=8, p=1, dkLen=64). This catches regressions in the underlying // scrypt implementation or changes to the hash encoding format. func TestKnownHash(t *testing.T) { // salt = hex("SodiumChloride"), dk = RFC 7914 expected output known := []byte("16384$8$1$536f6469756d43686c6f72696465$" + "7023bdcb3afd7348461c06cd81fd38eb" + "fda8fbba904f8e3ea9b543f6545da1f2" + "d5432955613f0fcf62d49705242a9af9" + "e61e85dc0d651e40dfcf017b45575887") if err := CompareHashAndPassword(known, []byte("pleaseletmein")); err != nil { t.Fatalf("RFC 7914 known-good hash failed verification: %v", err) } if err := CompareHashAndPassword(known, []byte("wrong-password")); err == nil { t.Fatal("known hash matched incorrect password") } } func TestCheck(t *testing.T) { tests := []struct { pass bool params Params desc string }{ {true, Params{2, 1, 1, 8, 16}, "minimum valid params"}, {true, Params{16384, 8, 1, 16, 32}, "default params"}, {false, Params{3, 8, 1, 16, 32}, "N not power of 2"}, {false, Params{0, 8, 1, 16, 32}, "N is zero"}, {false, Params{1, 8, 1, 16, 32}, "N is 1"}, {false, Params{-1, 8, 1, 16, 32}, "N is negative"}, {false, Params{16384, 0, 1, 16, 32}, "R is zero"}, {false, Params{16384, 8, 0, 16, 32}, "P is zero"}, {false, Params{16384, 8, 1, 7, 32}, "SaltLen below minimum"}, {true, Params{16384, 8, 1, 8, 32}, "SaltLen at minimum"}, {false, Params{16384, 8, 1, 16, 15}, "DKLen below minimum"}, {true, Params{16384, 8, 1, 16, 16}, "DKLen at minimum"}, } for _, tc := range tests { err := tc.params.Check() if tc.pass && err != nil { t.Errorf("%s: unexpected error: %v", tc.desc, err) } if !tc.pass && err == nil { t.Errorf("%s: expected error, got nil", tc.desc) } } } func TestCalibrate(t *testing.T) { timeout := 500 * time.Millisecond for testNum, tc := range []struct { MemMiB int }{ {512}, {256}, {128}, {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/4 { t.Errorf("%d. GenerateFromPassword was too fast (expected at least %s, got %s) with %#v.", testNum, timeout/4, dur, p) } else if 3*timeout < dur { t.Errorf("%d. GenerateFromPassword took too long (expected at most %s, got %s) with %#v.", testNum, 3*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) }