Pull to refresh

Comments 6

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


Судя по всему Вы тоже просто прочли оригинальную статью и поверили ей на слово.


1.


Разработчики… создают запрос на сервер, который напрямую посылает логин и пароль.

Автор будет чрезвычайно удивлен, узнав, что это… не влияет ни на что! Хэширование, о котором он говорит, должно происходить на сервере при сохранении паролей в базу. И необходимо оно для того, чтобы злые хацкеры не смогли восстановить пароль, если они взломают сервак.


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


2.


HKDF это функция формирования ключа

Почему использовался HKDF, а не PBKDF2, не scrypt? Просто в образовательных целях: это функции формирования ключа шифрования. То есть прямое назначение этих функций — это из короткого и неслучайного массива байт сделать более длинный и выглядящий как случайный. И "соль" играет здесь огромную роль.


3.


let salt = Array("salty".utf8)
сервер должен знать, что зашифровано в нашей переменной salt. Бэкэнд сможет сравнить ключи используя тот же алгоритм, чтобы идентифицировать пользователя.

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


f(s, P, a) -> k

P — это пароль. Секретное, но НЕ случайное значение.
s — соль. НЕ секретное, но псевдослучайное значение.
a — алгоритм хэширования, используемый внутри функции формирования ключа.
k — псевдослучайное значение на выходе.


Обатите внимание, что функция вернет псевдослучайное значение, если ну хотя бы один из ее параметров будет псевдослучайным. Откуда эта случайность вообще появится, если все ее параметры — это "salty" и (допустим) "mysuperpass"? Когда соль случайна, то на выходе каждый раз будет новое значение, даже для одних и тех же паролей.


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


Как же сгенерировать по-настоящему псевдослучайное значение? Нужно где-то взять генератор, сгенерить это значение определенной длины, как того требует выбранный Вами алгоритм. Автор использует какой-то CryptoSwift? Что ж посмотрим, где у него там генератор, заходим на https://github.com/krzyzanowskim/CryptoSwift, на главной странице нет ничего про это. Смотрим исходники, видим файл RandomBytesSequence.swift, смотрим содержимое:


struct RandomBytesSequence: Sequence {
    let size: Int

    func makeIterator() -> AnyIterator<UInt8> {
        var count = 0
        return AnyIterator<UInt8>.init { () -> UInt8? in
//...
            #if os(Linux) || os(Android) || os(FreeBSD)
//...
            #else
                return UInt8(arc4random_uniform(UInt32(UInt8.max) + 1))
            #endif
        }
    }
}

Я даже не знаю, стоит ли этот arc4random_uniform комментировать. Использование? Например, файл Cryptors.swift:


extension Cryptors {
    public static func randomIV(_ blockSize: Int) -> Array<UInt8> {
        var randomIV: Array<UInt8> = Array<UInt8>()
        randomIV.reserveCapacity(blockSize)
        for randomByte in RandomBytesSequence(size: blockSize) {
            randomIV.append(randomByte)
        }
        return randomIV
    }

Вот такой вот у нас "рэндомный" IV получился.
Не буду вдаваться в подробности, просто не используйте это либу никогда и ни за что. Почитайте лучше про CSPRNG.


4.
Ну и финальное. Если Вы будете хранить пароль в Keychain, этого будет вполне достаточно, чтобы знать, что он уже зашифрован. Эппловская "свзяка ключей" как раз и использует KDF для того, чтобы зашифровать ВСЕ ваши данные, которые вы в этой связке храните.


Поэтому все действия, описанные в этой статье (кроме того, что не нужно хранить пароли в NSUserDefaults), лишние. И, судя по всему, даже вредные.

Ого, спасибо большое за комментарий, очень интересно! Я действительно переводила статью потому что она мне показалась достаточно понятной начинающим разработчикам (как я), но в который раз убеждаюсь, что копать нужно гораздо глубже и каждый момент разбирать тщательнее.

По поводу п.1 позвольте не согласиться, разница все же есть. Многие используют один и тот же пароль на нескольких сервисах, и если кто-то взломает конкретный TLS канал и узнает пароль в открытом виде — это даёт хакеру потенциальную возможность зайти с этим же паролем в другие сервисы.

Про пункт 1 не соглашусь, как и Funbit. Все действия по шифрованию передаваемых данных будут «лишними» только при условии 100% гарантии, что канал передачи данных не взломан. А если MITM? А если подмена сертификатов? В случае отсутствия дополнительных ступеней защиты, после взлома канала передачи данных все данные будут скомпрометированы. А вот если данные зашифрованы, то вскрытие канала взломщику не то чтобы прям сильно поможет. Безопасности много не бывает, чем больше замков на двери — тем меньше шанс, что ее вскроют за разумное время и деньги.
В случае MITM злоумышленник получит доступ к захэшированному паролю и это не помешает ему использовать его для успешной аутентификации. Проблема здесь в том, что для того, чтобы грамотно захэшировать пароль, нужна рандомная соль, что даст два разных значения на клиенте и на сервере. Как Вы решите эту проблему?
Upd: оказывается, arc4random_uniform кое-где заявляется как все еще криптографически стойкий, так что его использование — наверное не такая уж и дикая ошибка. В любом случае, в iOS для таких целей использование SecRandomCopyBytes является рекомендованным.
Sign up to leave a comment.

Articles