Аутсорсинг шифрования ключей

Я создаю менеджер паролей, который использует базу данных SQLite для хранения паролей пользователей. Я шифрую пароли алгоритмом AES, но не знаю, где сохранить ключ шифрования, сгенерированный из мастер-пароля.


person Mohamed Zemzem    schedule 26.08.2014    source источник
comment
Вы не должны шифровать пароли, а хэшировать их.   -  person CL.    schedule 26.08.2014
comment
Серьезно, прочитайте это: crackstation.net/hashing-security.htm   -  person Alexander Tobias Bockstaller    schedule 26.08.2014
comment
Мое приложение - менеджер паролей. Если я правильно понимаю, мне нужно хешировать мастер-пароль и шифровать пароли таблицы SQLite? потому что хэш не может восстановить исходный текст.   -  person Mohamed Zemzem    schedule 26.08.2014
comment
@AlexanderTobiasHeinrich, эта ссылка просто потрясающая.   -  person Gimby    schedule 26.08.2014


Ответы (1)


Важно: наймите эксперта. Шутки в сторону. Это тяжело. Даже специалисты ошибаются. Но они знают, на что обращать внимание.

При этом сказал:

Так что это довольно распространенная схема. Есть несколько подходов, но все они имеют несколько общих шаблонов.

Вывод ключа

Шаг получения ключа происходит, когда вы берете секрет и извлекаете из него ключ. В этом процессе используется KDF (функция формирования ключа).

В этом случае вам нужна PBKDF, которая представляет собой функцию получения ключа на основе пароля. Эта функция похожа на KDF, но предназначена для секретов с низкой энтропией. Таким образом, он добавляет уровень защиты, усложняя поиск ссылки от секрета к ключу (и, следовательно, затрудняя попытку грубой силы).

Распространенной функцией PBKDF является PBKDF2. Вы также можете использовать scrypt (который новее, но НАМНОГО мощнее). С этого момента я буду использовать pbkdf2, но обязательно загляну и в scrypt (у него больше параметров настройки, но в остальном то же самое с точки зрения использования).

Во-первых, вам нужно создать соль. Это случайное значение. Это позволяет одному и тому же паролю создавать разные ключи в разных системах. Соль не является секретом.

salt := genRandom(16)

Эту соль нужно где-то хранить (вероятно, в базе данных или где-то в этом роде).

Затем извлеките ключ из пароля. Обратите внимание, что для каждой деривации с одинаковыми настройками (и солью) один и тот же пароль будет создавать один и тот же ключ. Поэтому мы не храним этот ключ. Это вычисляется каждый раз, когда нам это нужно.

key := pbkdf2('sha256', password, salt, count, keyLength)

Обратите внимание на параметр count. Это обеспечивает защиту от грубой силы. Чем выше вы сделаете это, тем дольше будет выполняться функция. Для онлайн-использования (например, в веб-запросе) подходят значения от 10 000 до 20 000. Для автономного использования (как у вас) вы можете терпеть намного выше (и чем выше, тем лучше).

Итак, теперь мы получили ключ из пароля.

Шифрование

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

Это палка о двух концах. Это просто. Но это также означает, что если кто-то сможет прочитать ключ из памяти, он может начать перебирать пароль.

Другим вариантом было бы хранить мастер-ключ. Он будет сгенерирован при установке и зашифрован с использованием производного ключа. Таким образом, когда пользователь входит в систему, вы получаете ключ, извлекаете главный ключ и стираете полученный ключ. Это обеспечивает некоторую защиту от грубой силы и позволяет шифрованию использовать гораздо более надежный ключ. Он также предоставляет возможность менять пароли без необходимости повторного шифрования всего.

Таким образом, поток будет выглядеть так:

private masterKey;
function login(password) {
    salt = lookupSalt()
    derivedKey = pbkdf2('sha256', password, salt, count, keyLength)
    masterKeyEncrypted = lookupEncryptedMasterKey()
    // NOTE that you **must** authenticate this encryption
    masterKey = decrypt(masterKeyEncrypted, derivedKey)
    derivedKey = null 
}

Затем просто зашифруйте и расшифруйте, используя этот мастер-ключ.

Аутентификация

Итак, я упомянул, что вы должны аутентифицировать шифрование. По сути, это означает использование Encrypt-Then-Mac.

В псевдокоде

function encrypt(data, key) {
    cipherKey = key[0...256] // first 256 bits
    macKey = key[256...512] // second 256 bits
    iv = key[512...640] // final 128 bits
    cipherText = AES-256-CBC-ENCRYPT(data, cipherKey, iv)
    mac = HMAC('sha256', cipherText | iv, macKey)
    return mac | cipherText
}

Затем при расшифровке вы сделаете то же самое:

function decrypt(data, key) {
    cipherKey = key[0...256] // first 256 bits
    macKey = key[256...512] // second 256 bits
    iv = key[512...640] // final 128 bits
    mac = data[0...256] // first 256 bits
    cipherText = data[256...] // rest
    if ( mac != HMAC('sha256', cipherText | iv, macKey)) 
        throw Exception "Invalid Data"
    return AES-256-CBC-DECRYPT(cipherText, cipherKey, iv)
}

Теперь проверка MAC-адресов должна выполняться с безопасным сравнением по времени (чтобы предотвратить атаки по времени по сторонним каналам).

Обратите внимание, что поскольку аутентификация зависит от пароля, неверный пароль приведет к сбою аутентификации. Но также обратите внимание, что вы не можете отличить неверный пароль от того, что кто-то подделывает сохраненный ключ.

Если это серьезная проблема, вы можете снова хэшировать пароль (используя другую соль) и хранить его в зашифрованном виде в базе данных. Затем, после расшифровки главного ключа, расшифруйте сохраненный хэш пароля и убедитесь, что пароль соответствует хэшу. Если вы сделаете это, абсолютно убедитесь в том, что хэш зашифрован и используется другая соль (в противном случае выбрасывайте остальную часть работы).

person ircmaxell    schedule 26.08.2014
comment
не следует проводить аудит, чтобы убедиться в правильности введенного пароля? - person Mohamed Zemzem; 27.08.2014