Java实现RSA文件加密工具:从非对称加密原理到桌面应用开发
1. 项目概述与核心价值最近在整理过往的项目资料翻到了一个几年前做的、现在看来依然很有代表性的小系统——基于JAVA的RSA文件加密工具。当时做这个的初衷很简单就是想把RSA这套听起来高大上的非对称加密理论真正落地成一个能“拿起来就用”的桌面程序解决一些敏感文件比如合同、设计稿、个人资料在本地存储或点对点传输时的保密需求。它不是那种复杂的、需要部署服务器的企业级方案而是一个轻量级的、面向开发者和有一定技术背景的普通用户的工具。核心功能就两块一是用RSA公钥加密任意文件生成一个只有对应私钥才能解密的密文二是用匹配的私钥来解密还原文件。整个过程密钥对由用户自己生成和管理系统不接触、不存储任何密钥保证了“端到端”的安全本质。你可能要问现在网盘、聊天软件不都自带加密传输吗为什么还要自己折腾这里面的区别就在于“控制权”。第三方服务的加密密钥往往掌握在服务商手里存在理论上的后台访问风险尽管他们声称不会。而自己用RSA加密文件相当于你把文件锁进一个只有你自己有钥匙的保险箱然后再把这个保险箱交给快递员网盘或聊天软件去运送或存放。快递员只能搬运箱子但绝对打不开它。这种“我的数据我做主”的安全感是很多对隐私有高要求场景的刚需比如律师传递案件材料、摄影师交付未公开的成片、或者团队间传输尚未发布的商业计划书。这个项目麻雀虽小五脏俱全。它完整地走完了一个小型软件系统的生命周期从密码学原理的理解、JAVA核心加密库javax.crypto的运用、到桌面GUI我用的是Swing简单直接的设计实现再到性能优化和异常处理。对于想深入理解非对称加密如何从理论走向实践或者想锻炼自己综合运用JAVA进行小型系统开发能力的朋友来说这是一个绝佳的练手项目。接下来我就把这个项目的设计思路、关键实现、踩过的坑以及源码中的精华部分掰开揉碎了和大家分享一下。2. 系统核心设计与思路拆解2.1 为什么选择RSA而非AES在项目启动时第一个要决定的就是加密算法。对称加密如AES和非对称加密如RSA是两条主要路径。AES速度快适合加密大文件但它有一个致命问题密钥分发。加密和解密用的是同一把钥匙你怎么安全地把这把“钥匙”交给对方呢打电话告诉对方发邮件这本身就成了一个新的安全问题。RSA的巧妙之处在于“非对称”。它有一对密钥公钥和私钥。公钥可以公开给任何人就像你的公开邮箱地址私钥必须严格保密就像你的邮箱密码。任何人可以用你的公钥加密信息但只有持有私钥的你才能解密。这个特性完美解决了密钥分发难题我只需要拿到你的公钥就能加密文件发给你而我完全不需要知道你的私钥你也无需担心公钥在传输中被窃听因为它本来就是公开的。所以在这个文件加密系统的核心场景里RSA是更自然的选择。它的流程非常直观接收方Bob生成一对RSA密钥公钥私钥私钥自己妥善保存公钥可以公开发给任何人。发送方Alice拿到Bob的公钥用它对文件进行加密。Alice将加密后的文件发送给Bob。Bob用自己的私钥解密文件。整个过程私钥从未离开过Bob的机器从根本上杜绝了密钥在传输中被截获的风险。当然RSA也有缺点主要是速度慢不适合直接加密非常大的文件。我们会在后续章节讲解如何通过“混合加密”模式来克服这一点。2.2 整体架构与模块划分为了让系统清晰、可维护我将整个项目分成了几个核心模块这也是一个良好软件设计的起点。1. 核心加密模块 (core):这是系统的“发动机”。它不关心界面只负责最纯粹的加密/解密逻辑。主要包含KeyPairGenerator: 负责生成指定长度的RSA密钥对如2048位。这里的关键是选择安全的随机数源。FileEncryptor: 加密器。输入一个文件和一个公钥输出加密后的文件。这里需要处理RSA加密的数据长度限制。FileDecryptor: 解密器。输入一个加密文件和一个私钥输出原始文件。需要与加密器的逻辑严格对应。CryptoUtils: 一些通用的密码学工具方法比如字节数组到Base64字符串的转换方便密钥的显示和文本传输或者计算文件的哈希值用于完整性校验。2. 密钥管理模块 (keymgmt):安全的核心是密钥管理。这个模块负责密钥的持久化、加载和格式处理。KeyStorage: 定义密钥如何存储。通常我们将公钥和私钥分别保存为文件如public_key.pem,private_key.pem并使用PEMPrivacy-Enhanced Mail格式这是一种常见的、可读的密钥存储格式。KeyLoader: 负责从PEM格式的文件中将字符串解析回JAVA可用的PublicKey或PrivateKey对象。这里要注意处理各种PEM文件头尾的标记如-----BEGIN PUBLIC KEY-----。3. 用户界面模块 (ui):为了让非命令行用户也能方便使用我选择了Java Swing构建一个简单的图形界面。主要窗口包含密钥生成区按钮生成新密钥对并显示公钥的指纹如MD5或SHA-256摘要用于快速比对。文件选择区通过JFileChooser让用户选择待加密或待解密的文件。密钥加载区选择用于加密的公钥文件或用于解密的私钥文件。操作执行区“加密”和“解密”按钮并配有进度条对于大文件操作很重要。日志显示区一个JTextArea实时显示操作步骤、成功或错误信息方便用户追踪和排错。4. 业务逻辑控制器 (controller):这是连接UI和核心模块的“粘合剂”。它监听UI按钮的点击事件然后调用相应的核心模块功能并更新UI状态如进度条、日志。它处理了主要的业务逻辑流并负责捕获和处理所有可能抛出的异常如文件不存在、密钥格式错误、解密失败等将其转化为用户友好的提示信息。这种分层架构的好处是显而易见的核心加密逻辑独立于UI可以单独进行单元测试密钥管理逻辑被封装安全性更高UI层只负责展示和交互职责单一。未来如果想换用JavaFX甚至做一个Web版本只需要替换UI层和控制器层核心模块可以完全复用。3. 关键技术细节与实现解析3.1 RSA密钥的生成与存储在JAVA中生成RSA密钥对非常简单但魔鬼在细节里。import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; public class RSAKeyPairGenerator { public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException { // 1. 获取RSA密钥对生成器实例 KeyPairGenerator keyPairGen KeyPairGenerator.getInstance(RSA); // 2. 初始化生成器指定密钥长度和随机源 SecureRandom secureRandom new SecureRandom(); // 使用强随机数源至关重要 keyPairGen.initialize(keySize, secureRandom); // 3. 生成密钥对 return keyPairGen.generateKeyPair(); } }这里有几个关键点密钥长度keySize通常选择2048。1024位在现代计算能力下已不再安全3072或4096位更安全但速度更慢。2048位是目前安全与性能的最佳平衡点。随机数源SecureRandom是密码学安全的随机数生成器。绝对不要使用java.util.Random因为它产生的随机数可预测会严重削弱密钥的安全性。SecureRandom会利用操作系统提供的熵源如硬件噪声来生成真正的随机数。生成密钥对后我们需要将其保存到文件。直接保存二进制对象 (Key.getEncoded()) 不方便查看和交换。通常我们将其转换为PEM格式。import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemWriter; import java.io.FileWriter; import java.security.PrivateKey; import java.security.PublicKey; public class KeyStorage { public static void savePublicKey(PublicKey publicKey, String filePath) throws IOException { PemObject pemObject new PemObject(PUBLIC KEY, publicKey.getEncoded()); try (PemWriter pemWriter new PemWriter(new FileWriter(filePath))) { pemWriter.writeObject(pemObject); } } // 保存私钥方法类似PemObject类型为 PRIVATE KEY }这里我引入了Bouncy Castle这个强大的密码学提供者库它简化了PEM格式的读写。你需要将其JAR包添加到项目依赖中。保存后的公钥文件内容大致如下-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1WgZk8L... ... (Base64编码的密钥数据) ... -----END PUBLIC KEY-----注意私钥文件是最高机密务必将其保存在安全的位置并考虑使用密码口令对私钥文件进行二次加密这可以通过PKCS#8标准来实现。在实现中KeyPairGenerator可以配合PBE基于密码的加密算法来生成受口令保护的私钥。3.2 文件加密解决RSA加密的长度限制这是实现中最容易踩坑的地方。纯RSA算法本身不能直接加密任意长度的数据。对于2048位的密钥其能加密的明文数据最大长度约为245字节具体算法和填充模式有关。这显然无法直接加密一个几MB的文件。解决方案是采用“混合加密”或“信封加密”模式系统随机生成一个一次性的、对称加密的密钥比如一个128位的AES密钥。这个密钥称为“会话密钥”或“数据加密密钥(DEK)”。使用这个AES密钥用快速的AES算法加密整个大文件。这一步解决了大文件加密的效率问题。然后使用接收方的RSA公钥去加密这个短暂的AES密钥。因为AES密钥本身很短比如16或32字节完全在RSA的加密能力范围内。最终我们将“用RSA公钥加密后的AES密钥”和“用该AES密钥加密后的文件数据”组合在一起存储或发送出去。解密时过程相反接收方用自己的RSA私钥解密出AES会话密钥。再用这个AES密钥去解密文件数据主体。在我的实现中加密后的文件结构我设计了一个简单的自定义格式[文件头标识][RSA加密的AES密钥的长度4字节整数][RSA加密的AES密钥][AES加密的文件数据]文件头标识是一个魔数如0xCAFEBABE用于快速识别这是我们的加密文件格式。先读取长度就能准确地将RSA加密的密钥部分和文件数据部分分隔开。// 伪代码示意加密流程 void encryptFile(File inputFile, PublicKey publicKey, File outputFile) { // 1. 生成随机的AES密钥 SecretKey aesKey generateAESKey(); // 2. 用AES密钥加密文件 byte[] encryptedFileData encryptWithAES(inputFile, aesKey); // 3. 用RSA公钥加密AES密钥 byte[] encryptedAesKey encryptWithRSA(aesKey.getEncoded(), publicKey); // 4. 组合并写入输出文件 writeToOutputFile(outputFile, FILE_HEADER, encryptedAesKey, encryptedFileData); }3.3 文件解密与完整性校验解密是加密的逆过程但需要更严谨的错误处理。// 伪代码示意解密流程 void decryptFile(File encryptedFile, PrivateKey privateKey, File outputFile) throws CryptoException { try (FileInputStream fis new FileInputStream(encryptedFile)) { // 1. 读取并验证文件头 byte[] header readBytes(fis, HEADER_LENGTH); if (!isValidHeader(header)) { throw new CryptoException(无效的加密文件格式); } // 2. 读取RSA加密的AES密钥长度和内容 int encAesKeyLen readInt(fis); byte[] encryptedAesKey readBytes(fis, encAesKeyLen); // 3. 用RSA私钥解密出AES密钥 byte[] aesKeyBytes decryptWithRSA(encryptedAesKey, privateKey); SecretKey aesKey reconstructAESKey(aesKeyBytes); // 4. 读取剩余的AES加密数据并解密 byte[] encryptedData readRemainingBytes(fis); byte[] decryptedData decryptWithAES(encryptedData, aesKey); // 5. 将解密数据写入输出文件 writeToFile(outputFile, decryptedData); } catch (IOException | GeneralSecurityException e) { throw new CryptoException(解密失败: e.getMessage(), e); } }完整性校验是一个重要的增强功能。为了确保文件在加密/解密过程中没有被意外损坏或篡改可以在加密前计算原文件的哈希值如SHA-256并将这个哈希值用RSA公钥加密或直接附在加密数据后但需注意保密性。解密还原文件后再次计算哈希值并与存储的原始哈希对比。如果不一致则说明文件可能已损坏。在我的实现中我将文件的SHA-256哈希值用AES密钥加密后因为哈希值本身不泄露明文信息存放在文件头后的一个固定位置。实操心得在解密逻辑中reconstructAESKey这一步很重要。从字节数组重建AES密钥时必须使用与加密时完全相同的算法、模式和填充方式如AES/CBC/PKCS5Padding。任何不一致都会导致解密失败且错误信息可能不直观。建议将这些参数作为常量定义在代码中。4. 图形界面设计与用户体验优化4.1 使用Swing构建主界面虽然现在JavaFX更现代但Swing对于这样一个小型桌面工具来说依然轻量且足够。我使用JFrame作为主窗口采用BorderLayout结合多个JPanel进行区域划分。核心UI组件包括JTextField/JLabel: 显示选择的文件路径、密钥路径。JButton: “浏览文件”、“加载密钥”、“生成密钥”、“加密”、“解密”。JTextArea(放在JScrollPane中): 作为操作日志输出区域。JProgressBar: 在执行耗时操作加密大文件时向用户提供反馈。事件处理采用ActionListener。关键在于所有耗时的加密/解密操作绝对不能在事件调度线程EDT上执行否则会导致界面“假死”。必须使用SwingWorker来在后台线程执行任务并在任务进行中更新进度条在完成后于EDT上更新日志和状态。// 加密按钮事件监听器示例 encryptButton.addActionListener(e - { File inputFile new File(inputPathField.getText()); File publicKeyFile new File(publicKeyPathField.getText()); // ... 参数校验 // 使用SwingWorker执行后台任务 SwingWorkerVoid, String worker new SwingWorker() { Override protected Void doInBackground() throws Exception { publish(开始加密文件: inputFile.getName()); // 调用核心加密模块 FileEncryptor.encrypt(inputFile, publicKeyFile, outputFile); publish(文件加密完成输出至: outputFile.getPath()); return null; } Override protected void process(ListString chunks) { // 在EDT上更新日志区域 for (String msg : chunks) { logArea.append(msg \n); } } Override protected void done() { // 任务结束恢复UI状态 encryptButton.setEnabled(true); progressBar.setIndeterminate(false); } }; encryptButton.setEnabled(false); progressBar.setIndeterminate(true); // 显示不确定进度 worker.execute(); });4.2 异常处理与用户反馈密码学操作和文件IO充满了各种潜在错误。良好的异常处理是提升用户体验的关键。我将所有可能抛出的检查型异常IOException,NoSuchAlgorithmException,InvalidKeyException等在控制器层进行捕获并转化为用户能理解的信息。try { // 执行加密或解密 cryptoService.encrypt(...); } catch (InvalidKeyException e) { logArea.append(错误密钥无效或格式不正确。请确认加载的是正确的公钥/私钥文件。\n); } catch (IllegalBlockSizeException | BadPaddingException e) { logArea.append(错误解密失败。可能原因1) 使用了错误的私钥2) 加密文件已损坏。\n); } catch (IOException e) { logArea.append(错误文件读写失败。请检查文件路径和权限。\n e.getMessage() \n); } catch (Exception e) { logArea.append(发生未知错误: e.getClass().getSimpleName() - e.getMessage() \n); e.printStackTrace(); // 开发时打印完整堆栈发布时可记录到日志文件 }此外在用户执行操作前应进行前置校验避免无效操作点击“加密”前检查输入文件是否存在、公钥文件是否已加载。点击“解密”前检查加密文件格式通过文件头、私钥文件是否已加载。密钥加载时可以尝试解析并显示密钥的简要信息如算法、长度让用户确认加载正确。5. 性能优化与安全加固实践5.1 处理大文件分块加密与流式处理即使采用了AES混合加密当面对数GB的大文件时一次性将整个文件读入内存Files.readAllBytes仍然会导致内存溢出OutOfMemoryError。正确的做法是使用流式处理Streaming。对于AES加密部分我们可以使用CipherInputStream和CipherOutputStream。它们包装了普通的IO流在读写数据的过程中实时进行加密/解密内存中只保留一个缓冲区大小的数据。void encryptFileLarge(Path inputPath, SecretKey aesKey, Path outputPath) throws ... { Cipher aesCipher Cipher.getInstance(AES/CBC/PKCS5Padding); // ... 初始化AES Cipher (设置IV等) try (InputStream is Files.newInputStream(inputPath); CipherInputStream cis new CipherInputStream(is, aesCipher); OutputStream os Files.newOutputStream(outputPath)) { byte[] buffer new byte[8192]; // 8KB缓冲区 int bytesRead; while ((bytesRead cis.read(buffer)) ! -1) { os.write(buffer, 0, bytesRead); // 这里可以更新进度条 } } }对于整个加密流程RSA加密密钥 AES加密数据流程如下生成AES密钥和IV初始化向量。用RSA公钥加密AES密钥 IVIV通常可以公开但一起加密更简单写入输出文件。用CipherOutputStream包装文件输出流以AES密钥和IV初始化然后流式读取原文件并写入完成加密。这样无论原文件多大内存占用都保持恒定缓冲区大小彻底解决了大文件处理的问题。5.2 密钥安全与最佳实践系统的安全性最终落脚在密钥安全上。除了前面提到的使用强随机数生成密钥、用口令保护私钥文件外还有几点需要注意密钥存储位置建议在首次生成密钥对时提示用户选择安全的存储目录如加密的磁盘卷、受密码保护的USB密钥。程序不应硬编码存储路径。内存中的密钥清理密钥对象PrivateKey,SecretKey在内存中使用后应尽快清除其残留。由于JAVA的垃圾回收时间不确定敏感信息可能在内存中驻留较长时间。可以尝试在使用后将引用这些密钥的字节数组用随机数据覆盖。byte[] sensitiveBytes privateKey.getEncoded(); // ... 使用 sensitiveBytes ... java.util.Arrays.fill(sensitiveBytes, (byte) 0); // 覆盖内存公钥的真实性RSA体系假设公钥的真实性。在实际应用中如何确保你拿到的公钥确实属于对方而不是中间人伪造的这需要借助数字证书Certificate和公钥基础设施PKI来解决。在这个简易系统中我们默认通过可信的侧信道如当面交换U盘、通过已加密的邮件发送来交换公钥。算法和参数选择RSA填充方案使用OAEPOptimal Asymmetric Encryption Padding填充模式如RSA/ECB/OAEPWithSHA-256AndMGF1Padding。它比旧的PKCS#1 v1.5填充更安全能抵抗选择密文攻击。AES模式使用CBC或GCM模式。CBC需要IVGCM同时提供加密和认证。避免使用ECB模式因为它不安全。密钥长度RSA至少2048位AES至少128位推荐256位。5.3 源码结构与工程化建议一个清晰的源码结构能极大提升项目的可读性和可维护性。我的项目目录结构大致如下RSAFileEncryptor/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── yourname/ │ │ │ └── rsaencrypt/ │ │ │ ├── core/ # 核心加密模块 │ │ │ │ ├── CryptoException.java │ │ │ │ ├── FileEncryptor.java │ │ │ │ ├── FileDecryptor.java │ │ │ │ └── CryptoUtils.java │ │ │ ├── keymgmt/ # 密钥管理模块 │ │ │ │ ├── KeyPairGen.java │ │ │ │ ├── KeyStorage.java │ │ │ │ └── KeyLoader.java │ │ │ ├── ui/ # 用户界面 │ │ │ │ └── MainFrame.java │ │ │ └── controller/ # 控制器 │ │ │ └── CryptoController.java │ │ └── resources/ # 图标等资源文件 │ └── test/ # 单元测试 │ └── java/ │ └── ... (针对各模块的测试类) ├── lib/ # 第三方库如Bouncy Castle ├── build.gradle 或 pom.xml # 构建脚本 └── README.md # 项目说明文档工程化建议使用构建工具使用Maven或Gradle管理项目依赖如Bouncy Castle简化构建流程。编写单元测试为核心模块如FileEncryptor/Decryptor,KeyLoader编写JUnit测试确保加密-解密循环的正确性以及异常情况的处理。日志记录使用SLF4J Logback等日志框架替代简单的System.out.println可以更好地控制日志级别和输出目的地。打包分发使用Maven Shade插件或Gradle的application插件将项目及其所有依赖打包成一个可执行的JAR文件uber-jar方便用户双击运行。6. 常见问题排查与调试技巧在实际开发和用户使用中肯定会遇到各种问题。这里记录几个最典型的场景和排查思路。6.1 “解密失败无效的密钥”或“BadPaddingException”这是最常见的问题几乎都源于加密和解密双方使用的密钥或参数不匹配。排查清单公私钥不配对这是最可能的原因。确认用于解密的私钥正是生成加密所用公钥的那个密钥对中的私钥。一个快速验证方法是用疑似配对的公钥加密一小段测试文本然后用私钥解密看是否成功。密钥文件损坏或格式错误检查密钥文件内容。PEM格式的密钥是否有完整的开始和结束标记是否在传输过程中被意外修改如多了空格、换行符不同可以用文本编辑器打开核对并与原始备份对比。填充模式不一致加密时使用的RSA填充模式如OAEPWithSHA-256AndMGF1Padding必须与解密时指定的模式完全一致。检查代码中Cipher.getInstance()方法传入的字符串。AES密钥/IV不匹配在混合加密中解密时重建AES密钥和IV所使用的算法、模式、参数必须与加密时完全相同。确保用于解密AES数据的密钥正是用RSA私钥解密出来的那个字节数组所重建的密钥。加密文件结构被破坏如果加密文件在传输或存储过程中部分损坏会导致读取的加密AES密钥或数据块错误。可以尝试重新传输或从备份恢复加密文件。调试技巧在开发阶段可以在加密和解密的关键节点打印或日志记录关键信息的摘要如公钥的Base64编码前16位、AES密钥的哈希值、IV值等。通过对比加密端和解密端的这些摘要可以快速定位是哪一部分数据出现了不一致。注意正式发布版本中应移除这些调试输出以免泄露敏感信息。6.2 “NoSuchAlgorithmException” 或 “NoSuchProviderException”这通常是因为JAVA运行环境没有找到对应的算法实现。检查算法名称拼写RSA,AES,SHA-256等名称必须准确无误。引入Bouncy Castle提供者如果你使用了Bouncy Castle的特定算法或PEM功能需要在代码启动时将其注册为安全提供者。import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public class Main { public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); // ... 启动你的程序 } }确保JAR包在类路径中将Bouncy Castle的JAR文件如bcprov-jdk15on-xxx.jar正确添加到项目的构建路径或可执行JAR的依赖中。6.3 大文件加密时内存溢出或速度极慢确认是否使用了流式处理检查代码确保没有使用Files.readAllBytes()或类似方法一次性读取整个大文件。必须使用CipherInputStream/CipherOutputStream进行分块处理。调整缓冲区大小流处理中缓冲区的大小会影响IO效率。通常8KB8192字节是一个较好的起点可以尝试调整为16KB或32KB观察性能变化。但不宜过大否则失去流式意义。检查进度更新频率在后台线程 (SwingWorker) 中更新UI进度条时过于频繁的更新比如每读取一个字节就更新会严重拖慢速度并阻塞EDT。可以设置一个阈值例如每处理1MB数据或每100毫秒才发布一次进度更新。硬件和JVM限制确保运行程序的机器有足够的内存。对于JVM可以尝试增加堆内存大小启动参数-Xmx2g表示最大堆内存2GB但这只是治标流式处理才是治本。6.4 图形界面无响应或卡死这一定是由于在事件调度线程EDT上执行了耗时操作IO、加密计算。解决方案铁律任何可能花费超过几十毫秒的操作都必须放在后台线程中执行。SwingWorker是Swing中用于此目的的最佳工具。确保doInBackground()方法中执行耗时任务。使用publish()/process()方法来从后台线程向EDT传递中间数据如日志消息。在done()方法中在EDT上执行进行最终的状态更新如启用按钮、显示完成对话框。一个简单的判断方法是如果你在按钮的ActionListener里直接调用了FileEncryptor.encrypt(...)这个方法而加密一个大文件需要10秒钟那么你的界面就会卡死10秒。必须把这个调用包装进SwingWorker。这个基于JAVA的RSA文件加密系统从原理到实现涉及了密码学应用、JAVA核心API、桌面GUI开发、异常处理和性能优化等多个方面。它就像一把精心打造的螺丝刀单一功能但非常实用。通过亲手实现它你不仅能深刻理解非对称加密的运作机制更能掌握如何将一个理论算法包装成用户友好的工具的全过程。源码中还有很多细节值得推敲比如更完善的错误恢复、国际化和本地化支持、或者添加对压缩的支持先压缩再加密可以节省空间。希望这个详细的拆解能给你带来启发也欢迎你基于这个框架打造出更强大、更符合自己需求的隐私保护工具。