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

  1. Capture the exchange with a protocol logger and base64-decode the 'v=' value to check its length against the mechanism's hash size.
  2. Bypass proxies/SSL inspection that may mangle the SASL continuation.
  3. Use a different mechanism (PLAIN/LOGIN over TLS) if the server's SCRAM implementation is nonconformant.
  4. 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

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


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)