将密码从 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的主要内容,如果未能解决你的问题,请参考以下文章

将密码字段迁移到 Django

将用户帐户从Joomla迁移到Django

无法将 Django 从 1.7 迁移到 1.8

从 South 迁移到 Django 1.7 迁移:可交换依赖项

Git仓库迁移方法

查询 django 迁移表