
简介这是一款用Python编写的文件加密解密工具面向需要保护敏感数据的企业或个人开发者也适合想研究加密机制与密钥管理思路的技术爱好者。工具在行为上与勒索病毒有相似之处加密后自动修改文件扩展名使原始数据无法直接访问但用途是安全加解密而非破坏。密钥统一存入SQL Server数据库的encryption_key字段需先自行创建空库连接失败时可借助msodbcsql工具排查运行后会在当前目录生成db_config配置文件用于保存数据库地址与凭证等信息。资源包共11个文件约22MB包含加密工具.exe、解密工具.exe两个可执行程序msodbcsql.msi数据库驱动安装包以及bat启动脚本、html说明页、url快捷入口、txt提示与jpg图片等辅助内容便于快速部署与查阅。目前已有113人学习下载。通过该工具可掌握Python实现文件加解密、扩展名处理、密钥入库与检索的完整流程并理解密钥备份与数据库安全配置的重要性避免操作不当导致数据永久丢失。1. 文件加密解密工具用 Python 写从「能跑」到「敢用」差在哪很多人第一次写文件加密解密工具都是被一个很具体的需求逼出来的手里有一批敏感文件要交给别人或者要放到一个自己都不太放心的存储位置于是搜到「python 文件加密」几个字复制一段cryptography的示例跑通了觉得这事成了。但真到用的时候才发现能跑和敢用之间隔着一整条工程链密钥怎么存、大文件怎么不爆内存、加密完怎么知道文件没被改过、密码输错了报什么错、换台机器还能不能解开。这篇笔记就围绕「文件加密解密工具 python 编写」这件事把我在实际项目里反复用的一套方案拆开讲。核心结论先放这里别自己发明加密算法用成熟的 AEAD 原语AES-GCM 或 ChaCha20-Poly1305把密钥派生、随机数、完整性校验这三件事做对工具才算立得住。适合两类人看一类是刚学完 Python 基础、想做一个真正能用的命令行小工具的另一类是已经在用脚本处理敏感数据、但方案还停留在「异或一下」或者「base64 当加密」阶段的。下面从选型讲到代码再讲到那些只有踩过才知道的坑。2. 选型先立住为什么是 AES-GCM 而不是自己写异或2.1 对称加密、非对称加密和哈希先分清谁干谁的活文件加密这件事绝大多数场景用的是对称加密也就是加密和解密用同一把密钥。原因很直接非对称加密RSA 那一类算得慢而且能加密的数据长度有限通常只用来加密「密钥」本身而不是加密整个文件。哈希SHA-256 这类不是加密它不可逆只能用来校验完整性不能拿来还原文件内容。所以一个文件加密工具的最小骨架是一把对称密钥 一个对称加密算法 一种把用户密码变成密钥的方法。这三块分别对应后面要讲的密钥派生KDF、加密算法本体、以及密钥管理。很多人翻车就翻在第一步——直接把用户输入的密码当密钥用密码长度不够、熵不够等于给锁配了一把一拧就断的钥匙。常见做法是用 PBKDF2、scrypt 或 Argon2 做密钥派生。Python 标准库hashlib自带pbkdf2_hmac不用装额外依赖对新手最友好如果追求抗暴力破解cryptography库提供了Scrypt和Argon2id。我一般默认用 scrypt内存开销可调比 PBKDF2 更能扛 GPU 爆破。2.2 AEAD 是什么为什么它比「加密 单独算哈希」更省心AEADAuthenticated Encryption with Associated Data带关联数据的认证加密是一类「加密的同时顺带认证」的算法。AES-GCM 和 ChaCha20-Poly1305 都属于这一类。它的价值在于解密时如果密文被篡改过一个字节解密会直接抛异常而不是悄悄给你一段乱码。对比一下老做法先用 AES-CBC 加密再单独算一个 HMAC 校验。这套组合本身没错但顺序、密钥复用、比较方式要用常量时间比较都有讲究写错一个就出安全漏洞。AEAD 把这些都封装好了你只要管好 nonce随机数不重复就行。对绝大多数文件加密场景直接用 AEAD 是更稳的选择。提示AES-GCM 的 nonce 一旦在同一把密钥下重复使用安全性会严重受损。文件加密里每次加密都要生成新的随机 nonce并把它和密文一起存下来。2.3 依赖怎么选标准库够不够什么时候必须上 cryptography先看一张对比表把两条路线讲清楚方案依赖支持算法适用场景主要限制纯标准库无PBKDF2 AES-CBC需自己拼 HMAC不能装第三方库的内网环境要手写认证逻辑易出错cryptography需 pip 安装AES-GCM、ChaCha20-Poly1305、scrypt、Argon2绝大多数生产场景需要能联网装包如果目标机器完全不能装第三方库那就只能走标准库路线用hashlib.pbkdf2_hmac派生密钥再用cryptography之外的方案——但标准库本身没有 AES得靠pycryptodome或系统 openssl 命令。所以现实里能装cryptography就装它是目前 Python 生态里最主流、维护最活跃的加密库安装命令就是一句pip install cryptography装完之后from cryptography.hazmat.primitives.ciphers.aead import AESGCM就能用。这一步没有玄学装不上通常是 Python 版本太老或者 pip 源的问题先python -m pip install --upgrade pip再试。3. 动手写核心密钥派生、加密、解密三段代码3.1 用 scrypt 把用户密码变成 32 字节密钥密钥派生是整个工具的地基。用户输入的密码往往只有十几个字符直接当 AES 密钥长度不对而且熵太低。scrypt 的作用就是把这个弱密码「拉伸」成一把足够强的密钥同时故意消耗计算和内存资源让暴力破解变贵。import os from cryptography.hazmat.primitives.kdf.scrypt import Scrypt def derive_key(password: str, salt: bytes) - bytes: # n2**14 是 CPU/内存开销r8 块大小p1 并行度 # 这三个参数决定派生一次要多久也决定爆破成本 kdf Scrypt( saltsalt, length32, # AES-256 需要 32 字节密钥 n2**14, r8, p1, ) return kdf.derive(password.encode(utf-8)) # salt 必须每次加密都重新随机生成并和密文一起保存 salt os.urandom(16) key derive_key(my-strong-password, salt)逻辑说明salt是随机盐作用是让相同的密码每次派生出的密钥都不同防止彩虹表。n、r、p三个参数控制计算成本n越大越慢越安全。参数说明n2**14在普通笔记本上大约几十毫秒属于体验和安全的平衡点如果文件极其敏感可以调到2**16代价是每次加解密多等一两百毫秒。length32对应 AES-256不要改成 16除非你明确要用 AES-128。3.2 AES-GCM 加密nonce 和 salt 必须跟着密文走加密这一步的关键是把 salt、nonce 和密文打包在一起。因为解密时你需要同样的 salt 重新派生密钥需要同样的 nonce 才能解密。这三样东西不敏感可以明文存但必须完整保存。from cryptography.hazmat.primitives.ciphers.aead import AESGCM def encrypt_file(password: str, data: bytes) - bytes: salt os.urandom(16) nonce os.urandom(12) # GCM 推荐 12 字节 nonce key derive_key(password, salt) aesgcm AESGCM(key) # associated_data 传 None表示没有额外认证数据 ciphertext aesgcm.encrypt(nonce, data, None) # 文件格式salt(16) nonce(12) 密文 return salt nonce ciphertext逻辑说明AESGCM(key).encrypt(nonce, data, None)返回的密文末尾已经附带了 GCM 的认证标签tag解密时会自动校验。参数说明nonce必须是 12 字节且每次加密唯一这里用os.urandom(12)保证随机性associated_data一般传None如果你想绑定文件名之类的元数据不被篡改可以传进去但解密时必须传相同的值。返回的字节流前 16 字节是 salt接着 12 字节是 nonce剩下的是密文这个格式要固定下来解密端按同样偏移量切。3.3 解密把异常处理写对别让错误密码变成崩溃解密端最容易出的问题是异常没接住用户输错密码直接看到一长串 traceback。正确做法是把InvalidTag异常捕获给出人话提示。from cryptography.exceptions import InvalidTag def decrypt_file(password: str, blob: bytes) - bytes: salt blob[:16] nonce blob[16:28] ciphertext blob[28:] key derive_key(password, salt) aesgcm AESGCM(key) try: return aesgcm.decrypt(nonce, ciphertext, None) except InvalidTag: # 密码错误或密文被篡改两种情况都会走到这里 raise ValueError(解密失败密码错误或文件已损坏)逻辑说明InvalidTag是 GCM 认证失败时抛出的异常密码错误和密文被改都会触发它所以提示语要把两种可能都覆盖。参数说明切片偏移量[:16]、[16:28]、[28:]必须和加密端严格对应改一处就要改两处建议把这三个长度定义成常量别散落在代码里。3.4 大文件别一次性读进内存分块加密的写法上面三个函数都是针对bytes的小文件没问题但一个 2GB 的视频直接read()进内存机器当场就卡死。大文件要分块处理。注意GCM 本身是流式的但cryptography的高层AESGCM接口要求一次性给完整数据所以分块要么用底层Cipher接口要么按固定块大小分别加密、各自带 nonce。CHUNK 1024 * 1024 # 每块 1MB def encrypt_large_file(password: str, in_path: str, out_path: str): salt os.urandom(16) key derive_key(password, salt) aesgcm AESGCM(key) with open(in_path, rb) as fin, open(out_path, wb) as fout: fout.write(salt) # 头部先写 salt while True: chunk fin.read(CHUNK) if not chunk: break nonce os.urandom(12) # 每块独立 nonce fout.write(nonce) ct aesgcm.encrypt(nonce, chunk, None) fout.write(len(ct).to_bytes(4, big)) # 记录密文长度 fout.write(ct)逻辑说明每块用独立 nonce 加密块之间互不影响解密时按「读 nonce → 读长度 → 读密文」的顺序还原。参数说明CHUNK设成 1MB 是内存和效率的折中太小会频繁 IO太大又失去分块意义len(ct).to_bytes(4, big)用 4 字节记录长度支持单块最大 4GB够用。这个格式比一次性加密复杂但换来的是能处理任意大小的文件。4. 避坑与排查文件加密工具最容易翻车的 5 个地方4.1 现象加密完再解密内容对不上原因最常见的是 nonce 或 salt 没跟着密文一起存解密端用了新生成的随机值派生出的密钥或解密参数和加密时不一致。另一种是切片偏移量写错比如加密端 salt 是 16 字节解密端按 8 字节切。解决把「salt nonce 密文」的格式固定成协议写成一个常量表加密解密共用。调试时先打印加密端和解密端各自用的 salt、nonce 十六进制值对不上就一目了然。4.2 现象换个 Python 版本或换台机器解密报 InvalidTag原因cryptography库版本差异导致某些默认参数行为不同或者 scrypt 参数在两端不一致。更隐蔽的是编码问题——密码里有中文一端用 utf-8 编码另一端用了系统默认编码。解决derive_key里显式写password.encode(utf-8)不要依赖默认编码。scrypt 的n、r、p要么写死成常量要么把参数也存进文件头别让两端各写各的。4.3 现象小文件正常大文件加密到一半内存爆掉原因用了fin.read()不带参数或者把整个文件读成 bytes 再传给AESGCM.encrypt。解决超过几十 MB 的文件一律走分块流程。判断标准很简单如果你不确定文件多大就默认分块。分块代码虽然多写二十行但省下的是半夜被 OOM 叫醒的后悔药。4.4 现象用户输错密码程序直接抛异常退出原因没捕获InvalidTag或者捕获了但把异常信息原样抛给用户。解决在解密入口统一 try/except把InvalidTag转成ValueError(密码错误或文件损坏)。注意不要提示「密码错误」这种过于确定的措辞因为密文损坏也是同样的异常提示要留余地。4.5 现象加密后的文件比原文件大不少怀疑有问题原因这是正常的。AES-GCM 每块会附加 16 字节认证标签加上 salt、nonce、长度字段开销是固定的。如果文件膨胀得离谱比如翻倍那多半是误用了 base64 编码——base64 会让数据膨胀约 33%它是编码不是加密别混用。解决确认输出是二进制写模式wb不是文本模式。如果确实需要把密文放进文本环境比如邮件正文再单独做 base64并且明确这是传输编码不是加密步骤。5. 进阶把工具做成能长期用的命令行程序5.1 用 argparse 把加解密做成两个子命令一个能长期用的工具入口要清晰。用标准库argparse做子命令encrypt和decrypt分开参数一目了然。import argparse def main(): parser argparse.ArgumentParser(description文件加密解密工具) sub parser.add_subparsers(destcmd, requiredTrue) p_enc sub.add_parser(encrypt, help加密文件) p_enc.add_argument(input) p_enc.add_argument(output) p_enc.add_argument(-p, --password, requiredTrue) p_dec sub.add_parser(decrypt, help解密文件) p_dec.add_argument(input) p_dec.add_argument(output) p_dec.add_argument(-p, --password, requiredTrue) args parser.parse_args() if args.cmd encrypt: encrypt_large_file(args.password, args.input, args.output) else: decrypt_large_file(args.password, args.input, args.output) if __name__ __main__: main()逻辑说明add_subparsers(requiredTrue)强制用户必须选一个子命令避免直接运行不知道干什么。参数说明-p/--password设成必填但更好的做法是不从命令行传密码——命令行参数会留在 shell 历史里。生产环境应该用getpass.getpass()交互式输入或者从环境变量读。5.2 密码别走命令行用 getpass 和环境变量命令行传密码的血泪经验history里躺着明文进程列表里也能被同机其他用户看到。改成交互输入import getpass password getpass.getpass(请输入密码)如果是自动化脚本没法交互就从环境变量读os.environ.get(FILE_ENC_PASSWORD)。环境变量也不是绝对安全但比命令行参数强。再进一步可以用密钥文件把随机生成的密钥存成一个只有本人可读的文件权限 600密码只用来保护这个密钥文件。5.3 怎么验证你的工具真的靠谱三个自测用例写完别急着用先跑三个用例。第一加密一个空文件再解密看是否得到空文件——这能暴露切片和长度处理的边界问题。第二加密一个 100MB 以上的文件观察内存占用是否平稳。第三故意改掉密文中间一个字节确认解密抛出InvalidTag而不是返回乱码。这三个用例覆盖了边界、性能和完整性校验跑通基本就能放心用。我自己现在的习惯是任何加密工具写完先拿一个已知内容的测试文件跑一遍「加密→解密→比对哈希」哈希一致才算过。这个习惯帮我拦下过好几次 nonce 复用和偏移量写错的问题。文件加密这件事能跑只是起点敢用才是终点。希望帮到你。本文还有配套的精品资源点击获取