身份认证框架和自定义密码哈希算法

4
我已经将身份框架添加到我的WebApi中,并按照此处概述的步骤进行了操作:http://bitoftech.net/2015/01/21/asp-net-identity-2-with-asp-net-web-api-2-accounts-management/。所有这些都很好地运作。
我遇到的问题是,我的客户有另一个系统,该API与之集成(以收集数据),并且具有其自己的登录方法。因此,考虑到这一点,我的客户要求我使用CustomPasswordHasher来加密和解密密码。他们想要做的是能够获取密码哈希并将其转换为实际密码,以便他们可以用它来登录旧系统(两个密码/账户将相同)。我知道这非常不正常,但我无法选择。
我的问题是,这样做有多容易?我找到了一些关于如何创建自定义密码哈希程序的主题,但没有显示我如何从哈希密码中获取密码,它们只显示如何进行比较。
目前,我有以下内容:
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;
    }
}

如果我真的想,我可以这样做:
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;
    }
}

但这样做很疯狂,因为所有密码都将以明文的方式存储。肯定有一种方法可以“加密”密码,在进行调用时再进行“解密”吧?


2
你永远不会解密密码,哈希只是单向的。这就是为什么没有人向你展示它,因为这样做是不正确的。你可能是一个黑客,使用彩虹表等等... 你不能只是将哈希密码转发到旧系统吗?除此之外,听起来你正在试图重新发明轮子。这就是OAuth被发明的原因... - Callum Linington
我理解这些,但这是我的要求,因为旧系统没有其他调用方式。我需要密码以便我可以在API中作为Web引用验证旧应用程序。 - r3plica
为什么不使用计算哈希的数据库视图来解决问题呢?这样你就不必存储真实密码了。 - Deblaton Jean-Philippe
那么加密就是你必须使用的方式了。Callum是对的,哈希是单向的(你无法从哈希中检索源)。如果你必须检索源,加密算法允许你同时进行加密和解密。需要注意的是,这不推荐用于存储密码。 - Kevin
1
对称加密的密码与明文密码(几乎)相同。无论代价如何,都要避免使用它。 - Federico Dipuma
1个回答

1

所以,我尽可能地保证安全。这是我所做的。 我创建了一个新的提供者:

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();
    }
}

然后我的 PasswordHasher,我改成了这个:
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;
    }
}

要使用此密码哈希器,您可以这样调用它:
var passwordHasher = new PasswordHasher(new AdvancedEncryptionStandardProvider(ConfigurationManager.AppSettings["as:key"], ConfigurationManager.AppSettings["as:secret"]));

这似乎符合我的条件。如果存在安全风险,请告诉我!

我有一个类似的情况,我需要使用当前的哈希算法而不是ASP.Net Identity的哈希算法,这样已经存在的用户就不需要重置密码了。你如何将这个密码哈希算法与UserManager连接起来? - Bochen Lin
1
因为 IPasswordHasher 来自于 Microsoft.AspNet.Identity,所以我只需使用 autofac 注册我的类,如 builder.RegisterType<PasswordHasher>().As<IPasswordHasher>().SingleInstance();。如果您正在使用 .net core,则可以执行类似的操作,然后在实例化任何服务(包括 UserManager)时,它们将使用该类。 - r3plica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接