Воспроизведение JS PBKDF2 Hash в C # - PullRequest
0 голосов
/ 08 ноября 2018

Мне пришлось реализовать некоторые новые функции безопасности для существующей базы данных.Они использовали для хэширования паролей на стороне клиента с солью из БД.Они использовали этот код для хеширования паролей перед отправкой их на сервер:

var hash = CryptoJS.PBKDF2(password, USER_SALT, {
    keySize: 4,
    iterations: 1000
});

Теперь, как вы уже можете догадаться, это крайне небезопасно, поскольку злоумышленник получает PW, как если бы он был отправлен в виде простого текстаесли пользователь получает Соль с сервера и хэширование выполняется на стороне клиента.Вот почему мне нужно сделать это на стороне сервера.

Теперь в БД есть несколько тысяч записей с хешированными паролями пользователей, которые используют эту БД.Я пытался реализовать подобный метод в C # со многими ссылками из Интернета, но я не мог получить те же хеши, которые хранятся в БД.

    public static string HashPassword(string password, string salt)
    {
        byte[] saltBytes = Encoding.UTF8.GetBytes(salt);
        using (var rfc2898 = new Rfc2898DeriveBytes(password, saltBytes, 1000))
        {
            byte[] hash = rfc2898.GetBytes(16);
            string hashString = string.Empty;
            foreach (byte x in hash)
            {
                hashString += String.Format("{0:x2}", x);
            }
            return hashString;
        }
    }

Этот метод я использовал.Я хотел воспроизвести размер ключа 4 в CryptoJS, чтобы получить хэши паролей длиной 4x = 32 цифры.Я получаю их, но они отличаются от тех, которые я получу в JS с CryptoJS.

Кто-нибудь знает, как получить тот же результат, что и версия CryptoJS?Поскольку все пароли в БД были сохранены таким образом, важно, чтобы они теперь генерировались одинаково снова только на стороне сервера.(Пароли теперь зашифрованы от атак).

1 Ответ

0 голосов
/ 09 ноября 2018

Использование JSFiddle для CryptoJS / PBKDF2 (хотя мне пришлось исправить ссылки CDN), соответствующий код которого

var password = $("[name='password']").val();

var iterations = 1000;
// sizes must be a multiple of 32
var keySize = 128;
var ivSize = 128;
var salt = CryptoJS.lib.WordArray.random(128/8);

$("#salt").html(salt.toString(CryptoJS.enc.Base64));
$("#iter").html(iterations);
$("#keysize").html(keySize);
$("#ivsize").html(ivSize);

var output = CryptoJS.PBKDF2(password, salt, {
    keySize: (keySize+ivSize)/32,
    iterations: iterations
});

// the underlying words arrays might have more content than was asked: remove insignificant words
output.clamp();

var key = CryptoJS.lib.WordArray.create(output.words.slice(0, keySize/32));
var iv = CryptoJS.lib.WordArray.create(output.words.slice(keySize/32));

$("#hasher").html("SHA1");

$("#key").html(key.toString(CryptoJS.enc.Base64));
$("#iv").html(iv.toString(CryptoJS.enc.Base64));

Я получил как один пример ввода / вывода "hello" с (base64) солью 0CD1HGFdkclqcWG5aV+rvw== (и алгоритмом хеширования по умолчанию, с указанными 1000 итерациями и 32 + 32 байтовыми выходами); который произвел tRczLRRuFy/zFiPn1PBKmQ== / dhyeE+0Dd9avSJbM/4TcNw==.

Затем использовался следующий код обнаружения C #:

string password = "hello";
byte[] salt = Convert.FromBase64String("0CD1HGFdkclqcWG5aV+rvw==");
int iterations = 1000;

using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA1))
{
    Console.WriteLine("UTF-8 / SHA-1");
    Console.Write(Convert.ToBase64String(pbkdf2.GetBytes(16)));
    Console.Write(' ');
    Console.WriteLine(Convert.ToBase64String(pbkdf2.GetBytes(16)));
}

using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
{
    Console.WriteLine("UTF-8 / SHA-2-256");
    Console.Write(Convert.ToBase64String(pbkdf2.GetBytes(16)));
    Console.Write(' ');
    Console.WriteLine(Convert.ToBase64String(pbkdf2.GetBytes(16)));
}

byte[] utf16 = Encoding.Unicode.GetBytes(password);
using (var pbkdf2 = new Rfc2898DeriveBytes(utf16, salt, iterations, HashAlgorithmName.SHA1))
{
    Console.WriteLine("UTF-16LE / SHA-2-256");
    Console.Write(Convert.ToBase64String(pbkdf2.GetBytes(16)));
    Console.Write(' ');
    Console.WriteLine(Convert.ToBase64String(pbkdf2.GetBytes(16)));
}

using (var pbkdf2 = new Rfc2898DeriveBytes(utf16, salt, iterations, HashAlgorithmName.SHA256))
{
    Console.WriteLine("UTF-16LE / SHA-2-256");
    Console.Write(Convert.ToBase64String(pbkdf2.GetBytes(16)));
    Console.Write(' ');
    Console.WriteLine(Convert.ToBase64String(pbkdf2.GetBytes(16)));
}

с выходом

UTF-8 / SHA-1
tRczLRRuFy/zFiPn1PBKmQ== dhyeE+0Dd9avSJbM/4TcNw==
UTF-8 / SHA-2-256
lkBtILt+xDNEQrX0aWUk3Q== ouOiijCw5sjfMcJo9YZ4Ug==
UTF-16LE / SHA-2-256
1T2gJFFECc5AnpvoiFrBwg== rmHsTuOQdM5YDsmzklMEUQ==
UTF-16LE / SHA-2-256
G4/Ik5vZAd2l8kwq45BKaw== Iqy61Eaf8jmoxO2TpA+rkg==

Вывод: CryptoJS.PBKDF2 использует SHA-1 и UTF-8, что означает, что наиболее вероятная проблема заключается в том, что вы загружаете не ту соль, если не получаете тот же ответ. Если это не строка UTF-8, это, вероятно, либо шестнадцатеричные данные, либо base64 (но это полностью зависит от вашего использования, здесь нет «правильного ответа», так как соль «просто байтов»).

...