Message reference
Every SCRAM message is a comma-separated list of <name>=<value> attributes. This library models
each attribute as a ScramAttribute subclass, and each message
as a class that owns the attributes it is allowed to carry.
The four messages
| Class | On the wire | Direction |
|---|---|---|
| ClientFirstMessage | n,,n=user,r=<nonce> |
client → server |
| ServerFirstMessage | r=<nonce>,s=<salt>,i=<iterations> |
server → client |
| ClientFinalMessage | c=<binding>,r=<nonce>,p=<proof> |
client → server |
| ServerFinalMessage | v=<signature> |
server → client |
Each one exposes a Message property holding the full text, and the messages the client receives
have static Parse and TryParse methods. Parse throws
MessageParseException on a message it cannot read; TryParse
returns false instead.
Two messages also expose the partial forms the signature is computed over —
ClientFirstMessage.BareMessage and ClientFinalMessage.MessageWithoutProof. They are public
because implementing a SCRAM server needs them; a client does not have to touch either.
Attributes
| Char | Class | Carries |
|---|---|---|
n |
UserAttribute | Username, SASLprep-normalised, with = and , escaped |
r |
NonceAttribute | Client nonce, then client + server nonce |
s |
SaltAttribute | Salt, base64 on the wire, byte[] in memory |
i |
IterationsAttribute | PBKDF2 iteration count |
c |
ChannelAttribute | base64(gs2-header + binding token) |
p |
ClientProofAttribute | The client's proof |
v |
ServerSignatureAttribute | The server's signature |
a |
AuthorizationIdentityAttribute | Authorization identity, when it differs from the username |
The GS2 header that leads a client-first message is Gs2Attribute; see channel binding.
Anything the library does not recognise becomes an UnknownAttribute rather than a parse failure, which is what RFC 5802 asks for — extension attributes must be tolerated, not rejected.
Comparing signatures
ServerSignatureAttribute defines == and != over a
constant-time comparison. Verifying the server's proof through those operators keeps the check off
the timing side channel:
if (serverFinal.ServerSignature != clientFinal.ServerSignature)
{
throw new AuthenticationException("The server failed to prove it knows the password.");
}
Hashing
Hash wraps the three hash functions SCRAM is defined over and exposes the three operations the mechanism needs:
ComputeHash(byte[])— a plain digest, used forStoredKey := H(ClientKey)ComputeHash(byte[], byte[])— HMAC, used for the client and server signaturesComputeHash(byte[], IEnumerable<byte>, int)— PBKDF2, used forSaltedPassword
The message classes call these for you. They are public because a SCRAM server implementation needs the same primitives to verify what a client sends.