mgth/LittleBigMouse · error · CryptographicException

Protected payload is truncated.

Error message

Protected payload is truncated.

What it means

For AES-GCM envelopes, the payload must contain at least NonceLength bytes of nonce plus TagLength bytes of authentication tag, followed by the ciphertext. If the base64-decoded payload is shorter than that combined minimum, decryption is impossible and this CryptographicException is thrown. It almost always means the encrypted payload was truncated or corrupted.

Solutions

  1. Re-encrypt the secret with SecretProtector.Protect to regenerate a complete nonce+ciphertext+tag payload.
  2. Verify the stored envelope is complete — re-copy the whole string with no truncation and re-check the base64 decodes cleanly.
  3. Delete the unreadable stored value and re-enter the secret so the app saves a fresh valid envelope.
  4. Catch CryptographicException during settings load and fall back to prompting the user for the secret again.

Example fix

// before (truncated payload)
var secret = protector.Unprotect(storedTruncatedEnvelope);
// after
try { var secret = protector.Unprotect(storedEnvelope); }
catch (CryptographicException) { secret = PromptUserForSecret(); }
Defensive patterns

Strategy: try-catch

Validate before calling

// rough pre-check: decoded payload must exceed nonce+tag length
var body = envelope.Substring(SecretProtector.Prefix.Length);
var payload = Convert.FromBase64String(body[(body.IndexOf('.') + 1)..]);
if (payload.Length < SecretProtector.NonceLength + SecretProtector.TagLength)
    throw new FormatException("Payload too short to contain nonce and tag.");

Type guard

static bool PayloadIsPlausible(string envelope, string prefix)
{
    try
    {
        var body = envelope.AsSpan(prefix.Length);
        var dot = body.IndexOf('.');
        if (dot < 0) return false;
        var payload = Convert.FromBase64String(body[(dot + 1)..].ToString());
        return payload.Length >= SecretProtector.NonceLength + SecretProtector.TagLength;
    }
    catch (FormatException) { return false; }
}

Try / catch

try
{
    secret = protector.Unprotect(envelope);
}
catch (CryptographicException ex) when (ex.Message.Contains("truncated"))
{
    Log.LogWarning("Encrypted payload truncated; prompting for secret again.");
    secret = PromptUserForSecret();
}

Prevention

When it happens

Trigger: Calling Unprotect on an AES-GCM envelope whose decoded payload is shorter than NonceLength + TagLength — e.g. a truncated settings value, a partially copied string, or a payload produced with different nonce/tag sizes by another version.

Common situations: Secret cut off during manual copy/paste, settings file corruption, or an app/config version change that altered nonce or tag lengths so old payloads no longer meet the size minimum.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

        if (!IsProtected(envelope))
            throw new CryptographicException("Not a protected payload.");

        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.");
        }
    }

View on GitHub (pinned to 7a42f01d47)