forked from TrueCloudLab/restic
Merge pull request #577 from restic/dynamic-scrypt
Dynamically calibrate scrypt parameters
This commit is contained in:
commit
11d01fcd32
19 changed files with 837 additions and 418 deletions
|
@ -197,6 +197,8 @@ func (o GlobalOptions) ReadPasswordTwice(prompt1, prompt2 string) string {
|
||||||
return pw1
|
return pw1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxKeys = 20
|
||||||
|
|
||||||
// OpenRepository reads the password and opens the repository.
|
// OpenRepository reads the password and opens the repository.
|
||||||
func (o GlobalOptions) OpenRepository() (*repository.Repository, error) {
|
func (o GlobalOptions) OpenRepository() (*repository.Repository, error) {
|
||||||
if o.Repo == "" {
|
if o.Repo == "" {
|
||||||
|
@ -214,7 +216,7 @@ func (o GlobalOptions) OpenRepository() (*repository.Repository, error) {
|
||||||
o.password = o.ReadPassword("enter password for repository: ")
|
o.password = o.ReadPassword("enter password for repository: ")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.SearchKey(o.password)
|
err = s.SearchKey(o.password, maxKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to open repo: %v", err)
|
return nil, fmt.Errorf("unable to open repo: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,8 @@ func parseIDsFromReader(t testing.TB, rd io.Reader) backend.IDs {
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdInit(t testing.TB, global GlobalOptions) {
|
func cmdInit(t testing.TB, global GlobalOptions) {
|
||||||
|
repository.TestUseLowSecurityKDFParameters(t)
|
||||||
|
|
||||||
cmd := &CmdInit{global: &global}
|
cmd := &CmdInit{global: &global}
|
||||||
OK(t, cmd.Execute(nil))
|
OK(t, cmd.Execute(nil))
|
||||||
|
|
||||||
|
|
|
@ -239,6 +239,8 @@ func induceError(data []byte) {
|
||||||
func TestCheckerModifiedData(t *testing.T) {
|
func TestCheckerModifiedData(t *testing.T) {
|
||||||
be := mem.New()
|
be := mem.New()
|
||||||
|
|
||||||
|
repository.TestUseLowSecurityKDFParameters(t)
|
||||||
|
|
||||||
repo := repository.New(be)
|
repo := repository.New(be)
|
||||||
OK(t, repo.Init(TestPassword))
|
OK(t, repo.Init(TestPassword))
|
||||||
|
|
||||||
|
@ -249,7 +251,7 @@ func TestCheckerModifiedData(t *testing.T) {
|
||||||
|
|
||||||
beError := &errorBackend{Backend: be}
|
beError := &errorBackend{Backend: be}
|
||||||
checkRepo := repository.New(beError)
|
checkRepo := repository.New(beError)
|
||||||
OK(t, checkRepo.SearchKey(TestPassword))
|
OK(t, checkRepo.SearchKey(TestPassword, 5))
|
||||||
|
|
||||||
chkr := checker.New(checkRepo)
|
chkr := checker.New(checkRepo)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/crypto/poly1305"
|
"golang.org/x/crypto/poly1305"
|
||||||
"golang.org/x/crypto/scrypt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -315,34 +314,6 @@ func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) ([]byte, error
|
||||||
return plaintext, nil
|
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.
|
// Valid tests if the key is valid.
|
||||||
func (k *Key) Valid() bool {
|
func (k *Key) Valid() bool {
|
||||||
return k.Encrypt.Valid() && k.MAC.Valid()
|
return k.Encrypt.Valid() && k.MAC.Valid()
|
||||||
|
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/chunker"
|
|
||||||
"restic/crypto"
|
"restic/crypto"
|
||||||
. "restic/test"
|
. "restic/test"
|
||||||
|
|
||||||
|
"github.com/restic/chunker"
|
||||||
)
|
)
|
||||||
|
|
||||||
const testLargeCrypto = false
|
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) {
|
func BenchmarkEncrypt(b *testing.B) {
|
||||||
size := 8 << 20 // 8MiB
|
size := 8 << 20 // 8MiB
|
||||||
data := make([]byte, size)
|
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) {
|
func BenchmarkDecrypt(b *testing.B) {
|
||||||
size := 8 << 20 // 8MiB
|
size := 8 << 20 // 8MiB
|
||||||
data := make([]byte, size)
|
data := make([]byte, size)
|
||||||
|
@ -232,101 +164,3 @@ func BenchmarkDecrypt(b *testing.B) {
|
||||||
OK(b, err)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
101
src/restic/crypto/kdf.go
Normal file
101
src/restic/crypto/kdf.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
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(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, p.N, p.R, p.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
14
src/restic/crypto/kdf_test.go
Normal file
14
src/restic/crypto/kdf_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"restic"
|
"restic"
|
||||||
"restic/backend"
|
"restic/backend"
|
||||||
"restic/backend/local"
|
|
||||||
"restic/pack"
|
"restic/pack"
|
||||||
"restic/repository"
|
"restic/repository"
|
||||||
. "restic/test"
|
. "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) {
|
func BenchmarkIndexNew(b *testing.B) {
|
||||||
repo, cleanup := createFilledRepo(b, 3, 0)
|
repo, cleanup := createFilledRepo(b, 3, 0)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -17,15 +16,9 @@ import (
|
||||||
var (
|
var (
|
||||||
// ErrNoKeyFound is returned when no key for the repository could be decrypted.
|
// ErrNoKeyFound is returned when no key for the repository could be decrypted.
|
||||||
ErrNoKeyFound = errors.New("wrong password or no key found")
|
ErrNoKeyFound = errors.New("wrong password or no key found")
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: figure out scrypt values on the fly depending on the current
|
// ErrMaxKeysReached is returned when the maximum number of keys was checked and no key could be found.
|
||||||
// hardware.
|
ErrMaxKeysReached = errors.New("maximum number of keys reached")
|
||||||
const (
|
|
||||||
scryptN = 65536
|
|
||||||
scryptR = 8
|
|
||||||
scryptP = 1
|
|
||||||
scryptSaltsize = 64
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Key represents an encrypted master key for a repository.
|
// Key represents an encrypted master key for a repository.
|
||||||
|
@ -47,6 +40,18 @@ type Key struct {
|
||||||
name string
|
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 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
|
// createMasterKey creates a new master key in the given backend and encrypts
|
||||||
// it with the password.
|
// it with the password.
|
||||||
func createMasterKey(s *Repository, password string) (*Key, error) {
|
func createMasterKey(s *Repository, password string) (*Key, error) {
|
||||||
|
@ -67,7 +72,12 @@ func OpenKey(s *Repository, name string, password string) (*Key, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// derive user key
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -94,18 +104,32 @@ func OpenKey(s *Repository, name string, password string) (*Key, error) {
|
||||||
return k, nil
|
return k, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchKey tries to decrypt all keys in the backend with the given password.
|
// SearchKey tries to decrypt at most maxKeys keys in the backend with the
|
||||||
// If none could be found, ErrNoKeyFound is returned.
|
// given password. If none could be found, ErrNoKeyFound is returned. When
|
||||||
func SearchKey(s *Repository, password string) (*Key, error) {
|
// maxKeys is reached, ErrMaxKeysReached is returned. When setting maxKeys to
|
||||||
// try all keys in repo
|
// 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{})
|
done := make(chan struct{})
|
||||||
defer close(done)
|
defer close(done)
|
||||||
for name := range s.Backend().List(backend.Key, 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])
|
debug.Log("SearchKey", "trying key %v", name[:12])
|
||||||
key, err := OpenKey(s, name, password)
|
key, err := OpenKey(s, name, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("SearchKey", "key %v returned error %v", name[:12], err)
|
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])
|
debug.Log("SearchKey", "successfully opened key %v", name[:12])
|
||||||
|
@ -134,13 +158,24 @@ func LoadKey(s *Repository, name string) (k *Key, err error) {
|
||||||
|
|
||||||
// AddKey adds a new key to an already existing repository.
|
// AddKey adds a new key to an already existing repository.
|
||||||
func AddKey(s *Repository, password string, template *crypto.Key) (*Key, error) {
|
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
|
// fill meta data about key
|
||||||
newkey := &Key{
|
newkey := &Key{
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
KDF: "scrypt",
|
KDF: "scrypt",
|
||||||
N: scryptN,
|
N: KDFParams.N,
|
||||||
R: scryptR,
|
R: KDFParams.R,
|
||||||
P: scryptP,
|
P: KDFParams.P,
|
||||||
}
|
}
|
||||||
|
|
||||||
hn, err := os.Hostname()
|
hn, err := os.Hostname()
|
||||||
|
@ -154,14 +189,13 @@ func AddKey(s *Repository, password string, template *crypto.Key) (*Key, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate random salt
|
// generate random salt
|
||||||
newkey.Salt = make([]byte, scryptSaltsize)
|
newkey.Salt, err = crypto.NewSalt()
|
||||||
n, err := rand.Read(newkey.Salt)
|
if err != nil {
|
||||||
if n != scryptSaltsize || err != nil {
|
panic("unable to read enough random bytes for salt: " + err.Error())
|
||||||
panic("unable to read enough random bytes for salt")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// call KDF to derive user key
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// SearchKey finds a key with the supplied password, afterwards the config is
|
||||||
// read and parsed.
|
// read and parsed. It tries at most maxKeys key files in the repo.
|
||||||
func (r *Repository) SearchKey(password string) error {
|
func (r *Repository) SearchKey(password string, maxKeys int) error {
|
||||||
key, err := SearchKey(r, password)
|
key, err := SearchKey(r, password, maxKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,25 @@ import (
|
||||||
"restic/backend"
|
"restic/backend"
|
||||||
"restic/backend/local"
|
"restic/backend/local"
|
||||||
"restic/backend/mem"
|
"restic/backend/mem"
|
||||||
|
"restic/crypto"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/chunker"
|
"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.
|
// TestBackend returns a fully configured in-memory backend.
|
||||||
func TestBackend(t testing.TB) (be backend.Backend, cleanup func()) {
|
func TestBackend(t testing.TB) (be backend.Backend, cleanup func()) {
|
||||||
return mem.New(), func() {}
|
return mem.New(), func() {}
|
||||||
|
@ -22,8 +36,10 @@ const testChunkerPol = chunker.Pol(0x3DA3358B4DC173)
|
||||||
|
|
||||||
// TestRepositoryWithBackend returns a repository initialized with a test
|
// TestRepositoryWithBackend returns a repository initialized with a test
|
||||||
// password. If be is nil, an in-memory backend is used. A constant polynomial
|
// 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()) {
|
func TestRepositoryWithBackend(t testing.TB, be backend.Backend) (r *Repository, cleanup func()) {
|
||||||
|
TestUseLowSecurityKDFParameters(t)
|
||||||
|
|
||||||
var beCleanup func()
|
var beCleanup func()
|
||||||
if be == nil {
|
if be == nil {
|
||||||
be, beCleanup = TestBackend(t)
|
be, beCleanup = TestBackend(t)
|
||||||
|
|
|
@ -214,7 +214,7 @@ func OpenLocalRepo(t testing.TB, dir string) *repository.Repository {
|
||||||
OK(t, err)
|
OK(t, err)
|
||||||
|
|
||||||
repo := repository.New(be)
|
repo := repository.New(be)
|
||||||
err = repo.SearchKey(TestPassword)
|
err = repo.SearchKey(TestPassword, 10)
|
||||||
OK(t, err)
|
OK(t, err)
|
||||||
|
|
||||||
return repo
|
return repo
|
||||||
|
|
6
vendor/manifest
vendored
6
vendor/manifest
vendored
|
@ -7,6 +7,12 @@
|
||||||
"revision": "18419ee53958df28fcfc9490fe6123bd59e237bb",
|
"revision": "18419ee53958df28fcfc9490fe6123bd59e237bb",
|
||||||
"branch": "HEAD"
|
"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",
|
"importpath": "github.com/jessevdk/go-flags",
|
||||||
"repository": "https://github.com/jessevdk/go-flags",
|
"repository": "https://github.com/jessevdk/go-flags",
|
||||||
|
|
22
vendor/src/github.com/elithrar/simple-scrypt/LICENSE
vendored
Normal file
22
vendor/src/github.com/elithrar/simple-scrypt/LICENSE
vendored
Normal 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.
|
||||||
|
|
155
vendor/src/github.com/elithrar/simple-scrypt/README.md
vendored
Normal file
155
vendor/src/github.com/elithrar/simple-scrypt/README.md
vendored
Normal 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.
|
||||||
|
|
295
vendor/src/github.com/elithrar/simple-scrypt/scrypt.go
vendored
Normal file
295
vendor/src/github.com/elithrar/simple-scrypt/scrypt.go
vendored
Normal 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()
|
||||||
|
}
|
156
vendor/src/github.com/elithrar/simple-scrypt/scrypt_test.go
vendored
Normal file
156
vendor/src/github.com/elithrar/simple-scrypt/scrypt_test.go
vendored
Normal 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)
|
||||||
|
}
|
Loading…
Reference in a new issue