Skip to content

SM3 摘要计算在特殊情况下会报错 #1

@det101

Description

@det101

Issue: SM3 摘要计算在特殊情况下会报错

问题描述

SinopecSmCryptoServiceImpl 的 SM3 摘要计算方法在以下特殊情况下会抛出异常或返回错误结果:

  1. 处理二进制文件(图片、PDF等)时,sm3Hash(InputStream) 方法会失败
  2. SDK 返回非预期格式时,sm3Hash(String) 方法可能返回错误结果
  3. 输入数据为空或 null 时,缺少明确的参数校验

影响范围

  • 受影响方法
    • SmCryptoService.sm3Hash(String data)
    • SmCryptoService.sm3Hash(InputStream inputStream)
    • SmCryptoService.sm3HashFile(File file)
  • 受影响实现SinopecSmCryptoServiceImpl
  • 影响场景
    • 计算二进制文件的 SM3 摘要(图片、PDF、压缩包等)
    • SDK 返回非 JSONObject 格式的结果
    • 传入 null 或空字符串
    • 处理大文件时可能出现内存问题

问题原因

问题1:二进制文件处理错误

SinopecSmCryptoServiceImpl.java 第 204-218 行,sm3Hash(InputStream) 方法存在缺陷:

public String sm3Hash(InputStream inputStream) {
    try {
        // 读取流数据
        byte[] buffer = new byte[8192];
        StringBuilder data = new StringBuilder();
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            data.append(new String(buffer, 0, bytesRead, StandardCharsets.UTF_8)); // ❌ 问题所在
        }
        
        return sm3Hash(data.toString());
    } catch (Exception e) {
        log.error("SM3流摘要计算失败", e);
        throw new RuntimeException("SM3流摘要计算失败: " + e.getMessage(), e);
    }
}

问题2:SDK 返回结果处理不完善

SinopecSmCryptoServiceImpl.java 第 166-186 行,sm3Hash(String) 方法:

Object result = sdkAdapter.callSDKMethod(...);

if (result instanceof JSONObject) {
    return ((JSONObject) result).getString("digest");
}
return result.toString(); // ❌ 如果 result 为 null 或其他类型,可能返回错误结果

问题分析

  1. UTF-8 解码错误:将二进制数据强制用 UTF-8 解码成字符串,对于非文本文件会导致:

    • 无效的 UTF-8 序列被替换为替换字符(U+FFFD),改变原始数据
    • 某些字节序列无法解码,可能抛出 CharacterCodingException
    • 即使不抛异常,解码后的字符串与原始字节数据不一致,导致摘要值错误
  2. 内存问题:对于大文件,将所有数据读入 StringBuilder 可能导致内存溢出(OOM)

  3. 结果处理不完善:当 SDK 返回 null 或非预期类型时,直接调用 toString() 可能返回 "null" 或其他错误值

  4. 缺少参数校验:未对输入参数进行 null 或空值检查

复现步骤

场景1:二进制文件处理失败

  1. 准备一个二进制文件(如 test.jpgtest.pdf
  2. 调用 smCryptoService.sm3HashFile("test.jpg")smCryptoService.sm3Hash(new FileInputStream(new File("test.jpg")))
  3. 观察结果:
    • 可能抛出 CharacterCodingExceptionRuntimeException
    • 或返回错误的摘要值(与正确实现不一致)

场景2:SDK 返回非预期格式

  1. Mock SDK 返回 null 或非 JSONObject 类型
  2. 调用 smCryptoService.sm3Hash("测试数据")
  3. 观察结果:可能返回 "null" 字符串或其他错误值

场景3:空值处理

  1. 调用 smCryptoService.sm3Hash(null)smCryptoService.sm3Hash("")
  2. 观察结果:可能抛出 NullPointerException 或返回错误结果

预期行为

  1. 二进制文件:应该直接对字节流进行摘要计算,不经过字符串转换
  2. SDK 返回结果:应该正确处理各种返回类型,对 null 或非预期类型抛出明确的异常
  3. 参数校验:应该在方法开始处检查参数,对 null 或空值抛出 IllegalArgumentException

建议修复方案

方案一:修复二进制文件处理(推荐)

@Override
public String sm3Hash(InputStream inputStream) {
    try {
        // 读取流数据为字节数组
        byte[] buffer = new byte[8192];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            baos.write(buffer, 0, bytesRead);
        }
        byte[] dataBytes = baos.toByteArray();
        
        // 将字节数组转换为Base64字符串(保持数据完整性)
        String dataBase64 = Base64.getEncoder().encodeToString(dataBytes);
        
        // 调用SDK接口
        Object result = sdkAdapter.callSDKMethod(
            "getDigest",
            sdkAdapter.getSed(),
            sdkAdapter.getLocalProxyUrl(),
            dataBase64, // Base64编码的字符串
            1 // Hash算法ID(1=SM3)
        );
        
        return extractDigest(result);
    } catch (Exception e) {
        log.error("SM3流摘要计算失败", e);
        throw new RuntimeException("SM3流摘要计算失败: " + e.getMessage(), e);
    }
}

方案二:完善结果处理和参数校验

@Override
public String sm3Hash(String data) {
    try {
        // 参数校验
        if (data == null) {
            throw new IllegalArgumentException("SM3摘要计算的输入数据不能为null");
        }
        
        Object result = sdkAdapter.callSDKMethod(
            "getDigest",
            sdkAdapter.getSed(),
            sdkAdapter.getLocalProxyUrl(),
            data,
            1
        );
        
        return extractDigest(result);
    } catch (Exception e) {
        log.error("SM3摘要计算失败", e);
        throw new RuntimeException("SM3摘要计算失败: " + e.getMessage(), e);
    }
}

private String extractDigest(Object result) {
    if (result == null) {
        throw new RuntimeException("SDK返回结果为空");
    }
    if (result instanceof JSONObject) {
        String digest = ((JSONObject) result).getString("digest");
        if (digest == null) {
            throw new RuntimeException("SDK返回的digest字段为空");
        }
        return digest;
    }
    // 如果返回的是字符串,直接返回
    if (result instanceof String) {
        return (String) result;
    }
    throw new RuntimeException("SDK返回了非预期的结果类型: " + result.getClass().getName());
}

相关代码位置

  • 问题代码1src/main/java/com/github/luxl/smcrypto/service/impl/SinopecSmCryptoServiceImpl.java 第 204-218 行(InputStream 处理)
  • 问题代码2src/main/java/com/github/luxl/smcrypto/service/impl/SinopecSmCryptoServiceImpl.java 第 166-186 行(结果处理)
  • 参考实现src/main/java/com/github/luxl/smcrypto/service/impl/DefaultSmCryptoServiceImpl.java 第 169-186 行(正确的流处理方式)

优先级

- 影响二进制文件的 SM3 摘要计算功能,可能导致数据完整性校验失败,且错误处理不完善会影响问题排查。

标签

  • bug
  • sm3
  • inputstream
  • binary-file
  • error-handling
  • sinopec-implementation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions