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
- Restore the original secrets.key file into the directory next to the data file (from backup or the original machine) and retry.
- 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.
- 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.
- 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
- Back up secrets.key together with any data files that contain LBM1.aesgcm envelopes.
- Never sync or copy a config directory without its secrets.key sibling file.
- Check stderr for 'Secret key ... is unreadable' warnings — the protector logs key replacement before failing.
- Keep key file permissions at 0600 so cleanup tools or other users cannot remove or corrupt it.
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
- Not a protected payload.
- Protected payload has no scheme.
- Protected payload is truncated.
- Payload was protected with
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)