1
votes

I want to verify the user credentials (name and password) for Linux users with native C# code (.NET Core). The user credentials on Linux systems are normally stored in the file: /etc/shadow.

For example the user called "csharp" with the password "1337" has the following entry inside the file /etc/shadow:

string entry = "csharp:$6$qVnvjWpk$DOUTmbC9ROZ6s1h0hCZTYWLFfVeUWDbz8f0EUFPEJEC2UKQV0gRiIfoGpnaA.8i4RCcrGpOEHEd9xrAUDpo3Y/:18337:0:99999:7:::"
  • The string before the first ":" is the name of the user => entry.Split(":")[0] (csharp)

  • The char after the first "$" is the hash method => entry.Split("$")[1] (6 = SHA256). Possible methods are: MD5 => 1; SHA256 => 5; SHA512 => 6

  • The string after the second "$" is the salt (or hash?) => entry.Split("$")[2] (qVnvjWpk)

When using the program openssl on the Linux system with "openssl passwd -6 -salt qVnvjWpk 1337", then it will give me the following output:

$6$qVnvjWpk$DOUTmbC9ROZ6s1h0hCZTYWLFfVeUWDbz8f0EUFPEJEC2UKQV0gRiIfoGpnaA.8i4RCcrGpOEHEd9xrAUDpo3Y/

As you can see, the openssl output is part of the /etc/shadow entry:

entry.Split(":")[1] == openssl_output

But how could this be done using native csharp code and without using the program openssl?

I have the following approach for doing this.

CheckCredentials("csharp", "1337");
public static bool CheckCredentials(string username, string password) {

    string[] shadow = File.ReadAllLines("/etc/shadow");

    foreach(string entry in shadow) {
        // entry = csharp:$6$qVnvjWpk$DOUTmbC9ROZ6s1h0hCZTYWLFfVeUWDbz8f0EUFPEJEC2UKQV0gRiIfoGpnaA.8i4RCcrGpOEHEd9xrAUDpo3Y/:18337:0:99999:7:::
        if(entry.Split(":")[0] == username) {

            // hash = 6f0ac65fe01188660aad900bfe16c566ebf0e56c0a7d4a15bd831049108de80bd3a2fbf1a8b91662433a40458ec208a207cab073f190bd65b889e95e4fca8e09
            // $6 => SHA512 
            string hash = HashSHA512(password);

            // salt = qVnvjWpk
            string salt = entry.Split("$")[2];

            // >>> how to check now the given password "1337"? <<< 

            break; 
        }

        throw new Exception($"User '{username}' was not found");

    }

    return false;

}
public static string HashSHA512(string input)
{
    SHA512Managed crypt = new SHA512Managed();
    StringBuilder hash = new StringBuilder();
    byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(input));

    foreach (byte append in crypto)
    {
        hash.Append(append.ToString("x2"));
    }
    return hash.ToString();
}
1
Have you found a solution to this? I need to do the same, verify passwords from an old database with a new application. It seems to be extremely complicated. I better find a library for that.ygoe

1 Answers

0
votes

It is indeed complicated. I found some code that implemented the original specification in C#. It was extended by SHA-512 support. Then I made some more changes to it and can now use it myself. Here's the code. (There's really not much more to mention in this answer...)