将密码从 Drupal 7 迁移到 Django
Posted
技术标签:
【中文标题】将密码从 Drupal 7 迁移到 Django【英文标题】:Migrate passwords from Drupal 7 to Django 【发布时间】:2012-04-10 05:26:18 【问题描述】:我正在将一个站点从 Drupal 7 迁移到 Django 1.4,包括当前用户。如何使用 Drupal 散列的密码?
根据this,Drupal 7 使用 SHA-512 对密码进行哈希处理(它们以“$S$”开头的字符串形式存储)。
Django 1.4 现在包含多个options 用于存储密码,默认为 SHA-256,但我找不到 SHA-512 的选项。虽然this app 似乎允许使用 SHA2 算法,但我不确定它是否与 Django 1.4 兼容(因为 1.4 具有灵活的密码哈希)。
最简单的方法是什么?
ETA:我构建了一个密码哈希器,它模仿 Drupal 的算法并使迁移变得容易。既然我已经接受了一个答案,我不会不接受,但是对于将来想要进行 Drupal 到 Django 迁移的任何人,代码存储在 Django snippets 和 GitHub gist 上。
【问题讨论】:
我正在尝试将用户从 Drupal 7 应用程序迁移到使用 Stormpath 的 Django 1.8 应用程序中。 (根据drupal.stackexchange.com/questions/176008/…)。我坚持的是如何从 Drupal 应用程序中获取密码哈希和盐。你能帮忙吗? 【参考方案1】:我不太了解 Drupal,但我认为密码是散列存储的。如果是这种情况,您将不得不复制密码(我的意思是,复制它们不变)并且您必须更改 Django 散列其密码的方式,使用与 Drupal 完全相同的方式,使用相同的 Security Salt。
我真的不知道该怎么做,但是密码的逻辑包含在 User 对象中。例如。 User.set_password()
函数(描述为here)使用make_password 函数。
我认为通过一些研究你会找到改变它的方法,但重要的是,记住函数必须相等!即:
drupal_hash(x) == django_hash(x) 用于允许密码集中的每个 x。
编辑:
深入了解 django 使用 get_hasher 函数获取 has 函数。现在在 1.4 版本中,有一种方法可以指定 Django 将如何选择该函数。看看这个:https://docs.djangoproject.com/en/dev/topics/auth/#how-django-stores-passwords
最后,为了创建自己的函数,你可以在MD5PasswordHasher 上看看它是如何完成的。看起来真的很简单。您可以使用hashlib python library 生成sha-512 算法。
更改编码方法需要类似以下内容:
def encode(self, password, salt):
assert password
assert salt and '$' not in salt
hash = hashlib.sha512(salt + password).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash)
【讨论】:
这正是我遇到的问题:我还没有找到在 Django 中使用 SHA-512 散列的方法,这似乎是 Drupal 7 的散列方法 确实如此!但正如@Alasdiar 所说,我不确定如何从存储的密码中提取盐...... 如果将来有人尝试:salt 是存储的 Drupal 字符串中的第 5 个到第 12 个字符 解决了!代码发布在我上面的问题中(通常我会回答我自己的问题并接受,但我不想不接受答案)【参考方案2】:感谢 David Robinson 的代码。这让我很开心!不过,它似乎有一个缺陷:如果 Drupal 决定不使用“C”而是“D”作为迭代次数,它就会失败。我稍微修正了类定义:
class DrupalPasswordHasher(BasePasswordHasher):
algorithm = "S"
iter_code = 'C'
salt_length = 8
def encode(self, password, salt, iter_code=None):
"""The Drupal 7 method of encoding passwords"""
if iter_code == None:
iterations = 2 ** _ITOA64.index(self.iter_code)
else:
iterations = 2 ** _ITOA64.index(iter_code)
hash = hashlib.sha512(salt + password).digest()
for i in range(iterations):
hash = hashlib.sha512(hash + password).digest()
l = len(hash)
output = ''
i = 0
while i < l:
value = ord(hash[i])
i = i + 1
output += _ITOA64[value & 0x3f]
if i < l:
value |= ord(hash[i]) << 8
output += _ITOA64[(value >> 6) & 0x3f]
if i >= l:
break
i += 1
if i < l:
value |= ord(hash[i]) << 16
output += _ITOA64[(value >> 12) & 0x3f]
if i >= l:
break
i += 1
output += _ITOA64[(value >> 18) & 0x3f]
longhashed = "%s$%s%s%s" % (self.algorithm, iter_code,
salt, output)
return longhashed[:54]
def verify(self, password, encoded):
hash = encoded.split("$")[1]
iter_code = hash[0]
salt = hash[1:1 + self.salt_length]
return encoded == self.encode(password, salt, iter_code)
【讨论】:
很高兴这能帮上忙,卡斯滕!我想我对您的代码有何不同感到困惑:这不是由原始行iterations = 2 ** _ITOA64.index(hash[0])
处理的吗?顺便说一句,我现在将代码维护为 GitHub 要点,有关详细信息,请参阅答案。 (我现在将其编辑为您的版本)。
(为了比较,here是原码)
重温这一点,我倾向于同意。可能这只是一个让我感到困惑的可读性问题,iterations
同时是'C'和2 ** _ITOA64.index('C')
,然后我通过使iter_code
有效地成为关键字符来解决这个问题,而iterations
两个整数的幂。现在我看到我可以通过更少的代码更改来实现这一点。无论如何,一段很棒的代码,谢谢你的帮助!
请您解释一下如何实现它?【参考方案3】:
您应该能够通过创建自己的 BasePasswordHasher
子类并将其添加到您的 PASSWORD_HASHERS
设置中来实现这一点。
Python 的 hashlib 实现了 sha512。
page David linked to in the question 解释了如何在哈希中编码迭代次数(Drupal 7 为 16385),但我不清楚如何获得盐。
编辑:在对@santiago 回答的评论中,David 说“盐是存储的 Drupal 字符串中的第 5 个到第 12 个字符”。
【讨论】:
drupal 中的 salt 存储在 settings.php 文件中。 不错的大卫。感谢您发布您的解决方案。【参考方案4】:这是 David 对 python 3 的出色回答的更新,因为 hashlib 不再接受字符串。此外,这包括对奇怪的“U$S$*”散列的支持,这些散列显然来自更新,我在我的 drupal 数据库中找到了一堆。
https://gist.github.com/skulegirl/bec420b5272b87d9e4dbd39e947062fc
作为奖励,这是我用来导入用户数据的 xml 文件的代码。(我只是在对 users 表进行查询后通过 sql 导出创建了 xml 文件。)
import xml.etree.ElementTree as ET
from django.contrib.auth.models import User
tree = ET.parse('/PATH/TO/Users.xml')
root = tree.getroot()
for row in root:
user_dict =
for field in row:
user_dict[field.attrib['name']] = field.text
user = User.objects.create_user(user_dict['name'], user_dict['mail'])
if user_dict['pass'][0] == '$':
user_dict['pass'] = user_dict['pass'][1:]
user.password = user_dict['pass']
user.save()
【讨论】:
以上是关于将密码从 Drupal 7 迁移到 Django的主要内容,如果未能解决你的问题,请参考以下文章