filterinputstream 是装饰器模式实现,通过包装 inputstream 并覆写 read() 方法实现在不修改源流前提下叠加解密逻辑;需严格匹配加密算法、密钥、iv 及填充模式,推荐预读魔数校验并用 cipher.dofinal() 解密。

FilterInputStream 是 Java I/O 中的经典装饰器模式实现,适合在不修改原始输入流行为的前提下,叠加额外处理逻辑——比如解密。要开发一个能自动解密变量加密文件的自定义输入流,关键不是重写整个读取流程,而是精准拦截字节流、在传递给上层前完成解密还原。
理解 FilterInputStream 的职责边界
它本身不持有数据源,只包装一个 InputStream(如 FileInputStream),所有 read() 方法默认委托给被包装流;你只需覆写关键方法(通常是 read() 单字节和 read(byte[], int, int) 批量读取),在返回前对字节做变换。解密逻辑必须与加密时使用的算法、密钥、模式(如 AES/CBC/PKCS5Padding)、IV 严格一致,否则会解出乱码或抛异常。
实现一个 AES 解密 FilterInputStream
假设文件是用 AES-128-CBC 加密,且 IV 内置在文件头前 16 字节中(常见做法),你可以这样设计:
- 构造时传入原始 InputStream 和解密密钥(SecretKeySpec)
- 覆写 read():先尝试从底层流读满 16 字节 IV(首次调用时),初始化 Cipher 为 DECRYPT_MODE
- 覆写 read(byte[] b, int off, int len):先读取加密数据到临时缓冲区,再用 Cipher.doFinal() 解密,最后拷贝到目标数组 b
- 注意:Cipher 是有状态的,不能复用;每次 doFinal() 后需重新 init,或使用 CipherInputStream 作为参考但避免直接继承它(它不暴露 IV 提取逻辑)
处理文件头与元信息的技巧
如果加密文件开头包含非加密的魔数(如 "ENC\0")或版本号,可在构造时预读并校验,再跳过这部分再启动解密流程。例如:
- 用 PushbackInputStream 包装原始流,先 peek 4 字节确认魔数
- 确认后调用 skip(4) 或读走魔数,再把剩余流交给你的解密 FilterInputStream
- 这样上层业务代码仍可直接 new Properties().load(decryptingStream),完全无感
安全与健壮性提醒
不要硬编码密钥,优先从环境变量、配置中心或 Keystore 加载;解密失败时抛 IOException 而非 RuntimeException,便于上层统一捕获;对小文件或单次 read() 场景,注意 Cipher 缓冲行为——CBC 模式要求输入长度是块大小(16 字节)整数倍,所以实际读取时建议按 16 字节对齐分配缓冲区,或使用 ByteArrayInputStream + Cipher 做完整解密后再封装成流。











