Skip to content

SRP6 calculating M1, M2 incorrectly #506

Open
@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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions