This article takes you to learn the Java implementation scheme of domestic encryption algorithm SM4

preface

Today, we bring you a Java back-end solution of domestic SM4 encryption and decryption algorithm. The code is complete and can be used directly. I hope to bring you help, especially as a developer of government system, it can be directly applied to the project for encryption and decryption. Draw the point! It's SM4 Oh, not SM. Ha ha, you should roam in knowledge, don't think crooked. Text start~

Concept introduction of domestic SM4 encryption and decryption algorithm

SMS4 algorithm is an encryption algorithm used in WAPI wireless network standard widely used in China. It is a 32 round iterative packet encryption algorithm with unbalanced Feistel structure. Its key length and packet length are 128. The algorithm used in the encryption and decryption process of SMS4 algorithm is exactly the same. The only difference is that the decryption key of the algorithm is obtained by reverse transformation of its encryption key. SMS4 block encryption algorithm is a block encryption algorithm used in China's wireless standard. In 2012, it has been determined as the national cryptographic industry standard by the State Administration of Commercial Cryptography. The standard number is GM / T 0002-2012 and is renamed SM4 algorithm. It is used as the national cryptographic industry standard together with SM2 elliptic curve public key cryptography algorithm and SM3 cryptographic hash algorithm, It plays an extremely important role in China's password industry. The packet length of SMS4 algorithm is 128bit, and the key length is also 128bit. The encryption and decryption algorithms adopt 32 rounds of unbalanced Feistel iterative structure, which first appears in the key expansion algorithm of block cipher Loki. SMS4 adds a reverse order transformation after 32 rounds of nonlinear iteration, so that the decryption algorithm can be consistent with the encryption algorithm only if the decryption key is in the reverse order of the encryption key. The structure of SMS4 encryption and decryption algorithm is exactly the same, except that when using round key, the decryption key is in the reverse order of the encryption key. S-box is a component of block cipher constructed by nonlinear transformation. It is mainly designed to realize the characteristics and design of confusion in the process of block cipher. At the beginning of the design, the S-box in SMS4 algorithm is completely carried out according to the design standards of European and American block ciphers. Its method is the affine function inverse mapping composite method which can well resist the difference attack.

SM4 encryption algorithm application scenario

SM4 is often used for data transmission encryption of government systems. For example, this algorithm can be used when we transmit parameters from the front end to the background. Encrypt the parameter data, then decrypt the encrypted data in the background and store it in the database to ensure that the data will not be leaked during data transmission. The scheme provided this time not only provides encryption and decryption of SM4, but also provides integrity tamper proof verification of MD5 algorithm.

Java side solution

For the Java side, we use the spring based AOP aspect and custom annotations to implement it. The overall idea is to find the annotated fields in the entity class for encryption and decryption when encryption and decryption are enabled in the background. Then take out the MD5 header from the request of the front-end transmission request to verify the integrity and tamper proof of MD5.

SM4Utils

public class Sm4Utils {

    private static final String ENCODING = "UTF-8";
    public static final String ALGORIGTHM_NAME = "SM4";
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS7Padding";
    public static final int DEFAULT_KEY_SIZE = 128;

    public Sm4Utils() {
    }

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     *  @Description:生成ecb暗号
     */
    private static Cipher generateEcbCipher(String algorithmName,int mode,byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithmName,BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key,ALGORIGTHM_NAME);
        cipher.init(mode,sm4Key);
        return cipher;
    }

    /**
     *  @Description:自动生成密钥
     */
    public static byte[] generateKey() throws Exception {
        return generateKey(DEFAULT_KEY_SIZE);
    }

    public static byte[] generateKey(int keySize) throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORIGTHM_NAME,BouncyCastleProvider.PROVIDER_NAME);
        kg.init(keySize,new SecureRandom());
        return kg.generateKey().getEncoded();
    }


    /**
     *  @Description:加密
     */
    public static String encryptEcb(String hexKey,String paramStr,String charset) throws Exception {
        String cipherText = "";
        if (null != paramStr && !"".equals(paramStr)) {
            byte[] keyData = ByteUtils.fromHexString(hexKey);
            charset = charset.trim();
            if (charset.length() <= 0) {
                charset = ENCODING;
            }
            byte[] srcData = paramStr.getBytes(charset);
            byte[] cipherArray = encrypt_Ecb_Padding(keyData,srcData);
            cipherText = ByteUtils.toHexString(cipherArray);
        }
        return cipherText;
    }

    /**
     *  @Description:加密模式之ecb
     */
    public static byte[] encrypt_Ecb_Padding(byte[] key,byte[] data) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING,Cipher.ENCRYPT_MODE,key);
        byte[] bs = cipher.doFinal(data);
        return bs;
    }

    /**
     *  @Description:sm4解密
     */
    public static String decryptEcb(String hexKey,String cipherText,String charset) throws Exception {
        String decryptStr = "";
        byte[] keyData = ByteUtils.fromHexString(hexKey);
        byte[] cipherData = ByteUtils.fromHexString(cipherText);
        byte[] srcData = decrypt_Ecb_Padding(keyData,cipherData);
        charset = charset.trim();
        if (charset.length() <= 0) {
            charset = ENCODING;
        }
        decryptStr = new String(srcData,charset);
        return decryptStr;
    }

    /**
     *  @Description:解密
     */
    public static byte[] decrypt_Ecb_Padding(byte[] key,byte[] cipherText) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING,Cipher.DECRYPT_MODE,key);
        return cipher.doFinal(cipherText);
    }

    /**
     *  @Description:密码校验
     */
    public static boolean verifyEcb(String hexKey,String paramStr) throws Exception {
        boolean flag = false;
        byte[] keyData = ByteUtils.fromHexString(hexKey);
        byte[] cipherData = ByteUtils.fromHexString(cipherText);
        byte[] decryptData = decrypt_Ecb_Padding(keyData,cipherData);
        byte[] srcData = paramStr.getBytes(ENCODING);
        flag = Arrays.equals(decryptData,srcData);
        return flag;
    }

    /**
     *  @Description:测试类
    */
    public static void main(String[] args) {
        try {
            String json = "{\"name\":\"color\",\"sex\":\"man\"}";
            // 自定义的32位16进制密钥
            String key = "cc9368581322479ebf3e79348a2757d9";
            String cipher = Sm4Utils.encryptEcb(key,json,ENCODING);
            System.out.println(cipher);
            System.out.println(Sm4Utils.verifyEcb(key,cipher,json));
            json = Sm4Utils.decryptEcb(key,ENCODING);
            System.out.println(json);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Let's take a closer look at the above code. First, we define four static immutable variables for the use of the following methods. Including code UTF-8, password name SM4, password grouping mode SM4 / ECB / pkcs7padding and default key value length 128. The whole method is divided into the algorithms of generating ECB code, automatically generating key, encryption, decryption and password verification. The above is the core code of sm4utils.

MD5Utils


/**
 *  @Description:md5加密工具
 */
public class Md5Utils {

    /**
     *  @Description:获得md5加密串
     */
    public static String getMD5String(String str) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(str.getBytes());
            return new BigInteger(1,md.digest()).toString(16);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

Md5utils is mainly used to verify the integrity of parameters to prevent tampering.

At this time, we have implemented the main encryption and decryption tools. Next, we will implement spring's AOP custom annotation. We need to implement three custom annotations. The first is the encryption annotation, which is used on the method, indicating that the parameters of the method need to be encrypted. The second is the decryption annotation, which is used on the method, indicating that the parameters of the method need to be decrypted. The third is the field encryption and decryption annotation, which is used to identify whether the field of the entity class needs to be encrypted and decrypted.

Encrypted custom annotations

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Order(-2147483648)
public @interface EncryptMethod {
}

You can see that an order annotation, - 2147483648 is marked to identify the highest priority.

Decrypted custom annotation

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Order(-2147483648)
@Inherited
public @interface DecryptMethod {
}

Custom annotation for encryption and decryption fields

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Order(-2147483648)
public @interface EncryptField {
}

The preparation of annotations has been completed, and the next step is to implement the faceted method of SM4. If you don't know the implementation method of spring AOP, you can supplement the relevant knowledge of spring first. I won't repeat it here. The facets we use here are all around notifications, and the pointcut of the facet is the annotation of encryption and decryption.

Implementation of encryption and decryption

Let's talk about the realization idea of cutting-edge answer. We can see @ conditionalonproperty (prefix = "SM4", value = "enable", matchifmissing = false). We have made a dynamic switch according to the configuration of the configuration file. We are in application The following configuration is performed in the YML file. In this way, whether the section takes effect depends on the configuration. Then we capture the encrypted and decrypted annotations, and then encrypt and decrypt the logic in the annotated method. The pointcut methods of the following code are encryptaopcut and decryptaopcut. Then snap the tangent point through around. The core encryption algorithm encryptmethodaop and decryptmethodaop are called respectively.

sm4:
  enable: true

The core encryption and decryption algorithms use the proceedingjoinpoint class around the notification. From its object, we can get various spring parameters, including request, request parameters and response object.


/**
 *  @Description:sm4加密解密切面
 */
@Order(-2147483648)
@Aspect
@Component
@ConditionalOnProperty(prefix = "sm4",matchIfMissing = false)
public class Sm4Aspect {

    private Logger log = LoggerFactory.getLogger(Sm4Aspect.class);
    private static final String DEFAULT_KEY = "cc9368581322479ebf3e79348a2757d9";

    public Sm4Aspect() {
    }

    @pointcut("@annotation(com.jichi.aop.sm4.EncryptMethod)")
    public void EncryptAOPCut() {
    }

    @pointcut("@annotation(com.jichi.aop.sm4.DecryptMethod)")
    public void DecryptAOPCut() {
    }

    @Around("EncryptAOPCut()")
    public Object encryptMethodAop(ProceedingJoinPoint joinPoint) {
        Object responSEObj = null;
        try {
            responSEObj = joinPoint.proceed();
            this.handleEncrypt(responSEObj);
            //md5加密
            String md5Data = Md5Utils.getMD5String(new Gson().toJson(responSEObj));
            SpringContextUtil.getHttpServletResponse().setHeader("md5",md5Data);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            this.log.error("encryptMethodAop处理出现异常{}",throwable);
        }
        return responSEObj;
    }

    @Around("DecryptAOPCut()")
    public Object decryptMethodAop(ProceedingJoinPoint joinPoint) {
        Object responSEObj = null;
        try {
            responSEObj = joinPoint.getArgs()[0];
            //throw new RuntimeException("md5校验失败");
            this.handleDecrypt(responSEObj);
            String md5 = "";
            md5 = Md5Utils.getMD5String(new Gson().toJson(responSEObj));
            System.out.println(md5);
            String origianlMd5 = "";
            origianlMd5 = SpringContextUtil.getHttpServletRequest().getHeader("md5");
            if(origianlMd5.equals(md5)){
                responSEObj = joinPoint.proceed();
            }else{
                this.log.error("参数的md5校验不同,可能存在篡改行为,请检查!");
                throw new Exception("参数的md5校验不同,可能存在篡改行为,请检查!");
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            this.log.error("decryptMethodAop处理出现异常{}",throwable);
        }
        return responSEObj;
    }

    private void handleEncrypt(Object requestObj) throws Exception {
        if (!Objects.isNull(requestObj)) {
            Field[] fields = requestObj.getClass().getDeclaredFields();
            Field[] fieldsCopy = fields;
            int fieldLength = fields.length;
            for(int i = 0; i < fieldLength; ++i) {
                Field field = fieldsCopy[i];
                boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
                if (hasSecureField) {
                    field.setAccessible(true);
                    String plaintextValue = (String)field.get(requestObj);
                    String encryptValue = Sm4Utils.encryptEcb(DEFAULT_KEY,plaintextValue,"");
                    field.set(requestObj,encryptValue);
                }
            }
        }
    }

    private Object handleDecrypt(Object responSEObj) throws Exception {
        if (Objects.isNull(responSEObj)) {
            return null;
        } else {
            Field[] fields = responSEObj.getClass().getDeclaredFields();
            Field[] fieldsCopy = fields;
            int fieldLength = fields.length;
            for(int i = 0; i < fieldLength; ++i) {
                Field field = fieldsCopy[i];
                boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
                if (hasSecureField) {
                    field.setAccessible(true);
                    String encryptValue = (String)field.get(responSEObj);
                    String plaintextValue = Sm4Utils.decryptEcb(DEFAULT_KEY,encryptValue,"");
                    field.set(responSEObj,plaintextValue);
                }
            }
            return responSEObj;
        }
    }
}

Code practical application

First, we can define an entity class to encrypt or decrypt the fields of the entity class. We have established an info entity class here. For the name attribute, we annotate the encryption and decryption field, and we do not do any processing for the sex attribute.

@Data
public class Info {

    @EncryptField
    private String name;

    private String sex;

}

Then we mark the controller method with encryption method or decryption method.

@RestController
@RequestMapping("/demo/test")
public class TestController {


    @PostMapping("/saveInfo")
    @DecryptMethod
    public HashMap<String,String> saveInfo(@RequestBody Info info) {
        HashMap<String,String> result = new HashMap<String,String>();
        String name = info.getName();
        System.out.println(name);
        String sex= info.getSex();
        System.out.println(sex);
        result.put("flag","1");
        result.put("msg","操作成功");
        return result;
    }

}

Notice the annotation @ decryptmethod on the method, which means that our method will be decrypted. If @ encryptmethod, it means that the method is encrypted.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>