Secure Remote Password Issue of hashing in C#

100 Views Asked by At

I want to write a function in C# that do the same thing as the function shown here; PHP and Python are both working, but the last one in C# does not work. I do not know what I am doing wrong, I cannot even debug because the hash is always different, please help me if do you know.

Thanks

PHP:

function calculateSRP6Verifier($username, $password, $salt)
{
    $g = gmp_init(7);
    $N = gmp_init('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16);
    $h1 = sha1(strtoupper($username . ':' . $password), TRUE);
    $h2 = sha1(strrev($salt) . $h1, TRUE);
    $h2 = gmp_import($h2, 1, GMP_LSW_FIRST);
    $verifier = gmp_powm($g, $h2, $N);
    $verifier = gmp_export($verifier, 1, GMP_LSW_FIRST);
    $verifier = str_pad($verifier, 32, chr(0), STR_PAD_RIGHT);
    $verifier = strrev($verifier);
    return $verifier;
}

function getRegistrationData($username, $password)
{
    $salt = random_bytes(32);
    $verifier = calculateSRP6Verifier($username, $password, $salt);
    $salt = strtoupper(bin2hex($salt));             // From haukw
    $verifier = strtoupper(bin2hex($verifier));
    return array($salt, $verifier);
}

Python:

def CalculateSRP6Verifier(username, password, salt):
    g = int(7)
    N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
    userpassupper = f'{username}:{password}'.upper()
    h1 = hashlib.sha1(userpassupper.encode('utf-8')).digest()
    salt = salt[::-1]
    h2 = hashlib.sha1(salt + h1)
    h2 = int.from_bytes(h2.digest(), 'little')
    verifier = pow(g,h2,N)
    verifier = verifier.to_bytes(32, 'little')
    verifier = verifier.ljust(32, b'\x00')
    verifier = verifier[::-1]
    return verifier

def GetSRP6RegistrationData(username, password):
    salt = secrets.token_bytes(32)
    verifier = CalculateSRP6Verifier(username, password, salt)
    print(salt.hex().upper())
    print(verifier.hex().upper())
    return [salt.hex().upper(), verifier.hex().upper()]

C#:

public static string ToHexString(this byte[] s)
{
    StringBuilder sub = new StringBuilder();

    foreach (var x in s)
    {
        sub.Append(x.ToString("X2"));
    }

    return sub.ToString();
}

static uint LeftRotate(this uint value, int shiftCount)
{
    return (value << shiftCount) | (value >> (0x20 - shiftCount));
}

public static byte[] GenerateRandomKey(this byte[] s, int length)
{
    var random = new Random((int)((uint)(Guid.NewGuid().GetHashCode() ^ 1 >> 89 << 2 ^ 42)).LeftRotate(13));
    var key = new byte[length];

    for (int i = 0; i < length; i++)
    {
        int randValue;

        do
        {
            randValue = (int)((uint)random.Next(0xFF)).LeftRotate(1) ^ i;
        } while (randValue > 0xFF && randValue <= 0);

        key[i] = (byte)randValue;
    }

    return key;
}

public static byte[] Combine(this byte[] data, params byte[][] pData)
{
    var combined = data;

    foreach (var arr in pData)
    {
        var currentSize = combined.Length;

        Array.Resize(ref combined, currentSize + arr.Length);

        Buffer.BlockCopy(arr, 0, combined, currentSize, arr.Length);
    }

    return combined;
}

public static bool Compare(this byte[] b, byte[] b2)
{
    for (int i = 0; i < b2.Length; i++)
        if (b[i] != b2[i])
            return false;

    return true;
}

public class SRP6
{
    public static (string Salt, string Verifier) MakeRegistrationData(string username, string password)
    {  
        var s = new byte[0].GenerateRandomKey(32); // random salt
        var v = CalculateVerifier(username, password, s);
        var salt = s.ToHexString().ToUpper();
        var verifier = v.ToHexString().ToUpper();

        return (salt, verifier);
    }
        
    public static byte[] CalculateVerifier(string username, string password, byte[] salt)
    {
        SHA1 _sha1 = SHA1.Create();
        BigInteger _g = new BigInteger(7);
        BigInteger _N = new BigInteger(new byte[]
            {
                0x89, 0x4B, 0x64, 0x5E, 0x89, 0xE1, 0x53, 0x5B, 0xBD, 0xAD, 0x5B, 0x8B, 0x29, 0x06, 0x50, 0x53,
                0x08, 0x01, 0xB1, 0x8E, 0xBF, 0xBF, 0x5E, 0x8F, 0xAB, 0x3C, 0x82, 0x87, 0x2A, 0x3E, 0x9B, 0xB7,
            }, true, false);
        string userPassUpper = username.ToUpper() + ":" + password.ToUpper();
        var h1 = _sha1.ComputeHash(Encoding.UTF8.GetBytes(userPassUpper));
        Array.Reverse(salt);
        var h2 = salt.Combine(h1);
        var h2int = new BigInteger(h2, false);
        var v = BigInteger.ModPow(_g, h2int, _N);
        byte[] verifier = v.ToByteArray();

        if (verifier.Length < 32)
        {
            var paddedVerifier = new byte[32];
            Array.Copy(verifier, 0, paddedVerifier, 32 - verifier.Length, verifier.Length);
            verifier = paddedVerifier;
        }

        Array.Reverse(verifier);

        return verifier;
    }

    public static string Reverse( string s )
    {
        char[] charArray = s.ToCharArray();
        Array.Reverse(charArray);
        return new string(charArray);
    }
}

I tried around everything that google showed me in first 3 pages, even in GitHub repositories and from many implementation I tried but it do not work, I spend more that 50 hours and no success

1

There are 1 best solutions below

0
Janez Kuhar On

I recognize the group parameters (g=7 and N=894B...9BB7). They've been used by WoW in their SRP-6 implementation.

You are defining your prime N everywhere in big-endian byte order: 894B...9BB7. This is okay for Python and PHP because the libraries in those languages work with big-endian numbers.

C#, on the other hand, by default assumes little-endian byte order in BigInteger constructors. So if you want your computations to work in C#, you have to reverse the byte array representation of N: B79B...4B89

Alternatively, you could call the BigInteger constructor with slightly different parameters:

BigInteger _N = new BigInteger(
    new byte[] {
        0x89, 0x4B, 0x64, 0x5E, 0x89, 0xE1, 0x53, 0x5B, 0xBD, 0xAD, 0x5B,
        0x8B, 0x29, 0x06, 0x50, 0x53, 0x08, 0x01, 0xB1, 0x8E, 0xBF, 0xBF,
        0x5E, 0x8F, 0xAB, 0x3C, 0x82, 0x87, 0x2A, 0x3E, 0x9B, 0xB7,
    },
    true, true);

This will create a new unsigned BigInteger from the specified byte array, indicating that the byte order is big-endian.