【问题标题】:Securely encrypt integers (up to 2^48) into the shortest possible URL-safe string将整数(最多 2^48)安全加密为最短的 URL 安全字符串
【发布时间】:2017-02-23 21:33:00
【问题描述】:

在我的 Django 应用程序中,我有分层 URL 结构:

webpage.com/property/PK/sub-property/PK/ 等等...

我不想暴露主键并造成漏洞。因此我是 将所有 PK 加密为所有模板和 URL 中的字符串。这是由this SO 用户编写的精彩库django-encrypted-id 完成的。 但是,该库最多支持 2^64 个长整数并产生 24 个字符输出(22 + 2 填充)。这会在我的嵌套结构中产生巨大的 URL。

因此,我想修补加密和解密功能并尝试缩短输出。这是原始代码(+我添加的填充处理):

# Remove the padding after encode and add it on decode
PADDING = '=='

def encode(the_id):
    assert 0 <= the_id < 2 ** 64

    crc = binascii.crc32(bytes(the_id)) & 0xffffff

    message = struct.pack(b"<IQxxxx", crc, the_id)
    assert len(message) == 16

    cypher = AES.new(
        settings.SECRET_KEY[:24], AES.MODE_CBC,
        settings.SECRET_KEY[-16:]
    )

    return base64.urlsafe_b64encode(cypher.encrypt(message)).rstrip(PADDING)


def decode(e):
    if isinstance(e, basestring):
        e = bytes(e.encode("ascii"))

    try:
        e += str(PADDING)
        e = base64.urlsafe_b64decode(e)
    except (TypeError, AttributeError):
        raise ValueError("Failed to decrypt, invalid input.")

    for skey in getattr(settings, "SECRET_KEYS", [settings.SECRET_KEY]):
        cypher = AES.new(skey[:24], AES.MODE_CBC, skey[-16:])
        msg = cypher.decrypt(e)

        crc, the_id = struct.unpack("<IQxxxx", msg)

        if crc != binascii.crc32(bytes(the_id)) & 0xffffff:
            continue

        return the_id
    raise ValueError("Failed to decrypt, CRC never matched.")

# Lets test with big numbers
for x in range(100000000, 100000003):
    ekey = encode(x)
    pk =  decode(ekey)
    print "Pk: %s Ekey: %s" % (pk, ekey)

输出(我稍微改变了字符串,所以不要试图破解我:P):

Pk: 100000000 Ekey: GNtOHji8rA42qfq3p5gNMI
Pk: 100000001 Ekey: tK6RcAZ2MrWmR3nB5qkQDe
Pk: 100000002 Ekey: a7VXIf8pEB6R7XvqwGQo6W

我尝试修改encode() 函数中的所有内容,但没有成功。生成的字符串的长度始终为 22。

这就是我想要的:

  • 保持加密强度接近原始水平或至少不要大幅降低它
  • 支持高达 2^48(~281 万亿)或 2^40 的整数,因为现在 2^64 太多了,我认为我们不会在数据库中拥有如此庞大的 PK。
  • 我对 14-20 之间的字符串长度感到满意。如果它是 20.. 那么是的,它仍然少 2 个字符..

【问题讨论】:

  • 出于好奇,为什么每次都要加解密呢?您是否只是试图通过切换 URL 中的密钥来防止访问随机属性?在那种情况下,我为每个对象生成了一个 UUID 并将其存储在模型中,然后用它来进行查找……效率更高。
  • 是的,这就是我这样做的原因:)。我们曾经有 UUID,但我们没有将它们存储在数据库中。另外,我在这里的目标是获得尽可能短的字符串。为了碰撞安全,我相信 UUID 相当长。而且我读过截断它们是不安全的。
  • 无论您使用什么方法(只要它是好的),您的碰撞机会将在很大程度上取决于标识符的长度。这也将是空间/时间的权衡:我个人只会生成一个随机 id 以保存到模型中,(并检查以确保它不在数据库中,如果是,则生成另一个)。我会占用额外的存储空间,而不是每次访问对象 URL 时都必须加密/解密(因为您也有子属性,所以更是如此)。

标签: python django encryption aes


【解决方案1】:

目前您正在使用带有静态 IV 的 CBC 模式,因此您拥有的代码无论如何都不安全,并且如您所说,会产生相当大的密文。

我建议从 CBC 模式切换到 CTR 模式,这样您就可以拥有可变长度的 IV。我认为,CTR 模式下 IV(或 nonce)的正常推荐长度是 12,但您可以根据需要将其向上或向下减小。 CTR 也是一种流密码,这意味着您输入的内容就是您在大小方面得到的内容。使用 AES,CBC 模式将始终以 16 个字节的块为您返回密文,因此即使您加密 6 个字节,您也会得到 16 个字节,因此不适合您。

如果您让您的 IV 说... 48 位长并且旨在加密不超过 48 位,您将能够生成 6 + 6 = 12 字节的原始输出,或者使用 base64,(4* (12/3)) = 16 字节。通过进一步减小 IV 和/或输入大小(2^40?),您将能够获得比这更低的输出。您可以在不损害安全性的情况下尽可能降低输入的可能值。

请记住,点击率确实存在缺陷。生成两个共享相同 IV 和密钥的密文意味着它们很容易被破解,因此请始终随机生成您的 IV(并且不要将其缩小太多)。

【讨论】:

  • 感谢您的解释。我正在尝试生成随机 IV/计数器,但解码功能需要它来解密密文。可以在不同的会话或应用程序的不同部分调用解密函数。那我怎样才能保留IV。我应该将它作为 PK 表的附加字段存储在数据库中吗?或者有没有办法只使用密文来安全解密?
  • 将其附加到密文本身! IV不需要保密,只是随机的。我把它作为 16 个字节的一部分。那是 6 个字节的密文,6 个字节的 IV,然后扩展为 16,因为您使用的是 base64。
  • 太好了,我现在试试。
  • 我完全理解您的提议,但我无法在 Python 中实现它。作为参考,我检查了this 问题的答案,但如果我尝试将秘密从 16 减少到 6,我会得到:TypeError: CTR counter function returned string not of length 16。我不知道如何继续,我非常感谢一些代码 sn-ps 让我朝着正确的方向前进。
  • 我不太懂 Python,所以我可能无法给你一个代码 sn-p。如果我是你,我会自己实现 CTR 模式。听起来他们仍然希望 CTR 返回一个完整的块......这是不必要的。您是否尝试过调整反馈大小?
猜你喜欢
  • 2010-10-08
  • 2023-03-10
  • 1970-01-01
  • 2011-06-07
  • 2019-08-15
  • 1970-01-01
  • 2011-02-04
  • 2012-01-07
  • 2017-01-17
相关资源
最近更新 更多