安全擦除内存中的密码 (Python)

Posted

技术标签:

【中文标题】安全擦除内存中的密码 (Python)【英文标题】:Securely Erasing Password in Memory (Python) 【发布时间】:2010-10-18 05:00:44 【问题描述】:

如何将用户输入的密码存储在内存中,并在不再需要后安全删除?

详细来说,目前我们有以下代码:

username = raw_input('User name: ')
password = getpass.getpass()
mail = imaplib.IMAP4(MAIL_HOST)
mail.login(username, password)

在调用login方法后,我们需要做些什么来用乱码填充包含密码的内存区域,以便有人无法通过核心转储来恢复密码?

有一个类似的问题,但是它是在 Java 中,并且解决方案使用字符数组: How does one store password hashes securely in memory, when creating accounts?

这可以在 Python 中完成吗?

【问题讨论】:

在IBM article 的底部附近,他们谈到了使用可变数据结构而不是不可变字符串。 上述评论中指向 IBM 文章的链接已失效,请使用 archived page。 我试图实现类似的东西并遇到了这个:sjoerdlangkemper.nl/2016/06/09/clearing-memory-in-python 【参考方案1】:

编辑:删除了不好的建议...

如果你愿意,你也可以使用像 java 例子这样的数组,但只要覆盖它就足够了。

http://docs.python.org/library/array.html

【讨论】:

所有 password = "somethingelse" 所做的只是删除前一行对旧密码的引用。它实际上并没有覆盖任何东西。【参考方案2】:

将密码存储在列表中,如果只是将列表设置为null,则列表中存储的数组的内存会自动释放。

【讨论】:

将字符串存储在列表中的间接级别提供零保护。 另外,没有规定释放后清除内存。内存将保持不变,并且随着时间的推移容易被映像或交换到磁盘。 有一篇很好的文章说明了为什么它不能正常工作:effbot.org/pyfaq/…【参考方案3】:

Python 对内存的控制水平没有那么低。接受它,然后继续前进。您可以做的最佳是在调用mail.login 之后del password,这样就不会保留对密码字符串对象的引用。任何声称能够做更多事情的解决方案只会给您一种错误的安全感。

Python 字符串对象是不可变的;创建字符串后,没有直接的方法可以更改字符串的内容。 即使您能够以某种方式覆盖password 引用的字符串的内容(这在技术上可以通过愚蠢的 ctypes 技巧实现),仍然会有其他已创建的密码副本在各种字符串操作中:

getpass 模块从输入的密码中去除尾随换行符 imaplib 模块在引用密码时创建完整的 IMAP 命令,然后将其传递给套接字

您将不得不以某种方式获取对所有这些字符串的引用并覆盖它们的内存。

【讨论】:

更不用说操作系统会将您的整个内存页面交换到磁盘上的可能性,它可能会在那里放置数月。 交换问题不是特定于 python 的 ofc,但这里是关于该部分的讨论:security.stackexchange.com/questions/29350/…【参考方案4】:

如果您完成后不需要保留邮件对象,我认为您最好的选择是在子进程中执行邮件工作(请参阅subprocess 模块。)这样,当子进程死了,你的密码也死了。

【讨论】:

除非在该子进程中主动清理,或者非常幸运地被系统重新分配给另一个进程并足够快地覆盖,……即使那样,在某些情况下,通过附近的内存单元推断——该值将持续存在并且可以通过幽灵、心血之类的东西到达。【参考方案5】:

实际上有一种方法可以在 Python 中安全地擦除字符串;使用 memset C 函数,按照Mark data as sensitive in python

在发布后很久编辑添加:here's a deeper dive into string interning。根据 CPython 引用计数 GC,在某些情况下(主要涉及非常量字符串)不会发生实习,从而使字符串值的清理更加明确。 (虽然仍然不是“擦洗”/“消毒”清理。)

【讨论】:

请注意,这取决于操作系统。 Windows 和 Linux 代码在链接的帖子中给出。 它还高度依赖于内部解释器细节,例如:与对象指针具有相同值的id、字符串数据与对象指针的偏移量等。不推荐。 @ConradMeyer 当然是。虽然这可能被抽象地认为是“脆弱的”,当然没有人推荐它,但它确实比当前接受的以“Python 没有那么低的水平”开头的答案更好地回答了“这可能”的问题控制记忆。接受它,然后继续前进。” ctypes 的存在立即证明了这绝对是错误的和无益的。这个解决方案实际上比你建议的还要糟糕;您将在应用程序范围内修改散列数据值并破坏表示某些字符串的能力。 我发现这个回答“有可能”的论点比公认的答案更好。正如您所提到的,它完全破坏了解释器;此外,它不适用于任何其他常规 Python 字符串功能或生成副本或临时值的库。它依赖于比普通 C 更弱的类型安全/警告/错误。所以你最好首先使用 C。我不会将其描述为“在 Python 中可能”。我也不高兴第一个答案是正确的,但不幸的是,它是。【参考方案6】:

这可以使用 numpy chararray 来完成:

import numpy as np

username = raw_input('User name: ')
mail = imaplib.IMAP4(MAIL_HOST)
x = np.chararray((20,))
x[:] = list(":<20".format(raw_input('Password: ')))
mail.login(username, x.tobytes().strip())
x[:] = ''

您必须确定密码的最大大小,但这应该会在被覆盖时删除数据。

【讨论】:

不幸的是,当 raw_input() 返回时你已经迷路了。再次调用 tobytes() 时。您可能删除了一个副本,但没有删除其他副本。【参考方案7】:

正确的解决方案是使用可变的 bytearray() ...,您可以安全地从 RAM 中清除密钥和敏感材料。

但是,有一些库,特别是 python“密码学”库,可以防止使用“bytearray”。这是有问题的......在某种程度上,这些加密库应该确保可变类型用于密钥材料。

SecureString 是一个 pip 模块,可让您从内存中完全删除密钥...(我对其进行了一些重构,并将其命名为 SecureBytes)。我编写了一些单元测试来证明密钥已被完全删除。

但是有一个很大的警告:如果某人的密码是“type”,那么“type”这个词将从所有 python 中删除......包括函数定义和对象属性。

换句话说...改变不可变类型是一个糟糕的主意,除非您非常小心,否则可能会立即使任何正在运行的程序崩溃。

正确的解决方案是:永远不要对密钥材料、密码等使用不可变类型。任何构建加密库或例程(如“getpass”)的人都应该使用“bytearray”而不是 python 字符串。

【讨论】:

作为后续工作,我移植了 SecureString 以处理整数和字节(称为 SecureBytes)。除非您小心使用加密密钥材料,否则两者都非常不安全……而不是可能传播到 python 其余部分的不可变事物。在 win/mac/linux 上测试。【参考方案8】:

这里:下面用零替换变量的内存地址字节,然后取消引用指向内存位置的指针。

在基于 Debian 的系统上测试。

import sys 
import ctypes

def nuke(var_to_nuke):
    strlen = len(var_to_nuke)
    offset = sys.getsizeof(var_to_nuke) - strlen - 1
    ctypes.memset(id(var_to_nuke) + offset, 0, strlen)
    del var_to_nuke               # derefrencing the pointer.

【讨论】:

这看起来取决于内存中 str 对象的内部表示

以上是关于安全擦除内存中的密码 (Python)的主要内容,如果未能解决你的问题,请参考以下文章

sha1 哈希密码在内存中的安全性

使用 JPA 和 JUnit 测试时如何一致地擦除内存数据库中的 H2 [重复]

Windows安全擦除硬盘擦除器

python 代码中的完整内存使用情况,带有 mediapipe 库

怎么设置需要账户密码才能解锁?

击败从内存中擦除 PE