mgth/LittleBigMouse · error · CryptographicException

No key at to read this payload with.

Error message

No key at {_keyFilePath} to read this payload with.

What it means

SecretProtector.Unprotect on Unix decrypts AES-GCM envelopes with a 32-byte key read from a 0600 key file (secrets.key) sitting next to the data file. This CryptographicException is thrown when the key file does not exist (or is unreadable/corrupt and therefore discarded) at the moment an already-protected envelope must be decrypted. Unlike Protect, which calls GetOrCreateKey and would create a fresh key, Unprotect deliberately fails loudly: decrypting with a new key would be impossible anyway, since the old key is gone.

Solutions

  1. Restore the original secrets.key file into the directory next to the data file (from backup or the original machine) and retry.
  2. If the key is unrecoverable, delete the protected values (or the whole store) and re-create them — paired televisions must be re-paired, since the secrets are unrecoverable by design.
  3. Check the directory next to the data file (Path.GetDirectoryName(dataFilePath) + '/secrets.key') exists and contains a 32-byte base64 key readable by the running user; fix permissions to 0600 if needed.
  4. On first run, call Protect (which creates the key) before any Unprotect of pre-existing envelopes, so a fresh install never tries to read a missing key.

Example fix

// before
var token = protector.Unprotect(savedEnvelope); // throws if secrets.key missing
// after
var token = SecretProtector.IsProtected(savedEnvelope) && File.Exists(keyPath)
    ? protector.Unprotect(savedEnvelope)
    : PairDeviceAgain(); // start fresh when the key is gone
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling Unprotect
if (SecretProtector.IsProtected(envelope) &&
    envelope.StartsWith("LBM1.aesgcm.", StringComparison.Ordinal) &&
    !File.Exists(keyFilePath))
    return RePairDevice(); // key gone; cannot decrypt
return protector.Unprotect(envelope);

Type guard

static bool CanUnprotectAes(string envelope, string keyFilePath) =>
    envelope.StartsWith("LBM1.aesgcm.", StringComparison.Ordinal) && File.Exists(keyFilePath);

Try / catch

try { return protector.Unprotect(envelope); }
catch (CryptographicException) { return null; /* treat as unreadable settings: start fresh / re-pair */ }

Prevention

When it happens

Trigger: Calling Unprotect on a 'LBM1.aesgcm.' envelope when secrets.key is missing from the directory next to the data file, was deleted, or exists but is corrupt/wrong-length (ReadKey returns null after logging a warning to stderr).

Common situations: The configuration directory was copied or synced without the key file; a backup restore brought back the settings file but not secrets.key; the user manually cleaned 'mystery' files from ~/.config; the key file was truncated or edited; the data file was moved to another machine whose secrets.key differs or is absent.

Related errors


AI-assisted analysis of mgth/LittleBigMouse@7a42f01d47 (2026-09-16). Data as JSON: /api/errors/81a75e825a835f94. Report an issue: GitHub.

Appendix: source

Thrown at LittleBigMouse.Plugins/LittleBigMouse.Plugins.Core/SecretProtector.cs:108

        var body = envelope.AsSpan(Prefix.Length);
        var separator = body.IndexOf('.');
        if (separator < 0) throw new CryptographicException("Protected payload has no scheme.");

        var scheme = body[..separator].ToString();
        var payload = Convert.FromBase64String(body[(separator + 1)..].ToString());

        switch (scheme)
        {
            case DpapiScheme when OperatingSystem.IsWindows():
                return Encoding.UTF8.GetString(
                    ProtectedData.Unprotect(payload, null, DataProtectionScope.CurrentUser));

            case AesGcmScheme:
                if (payload.Length < NonceLength + TagLength)
                    throw new CryptographicException("Protected payload is truncated.");

                var plain = new byte[payload.Length - NonceLength - TagLength];
                using (var aes = new AesGcm(ReadKey() ?? throw new CryptographicException(
                           $"No key at {_keyFilePath} to read this payload with."), TagLength))
                {
                    aes.Decrypt(
                        payload.AsSpan(0, NonceLength),
                        payload.AsSpan(NonceLength + TagLength),
                        payload.AsSpan(NonceLength, TagLength),
                        plain);
                }
                return Encoding.UTF8.GetString(plain);

            default:
                throw new CryptographicException(
                    $"Payload was protected with '{scheme}', unreadable on this system.");
        }
    }

    byte[] GetOrCreateKey()
    {

View on GitHub (pinned to 7a42f01d47)