java实现国产sm4加密算法

前言

今天给大家带来一个国产SM4加密解密算法的java后端解决方案,代码完整,可以直接使用,希望给大家带来帮助,尤其是做政府系统的开发人员,可以直接应用到项目中进行加密解密。
画重点!是SM4哦,不是SM。哈哈,各位要在知识里遨游,不要想歪。正文开始~

国产SM4加密解密算法概念介绍

SMS4算法是在国内广泛使用的WAPI无线网络标准中使用的加密算法,是一种32轮的迭代非平衡Feistel结构的分组加密算法,其密钥长度和分组长度均为128。SMS4算法的加解密过程中使用的算法是完全相同的,唯一不同点在于该算法的解密密钥是由它的加密密钥进行逆序变换后得到的。
SMS4分组加密算法是中国无线标准中使用的分组加密算法,在2012年已经被国家商用密码管理局确定为国家密码行业标准,标准编号GM/T 0002-2012并且改名为SM4算法,与SM2椭圆曲线公钥密码算法,SM3密码杂凑算法共同作为国家密码的行业标准,在我国密码行业中有着极其重要的位置。
SMS4算法的分组长度为128bit,密钥长度也是128bit。加解密算法均采用32轮非平衡Feistel迭代结构,该结构最先出现在分组密码LOKI的密钥扩展算法中。SMS4通过32轮非线性迭代后加上一个反序变换,这样只需要解密密钥是加密密钥的逆序,就能使得解密算法与加密算法保持一致。SMS4加解密算法的结构完全相同,只是在使用轮密钥时解密密钥是加密密钥的逆序。
S盒是一种利用非线性变换构造的分组密码的一个组件,主要是为了实现分组密码过程中的混淆的特性和设计的。SMS4算法中的S盒在设计之初完全按照欧美分组密码的设计标准进行,它采用的方法是能够很好抵抗差值攻击的仿射函数逆映射复合法。

SM4加密算法应用场景

SM4常用于政府系统的数据传输加密,比如当我们前端向后台传参数的时候,可以使用此算法。对参数的数据进行加密,然后后台对加密的数据进行解密再存储到数据库中,保证数据传输过程中,不受泄露。
本次提供的方案不仅提供sm4的加密解密,还提供了md5算法的完整性防篡改校验。

Java端解决方案

对于java端,我们使用的基于spring的aop切面和自定义注解来实现。整体思路为,当后台开启加密解密的时候,针对于打上注解的方法,寻找实体类中打上注解的字段进行加密和解密。再从前端传递请求的request中取出md5的header,进行md5的完整性,防篡改校验。

首先我们必须说的是两个工具类,一个是SM4Utils工具类,另一个则是md5工具类。
下面先来说一下SM4Utils。这个工具类用于SM4算法的加密和解密及密码校验。我们先直接看代码,然后后面对此进行解释。

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 cipherText,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, cipher,ENCODING);
      System.out.println(json);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

}

我们来仔细看一下上面的代码,首先定义了个4个静态不可修改都变量,用于下方方法的使用。包括编码utf-8,密码名称sm4,密码的分组方式SM4/ECB/PKCS7Padding和默认的key值长度128。
整体的方法我们分为生成ecb暗号,自动生成密钥,加密,解密,密码校验的算法。
以上为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主要用于对参数的完整性校验,防止篡改。

此时我们已经实现了主要的加密解密工具,接下来实现spring的aop自定义注解,自定义注解我们要实现三个。
第一个是加密注解,用于方法上的,表示该方法的参数需要被加密。
第二个是解密注解,用于方法上的,表示该方法的参数需要被解密。
第三个是字段加密解密注解,用于标识实体类的字段是否需要被加密和解密。

加密的自定义注解

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

可以看到此时打了一个order注解,-2147483648用于标识优先级最高。

解密的自定义注解

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

加密解密字段的自定义注解

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

注解的准备工作已经做完,接下来就是实现sm4的切面方法。不了解spring的aop的实现方法,可以去先补一下spring的相关知识,这里不做赘述。我们这里采用的切面都是环绕通知,切面的切点是加密解密的注解。

加密解密切面的实现

说一下切面的答题实现思路。我们可以看到@ConditionalOnProperty(prefix = "sm4", value = "enable", matchIfMissing = false),这个我们做成了根据配置文件的配置进行动态的开关。
我们在appication.yml文件中进行如下的配置。这样切面是否生效就取决于配置。然后我们捕捉加密的注解和解密的注解,然后对加了注解的方法中的逻辑进行加密和解密。
下方代码的切点方法为EncryptAOPCut和DecryptAOPCut。随后通过around对切点进行捕捉。分别调用的核心的加密算法encryptMethodAop和解密算法decryptMethodAop。

sm4:
 enable: true

核心的加密解密算法都是使用环绕通知的ProceedingJoinPoint类,从他的对象中我们可以取到spring的各种参数,包括request请求,请求的参数和response对象。

/**
 * @Description:sm4加密解密切面
 */
@Order(-2147483648)
@Aspect
@Component
@ConditionalOnProperty(prefix = "sm4", value = "enable", 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;
    }
  }
}

代码实际应用

首先我们可以定义一个实体类,对实体类的字段进行加密或解密的标识。我们这里建立了一个Info实体类,对于其中的name属性,我们加了注解加密解密字段,对于sex属性我们不做任何处理。

@Data
public class Info {

  @EncryptField
  private String name;

  private String sex;

}

然后我们对于controller方法打上加密的方法或解密的方法。

@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;
  }

}

注意到方法上的注解@DecryptMethod,以为这着我们的这个方法将会进行解密。如果是@EncryptMethod,则代表对方法进行加密。

总结

到此为止,涉及到java后端的代码解决方案已经完毕。示例代码已经给出,大家可以直接使用,本人亲测有效。文中难免有不足,欢迎大家批评指正。

以上就是java实现国产sm4加密算法的详细内容,更多关于Java 国产sm4加密算法的资料请关注我们其它相关文章!

(0)

相关推荐

  • java实现微信小程序加密数据解密算法

    一.概述 微信推出了小程序,很多公司的客户端应用不仅具有了APP.H5.还接入了小程序开发.但是,小程序中竟然没有提供Java版本的加密数据解密算法.这着实让广大的Java开发人员蛋疼. 微信小程序提供的加密数据解密算法链接 我们下载的算法示例如下: 木有Java!! 木有Java!! 木有Java!! 那么如何解决这个问题,我们一起来实现Java版本的微信小程序加密数据解密算法. 二.实现Java版本的微信小程序加密数据解密算法 1.创建项目 这里,我们创建一个Maven工程,具体创建步骤略.

  • Java sm3加密算法的实现

    1.准备工作 所需jar包: bcprov-jdk15on-1.59.jar commons-lang3-3.1.jar 对应的maven依赖 <!--sm3,sm4加密算法--> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.66</version> <

  • Java加密 消息摘要算法MAC实现详解

    MAC是消息摘要算法的第三种实现方式,另外两种方式分别为:MD2\4\5.SHA. MAC的jdk实现:1.默认密钥方式 private static void MAC_JDK(){ try { KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5");//初始化KeyGenerator SecretKey secretKey = keyGenerator.generateKey();//产生密钥 byte[] ke

  • Java加密 消息摘要算法SHA实现详解

    SHA是消息摘要算法的一种实现方式,前面已经总结过MD2\4\5的实现,接下来就为大家总结一下SHA的实现. SHA的jdk实现: private static void SHA_JDK(){ try { MessageDigest digest = MessageDigest.getInstance("SHA");//我们可以通过SHA\SHA-1\SHA-384\SHA-256\SHA-512来获得不同的消息摘要密钥 digest.update(src.getBytes()); S

  • java 非对称加密算法RSA实现详解

    现在就为大家介绍一种基于因子分解的RSA算法,这种加密算法有两种实现形式:1.公钥加密,私钥解密:2.私钥加密,公钥解密.下面就为大家分析一下实现代码,相对于DH算法,RSA显得有些简单. 初始化密钥: KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(512); KeyPair keyPair = keyPairGenerator.g

  • Java 实现常见的非对称加密算法

    概述 非对称加密算法与对称加密算法的主要差别在于非对称加密算法用于加密和解密的密钥不相同,非对称加密算法密钥分为公钥和私钥,公钥加密只能用私钥解密,反之私钥加密只能用公钥解密.相比对称加密算法,非对称加密算法加/解密效率低,但安全性高,这两种算法一般结合使用.常见非对称加密算法有RSA.ECC.Elgamal等. 使用RSA实现加密解密 公钥加密,私钥解密. package com.ss.utils; import javax.crypto.Cipher; import java.securit

  • Java实现的对称加密算法AES定义与用法详解

    本文实例讲述了Java实现的对称加密算法AES定义与用法.分享给大家供大家参考,具体如下: 一 简介 1.AES是目前使用最多的对称加密算法. 2.AES的优势之一是至今尚未被破解. 3.AES通常用于移动通信系统加密以及基于SSH协议(SSH Client.SecrueCRT)的软件. 二 特点 1.高级的 2.DES替代者 三 AES的参数说明 四 代码实现 package com.imooc.security.aes; import java.security.Key; import ja

  • Java实现的Base64加密算法示例

    本文实例讲述了Java实现的Base64加密算法.分享给大家供大家参考,具体如下: 一 算法实现 1.JDK 2.Commonc Codec 3.Bouncy Castle 二 代码 package com.imooc.security.base64; import java.io.IOException; import org.apache.commons.codec.binary.Base64; import sun.misc.BASE64Decoder; import sun.misc.B

  • java 非对称加密算法DH实现详解

    非对称加密算法是相对于对称加密算法来说的,对于对称加密算法请查阅之前的总结,今天为大家介绍一下DH算法,DH是一种密钥交换算法,接收方根据发送方加密时的密钥,生成接收方解密密钥.下面就一起来学习一下吧: 初始化发送方密钥: KeyPairGenerator sendKeyPairGenerator = KeyPairGenerator.getInstance("DH"); sendKeyPairGenerator.initialize(512); KeyPair sendKeyPair

  • java实现国产sm4加密算法

    前言 今天给大家带来一个国产SM4加密解密算法的java后端解决方案,代码完整,可以直接使用,希望给大家带来帮助,尤其是做政府系统的开发人员,可以直接应用到项目中进行加密解密. 画重点!是SM4哦,不是SM.哈哈,各位要在知识里遨游,不要想歪.正文开始~ 国产SM4加密解密算法概念介绍 SMS4算法是在国内广泛使用的WAPI无线网络标准中使用的加密算法,是一种32轮的迭代非平衡Feistel结构的分组加密算法,其密钥长度和分组长度均为128.SMS4算法的加解密过程中使用的算法是完全相同的,唯一

  • 分享Java常用几种加密算法(四种)

    对称加密算法是应用较早的加密算法,技术成熟.在对称加密算法中,数据发信方将明文(原始数据)和加密密钥(mi yue)一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去.收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文.在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥. 简单的java加密算法有: BASE 严格地说,属于编码格式,而非加密算法 MD(Mes

  • java基于AES对称加密算法实现的加密与解密功能示例

    本文实例讲述了java基于AES对称加密算法实现的加密与解密功能.分享给大家供大家参考,具体如下: package com.soufun.com; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import

  • java基于Des对称加密算法实现的加密与解密功能详解

    本文实例讲述了java基于Des对称加密算法实现的加密与解密功能.分享给大家供大家参考,具体如下: Des 加密相关类介绍: SecureRandom  这个类是继承自java.util.Random 这个类 SecureRandom 这个类的构造器有三种,下面例举两种: SecureRandom()构造一个实现默认随机数算法的安全随机数生成器 (RNG). SecureRandom(byte[] seed)构造一个实现默认随机数算法的安全随机数生成器 (RNG). DESKeySpec 这个类

  • java实现的AES加密算法完整实例

    本文实例讲述了java实现的AES加密算法.分享给大家供大家参考,具体如下: import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import android.util.Base64; /** * @author vipin.cb , vipin.cb@experionglobal.com <br> * Sep 27, 2013

  • java实现的DES加密算法详解

    本文实例讲述了java实现的DES加密算法.分享给大家供大家参考,具体如下: 一.DES加密算法介绍 1.要求密钥必须是8个字节,即64bit长度 2.因为密钥是byte[8] , 代表字符串也可以是非可见的字节,可以与Base64编码算法一起使用 3.加密.解密都需要通过字节数组作为数据和密钥进行处理 二.对称加密 DES加密算法属于对称加密. 即利用指定的密钥,按照密码的长度截取数据,分成数据块,和密钥进行复杂的移位.算数运算或者数据处理等操作,形成只有特定的密码才能够解开的数据. 加密与解

  • java实现的RSA加密算法详解

    本文实例讲述了java实现的RSA加密算法.分享给大家供大家参考,具体如下: 一.什么是非对称加密 1.加密的密钥与加密的密钥不相同,这样的加密算法称之为非对称加密 2.密钥分为:公钥,私钥 公钥:可以对外给任何人的加密和解密的密码,是公开的 私钥:通过私钥可以生成公钥,但从公钥被认为无法生成公钥(被推导出的概率小到不考虑) 3.当将要加密的内容用公钥加密的时候,只能用私钥来解密 当将要加密的内容用私钥加密的时候,只能用公钥来解密 4.公钥与私钥的关系,利用一个简单的公式来生成公钥和私钥,即非对

  • 基于java实现的ECC加密算法示例

    本文实例讲述了基于java实现的ECC加密算法.分享给大家供大家参考,具体如下: ECC ECC-Elliptic Curves Cryptography,椭圆曲线密码编码学,是目前已知的公钥体制中,对每比特所提供加密强度最高的一种体制.在软件注册保护方面起到很大的作用,一般的序列号通常由该算法产生. 当我开始整理<Java加密技术(二)>的时候,我就已经在开始研究ECC了,但是关于Java实现ECC算法的资料实在是太少了,无论是国内还是国外的 资料,无论是官方还是非官方的解释,最终只有一种答

  • Java语言实现Blowfish加密算法完整代码分享

    前几天网上突然出现流言:某东发生数据泄露12G,最终某东在一篇声明中没有否认,还算是勉强承认了吧,这件事对于一般人有什么影响.应该怎么做已经有一堆人说了,所以就不凑热闹了,咱来点对程序猿来说实际点的,说一个个人认为目前比较安全的加密算法:Blowfish. 上代码之前,先说几点Blowfish加密算法的特点: 1. 对称加密,即加密的密钥和解密的密钥是相同的: 2. 每次加密之后的结果是不同的(这也是老夫比较欣赏的一点): 3. 可逆的,和老夫之前的文章介绍的md5等摘要算法不一样,他是可逆的:

随机推荐