You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中如何安全清除内存中bytearray存储的敏感数据?

问题

我想在Python垃圾回收前正确清除存储敏感信息(如密码)的bytearray,因为Python垃圾回收只会移除指针,不会清零实际数据。我写了一段代码,但不确定两个点:

  1. 通过切片覆写bytearray是否会覆盖原内存区域?
  2. __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__的调用时机,可靠性更低。

正确的实现方式

  1. 主动显式清除,放弃依赖__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)
    
  2. 减少额外的敏感数据拷贝

    • password.encode("utf-8")会创建临时bytes对象,这部分内存无法被清零。可以直接用bytearray的构造方法跳过临时对象:
      self._pwd = bytearray(password, encoding="utf-8")
      
    • 另外,getpass.getpass()返回的字符串本身也会留在内存中直到被回收。如果要极致严谨,可以用ctypes等底层库直接读取输入到bytearray,避免创建字符串对象。
  3. 用上下文管理器自动清除(可选)
    如果密码的使用范围明确,用上下文管理器可以自动触发清除:

    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 08:37:20