Python代码安全加固:AES加密Token存储漏洞排查与单文件访问限制实现
问题背景
我正在开发涉及网络通信的高安全等级项目,基于pycrypto库编写了Token存储类,实现Token的AES加密、base64编解码及逆向解密功能,当前实现流程如下:
- 配置AES加密密钥
- 初始化Token对象
- 执行加密操作
需要排查当前实现的安全漏洞,同时解决技术问题:如何实现类、方法、变量仅能在单个文件内被访问?
现有实现代码如下:
from base64 import b64encode, b64decode from Crypto.Cipher import AES import sys import math class Token: print("[cobalt] setting up encrytion keys...") print("[cobalt] reading encryption key file") key = open('/usr/share/doc/cobalt/encryption', 'r') lines = key.readlines()[0].split(':') print("[cobalt] read values, writing private constants...") __COMMON_ENCRYPTION_KEY = lines[0].strip() __COMMON_AES_IV16 = lines[1].strip() del lines key.close() print("[cobalt] wrote constants, closing file stream...") print("[cobalt] setted up encryption keys, initializing token object...") def __init__(self, token:str): self._token = token self._encrypted = self.__encrypt() print("[cobalt] token object initialized") def __get_common(self): print("[cobalt] getting common AES...") try: return AES.new(Token.__COMMON_ENCRYPTION_KEY, AES.MODE_CBC, Token.__COMMON_AES_IV16) except ValueError as e: print(f"[cobalt]\033[1;31m error:\033[m {e}") sys.exit(1) def __encrypt(self) -> str: print("[cobalt] encrypting token...") common_cipher = self.__get_common(); print(f"[cobalt] got common AES: {common_cipher}") token_len = len(self._token); print(f"[cobalt] got token length: {token_len}") # padded token with next multiple of 16 padded_token = self._token.rjust(16 * math.ceil(token_len / 16)); print("[cobalt] got padded token") raw_encrypted_token = common_cipher.encrypt(padded_token); print("[cobalt] encrypted, encoding...\n") return b64encode(raw_encrypted_token).decode('utf-8') def _decrypt(self) -> str: print("[cobalt] decrypting token...") common_cipher = self.__get_common(); print(f"[cobalt] got common AES: {common_cipher}") raw_token = b64decode(self._encrypted); print("[cobalt] decoded raw token") decrypted_wpadding = common_cipher.decrypt(raw_token); print("[cobalt] decrypted token\n") return decrypted_wpadding.decode('utf-8').strip()
现有代码安全漏洞排查
- 依赖库安全风险:当前使用的
pycrypto库已停止维护多年,存在多个未修复的公开安全漏洞,应替换为目前社区维护活跃的兼容分支pycryptodome。 - 密钥加载逻辑风险:
- 文件读取操作直接写在类定义顶层,模块被导入时就会立刻执行IO操作,没有做异常捕获,一旦密钥文件不存在、权限不足、格式不符合预期,会直接抛出未处理异常导致导入失败,还可能泄露服务器文件路径信息。
- 密钥文件路径硬编码,部署灵活性差,若文件权限配置不当,低权限进程可直接读取文件窃取加密密钥。
- AES-CBC模式使用固定IV(初始化向量),会导致相同明文永远生成相同密文前缀,攻击者可通过密文比对直接推断明文内容,完全丧失CBC模式的语义安全能力。
- 加密填充逻辑错误:使用
rjust在明文左侧补空格做对齐填充,不符合AES要求的PKCS#7标准填充规范;解密时直接用strip()删除首尾所有空白字符,会破坏明文首尾本身携带的空格内容,且当明文长度恰好为16字节整数倍时,填充逻辑会多补16字节空格,导致解密后长度异常。 - 访问控制逻辑失效:双下划线开头的类属性/方法只是Python的名称改写机制,设计目的是避免子类继承时的命名冲突,并非真正的私有控制,外部代码可通过
_Token__COMMON_ENCRYPTION_KEY这类形式直接读取密钥、调用内部方法,没有实际防护能力。 - 运行风险:加密组件初始化失败时直接调用
sys.exit(1)终止整个进程,在服务端场景下会导致服务整体异常退出;代码中大量打印调试日志,甚至输出加密器对象信息,生产环境下会泄露内部运行逻辑,给攻击者提供信息。
Python单文件访问限制实现方案
Python没有语法层面的强制私有访问控制,所有访问限制均为约定性质的软限制,要实现类、方法、变量仅在当前文件内可访问的效果,可组合以下方案:
- 遵循命名约定:所有不希望外部访问的模块级变量、函数、类属性,统一使用单下划线
_开头。Python默认的导入机制在执行from 模块 import *时,不会导入单下划线开头的名称,这是Python社区通用的“内部实现,外部禁止访问”的约定标识。 - 配置模块导出白名单:在文件顶部定义
__all__列表,列表内仅写入允许外部导入的类、函数、变量名称,外部执行from 模块 import *时,只能获取到列表内声明的内容,其余内部实现不会被导出。示例配置:# 仅允许外部导入Token类,其余内部逻辑均不对外暴露 __all__ = ["Token"] - 敏感逻辑不绑定到类/实例属性:将密钥读取、核心加密逻辑实现为单下划线开头的模块级内部函数,类内部调用这些函数完成操作,不要将密钥、加密器实例这类敏感值绑定为类属性或实例属性,减少外部直接读取敏感信息的路径。
- 注意:不要依赖双下划线开头的名称改写做访问控制,该机制无法阻止外部代码访问内部属性,仅用于解决继承命名冲突问题。
代码修正方向
- 替换
pycrypto依赖为pycryptodome,导入接口保持兼容。 - 将密钥读取逻辑移到模块级内部函数中,增加完整的异常捕获,不要在类定义顶层直接执行IO操作。
- 废弃固定IV逻辑,每次加密时随机生成16字节IV,将IV和密文拼接后再做base64编码,解密时先拆分出前16字节作为IV再执行解密操作。
- 填充逻辑替换为标准PKCS#7填充,解密时按照填充规则移除填充字符,不要用
strip()粗暴删除首尾空白。 - 移除生产环境的调试打印,组件初始化失败时抛出明确的业务异常,由上层逻辑处理,不要直接终止进程。
- 配置
__all__导出白名单,所有内部实现统一加单下划线前缀标识。
内容的提问来源于stack exchange,提问作者vicsafe
相关产品推荐
相关产品推荐

