derive

package
v0.10.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package derive computes the login secret a client sends in place of a password.

The password is stretched with PBKDF2-SHA256 and the result is expanded through HKDF-SHA256 under a per-product label. kynotes-server and kypost-server both do this, each mirroring a browser-side TypeScript implementation, which makes it a contract across four programs rather than a helper: change any byte and every user is locked out. The golden vectors in the test come from kynotes-server's implementation, not this one.

Both existing copies import golang.org/x/crypto/pbkdf2 and .../hkdf. Both moved into the standard library in Go 1.24, so adopting this package deletes a dependency from each rather than adding one — which is why it can live here at all.

Index

Constants

View Source
const (
	// MinIterations and MaxIterations bound a value that arrives from a client or a
	// stored record. Below the floor the stretch is worthless; above the ceiling one
	// login can ask for minutes of CPU.
	//
	// The two products do not agree on the ceiling, and this constant takes the looser
	// of the two. kypost-server enforces 12,000,000 and nothing tighter sits in front of
	// it (users.MaxLoginIterations, checked in login_params.go). kynotes-server's
	// derivation carries the same 12,000,000 (auth.MaxLoginIterations), and four of its
	// five routes that accept a client iteration count cap it below that: registration
	// silently clamps an out-of-range value to 600,000 rather than refusing it
	// (httpapi/auth_routes.go:80), and password change, admin user creation, and admin
	// password reset each refuse anything outside 100,000-1,000,000
	// (auth_routes.go:227, admin_routes.go:135,246). The fifth, account recovery
	// (auth_routes.go:264), checks the client's count only through
	// auth.DeriveAuthSecret's own 100,000-12,000,000 bound and stores what it is given —
	// so kynotes does admit the full 12,000,000, just not on every route.
	//
	// Which ceiling is right is unresolved, and it is a product decision rather than a
	// library one. Adopting this package as the only bound would raise those four
	// routes' ceiling 12-fold; keep their tighter checks until that call is made.
	//
	// The cross-repo line numbers above are evidence this module cannot execute — it has
	// no access to those repositories. What it can pin is its own pair of numbers, so
	// that moving either one is a deliberate diff: TestIterationBoundsAreWhatTheDocSays.
	MinIterations = 100_000
	MaxIterations = 12_000_000
)
View Source
const (
	// MaxConcurrent bounds how many stretches run at once.
	//
	// MaxIterations bounds one call, which is not admission control: a burst all asking
	// for the ceiling occupies every core for as long as it takes, and everything sharing
	// the process waits behind it. The iteration count comes from a client or a stored
	// record, so the expensive number is the caller's to choose and the concurrency is
	// ours. PBKDF2 is single-threaded, so a slot really is a core.
	//
	// This budget is separate from password's, and deliberately: this package is
	// standard-library-only, and importing password would pull x/crypto into it. The two
	// also bound different resources — slots of CPU here, bytes and lanes of memory
	// there. A product adopting both is spending MaxConcurrent cores plus
	// password.MaxMemoryKiB of memory across password.MaxLanes lanes; add them up
	// against the box before deciding either is safe.
	MaxConcurrent = 4
)

ponytail: fixed budget and wait. Make them configurable if a deployment needs a different ceiling.

View Source
const MinSyntheticKeyBytes = 32

MinSyntheticKeyBytes is the floor for the key behind a synthetic salt.

The salt itself is not secret — its job is uniqueness. The key is: an attacker who can compute synthetic salts can tell them apart from the random salts real accounts carry, which is exactly the account-existence oracle this function exists to close. An empty or short key makes every synthetic salt predictable, so it is refused rather than used.

Variables

View Source
var ErrBusy = errors.New("derive: too many concurrent derivations")

ErrBusy reports that too many stretches are already running. Callers should answer 503 and must not spend a lockout strike: this is the server saying "not now", not the user getting the password wrong.

Functions

func AuthSecret

func AuthSecret(password, saltBase64 string, iterations int, label string) (string, error)

AuthSecret is AuthSecretContext without a deadline of its own. It derives the hex login secret for a password, a base64 salt and an iteration count, separated by label.

func AuthSecretContext

func AuthSecretContext(ctx context.Context, password, saltBase64 string, iterations int, label string) (string, error)

AuthSecretContext is AuthSecret, bounded by a context.

The context governs the wait for a derivation slot, not the derivation: PBKDF2 at these iteration counts is not interruptible, so a cancellation during the stretch itself is noticed when it finishes.

func SyntheticSalt

func SyntheticSalt(key []byte, label, username string) (string, error)

SyntheticSalt derives a stable per-user login salt for an account that has none, so a probe cannot tell a registered username from an unregistered one by whether a salt comes back.

The username is lower-cased first: keying anything off the raw string lets one account present as many, which quietly multiplies any per-account budget layered on top.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL