moonD4rk/HackBrowserData · error

os_crypt.encrypted_key not found in Local State

Error message

os_crypt.encrypted_key not found in Local State

What it means

Local State was read successfully, but the JSON path os_crypt.encrypted_key does not exist. Chrome stores the DPAPI-encrypted AES key there; without it the V10 key cannot be derived. This indicates a malformed, truncated, or very old/unusual Local State file.

Source

Thrown at masterkey/retriever_windows.go:26

	"os"

	"github.com/tidwall/gjson"

	"github.com/moond4rk/hackbrowserdata/crypto"
)

// DPAPIRetriever unwraps Chrome's Local State os_crypt.encrypted_key via Windows DPAPI.
type DPAPIRetriever struct{}

func (r *DPAPIRetriever) RetrieveKey(hints Hints) ([]byte, error) {
	data, err := os.ReadFile(hints.LocalStatePath)
	if err != nil {
		return nil, fmt.Errorf("read Local State: %w", err)
	}

	encryptedKey := gjson.GetBytes(data, "os_crypt.encrypted_key")
	if !encryptedKey.Exists() {
		return nil, fmt.Errorf("os_crypt.encrypted_key not found in Local State")
	}

	keyBytes, err := base64.StdEncoding.DecodeString(encryptedKey.String())
	if err != nil {
		return nil, fmt.Errorf("base64 decode encrypted_key: %w", err)
	}

	const dpapiPrefix = "DPAPI"
	if len(keyBytes) <= len(dpapiPrefix) {
		return nil, fmt.Errorf("encrypted_key too short: %d bytes", len(keyBytes))
	}
	if string(keyBytes[:len(dpapiPrefix)]) != dpapiPrefix {
		return nil, fmt.Errorf("encrypted_key unexpected prefix: got %q, want %q", keyBytes[:len(dpapiPrefix)], dpapiPrefix)
	}

	masterKey, err := crypto.DecryptDPAPI(keyBytes[len(dpapiPrefix):])
	if err != nil {
		return nil, fmt.Errorf("DPAPI decrypt: %w", err)

View on GitHub (pinned to 0503d04d7a)

Solutions

  1. Point hints.LocalStatePath at the User Data root's 'Local State' file (not a profile subfolder) and confirm the JSON contains os_crypt.encrypted_key: type "Local State" | findstr encrypted_key
  2. Launch Chrome once and let it encrypt cookies, which creates the encrypted_key entry, then retry
  3. If the file is truncated, close Chrome, restore the file (or delete and let Chrome rebuild it), then re-run
  4. Check file size — a 0-byte or tiny Local State means corruption; re-copy from a healthy profile backup

Example fix

// before
Hints{LocalStatePath: userDataDir + "\\Default\\Local State"}
// after
Hints{LocalStatePath: userDataDir + "\\Local State"} // User Data root, not the Default profile
Defensive patterns

Strategy: validation

Validate before calling

data, err := os.ReadFile(localStatePath)
if err == nil && !gjson.GetBytes(data, "os_crypt.encrypted_key").Exists() {
    // encrypted_key missing — file corrupt, wrong file, or profile never encrypted cookies
}

Try / catch

key, err := dpapiRetriever.RetrieveKey(hints)
if err != nil && strings.Contains(err.Error(), "os_crypt.encrypted_key not found") {
    // fall back to ABE (v20) tier or restore the Local State file
}

Prevention

When it happens

Trigger: gjson.GetBytes(data, "os_crypt.encrypted_key") returns !Exists(): the key is absent from the JSON, the file is empty/corrupt/truncated, or it is not the User Data-level Local State (e.g. a per-profile file that lacks os_crypt).

Common situations: Chrome crash or disk full left Local State truncated; pointing the hint at Default/Preferences instead of Local State; new profile never used for cookie encryption; Chromium forks with a different os_crypt layout; file partially synced by OneDrive/roaming.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of moonD4rk/HackBrowserData@0503d04d7a (2026-09-06). Data as JSON: /api/errors/8ab79e69bcc411f5. Report an issue: GitHub.