r3plica
r3plica

Reputation: 13367

Identity Framework and Custom Password Hasher

I have added identity framework to my WebApi and followed the steps outlined here:

http://bitoftech.net/2015/01/21/asp-net-identity-2-with-asp-net-web-api-2-accounts-management/

All of this is working fine. The problem I have, is that my client has another system of which the API integrates with (to collect data) and that has it's own login methods. So, with that in mind, my client has asked me to use a CustomPasswordHasher to encrypt and decrypt passwords. What they would like to do is be able to get a password hash and convert it into the actual password so that they can use it to log into the old system (both passwords / accounts will be the same). I know this is very unothadox but I have no choice in the matter.

My question is, how easy is this to do? I have found a few topics on how to create a custom password hasher, but none show me how to actually get the password from the hashed password, they only show how to compare.

Currently I have this:

public class PasswordHasher : IPasswordHasher
{
    private readonly int _saltSize;
    private readonly int _bytesRequired;
    private readonly int _iterations;

    public PasswordHasher()
    {
        this._saltSize = 128 / 8;
        this._bytesRequired = 32;
        this._iterations = 1000;
    }

    public string HashPassword(string password)
    {

        // Create our defaults
        var array = new byte[1 + this._saltSize + this._bytesRequired];

        // Try to hash our password
        using (var pbkdf2 = new Rfc2898DeriveBytes(password, this._saltSize, this._iterations))
        {
            var salt = pbkdf2.Salt;
            Buffer.BlockCopy(salt, 0, array, 1, this._saltSize);

            var bytes = pbkdf2.GetBytes(this._bytesRequired);
            Buffer.BlockCopy(bytes, 0, array, this._saltSize + 1, this._bytesRequired);
        }

        // Return the password base64 encoded
        return Convert.ToBase64String(array);
    }

    public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
    {

        // Throw an error if any of our passwords are null
        ThrowIf.ArgumentIsNull(() => hashedPassword, () => providedPassword);

        // Get our decoded hash
        var decodedHashedPassword = Convert.FromBase64String(hashedPassword);

        // If our password length is 0, return an error
        if (decodedHashedPassword.Length == 0)
            return PasswordVerificationResult.Failed;

        var t = decodedHashedPassword[0];

        // Do a switch
        switch (decodedHashedPassword[0])
        {
            case 0x00:
                return PasswordVerificationResult.Success;

            default:
                return PasswordVerificationResult.Failed;
        }
    }

    private bool VerifyHashedPassword(byte[] hashedPassword, string password)
    {

        // If we are not matching the original byte length, then we do not match
        if (hashedPassword.Length != 1 + this._saltSize + this._bytesRequired)
            return false;

        //// Get our salt
        //var salt = pbkdf2.Salt;
        //Buffer.BlockCopy(salt, 0, array, 1, this._saltSize);

        //var bytes = pbkdf2.GetBytes(this._bytesRequired);
        //Buffer.BlockCopy(bytes, 0, array, this._saltSize + 1, this._bytesRequired);

        return true;
    }
}

If I really wanted to I could just do this:

public class PasswordHasher : IPasswordHasher
{
    public string HashPassword(string password)
    {
        // Do no hashing
        return password;
    }

    public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
    {

        // Throw an error if any of our passwords are null
        ThrowIf.ArgumentIsNull(() => hashedPassword, () => providedPassword);

        // Just check if the two values are the same
        if (hashedPassword.Equals(providedPassword))
            return PasswordVerificationResult.Success;

        // Fallback
        return PasswordVerificationResult.Failed;
    }
}

but that would be crazy, because all the passwords would be stored as plain text. Surely there is a way to "encrypt" the password and "decrypt" it when I make a call?

Upvotes: 4

Views: 4445

Answers (1)

r3plica
r3plica

Reputation: 13367

So, I have tried to be as secure as possible. This is what I have done. I created a new provider:

public class AdvancedEncryptionStandardProvider
{

    // Private properties
    private readonly ICryptoTransform _encryptor, _decryptor;
    private UTF8Encoding _encoder;

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <param name="key">Our shared key</param>
    /// <param name="secret">Our secret</param>
    public AdvancedEncryptionStandardProvider(string key, string secret)
    {

        // Create our encoder
        this._encoder = new UTF8Encoding();

        // Get our bytes
        var _key = _encoder.GetBytes(key);
        var _secret = _encoder.GetBytes(secret);

        // Create our encryptor and decryptor
        var managedAlgorithm = new RijndaelManaged();
        managedAlgorithm.BlockSize = 128;
        managedAlgorithm.KeySize = 128;

        this._encryptor = managedAlgorithm.CreateEncryptor(_key, _secret);
        this._decryptor = managedAlgorithm.CreateDecryptor(_key, _secret);
    }

    /// <summary>
    /// Encrypt a string
    /// </summary>
    /// <param name="unencrypted">The un-encrypted string</param>
    /// <returns></returns>
    public string Encrypt(string unencrypted)
    {
        return Convert.ToBase64String(Encrypt(this._encoder.GetBytes(unencrypted)));
    }

    /// <summary>
    /// Decrypt a string
    /// </summary>
    /// <param name="encrypted">The encrypted string</param>
    /// <returns></returns>
    public string Decrypt(string encrypted)
    {
        return this._encoder.GetString(Decrypt(Convert.FromBase64String(encrypted)));
    }

    /// <summary>
    /// Encrypt some bytes
    /// </summary>
    /// <param name="buffer">The bytes to encrypt</param>
    /// <returns></returns>
    public byte[] Encrypt(byte[] buffer)
    {
        return Transform(buffer, this._encryptor);
    }

    /// <summary>
    /// Decrypt some bytes
    /// </summary>
    /// <param name="buffer">The bytes to decrypt</param>
    /// <returns></returns>
    public byte[] Decrypt(byte[] buffer)
    {
        return Transform(buffer, this._decryptor);
    }

    /// <summary>
    /// Writes bytes to memory
    /// </summary>
    /// <param name="buffer">The bytes</param>
    /// <param name="transform"></param>
    /// <returns></returns>
    protected byte[] Transform(byte[] buffer, ICryptoTransform transform)
    {

        // Create our memory stream
        var stream = new MemoryStream();

        // Write our bytes to the stream
        using (var cs = new CryptoStream(stream, transform, CryptoStreamMode.Write))
        {
            cs.Write(buffer, 0, buffer.Length);
        }

        // Retrun the stream as an array
        return stream.ToArray();
    }
}

Then my PasswordHasher, I changed to this:

public class PasswordHasher : IPasswordHasher
{

    // Private properties
    private readonly AdvancedEncryptionStandardProvider _provider;

    public PasswordHasher(AdvancedEncryptionStandardProvider provider)
    {
        this._provider = provider;
    }

    public string HashPassword(string password)
    {
        // Do no hashing
        return this._provider.Encrypt(password);
    }

    public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
    {

        // Throw an error if any of our passwords are null
        ThrowIf.ArgumentIsNull(() => hashedPassword, () => providedPassword);

        // Just check if the two values are the same
        if (hashedPassword.Equals(this.HashPassword(providedPassword)))
            return PasswordVerificationResult.Success;

        // Fallback
        return PasswordVerificationResult.Failed;
    }
}

To use this PasswordHasher, you invoke it like this:

var passwordHasher = new PasswordHasher(new AdvancedEncryptionStandardProvider(ConfigurationManager.AppSettings["as:key"], ConfigurationManager.AppSettings["as:secret"]));

This seems to satisfy my conditions. Let me know if there are security risks please!

Upvotes: 1

Related Questions