moonD4rk/HackBrowserData · error

encrypted_key too short: %d bytes

Error message

encrypted_key too short: %d bytes

What it means

After base64-decoding os_crypt.encrypted_key, RetrieveKey requires more bytes than the 5-byte 'DPAPI' prefix so it can slice safely. This error means the decoded key blob is 5 bytes or fewer, i.e. structurally invalid — a real Chrome encrypted_key is 'DPAPI' plus a DPAPI blob of dozens of bytes.

Source

Thrown at masterkey/retriever_windows.go:36

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)
	}
	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. Inspect the raw os_crypt.encrypted_key value — it should decode to tens/hundreds of bytes starting with 'DPAPI'.
  2. Use a Local State written by a real Chrome/Chromium install that has saved at least one password or cookie.
  3. If testing, replace placeholder keys with realistic-length dummy blobs prefixed with 'DPAPI'.
  4. Check that no post-processing step (JSON copy, regex extraction) truncated the string.

Example fix

// before: trusting the decoded length blindly
keyBytes, _ := base64.StdEncoding.DecodeString(encryptedKey.String())
// after: validate before use
keyBytes, err := base64.StdEncoding.DecodeString(encryptedKey.String())
if err != nil || len(keyBytes) <= 5 {
	return nil, fmt.Errorf("invalid encrypted_key (len=%d)", len(keyBytes))
}
Defensive patterns

Strategy: validation

Validate before calling

raw, _ := base64.StdEncoding.DecodeString(gjson.GetBytes(data, "os_crypt.encrypted_key").String())
if len(raw) <= 5+32 { return errors.New("encrypted_key implausibly short for a DPAPI blob") }

Type guard

func looksLikeDPAPIBlob(b []byte) bool { return len(b) > 5 && bytes.HasPrefix(b, []byte("DPAPI")) }

Try / catch

key, err := retriever.RetrieveKey(hints)
if err != nil && strings.Contains(err.Error(), "too short") {
	// replace fixture/corrupt Local State, then retry
}

Prevention

When it happens

Trigger: base64 decoding succeeded but produced <=5 bytes: the encrypted_key field held a trivially short string (empty, a placeholder like 'AAAA', or a truncated value).

Common situations: Fixture/test Local State files with dummy values; manual edits that truncated the key; copying only part of the value out of the JSON; a fresh or reset profile where the key was never written properly.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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