Как создать хэш пароля PBKDF2-SHA256 в C# / Bouncy Castle

Мне нужно создать хэш пароля PBKDF2-SHA256, но у меня возникли проблемы.

Я скачал репозиторий Bouncy Castle, но немного застрял в поиске того, что искал в модуле. Тесты.

Нашел образец кода здесь, но он работает только с SHA1. Ключевой бит кода:

/// <summary>
/// Computes the PBKDF2-SHA1 hash of a password.
/// </summary>
/// <param name="password">The password to hash.</param>
/// <param name="salt">The salt.</param>
/// <param name="iterations">The PBKDF2 iteration count.</param>
/// <param name="outputBytes">The length of the hash to generate, in bytes.</param>
/// <returns>A hash of the password.</returns>
private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
{
    var pdb = new Pkcs5S2ParametersGenerator();
    pdb.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt,
                 iterations);
    var key = (KeyParameter)pdb.GenerateDerivedMacParameters(outputBytes * 8);
    return key.GetKey();
}

Мне нужно изменить это с SHA1 на SHA256.

Из документации Java и этот пост казалось, что возможно следующее, но в конструкторе нет перегрузки библиотека С#.

var pdb = new Pkcs5S2ParametersGenerator(new Sha256Derived());

Найдя другую статью о переполнении стека, я подумал, что возможно следующее, но алгоритмы хэширования SHA отсутствуют в списке поиска. , поэтому следующее не будет работать.

var bcparam = (KeyParameter)pdb.GenerateDerivedParameters("sha256", outputBytes * 8);

Что мне нужно сделать, чтобы заставить это работать, пожалуйста?

Примечание. Если вы читаете это и не знаете, как в Bouncy Castle, но знаете другой способ, я все равно буду признателен за вашу помощь.


person HockeyJ    schedule 22.01.2016    source источник
comment
Исходный код Pkcs5S2ParametersGenerator() доступен для изменения. Почему бы не сделать это?   -  person President James K. Polk    schedule 23.01.2016
comment
@JamesKPolk - действительный комментарий. Причины таковы: А. Потому что я не очень хорошо в этом разбираюсь и Б. Я считаю, что сворачивать свои собственные любым способом считается серьезным «нет-нет», когда речь идет о шифровании. Однако, возможно, поскольку это просто вмешательство в настройку, все может быть в порядке. Я предположил, что должен быть способ, но, возможно, С# BouncyCastle отстает от версии Java и просто не включен? Если на С# действительно нет способа сделать это, я могу попробовать запрос на извлечение и обновить код на основе версии Java. Лучше бы было что-то доступное, что я мог бы просто использовать.   -  person HockeyJ    schedule 23.01.2016


Ответы (1)


ИЗМЕНИТЬ (предыдущая история ответов удалена для краткости)

Теперь существует пакет NuGet Bouncy Castle Crypto API, который можно использовал. Кроме того, вы можете получить исходный код непосредственно с GitHub, что будет работать. У меня был стандартный Bouncy Castle от NuGet, который на момент написания статьи не был обновлен до версии 1.8.1.

Для удобства искателей вот вспомогательный класс C# для хеширования. Проверено на нескольких потоках и вроде нормально.

ПРИМЕЧАНИЕ. Этот класс также работает для версии библиотеки .NET Core BouncyCastle.NetCore.

/// <summary>
/// Contains the relevant Bouncy Castle Methods required to encrypt a password.
/// References NuGet Package BouncyCastle.Crypto.dll
/// </summary>
public class BouncyCastleHashing
{
    private SecureRandom _cryptoRandom;

    public BouncyCastleHashing()
    {
        _cryptoRandom = new SecureRandom();
    }

    /// <summary>
    /// Random Salt Creation
    /// </summary>
    /// <param name="size">The size of the salt in bytes</param>
    /// <returns>A random salt of the required size.</returns>
    public byte[] CreateSalt(int size)
    {
        byte[] salt = new byte[size];
        _cryptoRandom.NextBytes(salt);
        return salt;
    }

    /// <summary>
    /// Gets a PBKDF2_SHA256 Hash  (Overload)
    /// </summary>
    /// <param name="password">The password as a plain text string</param>
    /// <param name="saltAsBase64String">The salt for the password</param>
    /// <param name="iterations">The number of times to encrypt the password</param>
    /// <param name="hashByteSize">The byte size of the final hash</param>
    /// <returns>A base64 string of the hash.</returns>
    public string PBKDF2_SHA256_GetHash(string password, string saltAsBase64String, int iterations, int hashByteSize)
    {
        var saltBytes = Convert.FromBase64String(saltAsBase64String);

        var hash = PBKDF2_SHA256_GetHash(password, saltBytes, iterations, hashByteSize);

        return Convert.ToBase64String(hash);
    }

    /// <summary>
    /// Gets a PBKDF2_SHA256 Hash (CORE METHOD)
    /// </summary>
    /// <param name="password">The password as a plain text string</param>
    /// <param name="salt">The salt as a byte array</param>
    /// <param name="iterations">The number of times to encrypt the password</param>
    /// <param name="hashByteSize">The byte size of the final hash</param>
    /// <returns>A the hash as a byte array.</returns>
    public byte[] PBKDF2_SHA256_GetHash(string password, byte[] salt, int iterations, int hashByteSize)
    {
        var pdb = new Pkcs5S2ParametersGenerator(new Org.BouncyCastle.Crypto.Digests.Sha256Digest());
        pdb.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt,
                     iterations);
        var key = (KeyParameter)pdb.GenerateDerivedMacParameters(hashByteSize * 8);
        return key.GetKey();
    }

    /// <summary>
    /// Validates a password given a hash of the correct one. (OVERLOAD)
    /// </summary>
    /// <param name="password">The original password to hash</param>
    /// <param name="salt">The salt that was used when hashing the password</param>
    /// <param name="iterations">The number of times it was encrypted</param>
    /// <param name="hashByteSize">The byte size of the final hash</param>
    /// <param name="hashAsBase64String">The hash the password previously provided as a base64 string</param>
    /// <returns>True if the hashes match</returns>
    public bool ValidatePassword(string password, string salt, int iterations, int hashByteSize, string hashAsBase64String)
    {
        byte[] saltBytes = Convert.FromBase64String(salt);
        byte[] actualHashBytes = Convert.FromBase64String(hashAsBase64String);
        return ValidatePassword(password, saltBytes, iterations, hashByteSize, actualHashBytes);
    }

    /// <summary>
    /// Validates a password given a hash of the correct one (MAIN METHOD).
    /// </summary>
    /// <param name="password">The password to check.</param>
    /// <param name="correctHash">A hash of the correct password.</param>
    /// <returns>True if the password is correct. False otherwise.</returns>
    public bool ValidatePassword(string password, byte[] saltBytes, int iterations, int hashByteSize, byte[] actualGainedHasAsByteArray)
    {
        byte[] testHash = PBKDF2_SHA256_GetHash(password, saltBytes, iterations, hashByteSize);
        return SlowEquals(actualGainedHasAsByteArray, testHash);
    }

    /// <summary>
    /// Compares two byte arrays in length-constant time. This comparison
    /// method is used so that password hashes cannot be extracted from
    /// on-line systems using a timing attack and then attacked off-line.
    /// </summary>
    /// <param name="a">The first byte array.</param>
    /// <param name="b">The second byte array.</param>
    /// <returns>True if both byte arrays are equal. False otherwise.</returns>
    private bool SlowEquals(byte[] a, byte[] b)
    {
        uint diff = (uint)a.Length ^ (uint)b.Length;
        for (int i = 0; i < a.Length && i < b.Length; i++)
            diff |= (uint)(a[i] ^ b[i]);
        return diff == 0;
    }

}

Пример использования

public void CreatePasswordHash_Single()
{
    int iterations = 100000; // The number of times to encrypt the password - change this
    int saltByteSize = 64; // the salt size - change this
    int hashByteSize = 128; // the final hash - change this

    BouncyCastleHashing mainHashingLib = new BouncyCastleHashing();

    var password = "password"; // That's really secure! :)

    byte[] saltBytes = mainHashingLib.CreateSalt(saltByteSize);
    string saltString = Convert.ToBase64String(saltBytes);

    string pwdHash = mainHashingLib.PBKDF2_SHA256_GetHash(password, saltString, iterations, hashByteSize);

    var isValid = mainHashingLib.ValidatePassword(password, saltBytes, iterations, hashByteSize, Convert.FromBase64String(pwdHash));

}
person HockeyJ    schedule 25.01.2016
comment
подтвержденный пакет nuget Bouncy Castle Crypto API, упомянутый выше, теперь имеет версию 1.8.1 - person kyle; 04.04.2017
comment
@HockeyJ Надеюсь, я не опаздываю на вечеринку, как определить, какое число является хорошим для параметра iterationCount? - person ramires.cabral; 04.05.2018
comment
@ramires.cabral - Это выбор между скоростью и безопасностью. В какой-то момент 4000 были нормой и, вероятно, до сих пор считаются «безопасными». Apple использует 10 000 для iTunes — owasp.org/index.php/Password_Storage_Cheat_Sheet#Work_Factor . В этом примере я выбрал 100 000, так как это очень безопасно и не выдаст число, которое я фактически использую при хэшировании. - person HockeyJ; 08.05.2018