// Package secret holds the small amount of cryptography the server needs: // random identifiers, bearer tokens and password verifiers. package secret import ( "crypto/pbkdf2" "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/base64" "encoding/hex" "errors" "fmt" "strconv" "strings" ) // iterations follows the OWASP recommendation for PBKDF2-HMAC-SHA256. const iterations = 600000 // ID returns a short random identifier, safe in URLs and filenames. func ID() string { return randHex(8) } // Token returns a 256-bit bearer token. func Token() string { return randHex(32) } // UserCode returns a short, human-transcribable code for device login. // The alphabet omits characters that are easy to confuse when read aloud. func UserCode() string { const alphabet = "BCDFGHJKLMNPQRSTVWXZ23456789" b := randBytes(8) out := make([]byte, 0, 9) for i, v := range b { if i == 4 { out = append(out, '-') } out = append(out, alphabet[int(v)%len(alphabet)]) } return string(out) } // Hash returns a verifier string for a password. func Hash(password string) (string, error) { if password == "" { return "", errors.New("secret: empty password") } salt := randBytes(16) dk, err := pbkdf2.Key(sha256.New, password, salt, iterations, 32) if err != nil { return "", err } return fmt.Sprintf("pbkdf2-sha256$%d$%s$%s", iterations, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(dk)), nil } // Verify reports whether password matches a verifier produced by Hash. // An unparsable or empty verifier never matches. func Verify(verifier, password string) bool { parts := strings.Split(verifier, "$") if len(parts) != 4 || parts[0] != "pbkdf2-sha256" { return false } iter, err := strconv.Atoi(parts[1]) if err != nil || iter < 1000 { return false } salt, err := base64.RawStdEncoding.DecodeString(parts[2]) if err != nil { return false } want, err := base64.RawStdEncoding.DecodeString(parts[3]) if err != nil { return false } got, err := pbkdf2.Key(sha256.New, password, salt, iter, len(want)) if err != nil { return false } return subtle.ConstantTimeCompare(got, want) == 1 } // Equal compares two secrets without leaking their contents through timing. func Equal(a, b string) bool { return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1 } func randBytes(n int) []byte { b := make([]byte, n) if _, err := rand.Read(b); err != nil { panic("secret: " + err.Error()) // crypto/rand never fails on supported platforms } return b } func randHex(n int) string { return hex.EncodeToString(randBytes(n)) }