将密码字段迁移到 Django
Posted
技术标签:
【中文标题】将密码字段迁移到 Django【英文标题】:Migrating a password field to Django 【发布时间】:2012-01-01 16:51:22 【问题描述】:我以前使用过 Django(1.2 版),总的来说我喜欢它……它特别擅长快速启动和运行一个全新的项目。但是,在这种情况下,我正在重写现有系统并将其移动到 Python/Django。所以,我已经有一个 mysql 数据库,其中有一个“用户”表......这个表使用 MySQL SHA1 函数(无盐等)存储用户的密码。
作为迁移的一部分,我将修复一些数据建模缺陷并移植到 PostgreSQL。
我真的很想使用 django.contrib.auth,但我不清楚我需要做什么。我已经阅读了文档,并且知道我可以将所需的用户信息和我拥有的“额外”信息分开并放入 UserProfile。
但是,如何处理存储在 MySQL 数据库中的密码?
以前有人处理过吗?你采取了什么方法?
【问题讨论】:
【参考方案1】:您可以将其直接放入 user_password
字段 - 请参阅 the Django docs。由于您没有盐,请尝试使用sha1$$password_hash
格式。我还没有调查过它是否适用于空白盐,但这可能是您能够在不破解 django.contrib.auth
或编写自己的身份验证后端的情况下迁移它的唯一方法。
否则,您可以为用户设置一个不可用的密码(规范的做法是将字段设置为!
)并将他们指向忘记密码系统。
【讨论】:
澄清一下,Dougai 建议将密码哈希放入 django,以 6 个文字字符“sha1$$
”为前缀,如果我的密码是“密码”,则在 mysql 中,哈希密码字段将是5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
所以你必须将sha1$$5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
插入到数据库中。
我终于回到了这个项目并有机会测试它。据说我报告说这没有没有工作。
@DavidS,这大概是因为relevant code does assert salt
。我认为(尽管这是未经测试的)正确的方法是制作一个不需要盐的自定义密码哈希。即:子类django.contrib.auth.hashers.SHA1PasswordHasher
,设置algorithm = "sha1unsalt"
,复制encode()
不带assert
,覆盖salt()
返回''
,将该类添加到settings.PASSWORD_HASHERS
,修改DB中的密码启动与sha1unsalt$$
。我认为这应该可行....
@Dougal。谢谢回复。这看起来是个不错的方法。一个问题。 PASSWORD_HASHERS 在 1.3 中可用吗?看起来它只是当前“开发”分支中的文档,并且似乎正在为 1.4 开发。对吗?
我不知道从什么时候开始,但在 1.5 中,django.contrib.auth.hashers 中已经有 UnsaltedSHA1PasswordHasher【参考方案2】:
这是我为使事情正常工作所做的。我创建了一个自定义身份验证后端。注意:我使用电子邮件地址作为用户名。
这是我的代码:
from django.db.models import get_model
from django.contrib.auth.models import User
from hashlib import sha1
class MyUserAuthBackend(object):
def check_legacy_password(self, db_password, supplied_password):
return constant_time_compare(sha1(supplied_password).hexdigest(), db_password)
def authenticate(self, username=None, password=None):
""" Authenticate a user based on email address as the user name. """
try:
user = User.objects.get(email=username)
if '$' not in user.password:
if self.check_legacy_password(user.password, password):
user.set_password(password)
user.save()
return user
else:
return None
else:
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
""" Get a User object from the user_id. """
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
然后我在 settings.py 中添加了以下内容:
AUTHENTICATION_BACKENDS = (
'my_website.my_app.my_file.MyUserAuthBackend',
)
@Dougal 的建议似乎是针对 Django 的下一个版本,但我无法使用(我使用的是 1.3.1)。不过,这似乎是一个更好的解决方案。
【讨论】:
这是一个不错的解决方案,但需要注意几点。您可能希望使用恒定时间字符串比较:code.djangoproject.com/browser/django/tags/releases/1.3.1/…。您可能还想花时间使用 set_password 将旧密码迁移到 Django 格式,因为此时您已经有了密码。 @Mark - 我在开发解决方案时查看了 constant_time_compare 函数。我想我真的没有完全理解它的意义。此外,关于保存密码的要点。我会将它添加到上面的解决方案中。谢谢! constant_time_compare 函数可以防止定时攻击。 django-dev 线程的目的是一个很好的阅读:groups.google.com/group/django-developers/browse_thread/thread/… 谢谢,马克。我阅读了这些信息并决定用它更新答案/解决方案。【参考方案3】:最新版本的 Django 为未加盐的旧密码提供了哈希器。只需将其添加到您的设置文件中:
PASSWORD_HASHERS = (
...
'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher',
)
【讨论】:
以上是关于将密码字段迁移到 Django的主要内容,如果未能解决你的问题,请参考以下文章