package scrypt import ( "crypto/rand" "encoding/hex" "errors" "fmt" "io" "os" "slices" ) /* #define _XOPEN_SOURCE 700 #include #include */ import "C" const ( MinimumPasswordLength = 16 _SALT_MIN_LENGTH = 32 _DESIRED_LENGTH = 32 _N = 1 << 15 r = 8 p = 1 ) var ( ErrSaltTooSmall = errors.New("scrypt: salt is too small") ErrInternal = errors.New("scrypt: internal error") ) // Package scrypt implements the scrypt key derivation function as defined in // Colin Percival's paper "Stronger Key Derivation via Sequential Memory-Hard // Functions" (https://www.tarsnap.com/scrypt/scrypt.pdf). // // // Key derives a key from the password, salt, and cost parameters, returning // a byte slice of length keyLen that can be used as cryptographic key. // // N is a CPU/memory cost parameter, which must be a power of 2 greater than 1. // r and p must satisfy r * p < 2³⁰. If the parameters do not satisfy the // limits, the function returns a nil byte slice and an error. // // For example, you can get a derived key for e.g. AES-256 (which needs a // 32-byte key) by doing: // // dk, err := scrypt.Key([]byte("some password"), salt, 32768, 8, 1, 32) // // The recommended parameters for interactive logins as of 2017 are N=32768, r=8 // and p=1. The parameters N, r, and p should be increased as memory latency and // CPU parallelism increases; consider setting N to the highest power of 2 you // can derive within 100 milliseconds. Remember to get a good random salt. func scrypt( password []byte, salt []byte, N int, r int, p int, outlen int, ) ([]byte, error) { passwordbuf := C.CBytes(password) saltbuf := C.CBytes(salt) defer C.free(passwordbuf) defer C.free(saltbuf) outbuf := C.malloc(C.size_t(outlen)) defer C.free(outbuf) rv := C.scrypt_kdf( (*C.uint8_t)(passwordbuf), C.size_t(len(password)), (*C.uint8_t)(saltbuf), C.size_t(len(salt)), C.uint64_t(N), C.uint32_t(r), C.uint32_t(p), (*C.uint8_t)(outbuf), C.size_t(outlen), ) if rv != 0 { return nil, ErrInternal } out := C.GoBytes(outbuf, C.int(outlen)) return out, nil } func Hash(password []byte, salt []byte) ([]byte, error) { if len(salt) < _SALT_MIN_LENGTH { return nil, ErrSaltTooSmall } hash, err := scrypt( password, salt, _N, r, p, _DESIRED_LENGTH, ) if err != nil { return nil, err } return hash, nil } func SaltFrom(r io.Reader) ([]byte, error) { buffer := make([]byte, _SALT_MIN_LENGTH) _, err := io.ReadFull(r, buffer) if err != nil { return nil, err } return buffer, nil } func Salt() ([]byte, error) { return SaltFrom(rand.Reader) } func Check(password []byte, salt []byte, hash []byte) (bool, error) { candidate, err := Hash(password, salt) if err != nil { return false, err } return slices.Equal(candidate, hash), nil } func Main() { if len(os.Args) != 3 { fmt.Fprintf(os.Stderr, "Usage: scrypt PASSWORD SALT\n") os.Exit(2) } password := os.Args[1] salt := os.Args[2] payload, err := Hash([]byte(password), []byte(salt)) if err != nil { if err == ErrSaltTooSmall { fmt.Fprintln(os.Stderr, err) os.Exit(2) } panic(err) } fmt.Println(hex.EncodeToString(payload)) }