A Java library of cryptographic primitives required to implement the SRP-6 protocol, built with Caesar.
Some of the existing open source SRP-6 Java libraries include:
What I've found is that such libraries are not easy to extend.
Most of them work only with big-endian byte array representations and some of them aren't compliant with RFC 5054.
With that in mind, I decided to create a library of basic building blocks (SRP-6 Variables) that one can use to implement the protocol.
In order to use this library, you must first settle on a couple of constants:
- group parameters
- source of randomness
- hash function
- byte order
For the purposes of demonstration, the following constants will be used throughout this page:
// Group parameters
SRP6IntegerVariable N =
new SRP6CustomIntegerVariable(
new Hex(
"EEAF0AB9 ADB38DD6 9C33F80A FA8FC5E8 60726187 75FF3C0B"
"9EA2314C 9C256576 D674DF74 96EA81D3 383B4813 D692C6E0"
"E0D5D8E2 50B98BE4 8E495C1D 6089DAD1 5DC7D7B4 6154D6B6"
"CE8EF4AD 69B15D49 82559B29 7BCF1885 C529F566 660E57EC"
"68EDBC3C 05726CC0 2FD4CBF4 976EAA9A FD5138FE 8376435B"
"9FC61D2F C0EB06E3"
),
ByteOrder.BIG_ENDIAN
);
SRP6IntegerVariable g =
new SRP6CustomIntegerVariable(
BigInteger.valueOf(2)
);
// Source of randomness
SecureRandom rng = new SecureRandom();
// Hash function
ImmutableMessageDigest imd =
new ImmutableMessageDigest(
MessageDigest.getInstance("SHA-256")
);
// Byte order
ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;
Say we want to create a new record of the form <I, s, v>
for user "alice"
with password "password123"
:
Bytes I = new PlainText("alice");
Bytes P = new PlainText("password123");
Bytes s = Bytes.wrapped(rng.generateSeed(32));
SRP6IntegerVariable x = new SRP6PrivateKey(imd, s, I, P, byteOrder);
SRP6IntegerVariable v = new SRP6Verifier(N, g, x);
This example is based on the optimized message ordering, as described here:
-
First, client sends his or her username (
I
) to the server. -
The server then responds with:
N
,g
,s
andB
. -
Client then performs the following computations:
SRP6IntegerVariable x = new SRP6PrivateKey(imd, s, I, P, byteOrder); SRP6IntegerVariable a = new SRP6RandomEphemeral(rng, N); SRP6IntegerVariable A = new SRP6ClientPublicKey(N, g, a); SRP6IntegerVariable u = new SRP6ScramblingParameter(imd, A, B, N, byteOrder); SRP6IntegerVariable k = new SRP6Multiplier(); // for SRP-6a: // SRP6IntegerVariable k = new SRP6Multiplier(imd, N, g, byteOrder); SRP6IntegerVariable S = new SRP6ClientSharedSecret(N, g, k, B, x, u, a); Bytes K = new SessionKey(imd, S, byteOrder); Bytes M1 = new ClientSessionProof(imd, N, g, I, s, A, B, K, byteOrder);
and responds with
A
andM1
:try { byte[] bufferA = A.bytes(byteOrder).asArray(); byte[] bufferM1 = M1.asArray(); // send over 'bufferA' and 'bufferM1' to the server // ... } catch (IllegalStateException e) { // Immediately abort SRP-6 login! // Under no circumstances show the server A and M1! }
-
Finally, the server responds with
M2
and client checks its validity:Bytes cM2 = new ServerSessionProof(imd, N, A, M1, K, byteOrder); try { if (cM2.equals(M2)) { // Authentication successful! } else { // Authentication failed: server proof mismatch. } } catch (IllegalStateException e) { // Immediately abort SRP-6 login! }
This example is based on the optimized message ordering, as described here:
-
First, the server receives client username (
I
). -
Then the server performs the following computations:
// lookup and fetch the record by I -> <I, s, v> SRP6IntegerVariable b = new SRP6RandomEphemeral(rng, N); SRP6IntegerVariable k = new SRP6Multiplier(); // for SRP-6a: // SRP6IntegerVariable k = new SRP6Multiplier(imd, N, g, byteOrder); SRP6IntegerVariable B = new SRP6ServerPublicKey(N, g, k, v, b);
and responds with:
N
,g
,s
, andB
:try { byte[] bufferN = N.bytes(byteOrder).asArray(); byte[] buffer_g = g.bytes(byteOrder).asArray(); byte[] buffer_s = s.asArray(); byte[] bufferB = B.bytes(byteOrder).asArray(); // send over 'bufferN', 'buffer_g', 'buffer_s', 'bufferB' to the client // ... } catch (IllegalStateException e) { // Immediately abort SRP-6 login! // Under no circumstances show the client N, g, s, or B. }
-
Client then responds with
A
andM1
and the server performs these additional computations and responds withM2
:SRP6IntegerVariable u = new SRP6ScramblingParameter(imd, A, B, N, byteOrder); SRP6IntegerVariable S = new ServerSharedSecret(N, A, v, u, b); Bytes K = new SessionKey(imd, S, byteOrder); Bytes sM1 = new ClientSessionProof(imd, N, g, I, s, A, B, K, byteOrder); try { if (sM1.equals(M1)) { // Authentication successful! Bytes M2 = new ServerSessionProof(imd, N, A, M1, K, byteOrder); byte[] buffer_M2 = M2.asArray(); // send over 'bufferM2' to the client // ... } else { // Authentication failed: client proof mismatch. } } catch (IllegalStateException e) { // Immediately abort SRP-6 login! // Under no circumstances show the client M2. }
Use the release script with the following arguments:
-
release
- the next release version -
snapshot
- the next snapshot version -
dryRun
(optional) - if set totrue
, the changes will not be pushed to the remote repository
Example:
./release.sh 0.1.1 0.1.2-SNAPSHOT