PHP转Java:SEED加密TPF文件读写解密及编码问题排查
问题分析与解决方案
核心问题总结
你在将PHP SEED解密逻辑转Java时,遇到的核心问题出在块解密的边界处理、编码转换时机和二进制数据处理方式上,导致解密后内容长度、编码与PHP不一致。
错误点拆解
- 分段读取的边界错误:原代码中
fis.read(fileContentArray)可能读取不足16字节(文件末尾段),但直接解密整个16字节数组并写入全部16字节结果,会引入无效填充字节,导致总长度异常。 - 编码转换时机错误:每段解密后立即转MS949字符串拼接,会破坏多字节字符的完整性(MS949是多字节编码,跨16字节块的字符会被拆分),引发乱码和长度变化。
- 二进制数据处理错误:图片是二进制内容,转成MS949字符串再提取字节,会因编码转换破坏原始二进制数据,导致Base64结果完全错误。
- 循环终止逻辑错误:
nRead == -1时仍执行了解密操作,会处理无效的空数据。
修正后的代码实现
1. 正确的文件解密与字节合并
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.util.Arrays; // 初始化变量 int nReadCur = 0; byte[] fileContentArray = new byte[16]; byte[] outbuf = new byte[16]; int nRead; FileInputStream fis = new FileInputStream(tpf); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { while ((nRead = fis.read(fileContentArray)) != -1) { // 处理SEED块加密的填充:和PHP端保持一致(通常用PKCS#7填充) if (nRead < 16) { // PKCS#7填充规则:缺少多少字节就填充多少个对应数值的字节 Arrays.fill(fileContentArray, nRead, 16, (byte)(16 - nRead)); } // 执行SEED解密 seed.SeedDecrypt(fileContentArray, pdwRoundKey, outbuf); // 处理最后一段:去掉PKCS#7填充的无效字节 if (nReadCur + nRead >= nFileSize) { int actualLength = 16 - outbuf[15]; // 最后一个字节的值为填充长度 baos.write(outbuf, 0, actualLength); } else { baos.write(outbuf, 0, 16); } nReadCur += nRead; } } finally { fis.close(); } // 获取完整的解密后原始字节数组 byte[] decryptedBytes = baos.toByteArray();
2. 正确分离文本与图片数据
直接在字节数组中查找分隔符,避免编码转换破坏数据:
import java.util.Base64; import org.json.JSONArray; // 定义分隔符的MS949编码字节数组 byte[] textStartDelim = "<TextData>".getBytes("MS949"); byte[] textEndDelim = "</TextData>".getBytes("MS949"); byte[] fileInfoEndDelim = "</FileInfo>".getBytes("MS949"); // 查找分隔符位置(工具方法见下方) int textStartIdx = indexOf(decryptedBytes, textStartDelim) + textStartDelim.length; int textEndIdx = indexOf(decryptedBytes, textEndDelim, textStartIdx); int fileInfoEndIdx = indexOf(decryptedBytes, fileInfoEndDelim, textEndIdx); // 提取文本数据(转MS949字符串) byte[] textBytes = Arrays.copyOfRange(decryptedBytes, textStartIdx, textEndIdx); String textData = new String(textBytes, "MS949"); // 提取图片原始二进制数据(直接用字节数组,不转字符串) byte[] imageBytes = Arrays.copyOfRange(decryptedBytes, fileInfoEndIdx + fileInfoEndDelim.length, decryptedBytes.length); // 图片字节直接Base64编码 String imageBase64 = Base64.getEncoder().encodeToString(imageBytes); // 组装JSON返回 JSONArray ja = new JSONArray(); ja.put(textData); ja.put(imageBase64); result.put("imageContent", ja);
3. 字节数组查找工具方法
private static int indexOf(byte[] source, byte[] target) { return indexOf(source, target, 0); } private static int indexOf(byte[] source, byte[] target, int startIdx) { if (target.length == 0 || startIdx + target.length > source.length) { return -1; } for (int i = startIdx; i <= source.length - target.length; i++) { boolean match = true; for (int j = 0; j < target.length; j++) { if (source[i + j] != target[j]) { match = false; break; } } if (match) { return i; } } return -1; }
关键注意事项
- 确保SEED加密的填充规则和PHP端完全一致(比如PHP用PKCS#7,Java也要对应处理),否则解密后会出现多余字节。
- 二进制数据(图片)绝对不能经过字符串编码转换,必须直接操作字节数组。
- 多字节编码的文本,必须在获取完整解密字节数组后再转字符串,避免跨块拆分字符。
内容的提问来源于stack exchange,提问作者may
相关产品推荐
相关产品推荐

