Python中如何安全清除内存中bytearray存储的敏感数据?
问题
我想在Python垃圾回收前正确清除存储敏感信息(如密码)的bytearray,因为Python垃圾回收只会移除指针,不会清零实际数据。我写了一段代码,但不确定两个点:
- 通过切片覆写
bytearray是否会覆盖原内存区域? __del__方法是否会被垃圾回收器及时调用?
希望了解正确的实现方式。
代码示例:
from typing import Optional import getpass from PyQt5 import QtWidgets class CachedPasswordWidget(QtWidgets.QWidget): """A base class for widgets that may prompt the user for a password and remember that password for the lifetime of that widget. """ def __init__( self, parent: Optional[QtWidgets.QWidget] = None, ): super().__init__(parent) # store the password as a mutable type so the memory can be zeroed after it is no longer needed self._pwd: Optional[bytearray] = None @property def pwd(self) -> Optional[str]: """Return password. Open a dialog to ask for the wallet password if necessary, and cache it. If the password dialog is cancelled, return None. """ if self._pwd is not None: return self._pwd.decode("utf-8") # password = PasswordDialog(parent=self).run() password = getpass.getpass() if password is None: # dialog cancelled return self._pwd = bytearray(password.encode("utf-8")) return self._pwd.decode("utf-8") def __del__(self): if self._pwd is not None: self._pwd[:] = b"\0" * len(self._pwd)
回答
关于切片覆写bytearray的内存覆盖
用self._pwd[:] = b"\0" * len(self._pwd)这种切片赋值方式,确实会覆盖bytearray原有的内存区域。因为bytearray是可变序列,切片赋值操作会直接修改底层缓冲区的内容,不会创建新对象,原存储密码的内存会被清零,不会残留敏感数据。
关于__del__方法的调用时机
__del__的调用时间完全不可控:
- 只有当对象的引用计数降为0,或者触发循环垃圾回收时,Python才会调用
__del__,无法保证“及时”执行。 - 如果对象陷入循环引用,
__del__可能延迟很久才执行,甚至到程序退出前都不触发。 - 结合Qt的Widget对象来说,父对象的引用关系会进一步干扰
__del__的调用时机,可靠性更低。
正确的实现方式
主动显式清除,放弃依赖
__del__
给类添加一个明确的清除方法,在确定不再需要密码时主动调用:def clear_password(self): if self._pwd is not None: self._pwd[:] = b"\0" * len(self._pwd) self._pwd = None # 彻底移除引用,加速垃圾回收比如在Widget关闭时主动触发清除,重写
closeEvent:def closeEvent(self, event): self.clear_password() super().closeEvent(event)减少额外的敏感数据拷贝
password.encode("utf-8")会创建临时bytes对象,这部分内存无法被清零。可以直接用bytearray的构造方法跳过临时对象:self._pwd = bytearray(password, encoding="utf-8")- 另外,
getpass.getpass()返回的字符串本身也会留在内存中直到被回收。如果要极致严谨,可以用ctypes等底层库直接读取输入到bytearray,避免创建字符串对象。
用上下文管理器自动清除(可选)
如果密码的使用范围明确,用上下文管理器可以自动触发清除:from contextlib import contextmanager @contextmanager def use_password(self): try: yield self.pwd finally: self.clear_password()使用方式:
with widget.use_password() as pwd: if pwd is not None: # 执行需要密码的操作 do_something_with_pwd(pwd)
内容的提问来源于stack exchange,提问作者PiRK
相关产品推荐
相关产品推荐

