加密方法说明

使用非对称加密算法和对称加密算法混合模式,在支持对大量数据加解密的同时,保证了加解密速度,安全性更高,主要应用于一些用户信息、敏感信息加密的安全性要求比较高的场景。

  • SM2:非对称加密算法,由公钥和私钥组成秘钥对。
  • AES: 对称加密算法,使用AES秘钥。

加密流程如下所示:

API加密调用说明

header设置

若使用SM2 + AES加密,则需要在header中传入:

  • sm2PublicKey(SM2公钥,由数据服务提供)
  • encryptedAesKey(加密后的AES秘钥)

参数设置

在请求体中,需要传入加密后的密文cipherData:

  • POST请求体示例:
{
    "cipherData":"40de3124bb5f0d71703a320289752d75271986c9de05d98471ec7fa08019415c"
}
  • GET请求参数示例
http://api路径?cipherData=

响应结构

API会对查询结果加密,包括结果集、分页参数等,加密信息在data结构中,字段名为”cipherData“。

{
    "code": 0,
    "cost": 56,
    "data": {
        "cipherData": "f131c649c5ce319dea7f4c4b1255dce9e775eca3285af06a4492b79539c2bcd8e03794aea78d3a1c07c72003ecfe9bef0f028711ac6a1e9cbf4945c14a94858278da0a34d110e5e2905688efcfd6f04d94f98acd31ca7c2bc522da4ef403aff83932e90b19ebd19b2049209347766cebe45539b032e13753a2d66ab74df85b00e25bcbcd1d96cb97df8914b7272392b0d357eb8827190ce58e607cd92cf2867b5822d73ab0bb01ef6569e8b3b1f54779c89bfd2a782f73744df238d049e393f40b2b61bd5bc8a74582b14776194d7ef4cc26122e4abab37aff805bd345ca1812288cd846299c90d17a37950d683711bb4b2f640ad42bd6e1657a77ec0e49d7077977df413e659c5b873dcbbb24304d4eeaa8a80b747c1749ca348b9c1f245ea590bb19b0e9be430ea0e00d01e6b10da453abe176ac0350ca234d88903fef00bd8e8e3b8e025f347604eeedf38fedcaf8cd410bae3defb5c790a40e4e0f8cdbf3533fd1d97dbb3c8a1b6ec64a930745d758dfe7f762f769601b1774644b5d528e3b6523f4ef4a2884767426468f3e333f2085921f5ad6805d73d691fb662fd0e6b80653016ddbb3f9e361798521630141a398ae5e42d699c67bf3d1c1805063e504a5b6a93cd860613029b4772f315533aa38f0143146da19338c0c3fe55d8af4e7811aa1d2e33311f2c1f09910fe9589ccbc15c9e2ab67e436d66f12167cfaa70f028711ac6a1e9cbf4945c14a9485825b2c9550dbb548ad1a4daef5e4d7b064fad08c25196df5a7de8b4eccef97e3cb6f6114511e46f2785575e7292d82ee6a22e66446e577de47029a055f65f47008c54a6b6db7a6d6efefc3cf08bf3b8a22894015ede132cdbd59d8852d6e545d65495d4106030ef6178de7669cc3d318d33d90ac2013b443af54ae0f06037ae0ab57fd64190401dbe606a53c9fb3b11fbea17911cd896f54a7a4bcadee05ee6bdd3bb80f53cf6fe9a8c84a27645ebb35d21c00b651084428a87c53fcf6a8197e9f10dc3af6354f52c0c3e8d740b8e03f61918c161761292eed97ad810ca8b48ab334ff369d4a70c622420626398a062787efdbc2073ab6a83ab621e891fa678b5c"
    },
    "message": "success",
    "desc": null,
    "reqId": "0c99d7116f7d498ba9f28f5b04aedf2c"
}

加密解密代码示例

请求参数加密

public static void main(String[] args) throws Exception {
  //GET请求参数明文
  String content = "field1=100&field2=abc";
  //POST请求明文 
  // String content = "{\"field1\":100,\"field2\":200}";
  //生成AES主秘钥
  String aesKey = AesUtil.genAESKey();
  //AES加密,生成请求密文
  String cipherData = AesUtil.encode(aesKey,content);

  //应用管理——应用详情中的SM2公钥
  String sm2PubKey = "033eae4b2981e6b4300b0d389528db789b3e003d4d2c605bdce5f992e78328c8fd";
  //给AES主秘钥加密
  String encryptedAesKey = SM2Util.encrypt(sm2PubKey,aesKey);
  //将请求密文cipherData、AES主密钥密文encryptedAesKey放到请求header中
}

响应结果解密

public static void main(String[] args) throws Exception {
  //加密后的响应结果
  String responseCipherData = "40de3124bb5f0d71703a320289752d75271986c9de05d98471ec7fa08019415c";
  //加密时生成的AES主秘钥
  String aesKey = "uBdUx82vPHkDKb284d7NkjFoNcKWBuka";
  //对响应密文解密
  String content = AesUtil.decode(aesKey,responseCipherData);
}

加密工具类

  • AES工具类
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

@Slf4j
public class AesUtil {

  /**
   生成AESKEY
  */  
  public static String genAESKey() throws NoSuchAlgorithmException {
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    SecureRandom secureRandom = new SecureRandom();
    kgen.init(256, secureRandom);
    SecretKey key = kgen.generateKey();
    return  Base64.encodeBase64String(key.getEncoded());
  }

  /**
   * AES加密
   * @param aesKey
   * @param data
   * @return
   */
  public static String encode(String aesKey, String data) throws Exception{
    // 转换KEY
    Key key = new SecretKeySpec(aesKey.getBytes("UTF-8"),"AES");

    // 加密
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] result = cipher.doFinal(data.getBytes());
    return Hex.encodeHexString(result);
  }

  /**
   * AES解密
   * @param aesKey
   * @param data
   * @return
   */
  public static String decode(String aesKey, String data) throws Exception {
    // 转换KEY
    Key key = new SecretKeySpec(aesKey.getBytes("UTF-8"),"AES");
    // 解密
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, key);
    byte[] result = cipher.doFinal(Hex.decodeHex(data));
    return new String(result);
  }

}
  • SM2工具类
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import javafx.util.Pair;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Component;

/**
 * SM2加解密工具类
 */
@Slf4j
@Component
public class SM2Util {

  private static BouncyCastleProvider provider;
  // SM2曲线参数
  private static X9ECParameters sm2ECParameters;
  // Domain参数
  static ECDomainParameters domainParameters;

  private SM2Util() {
    try {
      provider = new BouncyCastleProvider();
      sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
      domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(),
          sm2ECParameters.getN());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * SM2算法生成密钥对
   *
   * @return 密钥对信息
   */
  public static Pair<String, String> generateSm2KeyPair()
      throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
    final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
    // 获取一个椭圆曲线类型的密钥对生成器
    final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", provider);
    SecureRandom random = new SecureRandom();
    // 使用SM2的算法区域初始化密钥生成器
    kpg.initialize(sm2Spec, random);
    // 获取密钥对
    KeyPair keyPair = kpg.generateKeyPair();
    BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate();
    BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();
    String pubKey = new String(Hex.encode(publicKey.getQ().getEncoded(true)));
    String prvKey = privateKey.getD().toString(16);
    return new Pair<>(pubKey, prvKey);
  }

  /**
   * SM2加密算法
   *
   * @param publicKey 公钥
   * @param data      明文数据
   * @return 密文
   */
  public static String encrypt(String publicKey, String data) {
    //提取公钥点
    ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
    // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
    ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint,
        domainParameters);

    SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
    // 设置sm2为加密模式
    sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

    byte[] arrayOfBytes = null;
    try {
      byte[] in = data.getBytes();
      arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
    } catch (Exception e) {
      log.error("SM2加密时出现异常:" + e.getMessage());
    }
    return Hex.toHexString(arrayOfBytes);
  }

  /**
   * SM2解密算法
   *
   * @param privateKey 私钥
   * @param cipherData 密文数据
   * @return 明文
   */
  public static String decrypt(String privateKey, String cipherData) {
    // 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上
    if (!cipherData.startsWith("04")) {
      cipherData = "04" + cipherData;
    }
    byte[] cipherDataByte = Hex.decode(cipherData);

    BigInteger privateKeyD = new BigInteger(privateKey, 16);
    ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD,
        domainParameters);

    // 新排序模式
    SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
    // 设置sm2为解密模式
    sm2Engine.init(false, privateKeyParameters);

    String result = "";
    try {
      byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
      return new String(arrayOfBytes);
    } catch (Exception e) {
      log.error("SM2解密时出现异常:" + e.getMessage());
    }
    return result;
  }

}