├── .gitignore ├── .settings └── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── pzone │ │ └── crypto │ │ ├── Params.java │ │ ├── SM2.java │ │ ├── SM2KeyPair.java │ │ └── SM3.java └── resources │ ├── SM2公钥密码加密.pdf │ ├── SM2椭圆曲线推荐参数.pdf │ └── SM3密码杂凑算法.pdf └── test └── java └── org └── pzone └── crypto └── TestSM2.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.classpath 3 | /.project 4 | /.idea 5 | /*.iml 6 | -------------------------------------------------------------------------------- /.settings/.gitignore: -------------------------------------------------------------------------------- 1 | /org.eclipse.jdt.core.prefs 2 | /org.eclipse.m2e.core.prefs 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SM2Java 2 | 国密SM2,SM3 Java实现 3 | 4 | ### 实现了SM2中如下4部分 5 | 6 | 1.生成密钥对 7 | 8 | 2.签名与验签 9 | 10 | 3.密钥协商 11 | 12 | 4.公钥加解密 13 | 14 | 杂凑算法采用SM3 15 | 密钥派生算法参考国密办文档中的KDF实现 16 | 17 | 具体可查看resouces中三个文档 18 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.pzone.crypto 5 | SM2Java 6 | 0.0.1-SNAPSHOT 7 | SM2Java 8 | SM2Java 9 | 10 | 11 | 12 | 13 | org.bouncycastle 14 | bcprov-jdk15on 15 | 1.56 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/org/pzone/crypto/Params.java: -------------------------------------------------------------------------------- 1 | package org.pzone.crypto; 2 | 3 | import java.math.BigInteger; 4 | 5 | /** 6 | * 国密办文件中推荐的椭圆曲线相关参数 7 | * @author Potato 8 | * 9 | */ 10 | public class Params { 11 | private static BigInteger n = new BigInteger( 12 | "FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "7203DF6B" + "21C6052B" + "53BBF409" + "39D54123", 16); 13 | private static BigInteger p = new BigInteger( 14 | "FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFF", 16); 15 | private static BigInteger a = new BigInteger( 16 | "FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFC", 16); 17 | private static BigInteger b = new BigInteger( 18 | "28E9FA9E" + "9D9F5E34" + "4D5A9E4B" + "CF6509A7" + "F39789F5" + "15AB8F92" + "DDBCBD41" + "4D940E93", 16); 19 | private static BigInteger gx = new BigInteger( 20 | "32C4AE2C" + "1F198119" + "5F990446" + "6A39C994" + "8FE30BBF" + "F2660BE1" + "715A4589" + "334C74C7", 16); 21 | private static BigInteger gy = new BigInteger( 22 | "BC3736A2" + "F4F6779C" + "59BDCEE3" + "6B692153" + "D0A9877C" + "C62A4740" + "02DF32E5" + "2139F0A0", 16); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/pzone/crypto/SM2.java: -------------------------------------------------------------------------------- 1 | package org.pzone.crypto; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.ObjectInputStream; 9 | import java.io.ObjectOutputStream; 10 | import java.io.Serializable; 11 | import java.io.UnsupportedEncodingException; 12 | import java.math.BigInteger; 13 | import java.security.SecureRandom; 14 | import java.util.Arrays; 15 | 16 | import org.bouncycastle.crypto.params.ECDomainParameters; 17 | import org.bouncycastle.math.ec.ECCurve; 18 | import org.bouncycastle.math.ec.ECPoint; 19 | 20 | /** 21 | * SM2公钥加密算法实现 包括 -签名,验签 -密钥交换 -公钥加密,私钥解密 22 | * 23 | * @author Potato 24 | * 25 | */ 26 | public class SM2 { 27 | private static BigInteger n = new BigInteger( 28 | "FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "7203DF6B" + "21C6052B" + "53BBF409" + "39D54123", 16); 29 | private static BigInteger p = new BigInteger( 30 | "FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFF", 16); 31 | private static BigInteger a = new BigInteger( 32 | "FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFC", 16); 33 | private static BigInteger b = new BigInteger( 34 | "28E9FA9E" + "9D9F5E34" + "4D5A9E4B" + "CF6509A7" + "F39789F5" + "15AB8F92" + "DDBCBD41" + "4D940E93", 16); 35 | private static BigInteger gx = new BigInteger( 36 | "32C4AE2C" + "1F198119" + "5F990446" + "6A39C994" + "8FE30BBF" + "F2660BE1" + "715A4589" + "334C74C7", 16); 37 | private static BigInteger gy = new BigInteger( 38 | "BC3736A2" + "F4F6779C" + "59BDCEE3" + "6B692153" + "D0A9877C" + "C62A4740" + "02DF32E5" + "2139F0A0", 16); 39 | private static ECDomainParameters ecc_bc_spec; 40 | private static int w = (int) Math.ceil(n.bitLength() * 1.0 / 2) - 1; 41 | private static BigInteger _2w = new BigInteger("2").pow(w); 42 | private static final int DIGEST_LENGTH = 32; 43 | 44 | private static SecureRandom random = new SecureRandom(); 45 | private static ECCurve.Fp curve; 46 | private static ECPoint G; 47 | private boolean debug = false; 48 | 49 | public boolean isDebug() { 50 | return debug; 51 | } 52 | 53 | public void setDebug(boolean debug) { 54 | this.debug = debug; 55 | } 56 | 57 | /** 58 | * 以16进制打印字节数组 59 | * 60 | * @param b 61 | */ 62 | public static void printHexString(byte[] b) { 63 | for (int i = 0; i < b.length; i++) { 64 | String hex = Integer.toHexString(b[i] & 0xFF); 65 | if (hex.length() == 1) { 66 | hex = '0' + hex; 67 | } 68 | System.out.print(hex.toUpperCase()); 69 | } 70 | System.out.println(); 71 | } 72 | 73 | /** 74 | * 随机数生成器 75 | * 76 | * @param max 77 | * @return 78 | */ 79 | private static BigInteger random(BigInteger max) { 80 | 81 | BigInteger r = new BigInteger(256, random); 82 | // int count = 1; 83 | 84 | while (r.compareTo(max) >= 0) { 85 | r = new BigInteger(128, random); 86 | // count++; 87 | } 88 | 89 | // System.out.println("count: " + count); 90 | return r; 91 | } 92 | 93 | /** 94 | * 判断字节数组是否全0 95 | * 96 | * @param buffer 97 | * @return 98 | */ 99 | private boolean allZero(byte[] buffer) { 100 | for (int i = 0; i < buffer.length; i++) { 101 | if (buffer[i] != 0) 102 | return false; 103 | } 104 | return true; 105 | } 106 | 107 | /** 108 | * 公钥加密 109 | * 110 | * @param input 111 | * 加密原文 112 | * @param publicKey 113 | * 公钥 114 | * @return 115 | */ 116 | public byte[] encrypt(String input, ECPoint publicKey) { 117 | 118 | byte[] inputBuffer = input.getBytes(); 119 | if (debug) 120 | printHexString(inputBuffer); 121 | 122 | byte[] C1Buffer; 123 | ECPoint kpb; 124 | byte[] t; 125 | do { 126 | /* 1 产生随机数k,k属于[1, n-1] */ 127 | BigInteger k = random(n); 128 | if (debug) { 129 | System.out.print("k: "); 130 | printHexString(k.toByteArray()); 131 | } 132 | 133 | /* 2 计算椭圆曲线点C1 = [k]G = (x1, y1) */ 134 | ECPoint C1 = G.multiply(k); 135 | C1Buffer = C1.getEncoded(false); 136 | if (debug) { 137 | System.out.print("C1: "); 138 | printHexString(C1Buffer); 139 | } 140 | 141 | /* 142 | * 3 计算椭圆曲线点 S = [h]Pb 143 | */ 144 | BigInteger h = ecc_bc_spec.getH(); 145 | if (h != null) { 146 | ECPoint S = publicKey.multiply(h); 147 | if (S.isInfinity()) 148 | throw new IllegalStateException(); 149 | } 150 | 151 | /* 4 计算 [k]PB = (x2, y2) */ 152 | kpb = publicKey.multiply(k).normalize(); 153 | 154 | /* 5 计算 t = KDF(x2||y2, klen) */ 155 | byte[] kpbBytes = kpb.getEncoded(false); 156 | t = KDF(kpbBytes, inputBuffer.length); 157 | // DerivationFunction kdf = new KDF1BytesGenerator(new 158 | // ShortenedDigest(new SHA256Digest(), DIGEST_LENGTH)); 159 | // 160 | // t = new byte[inputBuffer.length]; 161 | // kdf.init(new ISO18033KDFParameters(kpbBytes)); 162 | // kdf.generateBytes(t, 0, t.length); 163 | } while (allZero(t)); 164 | 165 | /* 6 计算C2=M^t */ 166 | byte[] C2 = new byte[inputBuffer.length]; 167 | for (int i = 0; i < inputBuffer.length; i++) { 168 | C2[i] = (byte) (inputBuffer[i] ^ t[i]); 169 | } 170 | 171 | /* 7 计算C3 = Hash(x2 || M || y2) */ 172 | byte[] C3 = sm3hash(kpb.getXCoord().toBigInteger().toByteArray(), inputBuffer, 173 | kpb.getYCoord().toBigInteger().toByteArray()); 174 | 175 | /* 8 输出密文 C=C1 || C2 || C3 */ 176 | 177 | byte[] encryptResult = new byte[C1Buffer.length + C2.length + C3.length]; 178 | 179 | System.arraycopy(C1Buffer, 0, encryptResult, 0, C1Buffer.length); 180 | System.arraycopy(C2, 0, encryptResult, C1Buffer.length, C2.length); 181 | System.arraycopy(C3, 0, encryptResult, C1Buffer.length + C2.length, C3.length); 182 | 183 | if (debug) { 184 | System.out.print("密文: "); 185 | printHexString(encryptResult); 186 | } 187 | 188 | return encryptResult; 189 | } 190 | 191 | /** 192 | * 私钥解密 193 | * 194 | * @param encryptData 195 | * 密文数据字节数组 196 | * @param privateKey 197 | * 解密私钥 198 | * @return 199 | */ 200 | public String decrypt(byte[] encryptData, BigInteger privateKey) { 201 | 202 | if (debug) 203 | System.out.println("encryptData length: " + encryptData.length); 204 | 205 | byte[] C1Byte = new byte[65]; 206 | System.arraycopy(encryptData, 0, C1Byte, 0, C1Byte.length); 207 | 208 | ECPoint C1 = curve.decodePoint(C1Byte).normalize(); 209 | 210 | /* 211 | * 计算椭圆曲线点 S = [h]C1 是否为无穷点 212 | */ 213 | BigInteger h = ecc_bc_spec.getH(); 214 | if (h != null) { 215 | ECPoint S = C1.multiply(h); 216 | if (S.isInfinity()) 217 | throw new IllegalStateException(); 218 | } 219 | /* 计算[dB]C1 = (x2, y2) */ 220 | ECPoint dBC1 = C1.multiply(privateKey).normalize(); 221 | 222 | /* 计算t = KDF(x2 || y2, klen) */ 223 | byte[] dBC1Bytes = dBC1.getEncoded(false); 224 | int klen = encryptData.length - 65 - DIGEST_LENGTH; 225 | byte[] t = KDF(dBC1Bytes, klen); 226 | // DerivationFunction kdf = new KDF1BytesGenerator(new 227 | // ShortenedDigest(new SHA256Digest(), DIGEST_LENGTH)); 228 | // if (debug) 229 | // System.out.println("klen = " + klen); 230 | // kdf.init(new ISO18033KDFParameters(dBC1Bytes)); 231 | // kdf.generateBytes(t, 0, t.length); 232 | 233 | if (allZero(t)) { 234 | System.err.println("all zero"); 235 | throw new IllegalStateException(); 236 | } 237 | 238 | /* 5 计算M'=C2^t */ 239 | byte[] M = new byte[klen]; 240 | for (int i = 0; i < M.length; i++) { 241 | M[i] = (byte) (encryptData[C1Byte.length + i] ^ t[i]); 242 | } 243 | if (debug) 244 | printHexString(M); 245 | 246 | /* 6 计算 u = Hash(x2 || M' || y2) 判断 u == C3是否成立 */ 247 | byte[] C3 = new byte[DIGEST_LENGTH]; 248 | 249 | if (debug) 250 | try { 251 | System.out.println("M = " + new String(M, "UTF8")); 252 | } catch (UnsupportedEncodingException e1) { 253 | // TODO Auto-generated catch block 254 | e1.printStackTrace(); 255 | } 256 | 257 | System.arraycopy(encryptData, encryptData.length - DIGEST_LENGTH, C3, 0, DIGEST_LENGTH); 258 | byte[] u = sm3hash(dBC1.getXCoord().toBigInteger().toByteArray(), M, 259 | dBC1.getYCoord().toBigInteger().toByteArray()); 260 | if (Arrays.equals(u, C3)) { 261 | if (debug) 262 | System.out.println("解密成功"); 263 | try { 264 | return new String(M, "UTF8"); 265 | } catch (UnsupportedEncodingException e) { 266 | e.printStackTrace(); 267 | } 268 | return null; 269 | } else { 270 | if (debug) { 271 | System.out.print("u = "); 272 | printHexString(u); 273 | System.out.print("C3 = "); 274 | printHexString(C3); 275 | System.err.println("解密验证失败"); 276 | } 277 | return null; 278 | } 279 | 280 | } 281 | 282 | // /** 283 | // * SHA摘要 284 | // * @param x2 285 | // * @param M 286 | // * @param y2 287 | // * @return 288 | // */ 289 | // private byte[] calculateHash(BigInteger x2, byte[] M, BigInteger y2) { 290 | // ShortenedDigest digest = new ShortenedDigest(new SHA256Digest(), 291 | // DIGEST_LENGTH); 292 | // byte[] buf = x2.toByteArray(); 293 | // digest.update(buf, 0, buf.length); 294 | // digest.update(M, 0, M.length); 295 | // buf = y2.toByteArray(); 296 | // digest.update(buf, 0, buf.length); 297 | // 298 | // buf = new byte[DIGEST_LENGTH]; 299 | // digest.doFinal(buf, 0); 300 | // 301 | // return buf; 302 | // } 303 | 304 | /** 305 | * 判断是否在范围内 306 | * 307 | * @param param 308 | * @param min 309 | * @param max 310 | * @return 311 | */ 312 | private boolean between(BigInteger param, BigInteger min, BigInteger max) { 313 | if (param.compareTo(min) >= 0 && param.compareTo(max) < 0) { 314 | return true; 315 | } else { 316 | return false; 317 | } 318 | } 319 | 320 | /** 321 | * 判断生成的公钥是否合法 322 | * 323 | * @param publicKey 324 | * @return 325 | */ 326 | private boolean checkPublicKey(ECPoint publicKey) { 327 | 328 | if (!publicKey.isInfinity()) { 329 | 330 | BigInteger x = publicKey.getXCoord().toBigInteger(); 331 | BigInteger y = publicKey.getYCoord().toBigInteger(); 332 | 333 | if (between(x, new BigInteger("0"), p) && between(y, new BigInteger("0"), p)) { 334 | 335 | BigInteger xResult = x.pow(3).add(a.multiply(x)).add(b).mod(p); 336 | 337 | if (debug) 338 | System.out.println("xResult: " + xResult.toString()); 339 | 340 | BigInteger yResult = y.pow(2).mod(p); 341 | 342 | if (debug) 343 | System.out.println("yResult: " + yResult.toString()); 344 | 345 | if (yResult.equals(xResult) && publicKey.multiply(n).isInfinity()) { 346 | return true; 347 | } 348 | } 349 | } 350 | return false; 351 | } 352 | 353 | /** 354 | * 生成密钥对 355 | * 356 | * @return 357 | */ 358 | public SM2KeyPair generateKeyPair() { 359 | 360 | BigInteger d = random(n.subtract(new BigInteger("1"))); 361 | 362 | SM2KeyPair keyPair = new SM2KeyPair(G.multiply(d).normalize(), d); 363 | 364 | if (checkPublicKey(keyPair.getPublicKey())) { 365 | if (debug) 366 | System.out.println("generate key successfully"); 367 | return keyPair; 368 | } else { 369 | if (debug) 370 | System.err.println("generate key failed"); 371 | return null; 372 | } 373 | } 374 | 375 | public SM2() { 376 | curve = new ECCurve.Fp(p, // q 377 | a, // a 378 | b); // b 379 | G = curve.createPoint(gx, gy); 380 | ecc_bc_spec = new ECDomainParameters(curve, G, n); 381 | } 382 | 383 | public SM2(boolean debug) { 384 | this(); 385 | this.debug = debug; 386 | } 387 | 388 | /** 389 | * 导出公钥到本地 390 | * 391 | * @param publicKey 392 | * @param path 393 | */ 394 | public void exportPublicKey(ECPoint publicKey, String path) { 395 | File file = new File(path); 396 | try { 397 | if (!file.exists()) 398 | file.createNewFile(); 399 | byte buffer[] = publicKey.getEncoded(false); 400 | FileOutputStream fos = new FileOutputStream(file); 401 | fos.write(buffer); 402 | fos.close(); 403 | } catch (IOException e) { 404 | e.printStackTrace(); 405 | } 406 | } 407 | 408 | /** 409 | * 从本地导入公钥 410 | * 411 | * @param path 412 | * @return 413 | */ 414 | public ECPoint importPublicKey(String path) { 415 | File file = new File(path); 416 | try { 417 | if (!file.exists()) 418 | return null; 419 | FileInputStream fis = new FileInputStream(file); 420 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 421 | 422 | byte buffer[] = new byte[16]; 423 | int size; 424 | while ((size = fis.read(buffer)) != -1) { 425 | baos.write(buffer, 0, size); 426 | } 427 | fis.close(); 428 | return curve.decodePoint(baos.toByteArray()); 429 | } catch (IOException e) { 430 | e.printStackTrace(); 431 | } 432 | return null; 433 | } 434 | 435 | /** 436 | * 导出私钥到本地 437 | * 438 | * @param privateKey 439 | * @param path 440 | */ 441 | public void exportPrivateKey(BigInteger privateKey, String path) { 442 | File file = new File(path); 443 | try { 444 | if (!file.exists()) 445 | file.createNewFile(); 446 | ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); 447 | oos.writeObject(privateKey); 448 | oos.close(); 449 | } catch (IOException e) { 450 | e.printStackTrace(); 451 | } 452 | } 453 | 454 | /** 455 | * 从本地导入私钥 456 | * 457 | * @param path 458 | * @return 459 | */ 460 | public BigInteger importPrivateKey(String path) { 461 | File file = new File(path); 462 | try { 463 | if (!file.exists()) 464 | return null; 465 | FileInputStream fis = new FileInputStream(file); 466 | ObjectInputStream ois = new ObjectInputStream(fis); 467 | BigInteger res = (BigInteger) (ois.readObject()); 468 | ois.close(); 469 | fis.close(); 470 | return res; 471 | } catch (Exception e) { 472 | e.printStackTrace(); 473 | } 474 | return null; 475 | } 476 | 477 | /** 478 | * 字节数组拼接 479 | * 480 | * @param params 481 | * @return 482 | */ 483 | private static byte[] join(byte[]... params) { 484 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 485 | byte[] res = null; 486 | try { 487 | for (int i = 0; i < params.length; i++) { 488 | baos.write(params[i]); 489 | } 490 | res = baos.toByteArray(); 491 | } catch (IOException e) { 492 | e.printStackTrace(); 493 | } 494 | return res; 495 | } 496 | 497 | /** 498 | * sm3摘要 499 | * 500 | * @param params 501 | * @return 502 | */ 503 | private static byte[] sm3hash(byte[]... params) { 504 | byte[] res = null; 505 | try { 506 | res = SM3.hash(join(params)); 507 | } catch (IOException e) { 508 | // TODO Auto-generated catch block 509 | e.printStackTrace(); 510 | } 511 | return res; 512 | } 513 | 514 | /** 515 | * 取得用户标识字节数组 516 | * 517 | * @param IDA 518 | * @param aPublicKey 519 | * @return 520 | */ 521 | private static byte[] ZA(String IDA, ECPoint aPublicKey) { 522 | byte[] idaBytes = IDA.getBytes(); 523 | int entlenA = idaBytes.length * 8; 524 | byte[] ENTLA = new byte[] { (byte) (entlenA & 0xFF00), (byte) (entlenA & 0x00FF) }; 525 | byte[] ZA = sm3hash(ENTLA, idaBytes, a.toByteArray(), b.toByteArray(), gx.toByteArray(), gy.toByteArray(), 526 | aPublicKey.getXCoord().toBigInteger().toByteArray(), 527 | aPublicKey.getYCoord().toBigInteger().toByteArray()); 528 | return ZA; 529 | } 530 | 531 | /** 532 | * 签名 533 | * 534 | * @param M 535 | * 签名信息 536 | * @param IDA 537 | * 签名方唯一标识 538 | * @param keyPair 539 | * 签名方密钥对 540 | * @return 签名 541 | */ 542 | public Signature sign(String M, String IDA, SM2KeyPair keyPair) { 543 | byte[] ZA = ZA(IDA, keyPair.getPublicKey()); 544 | byte[] M_ = join(ZA, M.getBytes()); 545 | BigInteger e = new BigInteger(1, sm3hash(M_)); 546 | // BigInteger k = new BigInteger( 547 | // "6CB28D99 385C175C 94F94E93 4817663F C176D925 DD72B727 260DBAAE 548 | // 1FB2F96F".replace(" ", ""), 16); 549 | BigInteger k; 550 | BigInteger r; 551 | do { 552 | k = random(n); 553 | ECPoint p1 = G.multiply(k).normalize(); 554 | BigInteger x1 = p1.getXCoord().toBigInteger(); 555 | r = e.add(x1); 556 | r = r.mod(n); 557 | } while (r.equals(BigInteger.ZERO) || r.add(k).equals(n)); 558 | 559 | BigInteger s = ((keyPair.getPrivateKey().add(BigInteger.ONE).modInverse(n)) 560 | .multiply((k.subtract(r.multiply(keyPair.getPrivateKey()))).mod(n))).mod(n); 561 | 562 | return new Signature(r, s); 563 | } 564 | 565 | /** 566 | * 验签 567 | * 568 | * @param M 569 | * 签名信息 570 | * @param signature 571 | * 签名 572 | * @param IDA 573 | * 签名方唯一标识 574 | * @param aPublicKey 575 | * 签名方公钥 576 | * @return true or false 577 | */ 578 | public boolean verify(String M, Signature signature, String IDA, ECPoint aPublicKey) { 579 | if (!between(signature.r, BigInteger.ONE, n)) 580 | return false; 581 | if (!between(signature.s, BigInteger.ONE, n)) 582 | return false; 583 | 584 | byte[] M_ = join(ZA(IDA, aPublicKey), M.getBytes()); 585 | BigInteger e = new BigInteger(1, sm3hash(M_)); 586 | BigInteger t = signature.r.add(signature.s).mod(n); 587 | 588 | if (t.equals(BigInteger.ZERO)) 589 | return false; 590 | 591 | ECPoint p1 = G.multiply(signature.s).normalize(); 592 | ECPoint p2 = aPublicKey.multiply(t).normalize(); 593 | BigInteger x1 = p1.add(p2).normalize().getXCoord().toBigInteger(); 594 | BigInteger R = e.add(x1).mod(n); 595 | if (R.equals(signature.r)) 596 | return true; 597 | return false; 598 | } 599 | 600 | /** 601 | * 密钥派生函数 602 | * 603 | * @param Z 604 | * @param klen 605 | * 生成klen字节数长度的密钥 606 | * @return 607 | */ 608 | private static byte[] KDF(byte[] Z, int klen) { 609 | int ct = 1; 610 | int end = (int) Math.ceil(klen * 1.0 / 32); 611 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 612 | try { 613 | for (int i = 1; i < end; i++) { 614 | baos.write(sm3hash(Z, SM3.toByteArray(ct))); 615 | ct++; 616 | } 617 | byte[] last = sm3hash(Z, SM3.toByteArray(ct)); 618 | if (klen % 32 == 0) { 619 | baos.write(last); 620 | } else 621 | baos.write(last, 0, klen % 32); 622 | return baos.toByteArray(); 623 | } catch (Exception e) { 624 | e.printStackTrace(); 625 | } 626 | return null; 627 | } 628 | 629 | /** 630 | * 传输实体类 631 | * 632 | * @author Potato 633 | * 634 | */ 635 | private static class TransportEntity implements Serializable { 636 | final byte[] R; //R点 637 | final byte[] S; //验证S 638 | final byte[] Z; //用户标识 639 | final byte[] K; //公钥 640 | 641 | public TransportEntity(byte[] r, byte[] s,byte[] z,ECPoint pKey) { 642 | R = r; 643 | S = s; 644 | Z=z; 645 | K=pKey.getEncoded(false); 646 | } 647 | } 648 | 649 | /** 650 | * 密钥协商辅助类 651 | * 652 | * @author Potato 653 | * 654 | */ 655 | public static class KeyExchange { 656 | BigInteger rA; 657 | ECPoint RA; 658 | ECPoint V; 659 | byte[] Z; 660 | byte[] key; 661 | 662 | String ID; 663 | SM2KeyPair keyPair; 664 | 665 | public KeyExchange(String ID,SM2KeyPair keyPair) { 666 | this.ID=ID; 667 | this.keyPair = keyPair; 668 | this.Z=ZA(ID, keyPair.getPublicKey()); 669 | } 670 | 671 | /** 672 | * 密钥协商发起第一步 673 | * 674 | * @return 675 | */ 676 | public TransportEntity keyExchange_1() { 677 | rA = random(n); 678 | // rA=new BigInteger("83A2C9C8 B96E5AF7 0BD480B4 72409A9A 327257F1 679 | // EBB73F5B 073354B2 48668563".replace(" ", ""),16); 680 | RA = G.multiply(rA).normalize(); 681 | return new TransportEntity(RA.getEncoded(false), null,Z,keyPair.getPublicKey()); 682 | } 683 | 684 | /** 685 | * 密钥协商响应方 686 | * 687 | * @param entity 传输实体 688 | * @return 689 | */ 690 | public TransportEntity keyExchange_2(TransportEntity entity) { 691 | BigInteger rB = random(n); 692 | // BigInteger rB=new BigInteger("33FE2194 0342161C 55619C4A 0C060293 693 | // D543C80A F19748CE 176D8347 7DE71C80".replace(" ", ""),16); 694 | ECPoint RB = G.multiply(rB).normalize(); 695 | 696 | this.rA=rB; 697 | this.RA=RB; 698 | 699 | BigInteger x2 = RB.getXCoord().toBigInteger(); 700 | x2 = _2w.add(x2.and(_2w.subtract(BigInteger.ONE))); 701 | 702 | BigInteger tB = keyPair.getPrivateKey().add(x2.multiply(rB)).mod(n); 703 | ECPoint RA = curve.decodePoint(entity.R).normalize(); 704 | 705 | BigInteger x1 = RA.getXCoord().toBigInteger(); 706 | x1 = _2w.add(x1.and(_2w.subtract(BigInteger.ONE))); 707 | 708 | ECPoint aPublicKey=curve.decodePoint(entity.K).normalize(); 709 | ECPoint temp = aPublicKey.add(RA.multiply(x1).normalize()).normalize(); 710 | ECPoint V = temp.multiply(ecc_bc_spec.getH().multiply(tB)).normalize(); 711 | if (V.isInfinity()) 712 | throw new IllegalStateException(); 713 | this.V=V; 714 | 715 | byte[] xV = V.getXCoord().toBigInteger().toByteArray(); 716 | byte[] yV = V.getYCoord().toBigInteger().toByteArray(); 717 | byte[] KB = KDF(join(xV, yV, entity.Z, this.Z), 16); 718 | key = KB; 719 | System.out.print("协商得B密钥:"); 720 | printHexString(KB); 721 | byte[] sB = sm3hash(new byte[] { 0x02 }, yV, 722 | sm3hash(xV, entity.Z, this.Z, RA.getXCoord().toBigInteger().toByteArray(), 723 | RA.getYCoord().toBigInteger().toByteArray(), RB.getXCoord().toBigInteger().toByteArray(), 724 | RB.getYCoord().toBigInteger().toByteArray())); 725 | return new TransportEntity(RB.getEncoded(false), sB,this.Z,keyPair.getPublicKey()); 726 | } 727 | 728 | /** 729 | * 密钥协商发起方第二步 730 | * 731 | * @param entity 传输实体 732 | */ 733 | public TransportEntity keyExchange_3(TransportEntity entity) { 734 | BigInteger x1 = RA.getXCoord().toBigInteger(); 735 | x1 = _2w.add(x1.and(_2w.subtract(BigInteger.ONE))); 736 | 737 | BigInteger tA = keyPair.getPrivateKey().add(x1.multiply(rA)).mod(n); 738 | ECPoint RB = curve.decodePoint(entity.R).normalize(); 739 | 740 | BigInteger x2 = RB.getXCoord().toBigInteger(); 741 | x2 = _2w.add(x2.and(_2w.subtract(BigInteger.ONE))); 742 | 743 | ECPoint bPublicKey=curve.decodePoint(entity.K).normalize(); 744 | ECPoint temp = bPublicKey.add(RB.multiply(x2).normalize()).normalize(); 745 | ECPoint U = temp.multiply(ecc_bc_spec.getH().multiply(tA)).normalize(); 746 | if (U.isInfinity()) 747 | throw new IllegalStateException(); 748 | this.V=U; 749 | 750 | byte[] xU = U.getXCoord().toBigInteger().toByteArray(); 751 | byte[] yU = U.getYCoord().toBigInteger().toByteArray(); 752 | byte[] KA = KDF(join(xU, yU, 753 | this.Z, entity.Z), 16); 754 | key = KA; 755 | System.out.print("协商得A密钥:"); 756 | printHexString(KA); 757 | byte[] s1= sm3hash(new byte[] { 0x02 }, yU, 758 | sm3hash(xU, this.Z, entity.Z, RA.getXCoord().toBigInteger().toByteArray(), 759 | RA.getYCoord().toBigInteger().toByteArray(), RB.getXCoord().toBigInteger().toByteArray(), 760 | RB.getYCoord().toBigInteger().toByteArray())); 761 | if(Arrays.equals(entity.S, s1)) 762 | System.out.println("B->A 密钥确认成功"); 763 | else 764 | System.out.println("B->A 密钥确认失败"); 765 | byte[] sA= sm3hash(new byte[] { 0x03 }, yU, 766 | sm3hash(xU, this.Z, entity.Z, RA.getXCoord().toBigInteger().toByteArray(), 767 | RA.getYCoord().toBigInteger().toByteArray(), RB.getXCoord().toBigInteger().toByteArray(), 768 | RB.getYCoord().toBigInteger().toByteArray())); 769 | 770 | return new TransportEntity(RA.getEncoded(false), sA,this.Z,keyPair.getPublicKey()); 771 | } 772 | 773 | /** 774 | * 密钥确认最后一步 775 | * 776 | * @param entity 传输实体 777 | */ 778 | public void keyExchange_4(TransportEntity entity) { 779 | byte[] xV = V.getXCoord().toBigInteger().toByteArray(); 780 | byte[] yV = V.getYCoord().toBigInteger().toByteArray(); 781 | ECPoint RA = curve.decodePoint(entity.R).normalize(); 782 | byte[] s2= sm3hash(new byte[] { 0x03 }, yV, 783 | sm3hash(xV, entity.Z, this.Z, RA.getXCoord().toBigInteger().toByteArray(), 784 | RA.getYCoord().toBigInteger().toByteArray(), this.RA.getXCoord().toBigInteger().toByteArray(), 785 | this.RA.getYCoord().toBigInteger().toByteArray())); 786 | if(Arrays.equals(entity.S, s2)) 787 | System.out.println("A->B 密钥确认成功"); 788 | else 789 | System.out.println("A->B 密钥确认失败"); 790 | } 791 | } 792 | 793 | public static void main(String[] args) throws UnsupportedEncodingException { 794 | 795 | SM2 sm02 = new SM2(); 796 | // BigInteger px = new BigInteger( 797 | // "0AE4C779 8AA0F119 471BEE11 825BE462 02BB79E2 A5844495 E97C04FF 798 | // 4DF2548A".replace(" ", ""), 16); 799 | // BigInteger py = new BigInteger( 800 | // "7C0240F8 8F1CD4E1 6352A73C 17B7F16F 07353E53 A176D684 A9FE0C6B 801 | // B798E857".replace(" ", ""), 16); 802 | // ECPoint publicKey = sm02.curve.createPoint(px, py); 803 | // BigInteger privateKey = new BigInteger( 804 | // "128B2FA8 BD433C6C 068C8D80 3DFF7979 2A519A55 171B1B65 0C23661D 805 | // 15897263".replace(" ", ""), 16); 806 | 807 | // SM2KeyPair keyPair = sm02.generateKeyPair(); 808 | // ECPoint publicKey=keyPair.getPublicKey(); 809 | // BigInteger privateKey=keyPair.getPrivateKey(); 810 | // sm02.exportPublicKey(publicKey, "E:/publickey.pem"); 811 | // sm02.exportPrivateKey(privateKey, "E:/privatekey.pem"); 812 | 813 | System.out.println("-----------------公钥加密与解密-----------------"); 814 | ECPoint publicKey = sm02.importPublicKey("E:/publickey.pem"); 815 | BigInteger privateKey = sm02.importPrivateKey("E:/privatekey.pem"); 816 | byte[] data = sm02.encrypt("测试加密aaaaaaaaaaa123aabb", publicKey); 817 | System.out.print("密文:"); 818 | SM2.printHexString(data); 819 | System.out.println("解密后明文:" + sm02.decrypt(data, privateKey)); 820 | 821 | System.out.println("-----------------签名与验签-----------------"); 822 | String IDA = "Heartbeats"; 823 | String M = "要签名的信息"; 824 | Signature signature = sm02.sign(M, IDA, new SM2KeyPair(publicKey, privateKey)); 825 | System.out.println("用户标识:" + IDA); 826 | System.out.println("签名信息:" + M); 827 | System.out.println("数字签名:" + signature); 828 | System.out.println("验证签名:" + sm02.verify(M, signature, IDA, publicKey)); 829 | 830 | System.out.println("-----------------密钥协商-----------------"); 831 | String aID = "AAAAAAAAAAAAA"; 832 | SM2KeyPair aKeyPair = sm02.generateKeyPair(); 833 | KeyExchange aKeyExchange = new KeyExchange(aID,aKeyPair); 834 | 835 | String bID = "BBBBBBBBBBBBB"; 836 | SM2KeyPair bKeyPair = sm02.generateKeyPair(); 837 | KeyExchange bKeyExchange = new KeyExchange(bID,bKeyPair); 838 | TransportEntity entity1 = aKeyExchange.keyExchange_1(); 839 | TransportEntity entity2 = bKeyExchange.keyExchange_2(entity1); 840 | TransportEntity entity3 = aKeyExchange.keyExchange_3(entity2); 841 | bKeyExchange.keyExchange_4(entity3); 842 | } 843 | 844 | public static class Signature { 845 | BigInteger r; 846 | BigInteger s; 847 | 848 | public Signature(BigInteger r, BigInteger s) { 849 | this.r = r; 850 | this.s = s; 851 | } 852 | 853 | public String toString() { 854 | return r.toString(16) + "," + s.toString(16); 855 | } 856 | } 857 | } 858 | -------------------------------------------------------------------------------- /src/main/java/org/pzone/crypto/SM2KeyPair.java: -------------------------------------------------------------------------------- 1 | package org.pzone.crypto; 2 | 3 | import java.math.BigInteger; 4 | 5 | import org.bouncycastle.math.ec.ECPoint; 6 | 7 | /** 8 | * SM2密钥对Bean 9 | * @author Potato 10 | * 11 | */ 12 | public class SM2KeyPair { 13 | 14 | private final ECPoint publicKey; 15 | private final BigInteger privateKey; 16 | 17 | SM2KeyPair(ECPoint publicKey, BigInteger privateKey) { 18 | this.publicKey = publicKey; 19 | this.privateKey = privateKey; 20 | } 21 | 22 | public ECPoint getPublicKey() { 23 | return publicKey; 24 | } 25 | 26 | public BigInteger getPrivateKey() { 27 | return privateKey; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/pzone/crypto/SM3.java: -------------------------------------------------------------------------------- 1 | package org.pzone.crypto; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.math.BigInteger; 6 | import java.util.Arrays; 7 | 8 | /** 9 | * SM3杂凑算法实现 10 | * @author Potato 11 | * 12 | */ 13 | public class SM3 { 14 | 15 | private static char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', 16 | '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 17 | private static final String ivHexStr = "7380166f 4914b2b9 172442d7 da8a0600 a96f30bc 163138aa e38dee4d b0fb0e4e"; 18 | private static final BigInteger IV = new BigInteger(ivHexStr.replaceAll(" ", 19 | ""), 16); 20 | private static final Integer Tj15 = Integer.valueOf("79cc4519", 16); 21 | private static final Integer Tj63 = Integer.valueOf("7a879d8a", 16); 22 | private static final byte[] FirstPadding = {(byte) 0x80}; 23 | private static final byte[] ZeroPadding = {(byte) 0x00}; 24 | 25 | private static int T(int j) { 26 | if (j >= 0 && j <= 15) { 27 | return Tj15.intValue(); 28 | } else if (j >= 16 && j <= 63) { 29 | return Tj63.intValue(); 30 | } else { 31 | throw new RuntimeException("data invalid"); 32 | } 33 | } 34 | 35 | private static Integer FF(Integer x, Integer y, Integer z, int j) { 36 | if (j >= 0 && j <= 15) { 37 | return Integer.valueOf(x.intValue() ^ y.intValue() ^ z.intValue()); 38 | } else if (j >= 16 && j <= 63) { 39 | return Integer.valueOf((x.intValue() & y.intValue()) 40 | | (x.intValue() & z.intValue()) 41 | | (y.intValue() & z.intValue())); 42 | } else { 43 | throw new RuntimeException("data invalid"); 44 | } 45 | } 46 | 47 | private static Integer GG(Integer x, Integer y, Integer z, int j) { 48 | if (j >= 0 && j <= 15) { 49 | return Integer.valueOf(x.intValue() ^ y.intValue() ^ z.intValue()); 50 | } else if (j >= 16 && j <= 63) { 51 | return Integer.valueOf((x.intValue() & y.intValue()) 52 | | (~x.intValue() & z.intValue())); 53 | } else { 54 | throw new RuntimeException("data invalid"); 55 | } 56 | } 57 | 58 | private static Integer P0(Integer x) { 59 | return Integer.valueOf(x.intValue() 60 | ^ Integer.rotateLeft(x.intValue(), 9) 61 | ^ Integer.rotateLeft(x.intValue(), 17)); 62 | } 63 | 64 | private static Integer P1(Integer x) { 65 | return Integer.valueOf(x.intValue() 66 | ^ Integer.rotateLeft(x.intValue(), 15) 67 | ^ Integer.rotateLeft(x.intValue(), 23)); 68 | } 69 | 70 | private static byte[] padding(byte[] source) throws IOException { 71 | if (source.length >= 0x2000000000000000l) { 72 | throw new RuntimeException("src data invalid."); 73 | } 74 | long l = source.length * 8; 75 | long k = 448 - (l + 1) % 512; 76 | if (k < 0) { 77 | k = k + 512; 78 | } 79 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 80 | baos.write(source); 81 | baos.write(FirstPadding); 82 | long i = k - 7; 83 | while (i > 0) { 84 | baos.write(ZeroPadding); 85 | i -= 8; 86 | } 87 | baos.write(long2bytes(l)); 88 | return baos.toByteArray(); 89 | } 90 | 91 | private static byte[] long2bytes(long l) { 92 | byte[] bytes = new byte[8]; 93 | for (int i = 0; i < 8; i++) { 94 | bytes[i] = (byte) (l >>> ((7 - i) * 8)); 95 | } 96 | return bytes; 97 | } 98 | 99 | public static byte[] hash(byte[] source) throws IOException { 100 | byte[] m1 = padding(source); 101 | int n = m1.length / (512 / 8); 102 | byte[] b; 103 | byte[] vi = IV.toByteArray(); 104 | byte[] vi1 = null; 105 | for (int i = 0; i < n; i++) { 106 | b = Arrays.copyOfRange(m1, i * 64, (i + 1) * 64); 107 | vi1 = CF(vi, b); 108 | vi = vi1; 109 | } 110 | return vi1; 111 | } 112 | 113 | private static byte[] CF(byte[] vi, byte[] bi) throws IOException { 114 | int a, b, c, d, e, f, g, h; 115 | a = toInteger(vi, 0); 116 | b = toInteger(vi, 1); 117 | c = toInteger(vi, 2); 118 | d = toInteger(vi, 3); 119 | e = toInteger(vi, 4); 120 | f = toInteger(vi, 5); 121 | g = toInteger(vi, 6); 122 | h = toInteger(vi, 7); 123 | 124 | int[] w = new int[68]; 125 | int[] w1 = new int[64]; 126 | for (int i = 0; i < 16; i++) { 127 | w[i] = toInteger(bi, i); 128 | } 129 | for (int j = 16; j < 68; j++) { 130 | w[j] = P1(w[j - 16] ^ w[j - 9] ^ Integer.rotateLeft(w[j - 3], 15)) 131 | ^ Integer.rotateLeft(w[j - 13], 7) ^ w[j - 6]; 132 | } 133 | for (int j = 0; j < 64; j++) { 134 | w1[j] = w[j] ^ w[j + 4]; 135 | } 136 | int ss1, ss2, tt1, tt2; 137 | for (int j = 0; j < 64; j++) { 138 | ss1 = Integer 139 | .rotateLeft( 140 | Integer.rotateLeft(a, 12) + e 141 | + Integer.rotateLeft(T(j), j), 7); 142 | ss2 = ss1 ^ Integer.rotateLeft(a, 12); 143 | tt1 = FF(a, b, c, j) + d + ss2 + w1[j]; 144 | tt2 = GG(e, f, g, j) + h + ss1 + w[j]; 145 | d = c; 146 | c = Integer.rotateLeft(b, 9); 147 | b = a; 148 | a = tt1; 149 | h = g; 150 | g = Integer.rotateLeft(f, 19); 151 | f = e; 152 | e = P0(tt2); 153 | } 154 | byte[] v = toByteArray(a, b, c, d, e, f, g, h); 155 | for (int i = 0; i < v.length; i++) { 156 | v[i] = (byte) (v[i] ^ vi[i]); 157 | } 158 | return v; 159 | } 160 | 161 | private static int toInteger(byte[] source, int index) { 162 | StringBuilder valueStr = new StringBuilder(""); 163 | for (int i = 0; i < 4; i++) { 164 | valueStr.append(hexDigits[(byte) ((source[index * 4 + i] & 0xF0) >> 4)]); 165 | valueStr.append(hexDigits[(byte) (source[index * 4 + i] & 0x0F)]); 166 | } 167 | return Long.valueOf(valueStr.toString(), 16).intValue(); 168 | 169 | } 170 | 171 | private static byte[] toByteArray(int a, int b, int c, int d, int e, int f, 172 | int g, int h) throws IOException { 173 | ByteArrayOutputStream baos = new ByteArrayOutputStream(32); 174 | baos.write(toByteArray(a)); 175 | baos.write(toByteArray(b)); 176 | baos.write(toByteArray(c)); 177 | baos.write(toByteArray(d)); 178 | baos.write(toByteArray(e)); 179 | baos.write(toByteArray(f)); 180 | baos.write(toByteArray(g)); 181 | baos.write(toByteArray(h)); 182 | return baos.toByteArray(); 183 | } 184 | 185 | public static byte[] toByteArray(int i) { 186 | byte[] byteArray = new byte[4]; 187 | byteArray[0] = (byte) (i >>> 24); 188 | byteArray[1] = (byte) ((i & 0xFFFFFF) >>> 16); 189 | byteArray[2] = (byte) ((i & 0xFFFF) >>> 8); 190 | byteArray[3] = (byte) (i & 0xFF); 191 | return byteArray; 192 | } 193 | private static String byteToHexString(byte b) { 194 | int n = b; 195 | if (n < 0) 196 | n = 256 + n; 197 | int d1 = n / 16; 198 | int d2 = n % 16; 199 | return ""+hexDigits[d1] + hexDigits[d2]; 200 | } 201 | 202 | public static String byteArrayToHexString(byte[] b) { 203 | StringBuffer resultSb = new StringBuffer(); 204 | for (int i = 0; i < b.length; i++) { 205 | resultSb.append(byteToHexString(b[i])); 206 | } 207 | return resultSb.toString(); 208 | } 209 | 210 | public static void main(String[] args) throws IOException { 211 | System.out.println(SM3.byteArrayToHexString(SM3.hash("test sm3 hash".getBytes()))); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/main/resources/SM2公钥密码加密.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopezLotado/SM2Java/5b04524990396d921e3d0e46239d0a91e3689bd0/src/main/resources/SM2公钥密码加密.pdf -------------------------------------------------------------------------------- /src/main/resources/SM2椭圆曲线推荐参数.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopezLotado/SM2Java/5b04524990396d921e3d0e46239d0a91e3689bd0/src/main/resources/SM2椭圆曲线推荐参数.pdf -------------------------------------------------------------------------------- /src/main/resources/SM3密码杂凑算法.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopezLotado/SM2Java/5b04524990396d921e3d0e46239d0a91e3689bd0/src/main/resources/SM3密码杂凑算法.pdf -------------------------------------------------------------------------------- /src/test/java/org/pzone/crypto/TestSM2.java: -------------------------------------------------------------------------------- 1 | package org.pzone.crypto; 2 | 3 | import org.bouncycastle.math.ec.ECPoint; 4 | 5 | import java.math.BigInteger; 6 | 7 | public class TestSM2 { 8 | 9 | public static void main(String[] args) { 10 | SM2 x = new SM2(); 11 | SM2KeyPair keys = x.generateKeyPair(); 12 | ECPoint pubKey = keys.getPublicKey(); 13 | BigInteger privKey = keys.getPrivateKey(); 14 | byte[] data = x.encrypt("Hello World", pubKey); 15 | System.out.println("encrypt: " + data); 16 | String origin = x.decrypt(data, privKey); 17 | System.out.println("decrypt: " + origin); 18 | } 19 | } 20 | --------------------------------------------------------------------------------