Рекомендации для кодов подтверждения по электронной почте

Я создаю веб-сайт PHP, на котором пользователи регистрируются, и мне интересно узнать о лучших практиках для кодов «подтверждения по электронной почте».

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

md5("xxxxxxxxx".$username."xxxxxxxxxxx".$timestamp."xxxxxxxxx");

Где $timestamp относится к времени создания пользователя. В целом меня это вполне устроило, но потом я задумался, достаточно ли это безопасно? А как насчет возможности столкновений? И мне также нужно сгенерировать коды для сброса пароля и т. д. Если бы я использовал подобную методологию, конфликт мог бы привести к тому, что один пользователь непреднамеренно сбросил бы пароль другого пользователя. И это нехорошо.

Итак, как вы делаете эти вещи? В моих мыслях была таблица следующего формата:

codePK (int, a-I), userID (int), type (int), code (varchar 32), date (timestamp)

Где «тип» будет 1, 2 или 3, что означает «активация», «изменение электронной почты» или «сброс пароля». Это хороший способ сделать это? У вас есть лучший способ?

Используя метод, аналогичный описанному выше, могу ли я автоматически удалить все, что старше двух дней, без использования cron-заданий? Мой хост (почти freespeech.net) их не поддерживает. Если это вообще возможно, я бы хотел избежать cron-job на внешнем хосте, который wget является скриптом, который удаляет вещи, так как это просто беспорядок =P.

Спасибо!
Мала

Обновление:
Для уточнения: я понял, что единственный способ безопасно и безопасно выполнить эту задачу — использовать базу данных, чего старалась избежать исходная функция. Мой вопрос о том, как должна быть структурирована таблица (или таблицы?). Кто-то предложил мне отказаться от codePK и просто сделать код PK. Короче говоря, мой вопрос: это то, что вы делаете?


person Mala    schedule 09.01.2010    source источник
comment
Важно, чтобы у пользователей был действующий адрес электронной почты в файле, хотя бы для сброса пароля. Хотя можно утверждать, что это ответственность пользователя, это реальный мир, с которым мы имеем дело... Как только первый парень, который НИКОГДА не забудет свой пароль, забудет свой пароль, вероятно, через неделю или две, моя система станет самый тупой из всех.   -  person Mala    schedule 10.01.2010


Ответы (5)


Когда мне нужны такие трюки, обычно это происходит по одной из двух причин, упомянутых вами:

  1. В качестве ключа используется для проверки электронных писем, отправленных пользователю
  2. В качестве ключа, используемого для ссылок сброса пароля

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

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

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

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

К чему это по сути сводится:

  • Хешируйте пароли пользователей, используя уникальную для пользователя соль (и, возможно, глобальную секретную соль).
  • Сгенерируйте ключ, хешировав ряд случайных или псевдослучайных источников, таких как временные метки, mt_rand() или даже random.org, если вам действительно нужны случайные вещи.
  • Никогда не используйте свою глобальную соль или соль, уникальную для пользователя, для хеширования чего-либо, к чему пользователь получает доступ, включая ключи сброса пароля, ключи активации и т. д.

Пожалуйста, обратите внимание, что я ни в коем случае не эксперт по безопасности, и я, вероятно, забыл ряд вещей, и, возможно, я упомянул некоторые очень плохие практики. Просто мои 5 копеек ;-)

person phidah    schedule 09.01.2010

Зачем использовать какие-либо данные пользователя в качестве основы для ключа авторизации?

Я предполагаю, что вы храните деактивированные данные в базе данных, так почему бы просто не добавить дополнительную запись, которая представляет собой просто случайный ключ (возможно, md5'ed uniqid с некоторыми дополнительными манипуляциями), а затем проверить это?

person John Parker    schedule 09.01.2010
comment
Первоначально я пытался избежать использования базы данных для этого - отсюда и использование пользовательских данных - person Mala; 09.01.2010
comment
Честно говоря, я не могу не думать, что база данных — это то, что нужно, иначе вам придется хэшировать/манипулировать довольно ограниченным пулом статических данных. - person John Parker; 09.01.2010

Он был достаточно безопасным вплоть до того момента, когда вы опубликовали свой метод в Интернете! Это потому, что вы полагались на безопасность по неизвестности, что не очень хорошая идея.

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

person David M    schedule 09.01.2010
comment
В этом случае вы создали хэш-функцию с ключом, и все готово. Наверное, я должен был догадаться... ;) - person David M; 09.01.2010
comment
;) извините, я думал, что указал это, но, видимо, я отредактировал часть, где я явно сказал это... в любом случае мне интересно, подходит ли структура таблицы/использование одной таблицы для всех кодов - person Mala; 09.01.2010

Почему бы не сделать поле кода уникальным индексом? Так что столкновений никогда не будет?

Кроме того, если вам не нужно создавать хэш из пользовательского ввода и сопоставлять его с хэшем базы данных (подтверждение электронной почты, сброс пароля и т. д.), вы можете добавить случайную строку в тело хеша, например md5('xxx'.$username.'xxx'.time().'xxx'.rand())

person a.yastreb    schedule 09.01.2010
comment
тогда я не смог бы проверить это без использования базы данных - весь смысл этой функции заключался в том, чтобы избежать их использования. Если я все равно буду использовать базу данных, я могу позволить всей строке быть полностью случайной. - person Mala; 09.01.2010

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

person George Bashi    schedule 09.01.2010
comment
В идеале они могли бы просто щелкнуть ссылку в электронном письме. Я не хочу, чтобы это было ужасно долго =P - person Mala; 09.01.2010