jstedfast/MailKit · error · SaslException
IncorrectHash
IncorrectHash
Error message
Challenge contained a signature with an invalid length.
What it means
The SCRAM server-final-message contained a 'v=' signature, but after base64-decoding it its byte length differs from the locally computed HMAC-based Server Signature length. SaslMechanismScramBase.Challenge throws SaslException with SaslErrorCode.IncorrectHash because a valid signature must match the hash output size of the negotiated SCRAM variant.
Solutions
- Capture the exchange with a protocol logger and base64-decode the 'v=' value to check its length against the mechanism's hash size.
- Bypass proxies/SSL inspection that may mangle the SASL continuation.
- Use a different mechanism (PLAIN/LOGIN over TLS) if the server's SCRAM implementation is nonconformant.
- Report the bug to the server vendor or upgrade the server; also try a different MailKit version to rule out client-side issues.
Example fix
// before client.Authenticate (new SaslMechanismScramSha512 (credentials)); // server's SCRAM-SHA-512 is buggy // after client.Authenticate (new SaslMechanismScramSha256 (credentials)); // known-good variant // or over TLS: // client.Authenticate (new SaslMechanismPlain (credentials));
Defensive patterns
Strategy: try-catch
Validate before calling
// cannot be validated before the call; verify the server's advertised mechanism // matches its implementation (e.g. SCRAM-SHA-256 must emit a 32-byte signature)
Type guard
static bool HasExpectedSignatureLength (byte[] signature, string mechanismName) =>
mechanismName.Contains ("SHA256") ? signature.Length == 32
: mechanismName.Contains ("SHA512") ? signature.Length == 64
: signature.Length == 20; Try / catch
try {
client.Authenticate (new SaslMechanismScramSha256 (credentials));
} catch (SaslException ex) when (ex.ErrorCode == SaslErrorCode.IncorrectHash) {
logger.LogError ("SCRAM signature length mismatch — server SASL implementation likely nonconformant");
throw; // or fall back to another mechanism
} Prevention
- Prefer well-known server implementations (Dovecot/Cyrus) with current versions
- Bypass proxies or TLS inspection appliances that can truncate SASL frames
- Pin the SCRAM variant to one the server demonstrably supports
- Keep a fallback authentication mechanism configured over TLS
When it happens
Trigger: Server returns 'v=' followed by base64 data whose decoded length does not equal the HMAC output size (20 bytes for SHA-1, 32 for SHA-256, 64 for SHA-512) — typically truncated or nonconformant server output.
Common situations: Proxy/gateway truncating the SASL payload, a server implementing a different SCRAM hash length than advertised in its mechanism name, or custom/buggy server SASL implementations.
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
- MissingChallenge
- IncompleteChallenge
- InvalidChallenge
- No compatible authentication mechanisms found.
- Invalid Type message.
AI-assisted analysis of jstedfast/MailKit@9d3859a785 (2026-09-15).
Data as JSON: /api/errors/37779e8d257d2b08.
Report an issue: GitHub.
Appendix: source
Thrown at MailKit/Security/SaslMechanismScramBase.cs:407
response = Encoding.UTF8.GetBytes (withoutProof + ",p=" + Convert.ToBase64String (key));
state = LoginState.Validate;
break;
case LoginState.Validate:
if (token == null)
throw new SaslException (MechanismName, SaslErrorCode.MissingChallenge, "Server response did not contain any authentication data.");
var challenge = Encoding.UTF8.GetString (token, startIndex, length);
if (!challenge.StartsWith ("v=", StringComparison.Ordinal))
throw new SaslException (MechanismName, SaslErrorCode.InvalidChallenge, "Challenge did not start with a signature.");
signature = Convert.FromBase64String (challenge.Substring (2));
var serverKey = HMAC (salted!, Encoding.ASCII.GetBytes ("Server Key"));
var calculated = HMAC (serverKey, auth!);
if (signature.Length != calculated.Length)
throw new SaslException (MechanismName, SaslErrorCode.IncorrectHash, "Challenge contained a signature with an invalid length.");
for (int i = 0; i < signature.Length; i++) {
if (signature[i] != calculated[i])
throw new SaslException (MechanismName, SaslErrorCode.IncorrectHash, $"Challenge contained an invalid signature. Expected: {Convert.ToBase64String (calculated)}");
}
negotiatedChannelBinding = channelBindingKind != ChannelBindingKind.Unknown;
IsAuthenticated = true;
response = Array.Empty<byte> ();
break;
default:
throw new IndexOutOfRangeException ("state");
}
return response;
}
/// <summary>View on GitHub (pinned to 9d3859a785)