Java小强个人技术博客站点    手机版
当前位置: 首页 >> Java >> 使用Java实现国密SM3算法

使用Java实现国密SM3算法

50 Java | 2025-11-13

国密即国家密码局认定的国产密码算法。主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。

SM1 为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。

SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。

SM3 消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。

SM4 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。

国密SM3算法.jpg

SM3是中华人民共和国政府采用的一种密码散列函数标准,由国家密码管理局于2010年12月17日发布。相关标准为“GM/T 0004-2012 《SM3密码杂凑算法》”。

SM3杂凑算法是我国自主设计的密码杂凑算法,适用于商用密码应用中的数字签名和验证消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。为了保证杂凑算法的安全性,其产生的杂凑值的长度不应太短,例如MD5输出128比特杂凑值,输出长度太短,影响其安全性SHA-1算法的输出长度为160比特,SM3算法的输出长度为256比特,因此SM3算法的安全性要高于MD5算法和SHA-1算法。


工具包SM3Digest类

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;

/**
 * SM3工具类 - 提供常用哈希操作
 */
public class SM3Digest {

    /**
     * 计算字符串的SM3哈希值
     */
    public static String hash(String data) {
        return SM3Util.hashToHex(data.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 计算字节数组的SM3哈希值
     */
    public static byte[] hash(byte[] data) {
        return SM3Util.hash(data);
    }

    /**
     * 计算文件的SM3哈希值
     */
    public static String hashFile(File file) throws IOException {
        try (InputStream is = new FileInputStream(file)) {
            return hashStream(is);
        }
    }

    /**
     * 计算输入流的SM3哈希值
     */
    public static String hashStream(InputStream is) throws IOException {
        // 使用自定义SM3实现
        SM3StreamHasher hasher = new SM3StreamHasher();
        return hasher.hashStream(is);
    }

    /**
     * 验证哈希值
     */
    public static boolean verify(String data, String expectedHash) {
        String actualHash = hash(data);
        return actualHash.equalsIgnoreCase(expectedHash);
    }

    public static boolean verify(byte[] data, String expectedHash) {
        String actualHash = SM3Util.hashToHex(data);
        return actualHash.equalsIgnoreCase(expectedHash);
    }

    /**
     * 与标准算法比较(用于测试)
     */
    public static boolean compareWithStandard(String data) throws NoSuchAlgorithmException {
        // 这里可以与其他实现比较,用于验证正确性
        String ourHash = hash(data);

        // 如果有其他实现可以比较
        // String otherHash = otherImplementation(data);
        // return ourHash.equals(otherHash);

        return true; // 实际使用时需要实现比较逻辑
    }
}


工具包SM3StreamHasher类

import java.io.IOException;
import java.io.InputStream;

/**
 * 流式SM3哈希计算
 */
public class SM3StreamHasher {
    private final SM3Context context = new SM3Context();

    public SM3StreamHasher() {
        reset();
    }

    /**
     * 重置哈希状态
     */
    public void reset() {
        context.reset();
    }

    /**
     * 更新哈希计算
     */
    public void update(byte[] data, int offset, int length) {
        context.update(data, offset, length);
    }

    public void update(byte[] data) {
        update(data, 0, data.length);
    }

    /**
     * 完成哈希计算
     */
    public byte[] digest() {
        return context.finish();
    }

    public String digestHex() {
        return SM3Util.bytesToHex(digest());
    }

    /**
     * 计算流的哈希值
     */
    public String hashStream(InputStream is) throws IOException {
        reset();

        byte[] buffer = new byte[8192];
        int bytesRead;

        while ((bytesRead = is.read(buffer)) != -1) {
            update(buffer, 0, bytesRead);
        }

        return digestHex();
    }
}

/**
 * SM3计算上下文
 */
class SM3Context {
    private int[] v = new int[8];
    private byte[] buffer = new byte[64];
    private int bufferOffset = 0;
    private long byteCount = 0;

    public SM3Context() {
        reset();
    }

    public void reset() {
        System.arraycopy(SM3Util.IV, 0, v, 0, 8);
        bufferOffset = 0;
        byteCount = 0;
    }

    public void update(byte[] data, int offset, int length) {
        byteCount += length;

        while (length > 0) {
            int toCopy = Math.min(64 - bufferOffset, length);
            System.arraycopy(data, offset, buffer, bufferOffset, toCopy);

            bufferOffset += toCopy;
            offset += toCopy;
            length -= toCopy;

            if (bufferOffset == 64) {
                processBlock();
                bufferOffset = 0;
            }
        }
    }

    private void processBlock() {
        int[] w = new int[68];

        // 消息扩展
        for (int i = 0; i < 16; i++) {
            w[i] = byteToInt(buffer, i * 4);
        }

        for (int j = 16; j < 68; j++) {
            w[j] = SM3Util.P1(w[j - 16] ^ w[j - 9] ^ Integer.rotateLeft(w[j - 3], 15))
                    ^ Integer.rotateLeft(w[j - 13], 7) ^ w[j - 6];
        }

        // 压缩函数
        int a = v[0], b = v[1], c = v[2], d = v[3];
        int e = v[4], f = v[5], g = v[6], h = v[7];

        for (int j = 0; j < 64; j++) {
            int ss1 = Integer.rotateLeft(Integer.rotateLeft(a, 12) + e + Integer.rotateLeft(SM3Util.T(j), j), 7);
            int ss2 = ss1 ^ Integer.rotateLeft(a, 12);
            int tt1 = SM3Util.FF(a, b, c, j) + d + ss2 + (w[j] ^ w[j + 4]);
            int tt2 = SM3Util.GG(e, f, g, j) + h + ss1 + w[j];
            d = c;
            c = Integer.rotateLeft(b, 9);
            b = a;
            a = tt1;
            h = g;
            g = Integer.rotateLeft(f, 19);
            f = e;
            e = SM3Util.P0(tt2);
        }

        v[0] ^= a; v[1] ^= b; v[2] ^= c; v[3] ^= d;
        v[4] ^= e; v[5] ^= f; v[6] ^= g; v[7] ^= h;
    }

    public byte[] finish() {
        // 添加填充
        long bitLength = byteCount * 8;
        byte[] padding = new byte[64];
        int paddingLength = 64 - bufferOffset;

        if (paddingLength < 9) {
            paddingLength += 64;
        }

        padding[0] = (byte) 0x80;
        for (int i = 1; i < paddingLength - 8; i++) {
            padding[i] = 0;
        }

        // 添加长度
        for (int i = 0; i < 8; i++) {
            padding[paddingLength - 8 + i] = (byte) (bitLength >>> (56 - i * 8));
        }

        update(padding, 0, paddingLength);

        return intArrayToByteArray(v);
    }

    private int byteToInt(byte[] b, int offset) {
        return ((b[offset] & 0xFF) << 24) |
                ((b[offset + 1] & 0xFF) << 16) |
                ((b[offset + 2] & 0xFF) << 8) |
                (b[offset + 3] & 0xFF);
    }

    private byte[] intArrayToByteArray(int[] arr) {
        byte[] result = new byte[arr.length * 4];
        for (int i = 0; i < arr.length; i++) {
            int value = arr[i];
            result[i * 4] = (byte) (value >>> 24);
            result[i * 4 + 1] = (byte) (value >>> 16);
            result[i * 4 + 2] = (byte) (value >>> 8);
            result[i * 4 + 3] = (byte) value;
        }
        return result;
    }
}


工具包SM3Util类

import java.util.Arrays;

/**
 * SM3国密哈希算法实现
 * 参考标准:GM/T 0004-2012 SM3密码杂凑算法
 */
public class SM3Util {

    // 初始IV值
    public static final int[] IV = {
            0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600,
            0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E
    };

    // 常量T_j
    public static final int T_J_15 = 0x79CC4519;
    public static final int T_J_63 = 0x7A879D8A;

    // 填充字节
    public static final byte FIRST_PADDING_BYTE = (byte) 0x80;

    private SM3Util() {
        // 工具类,禁止实例化
    }

    /**
     * 计算SM3哈希值
     */
    public static byte[] hash(byte[] data) {
        if (data == null) {
            return null;
        }

        // 1. 消息填充
        byte[] padded = padding(data);

        // 2. 迭代压缩
        int[] v = Arrays.copyOf(IV, IV.length);

        int blocks = padded.length / 64;
        for (int i = 0; i < blocks; i++) {
            byte[] block = Arrays.copyOfRange(padded, i * 64, (i + 1) * 64);
            v = CF(v, block);
        }

        // 3. 转换为字节数组输出
        return intArrayToByteArray(v);
    }

    /**
     * 消息填充
     */
    public static byte[] padding(byte[] data) {
        int originalLength = data.length;
        long bitLength = (long) originalLength * 8;

        // 计算填充后长度:k = (448 - (l + 1) mod 512) mod 512
        int k = (int) ((448 - (bitLength + 1)) % 512);
        if (k < 0) {
            k += 512;
        }

        // 总长度 = 原始数据 + 1字节(0x80) + k位0 + 64位长度表示
        int totalLength = originalLength + 1 + (k + 64) / 8;
        byte[] padded = new byte[totalLength];

        // 复制原始数据
        System.arraycopy(data, 0, padded, 0, originalLength);

        // 添加填充字节 0x80
        padded[originalLength] = FIRST_PADDING_BYTE;

        // 添加长度信息(大端序,64位)
        for (int i = 0; i < 8; i++) {
            padded[totalLength - 8 + i] = (byte) ((bitLength >>> (56 - i * 8)) & 0xFF);
        }

        return padded;
    }

    /**
     * 压缩函数
     */
    public static int[] CF(int[] v, byte[] b) {
        // 消息扩展
        int[] w = new int[68];
        int[] w1 = new int[64];

        // 将512位消息分组划分为16个32位字
        for (int i = 0; i < 16; i++) {
            w[i] = byteToInt(b, i * 4);
        }

        // 生成132个字
        for (int j = 16; j < 68; j++) {
            w[j] = P1(w[j - 16] ^ w[j - 9] ^ Integer.rotateLeft(w[j - 3], 15))
                    ^ Integer.rotateLeft(w[j - 13], 7) ^ w[j - 6];
        }

        for (int j = 0; j < 64; j++) {
            w1[j] = w[j] ^ w[j + 4];
        }

        // 压缩
        int a = v[0], b1 = v[1], c = v[2], d = v[3];
        int e = v[4], f = v[5], g = v[6], h = v[7];

        for (int j = 0; j < 64; j++) {
            int ss1 = Integer.rotateLeft(Integer.rotateLeft(a, 12) + e + Integer.rotateLeft(T(j), j), 7);
            int ss2 = ss1 ^ Integer.rotateLeft(a, 12);
            int tt1 = FF(a, b1, c, j) + d + ss2 + w1[j];
            int tt2 = GG(e, f, g, j) + h + ss1 + w[j];
            d = c;
            c = Integer.rotateLeft(b1, 9);
            b1 = a;
            a = tt1;
            h = g;
            g = Integer.rotateLeft(f, 19);
            f = e;
            e = P0(tt2);
        }

        return new int[] {
                v[0] ^ a, v[1] ^ b1, v[2] ^ c, v[3] ^ d,
                v[4] ^ e, v[5] ^ f, v[6] ^ g, v[7] ^ h
        };
    }

    /**
     * 布尔函数FF
     */
    public static int FF(int x, int y, int z, int j) {
        if (j >= 0 && j <= 15) {
            return x ^ y ^ z;
        } else {
            return (x & y) | (x & z) | (y & z);
        }
    }

    /**
     * 布尔函数GG
     */
    public static int GG(int x, int y, int z, int j) {
        if (j >= 0 && j <= 15) {
            return x ^ y ^ z;
        } else {
            return (x & y) | (~x & z);
        }
    }

    /**
     * 置换函数P0
     */
    public static int P0(int x) {
        return x ^ Integer.rotateLeft(x, 9) ^ Integer.rotateLeft(x, 17);
    }

    /**
     * 置换函数P1
     */
    public static int P1(int x) {
        return x ^ Integer.rotateLeft(x, 15) ^ Integer.rotateLeft(x, 23);
    }

    /**
     * 常量函数T
     */
    public static int T(int j) {
        if (j >= 0 && j <= 15) {
            return T_J_15;
        } else {
            return T_J_63;
        }
    }

    /**
     * 字节数组转int(大端序)
     */
    public static int byteToInt(byte[] b, int offset) {
        return ((b[offset] & 0xFF) << 24) |
                ((b[offset + 1] & 0xFF) << 16) |
                ((b[offset + 2] & 0xFF) << 8) |
                (b[offset + 3] & 0xFF);
    }

    /**
     * int数组转字节数组
     */
    public static byte[] intArrayToByteArray(int[] arr) {
        byte[] result = new byte[arr.length * 4];
        for (int i = 0; i < arr.length; i++) {
            int value = arr[i];
            result[i * 4] = (byte) (value >>> 24);
            result[i * 4 + 1] = (byte) (value >>> 16);
            result[i * 4 + 2] = (byte) (value >>> 8);
            result[i * 4 + 3] = (byte) value;
        }
        return result;
    }

    /**
     * 将哈希结果转换为十六进制字符串
     */
    public static String hashToHex(byte[] data) {
        byte[] hash = hash(data);
        return bytesToHex(hash);
    }

    /**
     * 字节数组转十六进制字符串
     */
    public static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b & 0xFF));
        }
        return sb.toString();
    }

    /**
     * 十六进制字符串转字节数组
     */
    public static byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                    + Character.digit(hex.charAt(i + 1), 16));
        }
        return data;
    }
}


编写测试类

/**
 * 国密算法 测试类
 */
public class SmTest {
    @Test
    public void testSM3Hash() {
        // 测试空字符串
        String emptyHash = SM3Digest.hash("");
        System.out.println("加密后:" + emptyHash);
        // 测试
        String abcHash = SM3Digest.hash("javaCui2025" + "nb3q");
        System.out.println("加密后:" + abcHash);
    }
    @Test
    public void testSM3Verify() {
        String data = "javaCui2025" + "nb3q";
        String hash = "ed41f67cd656d061cf86069a039e7e04845ad4d999fb4c8117163783b4b9425c";
        System.out.println(SM3Digest.verify(data, hash));
    }
    @Test
    public void testSM3Performance() {
        // 性能测试
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            SM3Digest.hash("测试数据" + i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("1000次SM3计算耗时: " + (endTime - startTime) + "ms");
    }
}


打印

加密后:1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b
加密后:ed41f67cd656d061cf86069a039e7e04845ad4d999fb4c8117163783b4b9425c


第三方方法,是用于验证摘要是否匹配。

后面加的字符串,就是盐,实际使用时是随机生成的一个字符串,即每个需要加密的字符串,都会随机加盐。

推荐您阅读更多有关于“ 算法 国密 SM3 摘要 MD5 ”的文章

下一篇:使用FFmpeg通过RSTP协议拉取视频保存到本地

猜你喜欢

发表评论: