Question

I'm attempting to implement a solution to hash and salt a password in SQL Server 2008 using the SHA-512 algorithm. This solution is based on the book "Expert SQL Server 2008 Encryption" by Michael Coles. Based on his examples, I am able to build the project in visual studio 2010 (.NET 3.5 in C#) and deploy to SQL Server 2008 (as shown in the code below).

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Security.Cryptography;

namespace Apress.Samples
{
    public partial class CustomEncryption
    {
    [Microsoft.SqlServer.Server.SqlFunction
    (
      IsDeterministic = true,
      DataAccess = DataAccessKind.None
    )]
    public static SqlBytes SaltedHash
    (
      SqlString Algorithm,
      [SqlFacet(MaxSize = -1)] SqlBytes PlainText,
      SqlBytes Salt
    )
    {
        // Return NULL if any of the parameters is NULL
        if (Algorithm.IsNull || PlainText.IsNull || Salt.IsNull)
            return SqlBytes.Null;

        // Determine which algorithm to use
        bool HashDefined = true;
        HashAlgorithm Hash = null;
        switch (Algorithm.Value.ToUpper())
        {
            case "SHA256":
                Hash = new SHA256Managed();
                break;

            case "SHA384":
                Hash = new SHA384Managed();
                break;

            case "SHA512":
                Hash = new SHA512Managed();
                break;

            default:
                HashDefined = false;
                break;
        }
        if (!HashDefined)
            throw new Exception
              ("Unsupported hash algorithm - use SHA256, SHA384 or SHA512");

        // Combine the plaintext with the salt
        byte[] PlainTextWithSalt = new byte[PlainText.Length + Salt.Length];
        for (long i = 0; i < Salt.Length; i++)
            PlainTextWithSalt[i] = Salt[i];
        for (long i = Salt.Length; i < PlainText.Length; i++)
            PlainTextWithSalt[i] = PlainText.Value[i - Salt.Length];

        // Generate the hash and return the result
        byte[] HashBytes = Hash.ComputeHash(PlainTextWithSalt);
        return new SqlBytes(HashBytes);
    }
}
}

When I perform a test from SQL using the code below, the hash is generated as expected for each algorithm.

DECLARE @plaintext varchar(15);
SET @plaintext = 'ABCDEFGHIJ';

DECLARE @salt varbinary(16);
SET @salt = Crypt_Gen_Random(16);

DECLARE @sha256 varbinary(32)
DECLARE @sha384 varbinary(48)
DECLARE @sha512 varbinary(64)

SELECT @sha256 = dbo.SaltedHash('SHA256', CAST(@plaintext AS varbinary(max)), @salt);
SELECT @sha384 = dbo.SaltedHash('SHA384', CAST(@plaintext AS varbinary(max)), @salt);
SELECT @sha512 = dbo.SaltedHash('SHA512', CAST(@plaintext AS varbinary(max)), @salt);

SELECT 'SHA-256' AS algorithm, @sha256 AS hash
UNION ALL
SELECT 'SHA-384', @sha384
UNION ALL
SELECT 'SHA-512', @sha512;

I would like to use this for validating a login, where I'm assuming I would need to retrieve the salt value that is stored for the user record and pass it along to the SaltedHash function, where it would return the hashed value. From there, I would compare the hashed value returned by the function with the hashed value stored in the user record.

The problem I'm encountering is when I test passing a hardcoded salt value ('0x0E5ECC235FF6BD7337FFDDE5799D4EEA'), along with the plaintext ('ABCDEFGHIJ') to simulate retrieving the hashed value (for comparing hashed passwords for example). If I provide the plaintext value of ('1234567890') with the same hardcoded salt value, it returns the exact same hashed value. Actually, any 10 character plaintext value returns the same hashed value.

DECLARE @plaintext varchar(15);
SET @plaintext = 'ABCDE12345';

DECLARE @salt varbinary(16);
SET @salt = 0x0E5ECC235FF6BD7337FFDDE5799D4EEA; // Hardcoded salt value!

SELECT @salt

DECLARE @sha256 varbinary(32)
DECLARE @sha384 varbinary(48)
DECLARE @sha512 varbinary(64)

SELECT @sha256 = dbo.SaltedHash('SHA256', CAST(@plaintext AS varbinary(max)), @salt);
SELECT @sha384 = dbo.SaltedHash('SHA384', CAST(@plaintext AS varbinary(max)), @salt);
SELECT @sha512 = dbo.SaltedHash('SHA512', CAST(@plaintext AS varbinary(max)), @salt);

SELECT 'SHA-256' AS algorithm, @sha256 AS hash
UNION ALL
SELECT 'SHA-384', @sha384
UNION ALL
SELECT 'SHA-512', @sha512;

I'm assuming the problem lies with the "combining the plaintext with the salt" code, but unsure.

Any ideas as to how to solve this problem?

Was it helpful?

Solution

Yes you made a error with the array copy, you never copy the last Salt.Length bytes of PlainText in to the destination array. Here is a corrected version of your original code. All that needed to be done is i needs to go up to PlainText.Length + Salt.Length

// Combine the plaintext with the salt
byte[] PlainTextWithSalt = new byte[PlainText.Length + Salt.Length];
for (long i = 0; i < Salt.Length; i++)
    PlainTextWithSalt[i] = Salt[i];
for (long i = Salt.Length; i < PlainText.Length + Salt.Length; i++)
    PlainTextWithSalt[i] = PlainText.Value[i - Salt.Length];

However it is a lot easier to do by using Array.Copy

byte[] PlainTextWithSalt = new byte[PlainText.Length + Salt.Length];
Array.Copy(Salt, PlainTextWithSalt, Salt.Length);
Array.Copy(PlainText, PlainTextWithSalt, Salt.Length, PlainText.Length);

One other thing to note. Salting your passwords is good, but that is only half the battle, you also need to make the hashing SLOW. Normally this is done with functions like bcrypt or PBKDF2, these functions manage mixing the salt and password for you plus they also let you set the number of iterations of hashing to do on the password (you would usually choose a large number, you want the number as large as is tolerable on your end system for slowness).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top