SM4对称算法

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


一、pom配置

<!– 国密 –>

<dependency>

<groupId>org.bouncycastle</groupId>

<artifactId>bcprov-jdk15to18</artifactId>

<version>1.66</version>

</dependency>

 

二、代码集成

2.1、目录结构

image.png


2.2、源码

ConfigBean.java


package com.secret.sm4;


public class ConfigBean {


    /**

     * 秘钥空间大小

     */

    public static final int SM4_KEY_SIZE = 128;


    /**

     * 算法编号

     */

    public static final String ALGORITHM_NAME  = "SM4";


    /**

     * 首次加密初始向量  01030507090B0D0F11131517191B1D1F

     */

    public static final byte[] SM4_KEY_IV = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 };


    /**

     * ECB模式串

     */

    public static final String ALGORITHM_ECB_PADDING = "SM4/ECB/PKCS5Padding";

    /**

     * CBC模式串

     */

    public static final String ALGORITHM_CBC_PADDING = "SM4/CBC/PKCS5Padding";


}


 

ProviderSingleton.java


package com.secret.sm4;


import org.bouncycastle.jce.provider.BouncyCastleProvider;


public class ProviderSingleton {


    private static BouncyCastleProvider instance = null;


    private ProviderSingleton() {

    }


    private static synchronized void syncInit() {

        if (instance == null) {

            instance = new BouncyCastleProvider();

        }

    }


    public static BouncyCastleProvider getInstance() {

        if (instance == null) {

            syncInit();

        }

        return instance;

    }


}

 

SecretCommon.java


package com.secret.sm4;


import org.apache.tomcat.util.codec.binary.Base64;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import javax.crypto.BadPaddingException;

import javax.crypto.Cipher;

import javax.crypto.IllegalBlockSizeException;

import javax.crypto.KeyGenerator;

import javax.crypto.NoSuchPaddingException;

import javax.crypto.spec.IvParameterSpec;

import javax.crypto.spec.SecretKeySpec;

import java.nio.charset.StandardCharsets;

import java.security.InvalidAlgorithmParameterException;

import java.security.InvalidKeyException;

import java.security.NoSuchAlgorithmException;

import java.security.NoSuchProviderException;

import java.security.SecureRandom;

import java.security.Security;


/**

 * SM4分组对称密码算法(对称算法)

 * SM4分组密码算法是我国自主设计的分组对称密码算法,用于实现数据的加密/解密运算,以保证数据和信息的机密性。

 * 要保证一个对称密码算法的安全性的基本条件是其具备足够的密钥长度,SM4算法与AES算法具有相同的密钥长度分组长度128比特,因此在安全性上高于3DES算法。

 */

public class SecretCommon {


    static {

        Security.addProvider(new BouncyCastleProvider());

    }


    /**

     * 生成秘钥

     */

    public static String generateKey() throws NoSuchAlgorithmException {

        return ByteUtils.toHexString(generateKeyByte(ConfigBean.SM4_KEY_SIZE, ProviderSingleton.getInstance()));

    }


    /**

     * 生成秘钥

     */

    public static String generateKeyBC() throws NoSuchProviderException, NoSuchAlgorithmException {

        return ByteUtils.toHexString(generateKeyByte(ConfigBean.SM4_KEY_SIZE, BouncyCastleProvider.PROVIDER_NAME));

    }


    public static byte[] generateKeyByte(int keySize, String var) throws NoSuchAlgorithmException, NoSuchProviderException {

        KeyGenerator keyGenerator = KeyGenerator.getInstance(ConfigBean.ALGORITHM_NAME, var);

        keyGenerator.init(keySize, new SecureRandom());

        return keyGenerator.generateKey().getEncoded();

    }


    public static byte[] generateKeyByte(int keySize, BouncyCastleProvider var) throws NoSuchAlgorithmException {

        KeyGenerator keyGenerator = KeyGenerator.getInstance(ConfigBean.ALGORITHM_NAME, var);

        keyGenerator.init(keySize, new SecureRandom());

        return keyGenerator.generateKey().getEncoded();

    }


    /**

     * ECB模式,加密

     * @param plainText 明文字符串

     * @param keyText 秘钥内容

     */

    public static String encryptECB(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return Base64.encodeBase64String(cipherECB(Cipher.ENCRYPT_MODE, plainText.getBytes(StandardCharsets.UTF_8), keyText.getBytes()));

    }


    /**

     * ECB模式,解密

     * @param cipherText 密文字符串

     * @param keyText 秘钥内容

     */

    public static String decryptECB(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return new String(cipherECB(Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), keyText.getBytes()));

    }


    /**

     * ECB模式,加解密算法基础方法

     * @param mode 加解密模式 Cipher.ENCRYPT_MODE 1:加密/ Cipher.DECRYPT_MODE 2:解密

     * @param plainByte 需加密明文内容/待解密密文内容

     * @param keyByte 秘钥内容

     */

    public static byte[] cipherECB(int mode, byte[] plainByte, byte[] keyByte) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        Cipher cipher = Cipher.getInstance(ConfigBean.ALGORITHM_ECB_PADDING, ProviderSingleton.getInstance());

        cipher.init(mode, new SecretKeySpec(keyByte, ConfigBean.ALGORITHM_NAME));

        return cipher.doFinal(plainByte);

    }


    /**

     * CBC模式,加密

     * @param plainText 明文字符串

     * @param keyText 秘钥内容

     */

    public static String encryptCBC(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return Base64.encodeBase64String(cipherCBC(Cipher.ENCRYPT_MODE, plainText.getBytes(StandardCharsets.UTF_8), keyText.getBytes()));

    }


    /**

     * CBC模式,解密

     * @param cipherText 密文字符串

     * @param keyText 秘钥内容

     */

    public static String decryptCBC(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return new String(cipherCBC(Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), keyText.getBytes()));

    }


    /**

     * CBC模式,加解密算法基础方法

     * @param mode 加解密模式 Cipher.ENCRYPT_MODE 1:加密/ Cipher.DECRYPT_MODE 2:解密

     * @param plainByte 需加密明文内容/待解密密文内容

     * @param keyByte 秘钥内容

     */

    public static byte[] cipherCBC(int mode, byte[] plainByte, byte[] keyByte) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        Cipher cipher = Cipher.getInstance(ConfigBean.ALGORITHM_CBC_PADDING, ProviderSingleton.getInstance());

        cipher.init(mode, new SecretKeySpec(keyByte, ConfigBean.ALGORITHM_NAME), new IvParameterSpec(ConfigBean.SM4_KEY_IV));

        return cipher.doFinal(plainByte);

    }


}

 

Utils.java


package com.secret.sm4;


import javax.crypto.BadPaddingException;

import javax.crypto.IllegalBlockSizeException;

import javax.crypto.NoSuchPaddingException;

import java.security.InvalidAlgorithmParameterException;

import java.security.InvalidKeyException;

import java.security.NoSuchAlgorithmException;


public class Utils {


    /**

     * 生成秘钥

     */

    public static String generateKey() throws NoSuchAlgorithmException {

        return SecretCommon.generateKey().substring(0, 16);

    }


    /**

     * 默认加密方法(ECB)

     */

    public static String encrypt(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return encryptECB(plainText, keyText);

    }


    /**

     * 默认解密方法(ECB)

     */

    public static String decrypt(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return decryptECB(cipherText, keyText);

    }


    /**

     * ECB模式,加密

     * @param plainText 明文字符串

     * @param keyText 秘钥内容

     */

    public static String encryptECB(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return SecretCommon.encryptECB(plainText, keyText);

    }


    /**

     * ECB模式,解密

     * @param cipherText 密文字符串

     * @param keyText 秘钥内容

     */

    public static String decryptECB(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return SecretCommon.decryptECB(cipherText, keyText);

    }


    /**

     * CBC模式,加密

     * @param plainText 明文字符串

     * @param keyText 秘钥内容

     */

    public static String encryptCBC(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return SecretCommon.encryptCBC(plainText, keyText);

    }


    /**

     * CBC模式,解密

     * @param cipherText 密文字符串

     * @param keyText 秘钥内容

     */

    public static String decryptCBC(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        return SecretCommon.decryptCBC(cipherText, keyText);

    }


}

 

测试类:Test.java


package com.secret.sm4;


public class Test {


    public static void main(String[] args) throws Exception{

        String key = Utils.generateKey();

        System.out.println("生成SM4秘钥:" + key);


        String plainText = "Believe in yourself, you are the best";

        String ECBText = Utils.encrypt(plainText, key);

        System.out.println("ECB默认加密后密文:" + ECBText);

        System.out.println("ECB默认解密后明文:" + Utils.decrypt(ECBText, key));


        String CBCText = Utils.encryptCBC(plainText, key);

        System.out.println("CBC加密后密文:" + CBCText);

        System.out.println("CBC解密后明文:" + Utils.decryptCBC(CBCText, key));

    }


}

 

2.3、测试


使用方法参考测试类即可。

image.png

三、遇到的坑

3.1、秘钥长度

使用KeyGenerator构建的秘钥长度太长无法使用,会提示:

image.png

秘钥取16位即可使用。


3.2、转码问题

在使用 ECB模式 时,加解密都会报错,头大。


加密报错如下:


Exception in thread "main" java.lang.IllegalArgumentException: Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible value. Expected the discarded bits to be zero.

at org.apache.tomcat.util.codec.binary.Base64.validateCharacter(Base64.java:429)

at org.apache.tomcat.util.codec.binary.Base64.decode(Base64.java:671)

at org.apache.tomcat.util.codec.binary.BaseNCodec.decode(BaseNCodec.java:362)

at org.apache.tomcat.util.codec.binary.BaseNCodec.decode(BaseNCodec.java:353)

at org.apache.tomcat.util.codec.binary.BaseNCodec.decode(BaseNCodec.java:379)

at org.apache.tomcat.util.codec.binary.Base64.decodeBase64(Base64.java:172)

at com.secret.sm4.SecretCommon.encryptECB(SecretCommon.java:64)

at com.secret.sm4.Utils.encryptECB(Utils.java:39)

at com.secret.sm4.Utils.encrypt(Utils.java:23)

at com.secret.sm4.Test.main(Test.java:10)

 

image.png


解密报错如下:


Exception in thread "main" javax.crypto.BadPaddingException: pad block corrupted

at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.doFinal(Unknown Source)

at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)

at javax.crypto.Cipher.doFinal(Cipher.java:2165)

at com.secret.sm4.SecretCommon.cipherECB(SecretCommon.java:85)

at com.secret.sm4.SecretCommon.decryptECB(SecretCommon.java:73)

at com.secret.sm4.Utils.decryptECB(Utils.java:48)

at com.secret.sm4.Utils.decrypt(Utils.java:30)

at com.secret.sm4.Test.main(Test.java:12)

 image.png


查阅了资料才发现,是转byte[]数组的问题。


String plainText = "Believe in yourself, you are the best";

byte[] byte1 = plainText.getBytes();

byte[] byte2 = plainText.getBytes(StandardCharsets.UTF_8);

byte[] byte3 = Base64.decodeBase64(plainText);

byte[] byte4 = Hex.decode(plainText);

Base64.encodeBase64String()

new String()

 

测试的时候发现,同样是转byte[],不同的方法,可能结果会不一样,可以自行尝试验证。


加密的时候:

入参直接使用 getBytes()获取byte数组,返回参数使用 Base64.encodeBase64String()即可。

解密的时候:

入参使用 Base64.decodeBase64()获取byte数组,返回参数直接new String() 即可。



————————————————

版权声明:本文为CSDN博主「message丶小和尚」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_38254635/article/details/131810715