远方蔚蓝
一刹那情真,相逢不如不见

文章数量 126

访问次数 199827

运行天数 1437

最近活跃 2024-10-04 23:36:48

进入后台管理系统

java使用SM2加解密工具类


  1. package wst.st.site.tools.sm2;
  2. import lombok.Data;
  3. @Data
  4. public class SM2KeyPair {
  5. public SM2KeyPair(String publicKey, String privateKey) {
  6. this.publicKey = publicKey;
  7. this.privateKey = privateKey;
  8. }
  9. /** 公钥 */
  10. private String publicKey;
  11. /** 私钥 */
  12. private String privateKey;
  13. }
  1. package wst.st.site.tools.sm2;
  2. import java.math.BigInteger;
  3. import java.security.SecureRandom;
  4. import org.bouncycastle.crypto.CipherParameters;
  5. import org.bouncycastle.crypto.Digest;
  6. import org.bouncycastle.crypto.InvalidCipherTextException;
  7. import org.bouncycastle.crypto.digests.SM3Digest;
  8. import org.bouncycastle.crypto.params.ECDomainParameters;
  9. import org.bouncycastle.crypto.params.ECKeyParameters;
  10. import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
  11. import org.bouncycastle.crypto.params.ECPublicKeyParameters;
  12. import org.bouncycastle.crypto.params.ParametersWithRandom;
  13. import org.bouncycastle.math.ec.ECConstants;
  14. import org.bouncycastle.math.ec.ECFieldElement;
  15. import org.bouncycastle.math.ec.ECPoint;
  16. import org.bouncycastle.util.Arrays;
  17. import org.bouncycastle.util.BigIntegers;
  18. public class SM2EngineExtend {
  19. private final Digest digest;
  20. /**是否为加密模式*/
  21. private boolean forEncryption;
  22. private ECKeyParameters ecKey;
  23. private ECDomainParameters ecParams;
  24. private int curveLength;
  25. private SecureRandom random;
  26. /**密文排序方式*/
  27. private int cipherMode;
  28. /**BC库默认排序方式-C1C2C3*/
  29. public static int CIPHERMODE_BC = 0;
  30. /**国密标准排序方式-C1C3C2*/
  31. public static int CIPHERMODE_NORM = 1;
  32. public SM2EngineExtend() {
  33. this(new SM3Digest());
  34. }
  35. public SM2EngineExtend(Digest digest) {
  36. this.digest = digest;
  37. }
  38. /**
  39. * 设置密文排序方式
  40. * @param cipherMode
  41. */
  42. public void setCipherMode(int cipherMode){
  43. this.cipherMode = cipherMode;
  44. }
  45. /**
  46. * 默认初始化方法,使用国密排序标准
  47. * @param forEncryption - 是否以加密模式初始化
  48. * @param param - 曲线参数
  49. */
  50. public void init(boolean forEncryption, CipherParameters param) {
  51. init(forEncryption, CIPHERMODE_NORM, param);
  52. }
  53. /**
  54. * 默认初始化方法,使用国密排序标准
  55. * @param forEncryption 是否以加密模式初始化
  56. * @param cipherMode 加密数据排列模式:1-标准排序;0-BC默认排序
  57. * @param param 曲线参数
  58. */
  59. public void init(boolean forEncryption, int cipherMode, CipherParameters param) {
  60. this.forEncryption = forEncryption;
  61. this.cipherMode = cipherMode;
  62. if (forEncryption) {
  63. ParametersWithRandom rParam = (ParametersWithRandom) param;
  64. ecKey = (ECKeyParameters) rParam.getParameters();
  65. ecParams = ecKey.getParameters();
  66. ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
  67. if (s.isInfinity()) {
  68. throw new IllegalArgumentException("invalid key: [h]Q at infinity");
  69. }
  70. random = rParam.getRandom();
  71. } else {
  72. ecKey = (ECKeyParameters) param;
  73. ecParams = ecKey.getParameters();
  74. }
  75. curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;
  76. }
  77. /**
  78. * 加密或解密输入数据
  79. * @param in
  80. * @param inOff
  81. * @param inLen
  82. * @return
  83. * @throws InvalidCipherTextException
  84. */
  85. public byte[] processBlock( byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
  86. if (forEncryption) {
  87. // 加密
  88. return encrypt(in, inOff, inLen);
  89. } else {
  90. return decrypt(in, inOff, inLen);
  91. }
  92. }
  93. /**
  94. * 加密实现,根据cipherMode输出指定排列的结果,默认按标准方式排列
  95. * @param in
  96. * @param inOff
  97. * @param inLen
  98. * @return
  99. * @throws InvalidCipherTextException
  100. */
  101. private byte[] encrypt(byte[] in, int inOff, int inLen)
  102. throws InvalidCipherTextException {
  103. byte[] c2 = new byte[inLen];
  104. System.arraycopy(in, inOff, c2, 0, c2.length);
  105. byte[] c1;
  106. ECPoint kPB;
  107. do {
  108. BigInteger k = nextK();
  109. ECPoint c1P = ecParams.getG().multiply(k).normalize();
  110. c1 = c1P.getEncoded(false);
  111. kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();
  112. kdf(digest, kPB, c2);
  113. }
  114. while (notEncrypted(c2, in, inOff));
  115. byte[] c3 = new byte[digest.getDigestSize()];
  116. addFieldElement(digest, kPB.getAffineXCoord());
  117. digest.update(in, inOff, inLen);
  118. addFieldElement(digest, kPB.getAffineYCoord());
  119. digest.doFinal(c3, 0);
  120. if (cipherMode == CIPHERMODE_NORM){
  121. return Arrays.concatenate(c1, c3, c2);
  122. }
  123. return Arrays.concatenate(c1, c2, c3);
  124. }
  125. /**
  126. * 解密实现,默认按标准排列方式解密,解密时解出c2部分原文并校验c3部分
  127. * @param in
  128. * @param inOff
  129. * @param inLen
  130. * @return
  131. * @throws InvalidCipherTextException
  132. */
  133. private byte[] decrypt(byte[] in, int inOff, int inLen)
  134. throws InvalidCipherTextException {
  135. byte[] c1 = new byte[curveLength * 2 + 1];
  136. System.arraycopy(in, inOff, c1, 0, c1.length);
  137. ECPoint c1P = ecParams.getCurve().decodePoint(c1);
  138. ECPoint s = c1P.multiply(ecParams.getH());
  139. if (s.isInfinity()) {
  140. throw new InvalidCipherTextException("[h]C1 at infinity");
  141. }
  142. c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();
  143. byte[] c2 = new byte[inLen - c1.length - digest.getDigestSize()];
  144. if (cipherMode == CIPHERMODE_BC) {
  145. System.arraycopy(in, inOff + c1.length, c2, 0, c2.length);
  146. }else{
  147. // C1 C3 C2
  148. System.arraycopy(in, inOff + c1.length + digest.getDigestSize(), c2, 0, c2.length);
  149. }
  150. kdf(digest, c1P, c2);
  151. byte[] c3 = new byte[digest.getDigestSize()];
  152. addFieldElement(digest, c1P.getAffineXCoord());
  153. digest.update(c2, 0, c2.length);
  154. addFieldElement(digest, c1P.getAffineYCoord());
  155. digest.doFinal(c3, 0);
  156. int check = 0;
  157. // 检查密文输入值C3部分和由摘要生成的C3是否一致
  158. if (cipherMode == CIPHERMODE_BC) {
  159. for (int i = 0; i != c3.length; i++) {
  160. check |= c3[i] ^ in[c1.length + c2.length + i];
  161. }
  162. }else{
  163. for (int i = 0; i != c3.length; i++) {
  164. check |= c3[i] ^ in[c1.length + i];
  165. }
  166. }
  167. clearBlock(c1);
  168. clearBlock(c3);
  169. if (check != 0) {
  170. clearBlock(c2);
  171. throw new InvalidCipherTextException("invalid cipher text");
  172. }
  173. return c2;
  174. }
  175. private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
  176. for (int i = 0; i != encData.length; i++) {
  177. if (encData[i] != in[inOff]) {
  178. return false;
  179. }
  180. }
  181. return true;
  182. }
  183. private void kdf(Digest digest, ECPoint c1, byte[] encData) {
  184. int ct = 1;
  185. int v = digest.getDigestSize();
  186. byte[] buf = new byte[digest.getDigestSize()];
  187. int off = 0;
  188. for (int i = 1; i <= ((encData.length + v - 1) / v); i++) {
  189. addFieldElement(digest, c1.getAffineXCoord());
  190. addFieldElement(digest, c1.getAffineYCoord());
  191. digest.update((byte) (ct >> 24));
  192. digest.update((byte) (ct >> 16));
  193. digest.update((byte) (ct >> 8));
  194. digest.update((byte) ct);
  195. digest.doFinal(buf, 0);
  196. if (off + buf.length < encData.length) {
  197. xor(encData, buf, off, buf.length);
  198. } else {
  199. xor(encData, buf, off, encData.length - off);
  200. }
  201. off += buf.length;
  202. ct++;
  203. }
  204. }
  205. private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
  206. for (int i = 0; i != dRemaining; i++) {
  207. data[dOff + i] ^= kdfOut[i];
  208. }
  209. }
  210. private BigInteger nextK() {
  211. int qBitLength = ecParams.getN().bitLength();
  212. BigInteger k;
  213. do {
  214. k = new BigInteger(qBitLength, random);
  215. }
  216. while (k.equals(ECConstants.ZERO) || k.compareTo(ecParams.getN()) >= 0);
  217. return k;
  218. }
  219. private void addFieldElement(Digest digest, ECFieldElement v) {
  220. byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());
  221. digest.update(p, 0, p.length);
  222. }
  223. /**
  224. * clear possible sensitive data
  225. */
  226. private void clearBlock(
  227. byte[] block) {
  228. for (int i = 0; i != block.length; i++) {
  229. block[i] = 0;
  230. }
  231. }
  232. }
  1. package wst.st.site.tools.sm2;
  2. import java.math.BigInteger;
  3. import java.security.NoSuchAlgorithmException;
  4. import java.security.SecureRandom;
  5. import org.bouncycastle.asn1.gm.GMNamedCurves;
  6. import org.bouncycastle.asn1.x9.X9ECParameters;
  7. import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
  8. import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
  9. import org.bouncycastle.crypto.params.ECDomainParameters;
  10. import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
  11. import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
  12. import org.bouncycastle.crypto.params.ECPublicKeyParameters;
  13. import org.bouncycastle.crypto.params.ParametersWithRandom;
  14. import org.bouncycastle.math.ec.ECPoint;
  15. import org.bouncycastle.util.encoders.Hex;
  16. import org.slf4j.Logger;
  17. import org.slf4j.LoggerFactory;
  18. public class SM2Utils {
  19. private static Logger logger = LoggerFactory.getLogger(SM2Utils.class);
  20. /**
  21. * SM2加密算法
  22. * @param publicKey 公钥
  23. * @param data 待加密的数据
  24. * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
  25. */
  26. public static String encrypt(String publicKey, String data){
  27. // 按国密排序标准加密
  28. return encrypt(publicKey, data, SM2EngineExtend.CIPHERMODE_NORM);
  29. }
  30. /**
  31. * SM2加密算法
  32. * @param publicKey 公钥
  33. * @param data 待加密的数据
  34. * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
  35. * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
  36. */
  37. public static String encrypt(String publicKey, String data, int cipherMode){
  38. // 获取一条SM2曲线参数
  39. X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
  40. // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
  41. ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
  42. //提取公钥点
  43. ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
  44. // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
  45. ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
  46. SM2EngineExtend sm2Engine = new SM2EngineExtend();
  47. // 设置sm2为加密模式
  48. sm2Engine.init(true, cipherMode, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
  49. byte[] arrayOfBytes = null;
  50. try {
  51. byte[] in = data.getBytes();
  52. arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
  53. } catch (Exception e) {
  54. logger.error("SM2加密时出现异常:{}", e.getMessage(), e);
  55. }
  56. return Hex.toHexString(arrayOfBytes);
  57. }
  58. /**
  59. * 获取sm2密钥对
  60. * BC库使用的公钥=64个字节+1个字节(04标志位),BC库使用的私钥=32个字节
  61. * SM2秘钥的组成部分有 私钥D 、公钥X 、 公钥Y , 他们都可以用长度为64的16进制的HEX串表示,
  62. * <br/>SM2公钥并不是直接由X+Y表示 , 而是额外添加了一个头,当启用压缩时:公钥=有头+公钥X ,即省略了公钥Y的部分
  63. * @param compressed 是否压缩公钥(加密解密都使用BC库才能使用压缩)
  64. * @return
  65. */
  66. public static SM2KeyPair getSm2Keys(boolean compressed){
  67. //获取一条SM2曲线参数
  68. X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
  69. //构造domain参数
  70. ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
  71. //1.创建密钥生成器
  72. ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
  73. //2.初始化生成器,带上随机数
  74. try {
  75. keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
  76. } catch (NoSuchAlgorithmException e) {
  77. logger.error("生成公私钥对时出现异常:", e);
  78. }
  79. //3.生成密钥对
  80. AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
  81. ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters)asymmetricCipherKeyPair.getPublic();
  82. ECPoint ecPoint = publicKeyParameters.getQ();
  83. // 把公钥放入map中,默认压缩公钥
  84. // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
  85. String publicKey = Hex.toHexString(ecPoint.getEncoded(compressed));
  86. ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
  87. BigInteger intPrivateKey = privateKeyParameters.getD();
  88. // 把私钥放入map中
  89. String privateKey = intPrivateKey.toString(16);
  90. return new SM2KeyPair(publicKey, privateKey);
  91. }
  92. /**
  93. * SM2解密算法
  94. * @param privateKey 私钥
  95. * @param cipherData 密文数据
  96. * @return
  97. */
  98. public static String decrypt(String privateKey, String cipherData) {
  99. // // 按国密排序标准解密
  100. return decrypt(privateKey, cipherData, SM2EngineExtend.CIPHERMODE_NORM);
  101. }
  102. /**
  103. * SM2解密算法
  104. * @param privateKey 私钥
  105. * @param cipherData 密文数据
  106. * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
  107. * @return
  108. */
  109. public static String decrypt(String privateKey, String cipherData, int cipherMode) {
  110. // 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上
  111. if (!cipherData.startsWith("04")){
  112. cipherData = "04" + cipherData;
  113. }
  114. byte[] cipherDataByte = Hex.decode(cipherData);
  115. //获取一条SM2曲线参数
  116. X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
  117. //构造domain参数
  118. ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
  119. BigInteger privateKeyD = new BigInteger(privateKey, 16);
  120. ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
  121. SM2EngineExtend sm2Engine = new SM2EngineExtend();
  122. // 设置sm2为解密模式
  123. sm2Engine.init(false, cipherMode, privateKeyParameters);
  124. String result = "";
  125. try {
  126. byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
  127. return new String(arrayOfBytes);
  128. } catch (Exception e) {
  129. logger.error("SM2解密时出现异常:{}", e.getMessage(), e);
  130. }
  131. return result;
  132. }
  133. public static void main(String[] args) {
  134. //获取公私钥
  135. SM2KeyPair sm2Keys = SM2Utils.getSm2Keys(false);
  136. System.out.println("公钥 :" + sm2Keys.getPublicKey());
  137. System.out.println("私钥 :" + sm2Keys.getPrivateKey());
  138. //需要加密的数据
  139. String data = "9ol.0p;/?<>$^MJU&";
  140. //公钥加密,获取密文
  141. String encrypt = SM2Utils.encrypt(sm2Keys.getPublicKey(), data);
  142. System.out.println("密文 :" + encrypt);
  143. //私钥解密
  144. String decrypt = SM2Utils.decrypt(sm2Keys.getPrivateKey(), encrypt);
  145. System.out.println("解密数据 : " + decrypt);
  146. System.out.println("明文密文是否相同 :" + data.equals(decrypt));
  147. }
  148. }