Issue With Encrypted Password in C# using Rfc2898DeriveBytes and MSSQL - c#

I have made a login system for my application, however, it is working extremely inconsistently.
Sometimes the password will work, but other times it says it's incorrect. I'm 100% sure I'm typing it correctly.
To store the passwords, I generate a random salt, hash the password with the salt and store the hashed password and salt along with the username in the database.
To authenticate the user, I select the hashed password and salt based on the given username. I then hash their password attempt with the salt and see if it matches their original hashed password, allowing them to log in if so.
My code is as follows:
private const int NumberOfRounds = 5000, SaltLength = 32;
public static byte[] GenerateSalt()
{
using (var rng = new RNGCryptoServiceProvider())
{
var randomNumber = new byte[SaltLength];
rng.GetBytes(randomNumber);
return randomNumber;
}
}
public static byte[] HashPassword(byte[] passwordToHash, byte[] salt)
{
using (var deriveBytes = new Rfc2898DeriveBytes(passwordToHash, salt, numberOfRounds))
{
return deriveBytes.GetBytes(32);
}
}
public static bool IsPasswordValid(string inputPassword, string hashedPassword, string salt)
{
byte[] potentialValidPassword = HashPassword(Encoding.Unicode.GetBytes(inputPassword),
Encoding.Unicode.GetBytes(salt));
string potentialAsString = Encoding.Unicode.GetString(potentialValidPassword);
return Encoding.Unicode.GetBytes(hashedPassword).SequenceEqual(potentialValidPassword) ||
hashedPassword.Equals(potentialAsString);
}
The reason my check compares both the byte array value and the string value is that sometimes the byte value comparison fails but the string value works.
My code to insert a user into the database is as follows
public SecurityReturnMessage AddUser(string username, string password)
{
byte[] salt = PasswordManagement.GenerateSalt();
byte[] hashedPassword = PasswordManagement.HashPassword(Encoding.Unicode.GetBytes(password), salt);
if (conn.State != ConnectionState.Open)
conn.Open();
int result;
using (IDbCommand comm = conn.CreateCommand())
{
comm.CommandText = "usp_IFRS_SEC_USER_INSERT";
comm.CommandType = CommandType.StoredProcedure;
SqlParameter hashPwd =new SqlParameter("#hashpwd", SqlDbType.NVarChar)
{
Value = Encoding.Unicode.GetString(hashedPassword)
};
SqlParameter saltParameter = new SqlParameter("#salt", SqlDbType.NVarChar)
{
Value = Encoding.Unicode.GetString(salt)
};
comm.Parameters.Add(Extensions.CreateParameter(comm, "#user", username));
comm.Parameters.Add(hashPwd);
comm.Parameters.Add(saltParameter);
var returnVal = comm.CreateParameter();
returnVal.Direction = ParameterDirection.ReturnValue;
comm.Parameters.Add(returnVal);
comm.ExecuteNonQuery();
result = (int)returnVal.Value;
}
if (conn.State != ConnectionState.Closed)
conn.Close();
return (SecurityReturnMessage)result;
}
If anyone could help me out with this I'd be extremely grateful.

I Suggest you to use the Base64 conversions as below when you convert values from Bytes to String and vice versa.
string base64 = Convert.ToBase64String(bytes);
byte[] bytes = Convert.FromBase64String(base64);
This way you can make sure that the unicode data is not lost or corrupted during either of the conversion process, which might be a valid reason for your code's inconsistency.

Related

Encrypted password ans salt does not match when I validate them

I use the following code to Encrypt and protect the password and I add salt to it, but when I try to validate it when the user login they don't match, I don't know why.
public static class Encrypt
{
public static string saltValue { get; set; }
public static string hashValue { get; set; }
public static void SecurePassword(string password)
{
// Create a truly random salt using RNGCryptoServiceProvider.
RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider();
byte[] salt = new byte[32];
csprng.GetBytes(salt);
// Get the salt value
saltValue = Convert.ToBase64String(salt);
// Salt the password
byte[] saltedPassword = Encoding.UTF8.GetBytes(saltValue + password);
// Hash the salted password using SHA256
SHA512Managed hashstring = new SHA512Managed();
byte[] hash = hashstring.ComputeHash(saltedPassword);
// Save both the salt and the hash in the user's database record.
saltValue = Convert.ToBase64String(salt);
hashValue = Convert.ToBase64String(hash);
}
public static void ValidateLogin(string password, string username)
{
// Read the user's salt value from the database
string saltValueFromDB = saltValue;
// Read the user's hash value from the database
string hashValueFromDB = hashValue;
byte[] saltedPassword = Encoding.UTF8.GetBytes(saltValueFromDB + password);
// Hash the salted password using SHA256
SHA512Managed hashstring = new SHA512Managed();
byte[] hash = hashstring.ComputeHash(saltedPassword);
string hashToCompare = Convert.ToBase64String(hash);
if (hashValueFromDB.Equals(hashToCompare))
Console.WriteLine("User Validated.");
else
Console.WriteLine("Login credentials incorrect. User not validated.");
}
}
Please advise. Thank you in advance
Changed your code a bit but this works:
public class Encrypt
{
public HashedCredential SecurePassword(string password, string salt = "")
{
var saltValue = salt;
if (string.IsNullOrEmpty(salt))
{
saltValue = GenertateSalt();
}
// Salt the password
byte[] saltedPassword = Encoding.UTF8.GetBytes(saltValue + password);
// Hash the salted password using SHA256
SHA512Managed hashstring = new SHA512Managed();
byte[] hash = hashstring.ComputeHash(saltedPassword);
return new HashedCredential(saltValue, Convert.ToBase64String(hash));
}
private string GenertateSalt()
{
RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider();
byte[] salt = new byte[32];
csprng.GetBytes(salt);
return Convert.ToBase64String(salt);
}
}
public class HashedCredential
{
public string SaltValue { get; }
public string HashValue { get; }
public HashedCredential(string saltValue, string hashValue)
{
SaltValue = saltValue;
HashValue = hashValue;
}
}
[TestMethod]
public void GenerateSalt()
{
// Arrange
var sut = new Encrypt();
// Act
var result = sut.SecurePassword("Test");
var resultB = sut.SecurePassword("Test", result.SaltValue);
// Assert
Console.WriteLine($"resultA:'{result.HashValue}'");
Console.WriteLine($"resultB:'{resultB.HashValue}'");
Assert.AreEqual(result.HashValue, resultB.HashValue);
}

Password hashing in UWP

I have the following code in .net framework.
public string GetHashedPassword(string password, string salt)
{
byte[] saltArray = Convert.FromBase64String(salt);
byte[] passArray = Convert.FromBase64String(password);
byte[] salted = new byte[saltArray.Length + passArray.Length];
byte[] hashed = null;
saltArray.CopyTo(salted, 0);
passArray.CopyTo(salted, saltArray.Length);
using (var hash = new SHA256Managed())
{
hashed = hash.ComputeHash(salted);
}
return Convert.ToBase64String(hashed);
}
I'm trying to create an equivalent in .net core for a UWP application. Here's what I have so far.
public string GetHashedPassword(string password, string salt)
{
IBuffer input = CryptographicBuffer.ConvertStringToBinary(password + salt, BinaryStringEncoding.Utf8);
var hashAlgorithm = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
var hash = hashAlgorithm.HashData(input);
//return CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, hash);
}
The last line, converting the buffer back to a string doesn't work. I get this exception:
No mapping for the Unicode character exists in the target multi-byte code page.
How can I convert the buffer back into a string?
I am assuming, that you want to get the hashed password in a base64-format, because you did that in your .net example.
To get this, change:
CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, hash);
to:
CryptographicBuffer.EncodeToBase64String(hash);
So the complete method looks like this:
public string GetHashedPassword(string password, string salt)
{
IBuffer input = CryptographicBuffer.ConvertStringToBinary(password + salt, BinaryStringEncoding.Utf8);
var hashAlgorithm = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
var hash = hashAlgorithm.HashData(input);
return CryptographicBuffer.EncodeToBase64String(hash);
}

Validate Stored Encrypted Password in SQL Server 2012

In SQL Server I run a command:
Select HASHBYTES('SHA2_256', '12345678') as EncryptedString
It gives 0xEF797C8118F02DFB649607DD5D3F8C7623048C9C063D532CC95C5ED7A898A64F this string as an output, this string has 66 characters.
On the same side, Itried to encrypt password from C# code, using this:
public string GetSHAEncryptedCode(string Text)
{
//SHA1 sha26 = new SHA1CryptoServiceProvider();
SHA256 sha26 = new SHA256CryptoServiceProvider();
byte[] sha256Bytes = System.Text.Encoding.UTF8.GetBytes(Text);
byte[] cryString = sha26.ComputeHash(sha256Bytes);
string sha256Str = string.Empty;
for (int i = 0; i < cryString.Length; i++)
{
sha256Str += cryString[i].ToString("X");
}
return sha256Str;
}
Suupose, if I enter same "12345678" in C# code it returns me a string of 62 character long, string is EF797C8118F02DFB64967DD5D3F8C762348C9C63D532CC95C5ED7A898A64F. Now how could i validate the encrypted string coming from sql server and the other string from C# code in order to login the user from login page?
Your C# format string is incorrect - it it missing leading 0s when the hex value is less than 10.
Instead you need to use "X2" as the format string so that it is padded to 2 numbers:
public string GetSHAEncryptedCode(string Text)
{
//SHA1 sha26 = new SHA1CryptoServiceProvider();
SHA256 sha26 = new SHA256CryptoServiceProvider();
byte[] sha256Bytes = System.Text.Encoding.UTF8.GetBytes(Text);
byte[] cryString = sha26.ComputeHash(sha256Bytes);
string sha256Str = string.Empty;
for (int i = 0; i < cryString.Length; i++)
{
sha256Str += cryString[i].ToString("X2");
}
return sha256Str;
}
This correctly returns EF797C8118F02DFB649607DD5D3F8C7623048C9C063D532CC95C5ED7A898A64F and then you can just append 0x to the start.
In any case, you should not really be converting the values to a string anyway. HASHBYTES() and sha256.ComputeHash() both return byte arrays, so it is more efficient and safer to just compare those instead. You can use the methods described in this answer to do that.
Or maybe better still, I assume you are storing the password in the database as encrypted (aren't you...?), so just encrypt the input value to a byte array in C#, then pass that to the database and use something like
SELECT * FROM users WHERE username = #username AND password = #passwordBytes

Creating a function in .net for login

I have salt and encrypted password in my db. and want to create a login function. I created a login function but it always returns false.
My function for conversion is here, is it right or not?
public static String HashPassword(String password, String salt)
{
var combinedPassword = String.Concat(password, salt);
var sha256 = new SHA256Managed();
var bytes = UTF8Encoding.UTF8.GetBytes(combinedPassword);
var hash = sha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
public static Boolean ValidatePassword(String enteredPassword, String storedHash, String storedSalt)
{
// Consider this function as an internal function where parameters like
// storedHash and storedSalt are read from the database and then passed.
var hash = HashPassword(enteredPassword, storedSalt);
return String.Equals(storedHash, hash);
}

How to Save a Hashed Password and Salt to Varbinary Table Column From Code First Seed Method?

I'm working with Entity Framework Code First Migrations and I'd like to have the Seed method in my Configuration.cs file create a default user for me. I'm storing the hashed user passwords in the database as varbinary(64) and the salt as varbinary(16). I copied the hash and salt of an existing user so the seed method can use it when creating the default user. The problem I'm having is converting the string representation of the password hash and salt into their corresponding sql data types. Here's what I've tried so far:
string hashString = "0x81E09FC75CFAB13F54DF1266ADCA53B9FAE45C1D80655C61DE88057846F9B61DC3ED257F2C7D7B73826F9DC0FFA 5FF987B1A594FD9DAE3DC492F5815E989CD34";
string saltString = "0x630FE0A0186365FF9CCBB0FA6161C08B";
Byte[] pbytes = Encoding.ASCII.GetBytes(hashString);
Byte[] sbytes = Encoding.ASCII.GetBytes(saltString);
context.Users.AddOrUpdate(p => p.FirstName,
new User
{
Id = Guid.NewGuid(),
FirstName = "John",
LastName = "Doe",
Username = "admin",
Email = "jdoe#test.com",
PasswordHash = pbytes,
PasswordSalt = sbytes,
RoleId = 1,
LastLoginDate = null
});
When I run update-database from the Package Manager Console, I get a "Validation failed for one or more entities" error. Just to test things, I copied my hashing code over and tried using that and it worked as expected. Obviously, I don't want to reuse this code in my seed method so I just need a way to convert the strings int Byte[] that the db will be happy with. I've included the code that works below.
byte[] saltBytes = new byte[16];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetNonZeroBytes(saltBytes);
byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes("admin");
byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + saltBytes.Length];
plainTextBytes.CopyTo(plainTextWithSaltBytes, 0);
saltBytes.CopyTo(plainTextWithSaltBytes, plainTextBytes.Length);
var hash = new SHA512Managed();
byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);
context.Users.AddOrUpdate(p => p.FirstName,
new User
{
Id = Guid.NewGuid(),
FirstName = "John",
LastName = "Doe",
Username = "admin",
Email = "jdoe#test.com",
PasswordHash = hashBytes,
PasswordSalt = saltBytes,
RoleId = 1,
LastLoginDate = null
});
Looks like you are trying to convert hexadecimal to Bytes not String to bytes.
So you want to start here....
How can I convert a hex string to a byte array?
but if you end up with a "normal" string to begin with...
public static byte[] Base64StringToByteArray(string base64String) {
return Convert.FromBase64String(base64String);
}
public static byte[] UnicodeStringToByteArray(this string source) {
return Encoding.Unicode.GetBytes(source);
}
public static byte[] UTF8StringToByteArray(this string source)
{
return Encoding.UTF8.GetBytes(source);
}

Categories

Resources