moonD4rk/HackBrowserData · error

encrypted_key unexpected prefix: got %q, want %q

Error message

encrypted_key unexpected prefix: got %q, want %q

What it means

Chrome's os_crypt.encrypted_key blob is defined as the ASCII string 'DPAPI' followed by a DPAPI-protected blob. RetrieveKey checks this magic prefix after base64 decoding and errors if the first 5 bytes differ, because the remaining bytes are about to be handed to DecryptDPAPI and would be meaningless without it.

Source

Thrown at masterkey/retriever_windows.go:39

		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)
	}
	return masterKey, nil
}

// DefaultRetrievers wires the Windows tiers: DPAPI for v10, ABE for v20 (Chrome 127+, via reflective
// injection). Both run — a profile upgraded from pre-v127 mixes v10+v20 and needs both (issue #578).
func DefaultRetrievers() Retrievers {
	return Retrievers{
		V10: &DPAPIRetriever{},
		V20: &ABERetriever{},
	}
}

View on GitHub (pinned to 0503d04d7a)

Solutions

  1. Confirm hints.LocalStatePath points to a Chromium-based browser's profile 'Local State' file (Chrome/Edge/Brave), not a Firefox profile.
  2. Verify the field is os_crypt.encrypted_key — do not pass cookie ciphertext or other blobs here.
  3. Check whether the target browser is an unusual Chromium fork with a different os_crypt scheme and route it accordingly.
  4. If the field is genuinely corrupt, re-launch the browser to regenerate it.

Example fix

// before: assuming any blob is a DPAPI key
masterKey, err := crypto.DecryptDPAPI(keyBytes)
// after: verify the magic prefix first
if !bytes.HasPrefix(keyBytes, []byte("DPAPI")) {
	return nil, fmt.Errorf("not a DPAPI key blob")
}
masterKey, err := crypto.DecryptDPAPI(keyBytes[5:])
Defensive patterns

Strategy: validation

Validate before calling

raw, _ := base64.StdEncoding.DecodeString(encKey)
if !bytes.HasPrefix(raw, []byte("DPAPI")) {
	return errors.New("not a Chromium os_crypt DPAPI key blob")
}

Type guard

func isChromiumEncryptedKey(b []byte) bool { return bytes.HasPrefix(b, []byte("DPAPI")) }

Try / catch

key, err := retriever.RetrieveKey(hints)
if err != nil && strings.Contains(err.Error(), "unexpected prefix") {
	// wrong browser/format; route to the correct retriever
}

Prevention

When it happens

Trigger: The decoded encrypted_key is longer than 5 bytes but does not start with the literal bytes 'DPAPI' — i.e. it is not a Chrome DPAPI key blob.

Common situations: Pointing at a Firefox/Gecko-based browser's key file or a non-Chromium browser with a different key format; a Chromium fork that changed the prefix; decrypting a value that is actually an AES-GCM v10 cookie ciphertext rather than the master key; hand-crafted test data.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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