You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP转Java:SEED加密TPF文件读写解密及编码问题排查

问题分析与解决方案

核心问题总结

你在将PHP SEED解密逻辑转Java时,遇到的核心问题出在块解密的边界处理、编码转换时机和二进制数据处理方式上,导致解密后内容长度、编码与PHP不一致。

错误点拆解

  1. 分段读取的边界错误:原代码中fis.read(fileContentArray)可能读取不足16字节(文件末尾段),但直接解密整个16字节数组并写入全部16字节结果,会引入无效填充字节,导致总长度异常。
  2. 编码转换时机错误:每段解密后立即转MS949字符串拼接,会破坏多字节字符的完整性(MS949是多字节编码,跨16字节块的字符会被拆分),引发乱码和长度变化。
  3. 二进制数据处理错误:图片是二进制内容,转成MS949字符串再提取字节,会因编码转换破坏原始二进制数据,导致Base64结果完全错误。
  4. 循环终止逻辑错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 21:40:27