有个项目需要用到SM3做摘要算法,在github上找了一个实现,很多博客里用的也是这一套,先附上链接:SM3-JAVA实现

看文件开头的声明类似下面,就基本是同一套实现方案了
在这里插入图片描述

算法的实现是通过获取源文件byte[],先调用一个叫padding的方法对末位填充,然后提取hash,起初因为只需要处理文本、图片等小文件,只加了一个接收inputstream的方法,调用原算法的内容转byte[],直到后来需要处理视频等大文件,生产上报了内存溢出,因为算法的所有分支,最终都是获取完整的byte[],没办法处理大文件

先看下原算法获取完整byte[]后,调用padding填充:

 private static byte[] padding(byte[] source) throws IOException {
        if (source.length >= 0x2000000000000000l) {
            throw new RuntimeException("src data invalid.");
        }
        long l = source.length * 8;
        long k = 448 - (l + 1) % 512;
        if (k < 0) {
            k = k + 512;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(source);
        baos.write(FirstPadding);
        long i = k - 7;
        while (i > 0) {
            baos.write(ZeroPadding);
            i -= 8;
        }
        baos.write(long2bytes(l));
        return baos.toByteArray();
    }

然后提取hash:

public static byte[] hash(byte[] source) throws IOException {
        byte[] m1 = padding(source, null);
        int n = m1.length / (512 / 8);
        byte[] b;
        byte[] vi = IV.toByteArray();
        byte[] vi1 = null;
        for (int i = 0; i < n; i++) {
            b = Arrays.copyOfRange(m1, i * 64, (i + 1) * 64);
            vi1 = CF(vi, b);
            vi = vi1;
        }
        return vi1;
    }

重点在这行:b = Arrays.copyOfRange(m1, i * 64, (i + 1) * 64);
可以看到原算法获取到完成的byte[]后,也是分64字节来处理的,可以参考这个实现,传入inputstream,每次读取64个字节,最后一次再调用padding做填充
参考hash(byte[])的实现,增加方法hash(InputStream):

private static byte[] hash(InputStream inputStream) throws IOException {
        byte[] vi = IV.toByteArray();
        byte[] vi1 = null;

        byte[] bytes = new byte[64];
        int length = -1;
        int totalLength = 0;
        while ((length = inputStream.read(bytes)) != -1) {
            totalLength += length;
            if (inputStream.available() == 0) {
                bytes = padding(Arrays.copyOfRange(bytes, 0, length), totalLength);
                //=========================补充更正=========================
                //如果padding后长度 > 64,分两次处理,感谢评论区朋友的提醒
                if (bytes.length > 64) {
                    for (int i = 0; i <= 1; i++) {
                        byte[] b = Arrays.copyOfRange(bytes, i * 64, (i + 1) * 64);
                        vi1 = CF(vi, b);
                        vi = vi1;
                    }
                    break;
                }
                //=========================补充更正=========================
            }
            vi1 = CF(vi, bytes);
            vi = vi1;
        }
        return vi1;
    }

需要注意,原来的padding算法最后写入了完整的字节长度:baos.write(long2bytes(l));
所以这里对padding方法增加一个入参length,记录字节数组长度,原来的调用传null,根据参数是否为空来设置最终写入的值:

private static byte[] padding(byte[] source, Integer length) throws IOException {
            if (source.length >= 0x2000000000000000l) {
                throw new RuntimeException("src data invalid.");
            }
            long l = length == null ? source.length * 8 : length * 8;
            long k = 448 - (l + 1) % 512;
            if (k < 0) {
                k = k + 512;
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            baos.write(source);
            baos.write(FirstPadding);
            long i = k - 7;
            while (i > 0) {
                baos.write(ZeroPadding);
                i -= 8;
            }
            baos.write(long2bytes(l));
            return baos.toByteArray();
        }

区别主要是入参和 第四行 l 的长度计算

后来测试发现这个填充没有的话也不影响最终生成的hash,如果最终填充的long小于实际长度的话会影响,应该是做一个校验用的,新增的调整最好不影响原算法的处理逻辑

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐