Skip to content

SRP6 calculating M1, M2 incorrectly #506

@jimm98y

Description

@jimm98y

I was trying to use BouncyCastle SRP6 on the server side for HomeKit and I found out that I can only generate B and S, but then M1 and M2 are calculated in BouncyCastle like:

M1 = H(A | B | S)
M2 = H(A | M1 | S)
K = H(S)

While they should be calculated as follows according to RFC2945:

K = H(S)
M1 = H(H(N) XOR H(g) | H(I) | s | A | B | K)
M2 = H(A | M1 | K)

In C#, I ended up using the following code:

private SecureRandom _random = new SecureRandom();
private readonly IDigest _digest = new Sha512Digest();
private Srp6Server _server;
private byte[] _N;
private byte[] _g;
private byte[] _s;
private byte[] _I;
private byte[] _B;
private byte[] _calculatedM1;
private byte[] _K;
private byte[] _A;

public byte[] GenerateSalt()
{
    byte[] s = new byte[16];
    _random.NextBytes(s);
    return s;
}

public byte[] GenerateServerCredentials(byte[] bN, byte[] bg, byte[] salt, string userName, string password)
{
    _N = bN;
    _g = bg;
    _s = salt;
    _I = Encoding.UTF8.GetBytes(userName);
    
    byte[] P = Encoding.UTF8.GetBytes(password);
    BigInteger N = new BigInteger(1, _N);
    BigInteger g = new BigInteger(1, _g);
    Srp6GroupParameters group = new Srp6GroupParameters(N, g);
    Srp6VerifierGenerator gen = new Srp6VerifierGenerator();

    gen.Init(group, _digest);

    BigInteger v = gen.GenerateVerifier(_s, _I, P);
    _server = new Srp6Server();
    _server.Init(group, v, _digest, _random);

    BigInteger B = _server.GenerateServerCredentials();
    _B = B.ToByteArrayUnsigned();
    return _B;
}

public bool VerifyClientEvidenceMessage(byte[] A, byte[] clientM1)
{
    //  BouncyCastle is using simplified calculation of M1 and M2 using only A, B and S.
    //_ = _server.CalculateSecret(new BigInteger(1, A));
    //return _server.VerifyClientEvidenceMessage(new BigInteger(1, clientM1));

    // let's calculate it using the correct formula: M1 = H(H(N) XOR H(g) | H(I) | s | A | B | K)
    _A = A;
    BigInteger S = _server.CalculateSecret(new BigInteger(1, A));
    _K = SHA(S.ToByteArrayUnsigned());
    _calculatedM1 = SHA(CONCAT(XOR(SHA(_N), SHA(_g)), CONCAT(SHA(_I), CONCAT(_s, CONCAT(A, CONCAT(_B, _K))))));
    bool result = _calculatedM1.SequenceEqual(clientM1);
    return result;
}

public byte[] CalculateServerEvidenceMessage()
{
    //return _server.CalculateServerEvidenceMessage().ToByteArrayUnsigned();
    return SHA(CONCAT(_A, CONCAT(_calculatedM1, _K)));
}

public byte[] CalculateSessionKey()
{
    //return _server.CalculateSessionKey().ToByteArrayUnsigned();   
    return _K;
}

private static byte[] CONCAT(byte[] a, byte[] b)
{
    return a.Concat(b).ToArray();
}

private byte[] SHA(byte[] bytes)
{
    _digest.Reset();
    _digest.BlockUpdate(bytes, 0, bytes.Length);
    byte[] rv = new byte[_digest.GetDigestSize()]; 
    _digest.DoFinal(rv, 0);
    return rv;
}

private static byte[] XOR(byte[] a, byte[] b)
{
    for (int i = 0; i < a.Length; i++)
    {
        a[i] ^= b[i];
    }
    return a;
}

This issue is not just related to C#, I can see the same code in Java as well. The current BouncyCastle implementation is working only when BouncyCastle is being used on both sides - client and the server.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions