Use Java Bouncy Castle to implement encryption of national secret algorithms SM4, SM3 and SM2

The implementation of the national secret algorithm relies on the Java library function Bouncy Castle. Please refer to the link for a tutorial on how to install and use the encryption library.

SM4

Introduction

SM4, also known as Shangmi algorithm, is a block cipher algorithm. It was released by the China Cryptography Technology Research Center (a member of the Chinese Cryptozoology Society) in 2012. It has now become my country's national cryptography algorithm and has been widely used in many fields. application. The SM4 algorithm uses a 32-round iteration structure, with a key length of 128 bits and a group length of 128 bits. It supports multiple grouping modes such as ECB and CBC, and has a good balance in security, efficiency and applicability. The SM4 algorithm has the characteristics of high speed, high security, and simple hardware implementation, and can be used in a variety of security scenarios, such as symmetric encryption, message authentication codes, etc. At the same time, the SM4 algorithm has undergone rigorous international standard testing and has become the ISO/IEC 18033-4 standard.

Note: The code encryption and decryption object is a file, please replace it yourself during testing.

code

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.security.Security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class SM4 {
    
    
    private static final String name="SM4";                               //算法名字
    private static final String transformation="SM4/CBC/PKCS5Padding";    //加密模式以及短快填充方式
    private static final String Default_iv="0123456789abcdef";            //加密使用的初始向量

/**
 * 加载指定文件,对其进行加密,并将加密结果写入指定输出文件中
 * @param inputFile 要加密的输入文件路径
 * @param outputFile 加密后的输出文件路径
 * @param key 加密所需的密钥
 * @throws Exception 如果文件读取、加密或写入时出现错误,则抛出异常
 */
private static void encodeFile(String inputFile, String outputFile, String key) throws Exception {
    
    
    // 读取输入文件中的所有字节
    byte [] inputBytes = Files.readAllBytes(Paths.get(inputFile));
    // 对输入字节数组进行加密
    byte [] encodeByte = encode(inputBytes, key.getBytes(StandardCharsets.UTF_8));
    // 将加密后的字节数组写入指定输出文件中
    Files.write(Paths.get(outputFile),encodeByte);
    System.out.println("File encoded successfully.");
}
    /**
     * 使用指定的加密算法和密钥对给定的字节数组进行加密
     * @param inputByte 要加密的字节数组
     * @param key 加密所需的密钥
     * @return 加密后的字节数组
     * @throws Exception 如果加密时发生错误,则抛出异常
     */
    public static byte [] encode(byte [] inputByte, byte [] key) throws Exception {
    
    
        // 获取加密实例
        Cipher c = Cipher.getInstance(transformation);
        // 根据密钥的字节数组创建 SecretKeySpec
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, name);
        // 创建 IvParameterSpec 对象,使用默认向量和字符集
        IvParameterSpec ivParameterSpec = new IvParameterSpec(Default_iv.getBytes(StandardCharsets.UTF_8));
        // 初始化加密实例
        c.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        // 返回加密后的字节数组
        return c.doFinal(inputByte);
    }

    public static void decodeFile(String inputFilePath, String outputFilePath, String key) throws Exception {
    
    
        byte[] inputBytes = Files.readAllBytes(Paths.get(inputFilePath));
        byte[] decodeBytes = decode(inputBytes, key.getBytes(StandardCharsets.UTF_8));
        Files.write(Paths.get(outputFilePath), decodeBytes);
        System.out.println("File decode successfully.");
    }



    private static byte[] decode(byte[] inputBytes, byte[] key) throws Exception {
    
    
        Cipher cipher = Cipher.getInstance(transformation);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, name);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(Default_iv.getBytes(StandardCharsets.UTF_8));
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        return cipher.doFinal(inputBytes);
    }

    public static void main(String[] args) throws Exception {
    
    
        Security.addProvider(new BouncyCastleProvider());
        String inputFile="Test.txt";              //需要加密的文件
        String enFile="Encode.txt";               //加密后的文件
        String deFile="Decode.txt";               //解密后的文件
        String key="0123456789ABCDEF";            //加密密钥,注意必须是128bits,即16个字节
        encodeFile(inputFile,enFile,key);
        decodeFile(enFile,deFile,key);


    }
}

Results (partial screenshot)

Test.txt

Test.txt

Encode.txt

Encode.txt

Decode.txt

Decode.txt

SM3

Introduction

SM3 is a national cryptographic algorithm, also known as a commercial cryptographic algorithm. It is a hash function in my country and is used in the field of information security. It is based on the Merkle–Damgård structure, uses bit operations similar to SHA-256, and designs an adjustable message perturbation function. SM3 ensures security strength under birthday attacks and provides protection against collision and pre-image attacks.

code

import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class SM3Test {
    
    
    /**
     * 使用SM3算法计算给定输入的摘要值
     * @param inputBytes 要进行摘要计算的输入字符串
     * @return 给定输入的摘要值
     */
    private static byte[] calculateSM3Digest( byte [] inputBytes) {
    
    
        //创建 SM3Digest 对象
        SM3Digest digest = new SM3Digest();
        //将输入字符串转为字节数组,并使用该字节数组更新摘要对象的内部状态,以便进行计算
       // byte[] inputBytes = input.getBytes();
        digest.update(inputBytes, 0, inputBytes.length);
        //创建一个输出字节数组,调用 doFinal 方法完成哈希计算,并将结果存入输出数组
        byte[] output = new byte[digest.getDigestSize()];
        digest.doFinal(output, 0);
        //返回摘要字节数组
        return output;
    }
    public static void main(String[] args) {
    
    
        String filePath = "src/Test.java";
        try {
    
    
           
            byte [] inputBytes = Files.readAllBytes(Paths.get(filePath));
            byte[] digest = calculateSM3Digest(inputBytes);
            String hexDigest = Hex.toHexString(digest);
            System.out.println("SM3 Digest: " + hexDigest);
        } catch (IOException e) {
    
    
            System.out.println("An error occurred while reading the file: " + e.getMessage());
        }
    }
    
}

result

Insert image description here

SM2

Introduction

SM2 is my country's national cryptographic algorithm. It adopts a public key cryptography system based on elliptic curve cryptography and can be widely used in digital signature, key negotiation, key exchange and public key encryption scenarios. The key length is 256 bits, and the security level is higher than the commonly used RSA or DSA algorithms. In the SM2 algorithm, key exchange, digital signature and public key encryption all use the same elliptic curve and the same hash algorithm, which has the characteristics of independent intellectual property rights, high efficiency and high security. And the SM2 algorithm has become the international standard ISO/IEC 14888-3 and has been widely used.

code



public class SM2 {
    
    

    public SM2(){
    
     }
    static {
    
      //加载BC驱动
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
            new BouncyCastleProvider();
    }

    // 生成sm2密钥对
    public KeyPair createECKeyPair() {
    
    
        //使用标准名称创建EC参数生成的参数规范
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");

        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg;
        try {
    
    
            kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
// 使用SM2算法域参数集初始化密钥生成器(默认使用最高优先级安装的提供者的 SecureRandom 的实现作为随机源)
            // kpg.initialize(sm2Spec);
            // 使用SM2的算法域参数集和指定的随机源初始化密钥生成器
            kpg.initialize(sm2Spec, new SecureRandom());
            // 通过密钥生成器生成密钥对
            return kpg.generateKeyPair();

        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }

    /*
     *公钥加密
     * @param publicKey SM2公钥
     * @param data      明文数据
     * @param modeType  加密模式
     * @return          密文数据
     */
    public byte[] encode(String publicKeyHex, byte[] inputBytes, int modeType){
    
    
        //加密模式
        //
        BCECPublicKey publicKey = getECPublicKeyByPublicKeyHex(publicKeyHex);
        SM2Engine.Mode mode;
        if (modeType == 1) {
    
    //采用新模式加密标准
            mode = SM2Engine.Mode.C1C3C2;
        } else {
    
    //采用旧模式加密标准
            mode = SM2Engine.Mode.C1C2C3;
        }
        //通过公钥对象获取公钥的基本域参数。
        ECParameterSpec ecParameterSpec = publicKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过公钥值和公钥基本参数创建公钥参数对象
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
        //根据加密模式实例化SM2公钥加密引擎
        SM2Engine sm2Engine = new SM2Engine(mode);
        //初始化加密引擎
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));

        byte[] arrayOfBytes = null;
        try {
    
    
            //通过加密引擎对字节数串行加密
            arrayOfBytes = sm2Engine.processBlock(inputBytes, 0, inputBytes.length);
        } catch (Exception e) {
    
    
            System.out.println("SM2加密时出现异常:" + e.getMessage());
            e.printStackTrace();
        }
        return arrayOfBytes;
    }
    /**
     * 私钥解密
     *
     * @param privateKeyHex  SM私钥
     * @param cipherBytes 密文数据
     * @param modeType 加密模式
     * @return           解密后的明文
     */
    public static byte[] decode(String privateKeyHex, byte[] cipherBytes, int modeType) {
    
    
        //解密模式
        SM2Engine.Mode mode;
        BCECPrivateKey privateKey = getBCECPrivateKeyByPrivateKeyHex(privateKeyHex);
        if (modeType == 1) {
    
    
            mode = SM2Engine.Mode.C1C3C2;
        } else {
    
    
            mode = SM2Engine.Mode.C1C2C3;
        }

        //通过私钥对象获取私钥的基本域参数。
        ECParameterSpec ecParameterSpec = privateKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());

        //通过私钥值和私钥基本参数创建私钥参数对象
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
                ecDomainParameters);

        //通过解密模式创建解密引擎并初始化
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(false, ecPrivateKeyParameters);

        byte[] arrayOfBytes = null;
        try {
    
    
            //通过解密引擎对密文字节串进行解密
            arrayOfBytes = sm2Engine.processBlock(cipherBytes, 0, cipherBytes.length);
        } catch (Exception e) {
    
    
            System.out.println("SM2解密时出现异常" + e.getMessage());
        }
        return arrayOfBytes;
    }


    //椭圆曲线ECParameters ASN.1 结构
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    //椭圆曲线公钥或私钥的基本域参数。
    private static ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    /**
     * 公钥字符串转换为 BCECPublicKey 公钥对象
     *
     * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首字节为0x04:表示该公钥为非压缩格式,操作时需要删除)
     * @return BCECPublicKey SM2公钥对象
     */

    public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
    
    
        //截取64字节有效的SM2公钥(如果公钥首字节为0x04)
        if (pubKeyHex.length() > 128) {
    
    
            pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
        }
        //将公钥拆分为x,y分量(各32字节)
        String stringX = pubKeyHex.substring(0, 64);
        String stringY = pubKeyHex.substring(stringX.length());
        //将公钥x、y分量转换为BigInteger类型
        BigInteger x = new BigInteger(stringX, 16);
        BigInteger y = new BigInteger(stringY, 16);
        //通过公钥x、y分量创建椭圆曲线公钥规范
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);
        //通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * 私钥字符串转换为 BCECPrivateKey 私钥对象
     *
     * @param privateKeyHex: 32字节十六进制私钥字符串
     * @return BCECPrivateKey:SM2私钥对象
     */
    public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
    
    
        //将十六进制私钥字符串转换为BigInteger对象
        BigInteger d = new BigInteger(privateKeyHex, 16);
        //通过私钥和私钥域参数集创建椭圆曲线私钥规范
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters);
        //通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    public static void main(String[] args) throws Exception {
    
    
        String Sm4Key="0123456789abcdef";
        String sm2PublicKeyHex=null;
        String sm2PrivateKeyHex=null;

        SM2 sm2=new SM2();
        // 生成SM2密钥
        KeyPair keyPair = sm2.createECKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();
        if (publicKey instanceof BCECPublicKey){
    
    
            //获取65字节非压缩的十六进制公钥串(0x04)
            byte[] publicKeyBytes = ((BCECPublicKey) publicKey).getQ().getEncoded(false);
            sm2PublicKeyHex = Hex.toHexString(publicKeyBytes);

        }
        if (privateKey instanceof BCECPrivateKey) {
    
    
            //获取32字节十六进制私钥串
            sm2PrivateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);

        }
        //Sm2加密密钥
        byte [] keyBytes=Sm4Key.getBytes("utf-8");
        byte[] encodeBytes = sm2.encode(sm2PublicKeyHex, keyBytes, 1);
        String encodeKey = Hex.toHexString(encodeBytes);
        //Sm2解密密钥
        byte [] decodeBytes=sm2.decode(sm2PrivateKeyHex,encodeBytes,1);
        String deKey=new String(decodeBytes,"utf-8");
        System.out.println("\ninitial key:"+Sm4Key+"\nencode key:"+encodeKey+"\ndecode key:"+deKey);
        System.out.println("complete!");
    }
}

result

SM2 results

Guess you like

Origin blog.csdn.net/Rm_mR/article/details/131290101