{"record":{"id":"b07a57e126d0e175","repo":"kopia/kopia","slug":"empty-key","errorCode":null,"errorMessage":"empty key","messagePattern":"empty key","errorType":"validation","errorClass":"errInvalidMasterKey","httpStatus":null,"severity":"critical","filePath":"internal/crypto/key_derivation.go","lineNumber":15,"sourceCode":"package crypto\n\nimport (\n\t\"crypto/hkdf\"\n\t\"crypto/sha256\"\n\n\t\"github.com/pkg/errors\"\n)\n\nvar errInvalidMasterKey = errors.New(\"invalid primary key\")\n\n// DeriveKeyFromMasterKey computes a key for a specific purpose and length using HKDF based on the master key.\nfunc DeriveKeyFromMasterKey(masterKey, salt []byte, purpose string, length int) (derivedKey []byte, err error) {\n\tif len(masterKey) == 0 {\n\t\treturn nil, errors.Wrap(errInvalidMasterKey, \"empty key\")\n\t}\n\n\tif derivedKey, err = hkdf.Key(sha256.New, masterKey, salt, purpose, length); err != nil {\n\t\treturn nil, errors.Wrap(err, \"unable to derive key\")\n\t}\n\n\treturn derivedKey, nil\n}\n","sourceCodeStart":1,"sourceCodeEnd":24,"githubUrl":"https://github.com/kopia/kopia/blob/82495e54b584c1ef6073c9e1be048f57f8aef078/internal/crypto/key_derivation.go#L1-L24","documentation":"DeriveKeyFromMasterKey returns this when the supplied master key is empty (len == 0), wrapping the sentinel 'invalid primary key'. HKDF requires non-empty key material, so all purpose-specific derivations (AES keys, auth data, HMAC secrets) are refused early with a clear message instead of a cryptic hkdf failure.","triggerScenarios":"Calling DeriveKeyFromMasterKey (directly or via initCrypto, deriveHMACSecret, DeriveKey) with a nil or zero-length masterKey slice.","commonSituations":"Repository locked / never unlocked; KOPIA_PASSWORD or key env var not set; reading a key file that is empty or failed to load; unit tests forgetting to provision a test key.","solutions":["Load/unlock the master key before any derivation call and assert it is non-empty","Check the key source (env var, config, key file) is actually populated at startup","Fix the unlock flow so key material is stored in the variable passed to derivation","In tests, use crypto.TestDeriveKey or generate a key explicitly instead of passing nil"],"exampleFix":"// before\nmasterKey := os.Getenv(\"KOPIA_MASTER_KEY\") // may be \"\"\nderived, err := crypto.DeriveKeyFromMasterKey([]byte(masterKey), salt, purpose, 32)\n// after\nmasterKey := os.Getenv(\"KOPIA_MASTER_KEY\")\nif masterKey == \"\" {\n    return nil, errors.New(\"KOPIA_MASTER_KEY is not set\")\n}\nderived, err := crypto.DeriveKeyFromMasterKey([]byte(masterKey), salt, purpose, 32)","handlingStrategy":"validation","validationCode":"if len(masterKey) == 0 {\n    return errors.New(\"master key is empty; load it before deriving subkeys\")\n}","typeGuard":"func validMasterKey(k []byte) bool { return len(k) > 0 }","tryCatchPattern":"derived, err := crypto.DeriveKeyFromMasterKey(masterKey, salt, purpose, 32)\nif err != nil {\n    if errors.Is(errors.Cause(err), crypto.ErrInvalidMasterKey) {\n        return loadAndUnlockKey(ctx)\n    }\n    return fmt.Errorf(\"key derivation failed: %w\", err)\n}","preventionTips":["Centralize master-key loading in one function that never returns an empty key","Check key presence at process startup","Set and verify key env vars in deployment configs and CI","Never pass nil keys in tests; use TestDeriveKey or generated keys"],"tags":["crypto","key-derivation","validation","go"],"backgroundTag":"missing-credentials","analyzedSha":"82495e54b584c1ef6073c9e1be048f57f8aef078","analyzedAt":"2026-09-07T20:35:21.689Z","contentChangedAt":"2026-09-07T20:35:21.689Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}