= data.length) {
348 | return false;
349 | }
350 | if ((byts[i]&0xff) != data[idx]) {
351 | return false;
352 | }
353 | }
354 | return true;
355 | }finally {
356 | idxO[0]=idx;
357 | }
358 | }
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 | /***
375 | * 将当前PEM中的密钥对复制出一个新的PEM对象
376 | * 。convertToPublic:等于true时含私钥的PEM将只返回公钥,仅含公钥的PEM不受影响
377 | */
378 | public RSA_PEM CopyToNew(boolean convertToPublic) {
379 | if (convertToPublic) {
380 | return new RSA_PEM(Key_Modulus, Key_Exponent, null, null, null, null, null, null);
381 | }
382 | return new RSA_PEM(Key_Modulus, Key_Exponent, Key_D, Val_P, Val_Q, Val_DP, Val_DQ, Val_InverseQ);
383 | }
384 | /***
385 | * 【不安全、不建议使用】对调交换公钥指数(Key_Exponent)和私钥指数(Key_D):把公钥当私钥使用(new.Key_D=this.Key_Exponent)、私钥当公钥使用(new.Key_Exponent=this.Key_D),返回一个新PEM对象;比如用于:私钥加密、公钥解密,这是非常规的用法
386 | * 。当前对象必须含私钥,否则无法交换会直接抛异常
387 | * 。注意:把公钥当私钥使用是非常不安全的,因为绝大部分生成的密钥的公钥指数为 0x10001(AQAB),太容易被猜测到,无法作为真正意义上的私钥
388 | */
389 | public RSA_PEM SwapKey_Exponent_D__Unsafe() throws Exception {
390 | if(Key_D==null) throw new Exception(T("SwapKey只支持私钥", "SwapKey only supports private keys"));
391 | return new RSA_PEM(Key_Modulus, Key_D, Key_Exponent);
392 | }
393 |
394 |
395 |
396 |
397 | /***
398 | * 将RSA中的密钥对转换成PEM PKCS#8格式
399 | * 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
400 | * 。公钥如:-----BEGIN RSA PUBLIC KEY-----,私钥如:-----BEGIN RSA PRIVATE KEY-----
401 | * 。似乎导出PKCS#1公钥用的比较少,PKCS#8的公钥用的多些,私钥#1#8都差不多
402 | */
403 | public String ToPEM_PKCS1(boolean convertToPublic) throws Exception {
404 | return ToPEM(convertToPublic, false, false);
405 | }
406 | /***
407 | * 将RSA中的密钥对转换成PEM PKCS#8格式
408 | * 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
409 | * 。公钥如:-----BEGIN PUBLIC KEY-----,私钥如:-----BEGIN PRIVATE KEY-----
410 | */
411 | public String ToPEM_PKCS8(boolean convertToPublic) throws Exception {
412 | return ToPEM(convertToPublic, true, true);
413 | }
414 | /***
415 | * 将RSA中的密钥对转换成PEM格式
416 | * 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
417 | * 。privateUsePKCS8:私钥的返回格式,等于true时返回PKCS#8格式(-----BEGIN PRIVATE KEY-----),否则返回PKCS#1格式(-----BEGIN RSA PRIVATE KEY-----),返回公钥时此参数无效;两种格式使用都比较常见
418 | * 。publicUsePKCS8:公钥的返回格式,等于true时返回PKCS#8格式(-----BEGIN PUBLIC KEY-----),否则返回PKCS#1格式(-----BEGIN RSA PUBLIC KEY-----),返回私钥时此参数无效;一般用的多的是true PKCS#8格式公钥,PKCS#1格式公钥似乎比较少见
419 | */
420 | public String ToPEM(boolean convertToPublic, boolean privateUsePKCS8, boolean publicUsePKCS8) throws Exception {
421 | byte[] der = ToDER(convertToPublic, privateUsePKCS8, publicUsePKCS8);
422 | if (this.Key_D==null || convertToPublic) {
423 | String flag = " PUBLIC KEY";
424 | if (!publicUsePKCS8) {
425 | flag = " RSA" + flag;
426 | }
427 | return "-----BEGIN" + flag + "-----\n" + TextBreak(Base64.getEncoder().encodeToString(der), 64) + "\n-----END" + flag + "-----";
428 | } else {
429 | String flag = " PRIVATE KEY";
430 | if (!privateUsePKCS8) {
431 | flag = " RSA" + flag;
432 | }
433 | return "-----BEGIN" + flag + "-----\n" + TextBreak(Base64.getEncoder().encodeToString(der), 64) + "\n-----END" + flag + "-----";
434 | }
435 | }
436 | /***
437 | * 将RSA中的密钥对转换成DER格式,DER格式为PEM中的Base64文本编码前的二进制数据
438 | * 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
439 | * 。privateUsePKCS8:私钥的返回格式,等于true时返回PKCS#8格式,否则返回PKCS#1格式,返回公钥时此参数无效;两种格式使用都比较常见
440 | * 。publicUsePKCS8:公钥的返回格式,等于true时返回PKCS#8格式,否则返回PKCS#1格式,返回私钥时此参数无效;一般用的多的是true PKCS#8格式公钥,PKCS#1格式似乎比较少见公钥
441 | */
442 | public byte[] ToDER(boolean convertToPublic, boolean privateUsePKCS8, boolean publicUsePKCS8) throws Exception {
443 | //https://www.jianshu.com/p/25803dd9527d
444 | //https://www.cnblogs.com/ylz8401/p/8443819.html
445 | //https://blog.csdn.net/jiayanhui2877/article/details/47187077
446 | //https://blog.csdn.net/xuanshao_/article/details/51679824
447 | //https://blog.csdn.net/xuanshao_/article/details/51672547
448 |
449 | ByteArrayOutputStream ms = new ByteArrayOutputStream();
450 |
451 |
452 | if (this.Key_D==null || convertToPublic) {
453 | /****生成公钥****/
454 |
455 | //写入总字节数,不含本段长度,额外需要24字节的头,后续计算好填入
456 | ms.write(0x30);
457 | int index1 = ms.size();
458 |
459 | //PKCS8 多一段数据
460 | int index2 = -1, index3 = -1;
461 | if (publicUsePKCS8) {
462 | //固定内容
463 | // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
464 | ms.write(_SeqOID);
465 |
466 | //从0x00开始的后续长度
467 | ms.write(0x03);
468 | index2 = ms.size();
469 | ms.write(0x00);
470 |
471 | //后续内容长度
472 | ms.write(0x30);
473 | index3 = ms.size();
474 | }
475 |
476 | //写入Modulus
477 | writeBlock(Key_Modulus, ms);
478 |
479 | //写入Exponent
480 | writeBlock(Key_Exponent, ms);
481 |
482 |
483 | //计算空缺的长度
484 | byte[] byts = ms.toByteArray();
485 |
486 | if (index2 != -1) {
487 | byts = writeLen(index3, byts, ms);
488 | byts = writeLen(index2, byts, ms);
489 | }
490 | byts = writeLen(index1, byts, ms);
491 |
492 | return byts;
493 | } else {
494 | /****生成私钥****/
495 |
496 | //写入总字节数,后续写入
497 | ms.write(0x30);
498 | int index1 = ms.size();
499 |
500 | //写入版本号
501 | ms.write(_Ver);
502 |
503 | //PKCS8 多一段数据
504 | int index2 = -1, index3 = -1;
505 | if (privateUsePKCS8) {
506 | //固定内容
507 | ms.write(_SeqOID);
508 |
509 | //后续内容长度
510 | ms.write(0x04);
511 | index2 = ms.size();
512 |
513 | //后续内容长度
514 | ms.write(0x30);
515 | index3 = ms.size();
516 |
517 | //写入版本号
518 | ms.write(_Ver);
519 | }
520 |
521 | //写入数据
522 | writeBlock(Key_Modulus, ms);
523 | writeBlock(Key_Exponent, ms);
524 | writeBlock(Key_D, ms);
525 | writeBlock(Val_P, ms);
526 | writeBlock(Val_Q, ms);
527 | writeBlock(Val_DP, ms);
528 | writeBlock(Val_DQ, ms);
529 | writeBlock(Val_InverseQ, ms);
530 |
531 |
532 | //计算空缺的长度
533 | byte[] byts = ms.toByteArray();
534 |
535 | if (index2 != -1) {
536 | byts = writeLen(index3, byts, ms);
537 | byts = writeLen(index2, byts, ms);
538 | }
539 | byts = writeLen(index1, byts, ms);
540 |
541 | return byts;
542 | }
543 | }
544 | /**写入一个长度字节码**/
545 | static private void writeLenByte(int len, ByteArrayOutputStream ms) {
546 | if (len < 0x80) {
547 | ms.write((byte)len);
548 | } else if (len <= 0xff) {
549 | ms.write(0x81);
550 | ms.write((byte)len);
551 | } else {
552 | ms.write(0x82);
553 | ms.write((byte)(len >> 8 & 0xff));
554 | ms.write((byte)(len & 0xff));
555 | }
556 | }
557 | /**写入一块数据**/
558 | static private void writeBlock(byte[] byts, ByteArrayOutputStream ms) throws Exception {
559 | boolean addZero = ((byts[0] & 0xff) >> 4) >= 0x8;
560 | ms.write(0x02);
561 | int len = byts.length + (addZero ? 1 : 0);
562 | writeLenByte(len, ms);
563 |
564 | if (addZero) {
565 | ms.write(0x00);
566 | }
567 | ms.write(byts);
568 | }
569 | /**根据后续内容长度写入长度数据**/
570 | static private byte[] writeLen(int index, byte[] byts, ByteArrayOutputStream ms) {
571 | int len = byts.length - index;
572 |
573 | ms.reset();
574 | ms.write(byts, 0, index);
575 | writeLenByte(len, ms);
576 | ms.write(byts, index, len);
577 |
578 | return ms.toByteArray();
579 | }
580 | /**把字符串按每行多少个字断行**/
581 | static private String TextBreak(String text, int line) {
582 | int idx = 0;
583 | int len = text.length();
584 | StringBuilder str = new StringBuilder();
585 | while (idx < len) {
586 | if (idx > 0) {
587 | str.append('\n');
588 | }
589 | if (idx + line >= len) {
590 | str.append(text.substring(idx));
591 | } else {
592 | str.append(text.substring(idx, idx+line));
593 | }
594 | idx += line;
595 | }
596 | return str.toString();
597 | }
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 | /***
620 | * 将XML格式密钥转成PEM,支持公钥xml、私钥xml
621 | */
622 | static public RSA_PEM FromXML(String xml) throws Exception {
623 | RSA_PEM rtv=new RSA_PEM();
624 |
625 | Matcher xmlM=xmlExp.matcher(xml);
626 | if(!xmlM.find()) {
627 | throw new Exception(T("XML内容不符合要求", "XML content does not meet requirements"));
628 | }
629 |
630 | Matcher tagM=xmlTagExp.matcher(xmlM.group(1));
631 | Base64.Decoder dec=Base64.getDecoder();
632 | while(tagM.find()) {
633 | String tag=tagM.group(1);
634 | String b64=tagM.group(2);
635 | byte[] val=dec.decode(b64);
636 | switch(tag) {
637 | case "Modulus":rtv.Key_Modulus=val;break;
638 | case "Exponent":rtv.Key_Exponent=val;break;
639 | case "D":rtv.Key_D=val;break;
640 |
641 | case "P":rtv.Val_P=val;break;
642 | case "Q":rtv.Val_Q=val;break;
643 | case "DP":rtv.Val_DP=val;break;
644 | case "DQ":rtv.Val_DQ=val;break;
645 | case "InverseQ":rtv.Val_InverseQ=val;break;
646 | }
647 | }
648 |
649 | if(rtv.Key_Modulus==null || rtv.Key_Exponent==null) {
650 | throw new Exception(T("XML公钥丢失", "Public key in XML is missing"));
651 | }
652 | if(rtv.Key_D!=null) {
653 | if(rtv.Val_P==null || rtv.Val_Q==null || rtv.Val_DP==null || rtv.Val_DQ==null || rtv.Val_InverseQ==null) {
654 | return new RSA_PEM(rtv.Key_Modulus, rtv.Key_Exponent, rtv.Key_D);
655 | }
656 | }
657 |
658 | return rtv;
659 | }
660 | static private Pattern xmlExp=Pattern.compile("\\s*([<>\\/\\+=\\w\\s]+)\\s*");
661 | static private Pattern xmlTagExp=Pattern.compile("<(.+?)>\\s*([^<]+?)\\s*");
662 |
663 |
664 |
665 |
666 |
667 |
668 | /***
669 | * 将RSA中的密钥对转换成XML格式
670 | * ,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
671 | */
672 | public String ToXML(boolean convertToPublic) {
673 | Base64.Encoder enc=Base64.getEncoder();
674 | StringBuilder str=new StringBuilder();
675 | str.append("");
676 | str.append(""+enc.encodeToString(Key_Modulus)+"");
677 | str.append(""+enc.encodeToString(Key_Exponent)+"");
678 | if (this.Key_D==null || convertToPublic) {
679 | /****生成公钥****/
680 | //NOOP
681 | } else {
682 | /****生成私钥****/
683 | str.append(""+enc.encodeToString(Val_P)+"
");
684 | str.append(""+enc.encodeToString(Val_Q)+"
");
685 | str.append(""+enc.encodeToString(Val_DP)+"");
686 | str.append(""+enc.encodeToString(Val_DQ)+"");
687 | str.append(""+enc.encodeToString(Val_InverseQ)+"");
688 | str.append(""+enc.encodeToString(Key_D)+"");
689 | }
690 | str.append("");
691 | return str.toString();
692 | }
693 |
694 |
695 |
696 | /***
697 | * 简版多语言支持,根据当前语言{@link #Lang()}返回中文或英文
698 | */
699 | static public String T(String zh, String en) {
700 | return "zh".equals(Lang()) ? zh : en;
701 | }
702 | static private String _lang;
703 | /**
704 | * 简版多语言支持,取值:zh(简体中文)、en(English-US),默认根据系统取值
705 | */
706 | static public String Lang() {
707 | if(_lang==null) {
708 | String locale=Locale.getDefault().toString().replace('_', '-').toLowerCase();
709 | if(Pattern.compile("\\b(zh|cn)\\b").matcher(locale).find()) {
710 | _lang = "zh";
711 | } else {
712 | _lang = "en";
713 | }
714 | }
715 | return _lang;
716 | }
717 | /**
718 | * 简版多语言支持,可设置值:zh(简体中文)、en(English-US)
719 | */
720 | static public void SetLang(String lang) {
721 | _lang=lang;
722 | }
723 |
724 | }
725 |
--------------------------------------------------------------------------------
/RSA_Util.java:
--------------------------------------------------------------------------------
1 | package com.github.xiangyuecn.rsajava;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.security.Key;
5 | import java.security.KeyFactory;
6 | import java.security.KeyPair;
7 | import java.security.KeyPairGenerator;
8 | import java.security.MessageDigest;
9 | import java.security.Provider;
10 | import java.security.SecureRandom;
11 | import java.security.Security;
12 | import java.security.Signature;
13 | import java.security.interfaces.RSAPrivateKey;
14 | import java.security.interfaces.RSAPublicKey;
15 | import java.security.spec.AlgorithmParameterSpec;
16 | import java.security.spec.MGF1ParameterSpec;
17 | import java.security.spec.PSSParameterSpec;
18 | import java.security.spec.RSAPrivateKeySpec;
19 | import java.util.Base64;
20 | import java.util.regex.Matcher;
21 | import java.util.regex.Pattern;
22 |
23 | import javax.crypto.Cipher;
24 | import javax.crypto.spec.OAEPParameterSpec;
25 | import javax.crypto.spec.PSource;
26 |
27 |
28 | /**
29 | * RSA操作封装
30 | *
31 | * GitHub:https://github.com/xiangyuecn/RSA-java
32 | */
33 | public class RSA_Util {
34 | /**
35 | * 导出XML格式密钥,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
36 | */
37 | public String ToXML(boolean convertToPublic) {
38 | return ToPEM(convertToPublic).ToXML(convertToPublic);
39 | }
40 | /**
41 | * 将密钥导出成PEM对象,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
42 | */
43 | public RSA_PEM ToPEM(boolean convertToPublic) {
44 | return new RSA_PEM(publicKey, convertToPublic?null:privateKey);
45 | }
46 | /***
47 | * 【不安全、不建议使用】对调交换公钥指数(Key_Exponent)和私钥指数(Key_D):把公钥当私钥使用(new.Key_D=this.Key_Exponent)、私钥当公钥使用(new.Key_Exponent=this.Key_D),返回一个新RSA对象;比如用于:私钥加密、公钥解密,这是非常规的用法。
48 | *
当前密钥如果是公钥,将不会发生对调,返回的新RSA将允许用公钥进行解密和签名操作。
49 | *
注意:把公钥当私钥使用是非常不安全的,因为绝大部分生成的密钥的公钥指数为 0x10001(AQAB),太容易被猜测到,无法作为真正意义上的私钥。
50 | *
部分私钥加密实现中,比如Java自带的RSA,使用非NoPadding填充方式时,用私钥对象进行加密可能会采用EMSA-PKCS1-v1_5填充方式(用私钥指数构造成公钥对象无此问题),因此在不同程序之间互通时,可能需要自行使用对应填充算法先对数据进行填充,然后再用NoPadding填充方式进行加密(解密也按NoPadding填充进行解密,然后去除填充数据)。
51 | */
52 | public RSA_Util SwapKey_Exponent_D__Unsafe() throws Exception {
53 | RSA_PEM pem=ToPEM(false);
54 | if(pem.Key_D==null) {
55 | RSA_Util rsa=new RSA_Util(pem);
56 | //公钥当成私钥使用
57 | RSAPrivateKeySpec spec=new RSAPrivateKeySpec(rsa.publicKey.getModulus(), rsa.publicKey.getPublicExponent());
58 | KeyFactory factory=KeyFactory.getInstance("RSA");
59 | rsa.allowKeyDNull=(RSAPrivateKey)factory.generatePrivate(spec);
60 | return rsa;
61 | }
62 | return new RSA_Util(pem.SwapKey_Exponent_D__Unsafe());
63 | }
64 |
65 |
66 |
67 | /** 内置加密解密填充方式列表 **/
68 | static public String[] RSAPadding_Enc_DefaultKeys() {
69 | String s="NO, PKCS1";
70 | s+=", OAEP+SHA1, OAEP+SHA256, OAEP+SHA224, OAEP+SHA384, OAEP+SHA512";
71 | s+=", OAEP+SHA-512/224, OAEP+SHA-512/256";
72 | s+=", OAEP+SHA3-256, OAEP+SHA3-224, OAEP+SHA3-384, OAEP+SHA3-512";
73 | s+=", OAEP+MD5";
74 | return s.split(", ");
75 | }
76 | /**
77 | * 将填充方式转换成Java Cipher支持的RSA加密解密填充模式,padding取值和对应的填充模式:
78 | *
79 | * null: 等同于PKCS1
80 | * "": 等同于PKCS1
81 | * RSA: 等同于PKCS1
82 | * PKCS: 等同于PKCS1
83 | * RAW: 等同于NO
84 | * OAEP: 等同于OAEP+SHA1
85 | * RSA/ECB/OAEPPadding: 等同于OAEP+SHA1
86 | *
87 | * NO: RSA/ECB/NoPadding
88 | * PKCS1: RSA/ECB/PKCS1Padding (默认值,等同于"RSA")
89 | * OAEP+SHA1 : RSA/ECB/OAEPwithSHA-1andMGF1Padding
90 | * OAEP+SHA256: RSA/ECB/OAEPwithSHA-256andMGF1Padding
91 | * OAEP+SHA224: RSA/ECB/OAEPwithSHA-224andMGF1Padding
92 | * OAEP+SHA384: RSA/ECB/OAEPwithSHA-384andMGF1Padding
93 | * OAEP+SHA512: RSA/ECB/OAEPwithSHA-512andMGF1Padding
94 | * OAEP+SHA-512/224: RSA/ECB/OAEPwithSHA-512/224andMGF1Padding (SHA-512/*** 2012年发布)
95 | * OAEP+SHA-512/256: RSA/ECB/OAEPwithSHA-512/256andMGF1Padding
96 | * OAEP+SHA3-256: RSA/ECB/OAEPwithSHA3-256andMGF1Padding (SHA3-*** 2015年发布)
97 | * OAEP+SHA3-224: RSA/ECB/OAEPwithSHA3-224andMGF1Padding
98 | * OAEP+SHA3-384: RSA/ECB/OAEPwithSHA3-384andMGF1Padding
99 | * OAEP+SHA3-512: RSA/ECB/OAEPwithSHA3-512andMGF1Padding
100 | * OAEP+MD5 : RSA/ECB/OAEPwithMD5andMGF1Padding
101 | *
102 | * 如果padding包含RSA字符串,将原样返回此值,用于提供Java支持的任何值
103 | * 非以上取值,将会抛异常
104 | *
105 | * 其中OAEP的掩码生成函数MGF1使用和OAEP相同的Hash算法,加密解密实现代码中统一采用:"RSA/ECB/OAEPPadding"模式+配置参数 这种形式进行Java底层调用
106 | *
107 | * 以上填充模式全部可用于BouncyCastle的RSA实现;但如果是使用的Java自带的RSA实现,将会有部分模式无法支持:SHA-512/256、SHA-512/224、SHA3,这三种需要Java9以上才支持
108 | *
109 | * 参考:
110 | * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
111 | * https://docs.oracle.com/en/java/javase/20/docs/specs/security/standard-names.html
112 | * https://developer.android.google.cn/reference/javax/crypto/Cipher
113 | *
114 | */
115 | static public String RSAPadding_Enc(String padding) {
116 | String val=padding;
117 | if(val==null || val.length()==0) val="PKCS1";
118 | val=val.toUpperCase();
119 |
120 | if("RSA".equals(val) || "PKCS".equals(val)) val="PKCS1";
121 | if("OAEP".equals(val) || val.endsWith("/OAEPPADDING")) val="OAEP+SHA1";
122 | if("RAW".equals(val)) val="NO";
123 | if(val.indexOf("RSA")!=-1) return padding;
124 |
125 | switch(val) {
126 | case "PKCS1": return "RSA/ECB/PKCS1Padding";
127 | case "NO": return "RSA/ECB/NoPadding";
128 | }
129 | if(val.startsWith("OAEP+")) {
130 | val=val.replace("OAEP+", "");
131 | switch(val) {
132 | case "SHA1":case "SHA256":case "SHA224":case "SHA384":case "SHA512":
133 | case "SHA512/224":case "SHA512/256":
134 | val="SHA-"+val.substring(3);
135 | }
136 | switch(val) {
137 | case "SHA-1":case "SHA-256":case "SHA-224":case "SHA-384":case "SHA-512":
138 | case "SHA3-256":case "SHA3-224":case "SHA3-384":case "SHA3-512":
139 | case "SHA-512/224":case "SHA-512/256":case "MD5":
140 | return "RSA/ECB/OAEPwith"+val+"andMGF1Padding";
141 | }
142 | }
143 | throw new RuntimeException(T("RSAPadding_Enc未定义Padding: ", "RSAPadding_Enc does not define Padding: ")+padding);
144 | }
145 |
146 | /** 内置签名填充方式列表 **/
147 | static public String[] RSAPadding_Sign_DefaultKeys() {
148 | String s="PKCS1+SHA1, PKCS1+SHA256, PKCS1+SHA224, PKCS1+SHA384, PKCS1+SHA512";
149 | s+=", PKCS1+SHA-512/224, PKCS1+SHA-512/256";
150 | s+=", PKCS1+SHA3-256, PKCS1+SHA3-224, PKCS1+SHA3-384, PKCS1+SHA3-512";
151 | s+=", PKCS1+MD5";
152 | s+=", PSS+SHA1, PSS+SHA256, PSS+SHA224, PSS+SHA384, PSS+SHA512";
153 | s+=", PSS+SHA-512/224, PSS+SHA-512/256";
154 | s+=", PSS+SHA3-256, PSS+SHA3-224, PSS+SHA3-384, PSS+SHA3-512";
155 | s+=", PSS+MD5";
156 | return s.split(", ");
157 | }
158 | /**
159 | * 将填充方式转换成Java Signature支持的RSA签名填充模式,hash取值和对应的填充模式:
160 | *
161 | * SHA*** : 等同于PKCS1+SHA***,比如"SHA256" == "PKCS1+SHA256"
162 | * MD5 : 等同于PKCS1+MD5
163 | * RSASSA-PSS: 等同于PSS+SHA1
164 | *
165 | * PKCS1+SHA1 : SHA1withRSA
166 | * PKCS1+SHA256: SHA256withRSA
167 | * PKCS1+SHA224: SHA224withRSA
168 | * PKCS1+SHA384: SHA384withRSA
169 | * PKCS1+SHA512: SHA512withRSA
170 | * PKCS1+SHA-512/224: SHA512/224withRSA (SHA-512/*** 2012年发布)
171 | * PKCS1+SHA-512/256: SHA512/256withRSA
172 | * PKCS1+SHA3-256: SHA3-256withRSA (SHA3-*** 2015年发布)
173 | * PKCS1+SHA3-224: SHA3-224withRSA
174 | * PKCS1+SHA3-384: SHA3-384withRSA
175 | * PKCS1+SHA3-512: SHA3-512withRSA
176 | * PKCS1+MD5 : MD5withRSA
177 | *
178 | * PSS+SHA1 : SHA1withRSA/PSS
179 | * PSS+SHA256: SHA256withRSA/PSS
180 | * PSS+SHA224: SHA224withRSA/PSS
181 | * PSS+SHA384: SHA384withRSA/PSS
182 | * PSS+SHA512: SHA512withRSA/PSS
183 | * PSS+SHA-512/224: SHA512/224withRSA/PSS (SHA-512/*** 2012年发布)
184 | * PSS+SHA-512/256: SHA512/256withRSA/PSS
185 | * PSS+SHA3-256: SHA3-256withRSA/PSS (SHA3-*** 2015年发布)
186 | * PSS+SHA3-224: SHA3-224withRSA/PSS
187 | * PSS+SHA3-384: SHA3-384withRSA/PSS
188 | * PSS+SHA3-512: SHA3-512withRSA/PSS
189 | * PSS+MD5 : MD5withRSA/PSS (此方式不同实现下不一定支持)
190 | *
191 | * 如果hash包含RSA字符串,将原样返回此值,用于提供Java支持的任何值
192 | * 非以上取值,将会抛异常
193 | *
194 | * 其中PSS的salt字节数等于使用的Hash算法字节数,PSS的掩码生成函数MGF1使用和PSS相同的Hash算法,跟踪属性TrailerField取值固定为0xBC(PSSParameterSpec.TRAILER_FIELD_BC),签名实现代码中统一采用:"RSASSA-PSS"模式+配置参数 这种形式进行Java底层调用
195 | *
196 | * 以上填充模式全部可用于BouncyCastle的RSA实现;但如果是使用的Java自带的RSA实现,将会有部分模式无法支持:所有PSS模式需要Java11以上才支持,SHA-512/256、SHA-512/224需要需要Java11以上,SHA3需要Java16以上
197 | *
198 | * 参考:
199 | * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
200 | * https://docs.oracle.com/en/java/javase/20/docs/specs/security/standard-names.html
201 | * https://developer.android.google.cn/reference/java/security/Signature
202 | *
203 | */
204 | static public String RSAPadding_Sign(String hash) {
205 | String val=hash==null?"":hash;
206 | val=val.toUpperCase();
207 |
208 | if("RSASSA-PSS".equals(val)) val="PSS+SHA1";
209 | if(val.indexOf("RSA")!=-1) return hash;
210 |
211 | String pss="";
212 | if(val.startsWith("PSS+")) {
213 | val=val.substring(4);
214 | pss="/PSS";
215 | }else if(val.startsWith("PKCS1+")) {
216 | val=val.substring(6);
217 | }
218 | switch(val) {
219 | case "SHA-1":case "SHA-256":case "SHA-224":case "SHA-384":case "SHA-512":
220 | case "SHA-512/224":case "SHA-512/256":
221 | val=val.replace("-", "");
222 | }
223 | switch(val) {
224 | case "SHA1":case "SHA256":case "SHA224":case "SHA384":case "SHA512":
225 | case "SHA3-256":case "SHA3-224":case "SHA3-384":case "SHA3-512":
226 | case "SHA512/224":case "SHA512/256":case "MD5":
227 | return val+"withRSA"+pss;
228 | }
229 | throw new RuntimeException(T("RSAPadding_Sign未定义Hash: ", "RSAPadding_Sign does not define Hash: ")+hash);
230 | }
231 |
232 | static private String JavaLowVerSupportMsg(String tag) {
233 | return T("低版本的Java不支持"+tag+",解决办法1:升级使用高版本Java;解决办法2:引入BouncyCastle的jar加密增强包来兼容低版本Java,可到 https://www.bouncycastle.org/latest_releases.html 下载 bcprov-jdk**-**.jar,然后在程序启动时调用"+Msg_Bc_Reg+"进行注册即可得到全部支持。", "The lower version of Java does not support "+tag+". Solution 1: Upgrade to a higher version of Java; Solution 2: Introduce BouncyCastle's jar encryption enhancement package to be compatible with the lower version of Java, you can download bcprov-jdk**-**.jar from https://www.bouncycastle.org/latest_releases.html, and then call"+Msg_Bc_Reg+"to register when the program starts to get full support.");
234 | }
235 | static private final String Msg_Bc_Reg=" `Security.addProvider(new BouncyCastleProvider()) + RSA_Util.UseBouncyCastle(BouncyCastleProvider.PROVIDER_NAME)` ";
236 | /** 是否是因为低版本Java兼容性产生的错误 **/
237 | static public boolean IsJavaLowVerSupportError(Throwable err) {
238 | Throwable e=err;
239 | while(e!=null) {
240 | if(e.getMessage().contains(Msg_Bc_Reg)) {
241 | return true;
242 | }
243 | e=e.getCause();
244 | }
245 | return false;
246 | }
247 | static private void checkSHA3Support() {
248 | try {
249 | MessageDigest.getInstance("SHA3-256");
250 | }catch(Exception e) {
251 | throw new RuntimeException(JavaLowVerSupportMsg(T("SHA3系列摘要算法","SHA3 series digest algorithm")));
252 | }
253 | }
254 | static private void checkSHA512xSupport(String hash) {
255 | try {
256 | MessageDigest.getInstance(hash);
257 | }catch(Exception e) {
258 | throw new RuntimeException(JavaLowVerSupportMsg(hash+T("摘要算法"," Digest Algorithm")));
259 | }
260 | }
261 | /** 简版多语言支持,根据当前语言返回中文或英文,简化调用{@link RSA_PEM#T(String, String)} **/
262 | static private String T(String zh, String en) {
263 | return RSA_PEM.T(zh, en);
264 | }
265 |
266 |
267 |
268 |
269 | /**
270 | * 加密任意长度字符串(utf-8)返回base64,出错抛异常。本方法线程安全。padding指定填充方式(如:PKCS1、OAEP+SHA256大写),使用空值时默认为PKCS1,取值参考{@link #RSAPadding_Enc}
271 | */
272 | public String Encrypt(String padding, String str) throws Exception {
273 | return Base64.getEncoder().encodeToString(Encrypt(padding,str.getBytes("utf-8")));
274 | }
275 | /**
276 | * 加密任意长度数据,出错抛异常。本方法线程安全。padding指定填充方式(如:PKCS1、OAEP+SHA256大写),使用空值时默认为PKCS1,取值参考{@link #RSAPadding_Enc}
277 | */
278 | public byte[] Encrypt(String padding, byte[] data) throws Exception {
279 | try(ByteArrayOutputStream stream=new ByteArrayOutputStream()){
280 | String ctype=RSAPadding_Enc(padding),CType=ctype.toUpperCase();
281 |
282 | AlgorithmParameterSpec[] param=null;
283 | int blockLen = keySize / 8;
284 | if(CType.indexOf("OAEP")!=-1) {
285 | //OAEP填充占用 2*hashLen+2 字节:https://www.rfc-editor.org/rfc/rfc8017.html#section-7.1.1
286 | String[] outType=new String[] { "" };
287 | int[] outLen=new int[] {0};
288 | param=createOaepParam(ctype,outType,outLen);
289 |
290 | int shaLen=outLen[0];
291 | int sub=2 * shaLen/8 + 2;
292 | blockLen -= sub;
293 | if(blockLen<1) {
294 | String min="NaN"; if(sub>0) min=(int)Math.pow(2, Math.ceil(Math.log(sub*8)/Math.log(2)))+"";
295 | throw new RuntimeException("RSA["+ctype+"][keySize="+keySize+"] "+T("密钥位数不能小于", "Key digits cannot be less than ")+min);
296 | }
297 | ctype=outType[0];
298 | } else if(CType.indexOf("NOPADDING")!=-1) {
299 | //NOOP 无填充,不够数量时会在开头给0
300 | } else {
301 | //PKCS1填充占用11字节:https://www.rfc-editor.org/rfc/rfc8017.html#section-7.2.1
302 | blockLen -= 11;
303 | }
304 | Cipher enc=Cipher_getInstance(true, ctype, param);
305 |
306 | int start=0;
307 | while(startdata.length) {
310 | len=data.length-start;
311 | }
312 |
313 | byte[] en = enc.doFinal(data, start, len);
314 | stream.write(en);
315 | start+=len;
316 | }
317 |
318 | return stream.toByteArray();
319 | }
320 | }
321 | /**
322 | * 解密任意长度密文(base64)得到字符串(utf-8),出错抛异常。本方法线程安全。padding指定填充方式(如:PKCS1、OAEP+SHA256大写),使用空值时默认为PKCS1,取值参考{@link #RSAPadding_Enc}
323 | */
324 | public String Decrypt(String padding, String str) throws Exception {
325 | if (str==null || str.length()==0) {
326 | return "";
327 | }
328 | byte[] byts = Base64.getDecoder().decode(str);
329 | byte[] val = Decrypt(padding,byts);
330 | return new String(val, "utf-8");
331 | }
332 | /**
333 | * 解密任意长度数据,出错抛异常。本方法线程安全。padding指定填充方式(如:PKCS1、OAEP+SHA256大写),使用空值时默认为PKCS1,取值参考{@link #RSAPadding_Enc}
334 | */
335 | public byte[] Decrypt(String padding, byte[] data) throws Exception {
336 | try(ByteArrayOutputStream stream=new ByteArrayOutputStream()){
337 | String ctype=RSAPadding_Enc(padding),CType=ctype.toUpperCase();
338 |
339 | AlgorithmParameterSpec[] param=null;
340 | if(CType.indexOf("OAEP")!=-1) {
341 | String[] outType=new String[] { "" };
342 | param=createOaepParam(ctype, outType, new int[1]);
343 | ctype=outType[0];
344 | }
345 | Cipher dec=Cipher_getInstance(false, ctype, param);
346 |
347 | int blockLen = keySize / 8;
348 | int start=0;
349 | while(start=data.length) {
352 | len=data.length-start;
353 | isEnd=true;
354 | }
355 |
356 | byte[] de = dec.doFinal(data, start, len);
357 | if(isEnd && CType.indexOf("NOPADDING")!=-1) {
358 | //没有填充时,去掉开头的0
359 | int idx=0;
360 | for(;idx/dev/null`; do jarPath=$f; done
39 | if [ "$jarPath" != "" ]; then
40 | echo2 "检测到已打包的jar:${jarPath},是否使用此jar参与测试?(Y/N) N" "A packaged jar is detected: ${jarPath}, do you want to use this jar to participate in the test? (Y/N) N"
41 | read -rp "> " step
42 | if [ "${step^^}" != "Y" ]; then jarPath=""; fi
43 | if [ "$jarPath" != "" ]; then
44 | echo2 "jar参与测试:$jarPath" "jar participates in the test: $jarPath"
45 | echo
46 | fi
47 | fi
48 |
49 | rootDir=rsaTest
50 | echo
51 | echo2 "正在创Java项目${rootDir}..." "Creating Java project ${rootDir}..."
52 | echo
53 | if [ ! -e $rootDir ]; then
54 | mkdir -p $rootDir
55 | else
56 | rm ${rootDir}/* > /dev/null 2>&1
57 | fi
58 |
59 | if [ "$jarPath" == "" ]; then
60 | cp *.java $rootDir > /dev/null
61 | else
62 | cp Test.java $rootDir > /dev/null
63 | cp $jarPath $rootDir > /dev/null
64 | fi
65 | if [ -e *.jar ]; then
66 | cp *.jar $rootDir > /dev/null
67 | fi
68 | cd $rootDir
69 |
70 |
71 | if [ "$jdkBinDir" == "" ]; then
72 | echo2 "正在读取JDK版本(如需指定JDK为特定版本或目录,请修改本sh文件内jdkBinDir为JDK bin目录):" "Reading the JDK Version (if you need to specify JDK as a specific version or directory, please modify the jdkBinDir in this sh file to the JDK bin directory):"
73 | else
74 | echo2 "正在读取JDK(${jdkBinDir})版本:" "Reading JDK (${jdkBinDir}) Version:"
75 | fi
76 |
77 | ${jdkBinDir}javac -version
78 | [ ! $? -eq 0 ] && {
79 | echo
80 | err "需要安装JDK才能编译运行java文件" "JDK needs to be installed to compile and run java files";
81 | exit2;
82 | }
83 |
84 | echo
85 | echo2 "正在编译Java文件..." "Compiling Java files...";
86 | echo
87 | ${jdkBinDir}javac -encoding utf-8 -cp "./*" *.java
88 | [ ! $? -eq 0 ] && {
89 | echo
90 | err "Java文件编译失败" "Java file compilation failed";
91 | exit2;
92 | }
93 |
94 | dir="com/github/xiangyuecn/rsajava"
95 | if [ ! -e $dir ]; then
96 | mkdir -p $dir
97 | else
98 | rm ${dir}/*.class > /dev/null 2>&1
99 | fi
100 | mv *.class ${dir}
101 |
102 | ${jdkBinDir}java -cp "./:./*" com.github.xiangyuecn.rsajava.Test -cmd=1 -zh=${isZh}
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/Test.java:
--------------------------------------------------------------------------------
1 | package com.github.xiangyuecn.rsajava;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.BufferedWriter;
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 | import java.io.FileOutputStream;
9 | import java.io.InputStreamReader;
10 | import java.io.OutputStreamWriter;
11 | import java.nio.charset.Charset;
12 | import java.security.MessageDigest;
13 | import java.security.Provider;
14 | import java.security.Security;
15 | import java.security.Signature;
16 | import java.util.ArrayList;
17 | import java.util.Arrays;
18 | import java.util.concurrent.atomic.AtomicBoolean;
19 | import java.util.concurrent.atomic.AtomicInteger;
20 | import java.util.regex.Matcher;
21 | import java.util.regex.Pattern;
22 |
23 | import javax.crypto.Cipher;
24 |
25 | /**
26 | * RSA_PEM测试控制台主程序
27 | *
28 | * GitHub:https://github.com/xiangyuecn/RSA-java
29 | */
30 | public class Test {
31 |
32 | public static void main(String[] args) throws Exception{
33 | //【请在这里编写你自己的测试代码】
34 |
35 | ShowMenu(args);
36 | }
37 |
38 | static void RSATest(boolean fast) throws Exception{
39 | //新生成一个RSA密钥,也可以通过已有的pem、xml文本密钥来创建RSA
40 | RSA_Util rsa = new RSA_Util(512);
41 | // RSA_Util rsa = new RSA_Util("pem或xml文本密钥");
42 | // RSA_Util rsa = new RSA_Util(RSA_PEM.FromPEM("pem文本密钥"));
43 | // RSA_Util rsa = new RSA_Util(RSA_PEM.FromXML("xml文本密钥"));
44 |
45 | //得到pem对象
46 | RSA_PEM pem=rsa.ToPEM(false);
47 | //提取密钥pem字符串
48 | String pem_pkcs1 = pem.ToPEM_PKCS1(false);
49 | String pem_pkcs8 = pem.ToPEM_PKCS8(false);
50 | //提取密钥xml字符串
51 | String xml = rsa.ToXML(false);
52 |
53 | AssertMsg(T("【"+rsa.keySize()+"私钥(XML)】:", "[ "+rsa.keySize()+" Private Key (XML) ]:"), rsa.keySize()==512);
54 | S(xml);
55 | S();
56 | ST("【"+rsa.keySize()+"私钥(PKCS#1)】:", "[ "+rsa.keySize()+" Private Key (PKCS#1) ]:");
57 | S(pem_pkcs1);
58 | S();
59 | ST("【"+rsa.keySize()+"公钥(PKCS#8)】:", "[ "+rsa.keySize()+" Public Key (PKCS#8) ]:");
60 | S(pem.ToPEM_PKCS8(true));
61 | S();
62 |
63 |
64 | String str = T("abc内容123", "abc123");
65 | String en=rsa.Encrypt("PKCS1", str);
66 | ST("【加密】:", "[ Encrypt ]:");
67 | S(en);
68 |
69 | ST("【解密】:", "[ Decrypt ]:");
70 | String de=rsa.Decrypt("PKCS1", en);
71 | AssertMsg(de, de.equals(str));
72 |
73 | if (!fast) {
74 | String str2 = str; for (int i = 0; i < 15; i++) str2 += str2;
75 | ST("【长文本加密解密】:", "[ Long text encryption and decryption ]:");
76 | AssertMsg(str2.length() + T("个字 OK"," characters OK"), rsa.Decrypt("PKCS1", rsa.Encrypt("PKCS1",str2)).equals(str2));
77 | }
78 |
79 |
80 | ST("【签名SHA1】:", "[ Signature SHA1 ]:");
81 | String sign = rsa.Sign("SHA1", str);
82 | S(sign);
83 | AssertMsg(T("校验 OK","Verify OK"), rsa.Verify("SHA1", sign, str));
84 | S();
85 |
86 |
87 |
88 | //用pem文本创建RSA
89 | RSA_Util rsa2=new RSA_Util(RSA_PEM.FromPEM(pem_pkcs8));
90 | RSA_PEM pem2=rsa2.ToPEM(false);
91 | ST("【用PEM新创建的RSA是否和上面的一致】:", "[ Is the newly created RSA with PEM consistent with the above ]:");
92 | Assert("XML:", pem2.ToXML(false) .equals( pem.ToXML(false) ));
93 | Assert("PKCS1:", pem2.ToPEM_PKCS1(false) .equals( pem.ToPEM_PKCS1(false) ));
94 | Assert("PKCS8:", pem2.ToPEM_PKCS8(false) .equals( pem.ToPEM_PKCS8(false) ));
95 |
96 | //用xml文本创建RSA
97 | RSA_Util rsa3=new RSA_Util(RSA_PEM.FromXML(xml));
98 | RSA_PEM pem3=rsa3.ToPEM(false);
99 | ST("【用XML新创建的RSA是否和上面的一致】:", "[ Is the newly created RSA with XML consistent with the above ]:");
100 | Assert("XML:", pem3.ToXML(false) .equals( pem.ToXML(false) ));
101 | Assert("PKCS1:", pem3.ToPEM_PKCS1(false) .equals( pem.ToPEM_PKCS1(false) ));
102 | Assert("PKCS8:", pem3.ToPEM_PKCS8(false) .equals( pem.ToPEM_PKCS8(false) ));
103 |
104 |
105 | //--------RSA_PEM私钥验证---------
106 | //使用PEM全量参数构造pem对象
107 | RSA_PEM pemX = new RSA_PEM(pem.Key_Modulus, pem.Key_Exponent, pem.Key_D
108 | , pem.Val_P, pem.Val_Q, pem.Val_DP, pem.Val_DQ, pem.Val_InverseQ);
109 | ST("【RSA_PEM是否和原始RSA一致】:", "[ Is RSA_PEM consistent with the original RSA ]:");
110 | S(pemX.keySize() + T("位"," bits"));
111 | Assert("XML:", pemX.ToXML(false) .equals( pem.ToXML(false) ));
112 | Assert("PKCS1:", pemX.ToPEM_PKCS1(false) .equals( pem.ToPEM_PKCS1(false) ));
113 | Assert("PKCS8:", pemX.ToPEM_PKCS8(false) .equals( pem.ToPEM_PKCS8(false) ));
114 | ST("仅公钥:", "Public Key Only:");
115 | Assert("XML:", pemX.ToXML(true) .equals( pem.ToXML(true) ));
116 | Assert("PKCS1:", pemX.ToPEM_PKCS1(true) .equals( pem.ToPEM_PKCS1(true) ));
117 | Assert("PKCS8:", pemX.ToPEM_PKCS8(true) .equals( pem.ToPEM_PKCS8(true) ));
118 |
119 | //--------RSA_PEM公钥验证---------
120 | RSA_PEM pemY = new RSA_PEM(pem.Key_Modulus, pem.Key_Exponent, null);
121 | ST("【RSA_PEM仅公钥是否和原始RSA一致】:", "[ RSA_PEM only public key is consistent with the original RSA ]:");
122 | S(pemY.keySize() + T("位"," bits"));
123 | Assert("XML:", pemY.ToXML(false) .equals( pem.ToXML(true) ));
124 | Assert("PKCS1:", pemY.ToPEM_PKCS1(false) .equals( pem.ToPEM_PKCS1(true) ));
125 | Assert("PKCS8:", pemY.ToPEM_PKCS8(false) .equals( pem.ToPEM_PKCS8(true) ));
126 |
127 |
128 | if (!fast) {
129 | //使用n、e、d构造pem对象
130 | RSA_PEM pem4 = new RSA_PEM(pem.Key_Modulus, pem.Key_Exponent, pem.Key_D);
131 | RSA_Util rsa4=new RSA_Util(pem4);
132 | ST("【用n、e、d构造解密】", "[ Construct decryption with n, e, d ]");
133 | de=rsa4.Decrypt("PKCS1",en);
134 | AssertMsg(de, de.equals(str));
135 | AssertMsg(T("校验 OK","Verify OK"), rsa4.Verify("SHA1", sign, str));
136 |
137 |
138 | //对调交换公钥私钥
139 | ST("【Unsafe|对调公钥私钥,私钥加密公钥解密】", "[ Unsafe | Swap the public key and private key, private key encryption and public key decryption ]");
140 | RSA_Util rsaPri=rsa.SwapKey_Exponent_D__Unsafe();
141 | RSA_Util rsaPub=new RSA_Util(rsa.ToPEM(true)).SwapKey_Exponent_D__Unsafe();
142 | String enPri=rsaPri.Encrypt("PKCS1", str);
143 | String signPub=rsaPub.Sign("SHA1", str);
144 | de=rsaPub.Decrypt("PKCS1",enPri);
145 | AssertMsg(de, de.equals(str));
146 | AssertMsg(T("校验 OK","Verify OK"), rsaPri.Verify("SHA1", signPub, str));
147 |
148 | rsa4 = rsaPri.SwapKey_Exponent_D__Unsafe();
149 | de=rsa4.Decrypt("PKCS1",en);
150 | AssertMsg(de, de.equals(str));
151 | AssertMsg(T("校验 OK","Verify OK"), rsa4.Verify("SHA1", sign, str));
152 | }
153 |
154 |
155 | if (!fast) {
156 | S();
157 | ST("【测试一遍所有的加密、解密填充方式】 按回车键继续测试...", "[ Test all the encryption and decryption padding mode ] Press Enter to continue testing...");
158 | ReadIn();
159 | RSA_Util rsa5 = new RSA_Util(2048);
160 | testPaddings(false, rsa5, new RSA_Util(rsa5.ToPEM(true)), true);
161 | }
162 | }
163 |
164 |
165 | static void Assert(String msg, boolean check) throws Exception {
166 | AssertMsg(msg + check, check);
167 | }
168 | static void AssertMsg(String msg, boolean check) throws Exception {
169 | if (!check) throw new Exception(msg);
170 | System.out.println(msg);
171 | }
172 |
173 |
174 |
175 | /** 控制台输出一个换行 **/
176 | static private void S() {
177 | System.out.println();
178 | }
179 | /** 控制台输出内容 **/
180 | static private void S(String s) {
181 | System.out.println(s);
182 | }
183 | /** 控制台输出内容 + 简版多语言支持,根据当前语言返回中文或英文,简化调用{@link RSA_PEM#T(String, String)} **/
184 | static private void ST(String zh, String en) {
185 | System.out.println(T(zh, en));
186 | }
187 | /** 简版多语言支持,根据当前语言返回中文或英文,简化调用{@link RSA_PEM#T(String, String)} **/
188 | static private String T(String zh, String en) {
189 | return RSA_PEM.T(zh, en);
190 | }
191 | static public String ReadIn() throws Exception {
192 | ByteArrayOutputStream in=new ByteArrayOutputStream();
193 | while(true) {
194 | int byt=System.in.read();
195 | if(byt=='\r') continue;
196 | if(byt=='\n') {
197 | break;
198 | }
199 | if(in.size()>=2048) {//防止内存溢出,某些环境下可能会有无限的输入
200 | byte[] bytes=in.toByteArray();
201 | in=new ByteArrayOutputStream();
202 | in.write(bytes, bytes.length-1024, 1024);
203 | }
204 | in.write(byt);
205 | }
206 | return in.toString();
207 | }
208 | static String ReadPath(String tips, String tips2) throws Exception {
209 | while (true) {
210 | ST("请输入"+tips+"路径"+tips2+": ","Please enter "+tips+" path"+tips2+":");
211 | System.out.print("> ");
212 | String path = ReadIn().trim();
213 | if(path.length()==0 || path.startsWith("+")) {
214 | return path;
215 | }
216 | if(!new File(path).exists()) {
217 | ST("文件[" + path + "]不存在","File [" + path + "] does not exist");
218 | continue;
219 | }
220 | return path;
221 | }
222 | }
223 | static byte[] ReadFile(String path) throws Exception {
224 | ByteArrayOutputStream bs=new ByteArrayOutputStream();
225 | byte[] buffer=new byte[32*1024]; int len;
226 | try(FileInputStream in=new FileInputStream(path)){
227 | while((len=in.read(buffer))!=-1) {
228 | bs.write(buffer, 0, len);
229 | }
230 | }
231 | return bs.toByteArray();
232 | }
233 | static void WriteFile(String path, byte[] val) throws Exception {
234 | try(FileOutputStream out=new FileOutputStream(path)){
235 | out.write(val);
236 | }
237 | }
238 | static String HR="-----------------------------------";
239 |
240 |
241 |
242 |
243 | static boolean CanLoad_BouncyCastle() {
244 | try {
245 | Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
246 | return true;
247 | }catch(Exception e) {
248 | return false;
249 | }
250 | }
251 | static void printEnv() {
252 | S("Java Version: "+System.getProperty("java.version")+" | "+System.getProperty("os.name")+" RSA_PEM.Lang="+RSA_PEM.Lang());
253 | String errs="";
254 | try {
255 | Signature.getInstance("RSASSA-PSS");
256 | }catch(Exception e) {
257 | errs+=errs.length()>0?T("、",", "):"";
258 | errs+=T("PSS签名填充模式(其他填充模式不影响)","PSS signature padding mode (other padding modes do not affect)");
259 | }
260 | try {
261 | MessageDigest.getInstance("SHA-512/256");
262 | }catch(Exception e) {
263 | errs+=errs.length()>0?T("、",", "):"";
264 | errs+=T("SHA-512/224(/256)摘要算法","SHA-512/224 (/256) digest algorithm");
265 | }
266 | try {
267 | MessageDigest.getInstance("SHA3-256");
268 | }catch(Exception e) {
269 | errs+=errs.length()>0?T("、",", "):"";
270 | errs+=T("SHA3系列摘要算法","SHA3 series digest algorithm");
271 | }
272 | if(errs.length()>0) {
273 | ST("*** 当前Java版本太低,不支持:"+errs+";如需获得这些功能支持,解决办法1:升级使用高版本Java来运行本测试程序;解决办法2:引入BouncyCastle的jar加密增强包来兼容低版本Java,先到 https://www.bouncycastle.org/latest_releases.html 下载 bcprov-jdk**-**.jar 放到本测试程序源码目录,然后通过测试菜单B进行注册即可得到全部支持。","*** The current Java version is too low to support: "+errs+"; if you want to obtain these functions, solution 1: upgrade and use a higher version of Java to run this test program; solution 2: introduce the jar encryption enhancement package of BouncyCastle to be compatible with lower Version Java, first go to https://www.bouncycastle.org/latest_releases.html to download bcprov-jdk**-**.jar and put it in the source code directory of this test program, and then register through test menu B to get full support.");
274 | }
275 | }
276 | static Provider BcProvider=null;
277 | static void testProvider(boolean checkOpenSSL) throws Exception{
278 | if(CanLoad_BouncyCastle()) {
279 | if(BcProvider==null) {
280 | ST("检测到BouncyCastle加密增强包,是否要进行注册?(Y/N) Y","The BouncyCastle encryption enhancement package is detected, do you want to register? (Y/N) Y");
281 | }else {
282 | ST("已注册BouncyCastle加密增强包,是否要保持注册?(Y/N) Y","BouncyCastle encryption enhancement package has been registered, do you want to keep it registered? (Y/N) Y");
283 | }
284 | System.out.print("> ");
285 | String val = ReadIn().trim().toUpperCase();
286 | try {
287 | if(BcProvider==null && !"N".equals(val)) {
288 | Class> cls=Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
289 | Provider bc=(Provider)cls.getConstructor().newInstance();
290 | Security.addProvider(bc);
291 | RSA_Util.UseBouncyCastle(bc.getName());
292 | BcProvider=bc;
293 | ST("已注册BouncyCastle加密增强包","BouncyCastle encryption enhancement package registered");
294 | }
295 | if(BcProvider!=null && "N".equals(val)) {
296 | Security.removeProvider(BcProvider.getName());
297 | RSA_Util.UseBouncyCastle(null);
298 | BcProvider=null;
299 | ST("已取消注册BouncyCastle加密增强包","Unregistered BouncyCastle encryption enhancement package");
300 | }
301 | }catch(Exception e) {
302 | S(T("BouncyCastle操作失败:","BouncyCastle operation failed: ")+e.getMessage());
303 | }
304 | }
305 | printEnv();
306 | S();
307 |
308 | S("Security.getProviders:");
309 | Provider[] ps=Security.getProviders();
310 | for(Provider s : ps) {
311 | S(" Provider: "+s.toString());
312 | }
313 | S();
314 |
315 | String[] Hashs=new String[] {
316 | "SHA-1","SHA-256","SHA-224","SHA-384","SHA-512"
317 | ,"SHA3-256","SHA3-224","SHA3-384","SHA3-512"
318 | ,"SHA-512/224","SHA-512/256","MD5"
319 | };
320 |
321 | S("MessageDigest.getInstance"+T("支持情况:"," support status:"));
322 | {
323 | ArrayList S=new ArrayList(Arrays.asList(Hashs));
324 | S.add("MD2");
325 | S.add("SHAKE128"); S.add("SHAKE256");//https://blog.csdn.net/weixin_42579622/article/details/111644921
326 | for(String s : S) {
327 | String key=s;
328 | try {
329 | MessageDigest v=MessageDigest.getInstance(key);
330 | S(" "+key+" | Provider: "+v.getProvider().toString());
331 | }catch(Exception e) {
332 | S(" [x] "+key);
333 | }
334 | }
335 | }
336 |
337 | S("Cipher.getInstance"+T("支持情况:"," support status:"));
338 | for(int i=0;i<1;i++) {
339 | String v1=i==9999?"NONE":"ECB";
340 | ArrayList S=new ArrayList<>(Arrays.asList(new String[] {"NoPadding"
341 | ,"PKCS1Padding"
342 | ,"OAEPPadding"}));
343 | for(String s : Hashs) {
344 | S.add("OAEPwith"+s+"andMGF1Padding");
345 | }
346 | for(String s : S) {
347 | String key="RSA/"+v1+"/"+s,key2=key;
348 | while(true) {
349 | try {
350 | Cipher v=Cipher.getInstance(key2);
351 | S(" "+key+" | Provider: "+v.getProvider().toString());
352 | }catch(Exception e) {
353 | if(key2.contains("512/2")) {
354 | key2=key2.replace("/224", "(224)").replace("/256", "(256)");
355 | continue;
356 | }
357 | S(" [x] "+key);
358 | }
359 | break;
360 | }
361 | }
362 | }
363 |
364 | S("Signature.getInstance"+T("支持情况:"," support status:"));
365 | for(int i=0;i<3;i++) {
366 | String v2=i==1?"/PSS":"";
367 | String[] S=i==2?new String[] {"RSASSA-PSS"}:Hashs;
368 | for(String s : S) {
369 | String key=i==2?s:(s.replace("SHA-", "SHA")+"withRSA"+v2),key2=key;
370 | while(true) {
371 | try {
372 | Signature v=Signature.getInstance(key2);
373 | S(" "+key+" | Provider: "+v.getProvider().toString());
374 | }catch(Exception e) {
375 | if(key2.contains("512/2")) {
376 | key2=key2.replace("/224", "(224)").replace("/256", "(256)");
377 | continue;
378 | }
379 | S(" [x] "+key);
380 | }
381 | break;
382 | }
383 | }
384 | }
385 |
386 | S(HR);
387 | ST("测试一遍所有的加密、解密填充方式:","Test all the encryption and decryption padding mode:");
388 | RSA_Util rsa = new RSA_Util(2048);
389 | testPaddings(checkOpenSSL, rsa, new RSA_Util(rsa.ToPEM(true)), true);
390 |
391 | S(HR);
392 | ST("Unsafe|是否要对调公钥私钥(私钥加密公钥解密)重新测试一遍?(Y/N) N", "Unsafe | Do you want to swap the public and private keys (private key encryption and public key decryption) and test again? (Y/N) N");
393 | System.out.print("> ");
394 | String yn = ReadIn().trim().toUpperCase();
395 | if (yn.equals("Y")) {
396 | RSA_Util rsaPri = rsa.SwapKey_Exponent_D__Unsafe();
397 | RSA_Util rsaPub = new RSA_Util(rsa.ToPEM(true)).SwapKey_Exponent_D__Unsafe();
398 | testPaddings(checkOpenSSL, rsaPub, rsaPri, true);
399 | }
400 | }
401 | /** 测试一遍所有的加密、解密填充方式 **/
402 | static int testPaddings(boolean checkOpenSSL, RSA_Util rsaPri, RSA_Util rsaPub, boolean log) {
403 | int errCount=0;
404 | ArrayList errMsgs=new ArrayList<>();
405 | String txt="1234567890";
406 | if(!checkOpenSSL) {
407 | txt+=txt+txt+txt+txt; txt+=txt;//100
408 | txt+=txt+txt+txt+txt; txt+=txt+"a";//1001
409 | }
410 | byte[] txtData=txt.getBytes(Charset.forName("utf-8"));
411 |
412 | if(checkOpenSSL) {
413 | try {
414 | runOpenSSL(rsaPri.hasPrivate()?rsaPri:rsaPub, txtData);
415 | }catch(Exception e) {
416 | S(T("运行OpenSSL失败:","Failed to run OpenSSL: ")+e.getMessage());
417 | return errCount;
418 | }
419 | }
420 |
421 | String[] encKeys=RSA_Util.RSAPadding_Enc_DefaultKeys();
422 | for(String type : encKeys) {
423 | String errMsg="";
424 | try {
425 | {
426 | byte[] enc=rsaPub.Encrypt(type, txtData);
427 | byte[] dec=rsaPri.Decrypt(type, enc);
428 | boolean isOk=true;
429 | if(dec.length!=txtData.length) {
430 | isOk=false;
431 | }else {
432 | for(int i=0;i0) {
535 | S(String.join("\n", errMsgs));
536 | }
537 | closeOpenSSL();
538 | return errCount;
539 | }
540 | /** 多线程并发调用同一个RSA **/
541 | static void threadRun() throws Exception {
542 | int ThreadCount=Math.max(5, Runtime.getRuntime().availableProcessors()-1);
543 | AtomicBoolean Abort=new AtomicBoolean(false);
544 | AtomicInteger Count=new AtomicInteger(0);
545 | AtomicInteger ErrCount=new AtomicInteger(0);
546 | RSA_Util rsa=new RSA_Util(2048);
547 | RSA_Util rsaPub=new RSA_Util(rsa.ToPEM(true));
548 | S(T("正在测试中,线程数:","Under test, number of threads: ")+ThreadCount+T(",按回车键结束测试...",", press enter to end the test..."));
549 |
550 | for(int i=0;i0) {
556 | ErrCount.addAndGet(err);
557 | }
558 | Count.incrementAndGet();
559 | }
560 | }
561 | }).start();
562 | }
563 |
564 | long t1=System.currentTimeMillis();
565 | new Thread(new Runnable() {
566 | public void run() {
567 | while(!Abort.get()) {
568 | System.out.print("\r"+T("已测试"+Count.get()+"次,","Tested "+Count.get()+" times, ")
569 | +ErrCount.get()+T("个错误,"," errors, ")
570 | +T("耗时","")+(System.currentTimeMillis()-t1)/1000+T("秒"," seconds total"));
571 | try {
572 | Thread.sleep(1000);
573 | }catch (Exception e) {}
574 | }
575 | }
576 | }).start();
577 |
578 | ReadIn();
579 | Abort.set(true);
580 | ST("多线程并发调用同一个RSA测试已结束。","Multiple threads concurrently calling the same RSA test is over.");
581 | S();
582 | }
583 |
584 |
585 |
586 | static void keyTools() throws Exception {
587 | ST("===== RSA密钥工具:生成密钥、转换密钥格式 ====="
588 | , "===== RSA key tool: generate key, convert key format =====");
589 | ST("请使用下面可用命令进行操作,命令[]内的为可选参数,参数可用\"\"包裹。","Please use the following commands to operate. The parameters in the command `[]` are optional parameters, and the parameters can be wrapped with \"\".");
590 | S(HR);
591 | S("`new 1024 [-pkcs8] [saveFile [puboutFile]]`: "+T("生成新的RSA密钥,指定位数和格式:xml、pkcs1、或pkcs8(默认),提供saveFile可保存私钥到文件,提供puboutFile可额外保存一个公钥文件","Generate a new RSA key, specify the number of digits and format: xml, pkcs1, or pkcs8 (default), provide saveFile to save the private key to a file, and provide puboutFile to save an additional public key file"));
592 | S(HR);
593 | S("`convert -pkcs1 [-pubout] [-swap] oldFile [newFile]`: "+T("转换密钥格式,提供已有密钥文件oldFile(支持xml、pem格式公钥或私钥),指定要转换成的格式:xml、pkcs1、或pkcs8,提供了-pubout时只导出公钥,提供了-swap时交换公钥指数私钥指数(非常规的:私钥加密公钥解密),提供newFile可保存到文件","To convert the key format, provide the existing key file oldFile (support xml, pem format public key or private key), specify the format to be converted into: xml, pkcs1, or pkcs8, only export the public key when -pubout is provided, swap public key exponent and private key exponent when -swap is provided (unconventional: private key encryption and public key decryption), and provide newFile Can save to file"));
594 | S(HR);
595 | S("`exit`: "+T("输入 exit 退出工具","Enter exit to quit the tool"));
596 | loop: while(true){
597 | System.out.print("> ");
598 | String inStr=ReadIn().trim();
599 | if(inStr.length()==0) {
600 | ST("输入为空,请重新输入!如需退出请输入exit","The input is empty, please re-enter! If you need to exit, please enter exit");
601 | continue;
602 | }
603 | if(inStr.toLowerCase().equals("exit")) {
604 | ST("bye! 已退出。","bye! has exited.");
605 | S();
606 | return;
607 | }
608 | ArrayList args=new ArrayList<>();
609 | Pattern exp=Pattern.compile("(-?)(?:([^\"\\s]+)|\"(.*?)\")\\s*");
610 | Matcher m=exp.matcher(inStr);
611 | StringBuffer sb = new StringBuffer();
612 | while(m.find()) {
613 | if(m.group(2)!=null&&m.group(2).length()>0) {
614 | args.add(m.group(1)+m.group(2));
615 | }else {
616 | args.add(m.group(1)+m.group(3));
617 | }
618 | m.appendReplacement(sb, "");
619 | }
620 | m.appendTail(sb);
621 | if(sb.length()>0) {
622 | ST("参数无效:"+sb,"Invalid parameter: "+sb);
623 | continue;
624 | }
625 |
626 | String cmdName=args.get(0).toLowerCase(); args.remove(0);
627 | boolean nextSave=false;
628 | RSA_Util rsa=null; String type="", save="", save2=""; boolean pubOut=false;
629 |
630 | if(cmdName.equals("new")) {// 生成新的pem密钥
631 | type="pkcs8"; String len="";
632 | while(args.size()>0) {
633 | String param=args.get(0),p=param.toLowerCase(); args.remove(0);
634 |
635 | m=Pattern.compile("^(\\d+)$").matcher(p);
636 | if(m.find()) { len=m.group(1); continue; }
637 |
638 | m=Pattern.compile("^-(xml|pkcs1|pkcs8)$").matcher(p);
639 | if(m.find()) { type=m.group(1); continue; }
640 |
641 | if(save.length()==0 && !p.startsWith("-")) { save=param; continue; }
642 | if(save2.length()==0 && !p.startsWith("-")) { save2=param; continue; }
643 |
644 | ST("未知参数:"+param,"Unknown parameter: "+param);
645 | continue loop;
646 | }
647 | if(len.length()==0) { ST("请提供密钥位数!","Please provide key digits!");continue loop; }
648 | try {
649 | rsa=new RSA_Util(Integer.parseInt(len));
650 | }catch(Exception e) {
651 | S(T("生成密钥出错:","Error generating key: ")+e.getMessage());
652 | continue loop;
653 | }
654 | nextSave=true;
655 | }
656 |
657 | if(cmdName.equals("convert")) {// 转换密钥格式
658 | String old=""; boolean swap=false;
659 | while(args.size()>0) {
660 | String param=args.get(0),p=param.toLowerCase(); args.remove(0);
661 |
662 | m=Pattern.compile("^-(xml|pkcs1|pkcs8)$").matcher(p);
663 | if(m.find()) { type=m.group(1); continue; }
664 |
665 | if(p.equals("-pubout")) { pubOut=true; continue; }
666 | if(p.equals("-swap")) { swap=true; continue; }
667 |
668 | if(old.length()==0 && !p.startsWith("-")) { old=param; continue; }
669 |
670 | if(save.length()==0 && !p.startsWith("-")) { save=param; continue; }
671 |
672 | ST("未知参数:"+param,"Unknown parameter: "+param);
673 | continue loop;
674 | }
675 | if(type.length()==0) { ST("请提供要转换成的格式!","Please provide the format to convert to!");continue loop; }
676 | if(old.length()==0) { ST("请提供已有密钥文件!","Please provide an existing key file!");continue loop; }
677 | try {
678 | String oldTxt=new String(ReadFile(old),"utf-8");
679 | rsa=new RSA_Util(oldTxt);
680 | if(swap) rsa=rsa.SwapKey_Exponent_D__Unsafe();
681 | }catch(Exception e) {
682 | S(T("读取密钥文件出错","Error reading key file ")+" ("+old+"): "+e.getMessage());
683 | continue loop;
684 | }
685 | nextSave=true;
686 | }
687 |
688 | while(nextSave) {
689 | String val;
690 | if(type.equals("xml")) {
691 | val=rsa.ToXML(pubOut);
692 | }else {
693 | boolean pkcs8=type.equals("pkcs8");
694 | val=rsa.ToPEM(false).ToPEM(pubOut, pkcs8, pkcs8);
695 | }
696 | if(save.length()==0) {
697 | S(val);
698 | }else {
699 | save=new File(save).getAbsolutePath();
700 | try{
701 | WriteFile(save, val.getBytes("utf-8"));
702 | }catch(Exception e) {
703 | S(T("保存文件出错","Error saving file ")+" ("+save+"): "+e.getMessage());
704 | }
705 | S(T("密钥文件已保存到:","The key file has been saved to: ")+save);
706 | }
707 | if(save2.length()>0) {
708 | save=save2; save2="";
709 | pubOut=true;
710 | continue;
711 | }
712 | S();
713 | continue loop;
714 | }
715 | ST("未知命令:"+cmdName,"Unknown command: "+cmdName);
716 | }
717 | }
718 |
719 |
720 |
721 | static RSA_PEM loadKey=null; static String loadKeyFile="";
722 | /** 设置:加载密钥PEM文件 **/
723 | static void setLoadKey() throws Exception {
724 | String path=ReadPath(T("密钥文件","Key File")
725 | , T(",或文件夹(内含private.pem、test.txt)。或输入'+1024 pkcs8'生成一个新密钥(填写位数、pkcs1、pkcs8)", ", or a folder (containing private.pem, test.txt). Or enter '+1024 pkcs8' to generate a new key (fill in digits, pkcs1, pkcs8) "));
726 | if(path.startsWith("+")) {//创建一个新密钥
727 | Matcher m=Pattern.compile("^\\+(\\d+)\\s+pkcs([18])$",Pattern.CASE_INSENSITIVE).matcher(path);
728 | if(!m.find()) {
729 | ST("格式不正确,请重新输入!","The format is incorrect, please re-enter!");
730 | setLoadKey();
731 | }else {
732 | int keySize=Integer.parseInt(m.group(1));
733 | RSA_Util rsa=new RSA_Util(keySize);
734 | boolean isPkcs8=m.group(2).equals("8");
735 | RSA_PEM pem=rsa.ToPEM(false);
736 | S(keySize+T("位私钥已生成,请复制此文本保存到private.pem文件:"," bit private key has been generated. Please copy this text and save it to the private.pem file:"));
737 | S(pem.ToPEM(false, isPkcs8, isPkcs8));
738 | S(keySize+T("位公钥已生成,请复制此文本保存到public.pem文件:"," bit public key has been generated. Please copy this text and save it to the public.pem file:"));
739 | S(pem.ToPEM(true, isPkcs8, isPkcs8));
740 | waitAnyKey=true;
741 | }
742 | return;
743 | }
744 | if(path.length()==0 && loadKeyFile.length()==0) {
745 | ST("未输入文件,已取消操作","No file input, operation cancelled");
746 | return;
747 | }
748 | if(path.length()==0) {
749 | path=loadKeyFile;
750 | ST("重新加载密钥文件","Reload key file");
751 | }
752 |
753 | if(new File(path).isDirectory()) {
754 | String txtPath=path+File.separator+"test.txt";
755 | path=path+File.separator+"private.pem";
756 | if(!new File(path).exists()) {
757 | ST("此文件夹中没有private.pem文件!","There is no private.pem file in this folder!");
758 | setLoadKey();
759 | return;
760 | }
761 | if(new File(txtPath).exists()) {//顺带加载文件夹里面的目标源文件
762 | loadSrcBytes=ReadFile(txtPath);
763 | loadSrcFile=txtPath;
764 | }
765 | }
766 | String pem=new String(ReadFile(path),"utf-8");
767 | loadKey=RSA_PEM.FromPEM(pem);
768 | loadKeyFile=path;
769 | }
770 |
771 | static byte[] loadSrcBytes=null; static String loadSrcFile="";
772 | /** 设置:加载目标源文件 **/
773 | static void setLoadSrcBytes() throws Exception {
774 | String path=ReadPath(T("目标源文件","Target Source File"), "");
775 | if(path.length()==0 && loadSrcFile.length()==0) {
776 | ST("未输入文件,已取消操作","No file input, operation cancelled");
777 | return;
778 | }
779 | if(path.length()==0) {
780 | path=loadSrcFile;
781 | ST("重新加载目标源文件","Reload target source file");
782 | }
783 | loadSrcBytes=ReadFile(path);
784 | loadSrcFile=path;
785 | }
786 |
787 | static String encType="";
788 | /** 设置加密填充模式 **/
789 | static boolean setEncType() throws Exception {
790 | S(T("请输入加密填充模式","Please enter the encryption Padding mode")
791 | +(encType.length()>0?T(",回车使用当前值",", press Enter to use the current value ")+encType:"")
792 | +T(";填充模式取值可选:","; Padding mode values: ")+String.join(", ", RSA_Util.RSAPadding_Enc_DefaultKeys())
793 | +T(", 或其他支持的值",", or other supported values"));
794 | System.out.print("> ");
795 | String val = ReadIn().trim();
796 | if(val.length()>0) {
797 | encType=val;
798 | }
799 | if(encType.length()==0) {
800 | ST("未设置,已取消操作","Not set, operation canceled");
801 | }
802 | return encType.length()>0;
803 | }
804 | /** 加密 **/
805 | static void execEnc() throws Exception {
806 | String save=loadSrcFile+".enc.bin";
807 | S(T("密钥文件:","Key file: ")+loadKeyFile);
808 | S(T("目标文件:","Target file: ")+loadSrcFile);
809 | S(T("填充模式:","Padding mode: ")+encType+" | "+RSA_Util.RSAPadding_Enc(encType));
810 | ST("正在加密目标源文件...","Encrypting target source file...");
811 | RSA_Util rsa=new RSA_Util(loadKey);
812 | long t1=System.currentTimeMillis();
813 | byte[] data=rsa.Encrypt(encType, loadSrcBytes);
814 | S(T("加密耗时:","Encryption time: ")+(System.currentTimeMillis()-t1)+"ms");
815 | WriteFile(save, data);
816 | S(T("已加密,结果已保存:","Encrypted, the result is saved: ")+save);
817 | }
818 | /** 解密对比 **/
819 | static void execDec() throws Exception {
820 | String encPath=loadSrcFile+".enc.bin";
821 | S(T("密钥文件:","Key file: ")+loadKeyFile);
822 | S(T("密文文件:","Ciphertext file: ")+encPath);
823 | S(T("对比文件:","Compare files: ")+loadSrcFile);
824 | S(T("填充模式:","Padding mode: ")+encType+" | "+RSA_Util.RSAPadding_Enc(encType));
825 | byte[] data=ReadFile(encPath);
826 | ST("正在解密文件...","Decrypting file...");
827 | RSA_Util rsa=new RSA_Util(loadKey);
828 | long t1=System.currentTimeMillis();
829 | byte[] val=rsa.Decrypt(encType, data);
830 | S(T("解密耗时:","Decryption time: ")+(System.currentTimeMillis()-t1)+"ms");
831 | WriteFile(loadSrcFile+".dec.txt",val);
832 | boolean isOk=true;
833 | if(val.length!=loadSrcBytes.length) {
834 | isOk=false;
835 | }else {
836 | for(int i=0;i0?T(",回车使用当前值",", press Enter to use the current value ")+signType:"")
855 | +T(";签名模式取值可选:","; Signature mode values: ")+String.join(", ", RSA_Util.RSAPadding_Sign_DefaultKeys())
856 | +T(", 或其他支持的值",", or other supported values"));
857 | System.out.print("> ");
858 | String val = ReadIn().trim();
859 | if(val.length()>0) {
860 | signType=val;
861 | }
862 | if(signType.length()==0) {
863 | ST("未设置,已取消操作","Not set, operation canceled");
864 | }
865 | return signType.length()>0;
866 | }
867 | /** 签名 **/
868 | static void execSign() throws Exception {
869 | String save=loadSrcFile+".sign.bin";
870 | S(T("密钥文件:","Key file: ")+loadKeyFile);
871 | S(T("目标文件:","Target file: ")+loadSrcFile);
872 | S(T("签名模式:","Signature mode: ")+signType+" | "+RSA_Util.RSAPadding_Sign(signType));
873 | ST("正在给目标源文件签名...","Signing target source file...");
874 | RSA_Util rsa=new RSA_Util(loadKey);
875 | byte[] data=rsa.Sign(signType, loadSrcBytes);
876 | WriteFile(save, data);
877 | S(T("已签名,结果已保存:","Signed, results saved: ")+save);
878 | }
879 | /** 验证签名 **/
880 | static void execVerify() throws Exception {
881 | String binPath=loadSrcFile+".sign.bin";
882 | S(T("密钥文件:","Key file: ")+loadKeyFile);
883 | S(T("目标文件:","Target file: ")+loadSrcFile);
884 | S(T("签名文件:","Signature file: ")+binPath);
885 | S(T("签名模式:","Signature mode: ")+signType+" | "+RSA_Util.RSAPadding_Sign(signType));
886 | byte[] data=ReadFile(binPath);
887 | ST("正在验证签名...","Verifying signature...");
888 | RSA_Util rsa=new RSA_Util(loadKey);
889 | boolean val=rsa.Verify(signType, data, loadSrcBytes);
890 | if(val) {
891 | ST("签名验证成功。","Signature verification successful.");
892 | return;
893 | }
894 | throw new Exception(T("签名验证失败!","Signature verification failed!"));
895 | }
896 |
897 |
898 |
899 |
900 |
901 | /** 调用openssl相关测试代码 **/
902 | static void runOpenSSL(RSA_Util rsa, byte[] data) throws Exception{
903 | String shell="/bin/bash", charset="utf-8";
904 | if(System.getProperty("os.name").toLowerCase().contains("windows")) {
905 | shell="cmd"; charset="gbk";
906 | }
907 |
908 | S(T("正在打开OpenSSL...","Opening OpenSSL...")+" Shell: "+shell);
909 | closeOpenSSL();
910 | openSSLProc=Runtime.getRuntime().exec(new String[] { shell });
911 | openSSLWrite=new BufferedWriter(new OutputStreamWriter(openSSLProc.getOutputStream(), charset));
912 | openSSLRead=new BufferedReader(new InputStreamReader(openSSLProc.getInputStream(), charset));
913 | openSSLErrRead=new BufferedReader(new InputStreamReader(openSSLProc.getErrorStream(), charset));
914 | openSSLBuffer=new StringBuffer();
915 | openSSLErrBuffer=new StringBuffer();
916 | openSSLThread1=new Thread(new Runnable() {
917 | public void run() {
918 | try {
919 | while(true){
920 | String line=openSSLRead.readLine();
921 | if(line!=null) {
922 | openSSLBuffer.append(line).append('\n');
923 | }
924 | }
925 | }catch (Exception e) { }
926 | }
927 | });
928 | openSSLThread2=new Thread(new Runnable() {
929 | public void run() {
930 | try {
931 | while(true){
932 | String line=openSSLErrRead.readLine();
933 | if(line!=null) {
934 | openSSLErrBuffer.append(line).append('\n');
935 | }
936 | }
937 | }catch (Exception e) { }
938 | }
939 | });
940 | openSSLThread1.start();
941 | openSSLThread2.start();
942 |
943 | WriteFile("test_openssl_key.pem", rsa.ToPEM(false).ToPEM_PKCS8(false).getBytes("utf-8"));
944 | WriteFile("test_openssl_data.txt", data);
945 |
946 | byte[] no=new byte[rsa.keySize()/8];
947 | System.arraycopy(data, 0, no, no.length-data.length, data.length);
948 | WriteFile("test_openssl_data.txt.nopadding.txt", no);
949 |
950 | openSSLWrite.write("openssl version\necho "+openSSLBoundary+"\n");
951 | openSSLWrite.flush();
952 | while(true) {
953 | if(openSSLBuffer.indexOf(openSSLBoundary)!=-1) {
954 | if(openSSLErrBuffer.length()>0) {
955 | closeOpenSSL();
956 | throw new Exception(T("打开OpenSSL出错:","Error opening OpenSSL: ")+openSSLErrBuffer.toString().trim());
957 | }
958 | S("OpenSSL Version: "+openSSLBuffer.toString().trim());
959 | break;
960 | }
961 | Thread.sleep(10);
962 | }
963 | }
964 | static private Process openSSLProc;
965 | static private BufferedWriter openSSLWrite;
966 | static private BufferedReader openSSLRead, openSSLErrRead;
967 | static private StringBuffer openSSLBuffer, openSSLErrBuffer;
968 | static private Thread openSSLThread1, openSSLThread2;
969 | static private final String openSSLBoundary="--openSSL boundary--";
970 | static void closeOpenSSL() {
971 | if(openSSLProc==null)return;
972 | try {openSSLWrite.close();}catch(Exception e) { }
973 | try {openSSLRead.close();}catch(Exception e) { }
974 | try {openSSLErrRead.close();}catch(Exception e) { }
975 | try {openSSLProc.destroy();}catch(Exception e) { }
976 | try {openSSLThread1.interrupt(); openSSLThread2.interrupt();}catch(Exception e) { }
977 | openSSLProc=null;
978 | }
979 | static byte[] testOpenSSL(boolean encOrSign, String mode) throws Exception {
980 | boolean debug=false; String cmd="";
981 | String keyFile="test_openssl_key.pem",txtFile="test_openssl_data.txt";
982 | String save=txtFile+(encOrSign?".enc.bin":".sign.bin");
983 | if(encOrSign) {//加密
984 | if(mode.equals("NO")) {
985 | cmd="openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:none -in "+txtFile+".nopadding.txt -inkey "+keyFile+" -out "+save;
986 | } else if(mode.equals("PKCS1")) {
987 | cmd="openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:pkcs1 -in "+txtFile+" -inkey "+keyFile+" -out "+save;
988 | } else if(mode.startsWith("OAEP+")) {
989 | String hash=mode.replace("OAEP+", "").replace("-512/", "512-");
990 | cmd="openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:"+hash+" -in "+txtFile+" -inkey "+keyFile+" -out "+save;
991 | }
992 | }else {//签名
993 | if(mode.startsWith("PKCS1+")) {
994 | String hash=mode.replace("PKCS1+", "").replace("-512/", "512-");
995 | cmd="openssl dgst -"+hash+" -binary -sign "+keyFile+" -out "+save+" "+txtFile;
996 | }else if(mode.startsWith("PSS+")) {
997 | String hash=mode.replace("PSS+", "").replace("-512/", "512-");
998 | cmd="openssl dgst -"+hash+" -binary -out "+txtFile+".hash "+txtFile;
999 | cmd+="\n";
1000 | cmd+="openssl pkeyutl -sign -pkeyopt digest:"+hash+" -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in "+txtFile+".hash -inkey "+keyFile+" -out "+save;
1001 | }
1002 | }
1003 | if(cmd.length()==0) {
1004 | String msg=T("无效mode:","Invalid mode: ")+mode;
1005 | S("[OpenSSL Code Error] "+msg);
1006 | throw new Exception(msg);
1007 | }
1008 | if(new File(save).exists()) {
1009 | new File(save).delete();
1010 | }
1011 |
1012 | if(debug) S("[OpenSSL Cmd]["+mode+"]"+cmd);
1013 | openSSLBuffer.setLength(0);
1014 | openSSLErrBuffer.setLength(0);
1015 | openSSLWrite.write(cmd+"\n");
1016 | openSSLWrite.write("echo "+openSSLBoundary+"\n");
1017 | openSSLWrite.flush();
1018 |
1019 | while (true) {
1020 | if(openSSLBuffer.indexOf(openSSLBoundary)!=-1) {
1021 | if(openSSLErrBuffer.length()>0) {
1022 | if(debug) S("[OpenSSL Error]\n"+openSSLErrBuffer+"\n[End]");
1023 | throw new Exception("OpenSSL Error: "+openSSLErrBuffer.toString().trim());
1024 | }
1025 | if(debug) S("[OpenSSL Output]\n"+openSSLBuffer+"\n[End] save:"+new File(save).getAbsolutePath());
1026 | break;
1027 | }
1028 | Thread.sleep(10);
1029 | }
1030 | return ReadFile(save);
1031 | }
1032 |
1033 | static void showOpenSSLTips() {
1034 | ST("===== OpenSSL中RSA相关的命令行调用命令 ====="
1035 | , "===== RSA-related command-line invocation commands in OpenSSL =====");
1036 | S();
1037 | ST("::先准备一个测试文件 test.txt 里面填少量内容,openssl不支持自动分段加密"
1038 | , "::First prepare a test file test.txt and fill in a small amount of content, openssl does not support automatic segmentation encryption");
1039 | S();
1040 | ST("::生成新密钥", "::Generate new key");
1041 | S("openssl genrsa -out private.pem 1024");
1042 | S();
1043 | ST("::提取公钥PKCS#8","::Extract public key PKCS#8");
1044 | S("openssl rsa -in private.pem -pubout -out public.pem");
1045 | S();
1046 | ST("::转换成RSAPublicKey PKCS#1", "::Convert to RSAPublicKey PKCS#1");
1047 | S("openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public.pem.rsakey");
1048 | ST("::测试RSAPublicKey PKCS#1,不出意外会出错。因为这个公钥里面没有OID,通过RSA_PEM转换成PKCS#8自动带上OID就能正常加密"
1049 | , "::Test RSAPublicKey PKCS#1, no accident will go wrong. Because there is no OID in this public key, it can be encrypted normally by converting RSA_PEM into PKCS#8 and automatically bringing OID");
1050 | S("echo abcd123 | openssl rsautl -encrypt -inkey public.pem.rsakey -pubin");
1051 | S();
1052 | S();
1053 | S();
1054 | ST("::加密和解密,填充方式:PKCS1"
1055 | , "::Encryption and decryption, padding mode: PKCS1");
1056 | S("openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin");
1057 | S("openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt");
1058 | S();
1059 | ST("::加密和解密,填充方式:OAEP+SHA256,掩码生成函数MGF1使用相同的hash算法"
1060 | , "::Encryption and decryption, padding mode: OAEP+SHA256, mask generation function MGF1 uses the same hash algorithm");
1061 | S("openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin");
1062 | S("openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt");
1063 | S();
1064 | S();
1065 | ST("::命令行参数中的sha256可以换成md5、sha1等;如需sha3系列,就换成sha3-256即可"
1066 | , "::The sha256 in the command line parameters can be replaced by md5, sha1, etc.; if you need the sha3 series, you can replace it with sha3-256");
1067 | S();
1068 | S();
1069 | ST("::签名和验证,填充方式:PKCS1+SHA256","::Signature and verification, padding mode: PKCS1+SHA256");
1070 | S("openssl dgst -sha256 -binary -sign private.pem -out test.txt.sign.bin test.txt");
1071 | S("openssl dgst -sha256 -binary -verify public.pem -signature test.txt.sign.bin test.txt");
1072 | S();
1073 | ST("::签名和验证,填充方式:PSS+SHA256 ,salt=-1使用hash长度=256/8,掩码生成函数MGF1使用相同的hash算法"
1074 | , "::Signature and verification, padding mode: PSS+SHA256, salt=-1 use hash length=256/8, mask generation function MGF1 uses the same hash algorithm");
1075 | S("openssl dgst -sha256 -binary -out test.txt.hash test.txt");
1076 | S("openssl pkeyutl -sign -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in test.txt.hash -inkey private.pem -out test.txt.sign.bin");
1077 | S("openssl pkeyutl -verify -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in test.txt.hash -pubin -inkey public.pem -sigfile test.txt.sign.bin");
1078 | S();
1079 | S();
1080 | }
1081 |
1082 |
1083 |
1084 |
1085 | static boolean waitAnyKey=true;
1086 | static void ShowMenu(String[] args) throws Exception {
1087 | if(args!=null && args.length>0) {
1088 | for(String v : args) {
1089 | if(v.startsWith("-zh=")) {
1090 | RSA_PEM.SetLang(v.startsWith("-zh=1")?"zh":"en");
1091 | }
1092 | }
1093 | S(args.length+T("个启动参数:", " startup parameters: ")+String.join(" ", args));
1094 | S();
1095 | }
1096 |
1097 | boolean newRun=true;
1098 | while(true) {
1099 | if(newRun) {
1100 | newRun=false;
1101 | S("====== https://github.com/xiangyuecn/RSA-java ======");
1102 | printEnv();
1103 | S(HR);
1104 | }
1105 |
1106 | boolean isSet=loadKeyFile.length()>0 && loadSrcFile.length()>0;
1107 | String setTips=isSet?"":" "+T("[不可用]请先设置4、5","[Unavailable] Please set 4, 5 first") + " ";
1108 | String floadTips=T("[已加载,修改后需重新加载]","[loaded, need to reload after modification]");
1109 | String fileName=loadSrcFile.length()>0?new File(loadSrcFile).getName():"test.txt";
1110 |
1111 | S(T("【功能菜单】","[ Menu ]")+" Java Version: "+System.getProperty("java.version")+" | "+System.getProperty("os.name"));
1112 | S("1. "+T("测试:运行基础功能测试(1次)","Test: Run basic functional tests (1 time)"));
1113 | S("2. "+T("测试:运行基础功能测试(1000次)","Test: Run basic functional tests (1000 times)"));
1114 | S("3. "+T("测试:多线程并发调用同一个RSA","Test: Multiple threads call the same RSA concurrently"));
1115 | S(HR);
1116 | S("4. "+T("设置:加载密钥PEM文件","Setup: Load key PEM file")+(loadKeyFile.length()>0?" "+floadTips+new File(loadKeyFile).getName()+" "+loadKey.keySize()+" bits":""));
1117 | S("5. "+T("设置:加载目标源文件","Setup: Load Target Source File")+(loadSrcFile.length()>0?" "+floadTips+fileName+" "+loadSrcBytes.length+" Bytes":""));
1118 | S("6. "+T("加密 ","Encrypt")+setTips+" "+fileName+" -> "+fileName+".enc.bin");
1119 | S("7. "+T("解密对比","Decrypt")+setTips+" "+fileName+".enc.bin -> "+fileName+".dec.txt");
1120 | S("8. "+T("签名 ","Sign ")+setTips+" "+fileName+" -> "+fileName+".sign.bin");
1121 | S("9. "+T("验证签名","Verify ")+setTips+" "+fileName+".sign.bin");
1122 | S(HR);
1123 | S("A. "+T("RSA密钥工具:生成密钥、转换密钥格式","RSA key tool: generate key, convert key format"));
1124 | S("B. "+T("显示当前环境支持的加密和签名填充模式,输入 B2 可同时对比OpenSSL结果", "Display the encryption and signature padding modes supported by the current environment, enter B2 to compare OpenSSL results at the same time")
1125 | +" ("+(CanLoad_BouncyCastle()?(BcProvider==null?
1126 | T("可注册BouncyCastle加密增强包","Can register BouncyCastle encryption enhancement package")
1127 | :T("已注册BouncyCastle加密增强包","BouncyCastle encryption enhancement package registered")
1128 | ):T("未检测到BouncyCastle的jar加密增强包","BouncyCastle's jar encryption enhancement package was not detected"))+")");
1129 | S("C. "+T("显示OpenSSL中RSA相关的命令行调用命令","Display RSA-related command line calls in OpenSSL"));
1130 | S("*. "+T("输入 exit 退出,输入 lang=zh|en 切换显示语言","Enter exit to exit, enter lang=zh|en to switch display language"));
1131 | S();
1132 | ST("请输入菜单序号:","Please enter the menu number:");
1133 | System.out.print("> ");
1134 |
1135 | waitAnyKey=true;
1136 | while(true) {
1137 | String inTxt=ReadIn().trim().toUpperCase();
1138 |
1139 | try {
1140 | if(inTxt.equals("1")) {
1141 | RSATest(false);
1142 | } else if(inTxt.equals("2")) {
1143 | for(int i=0;i<1000;i++){ST("第"+i+"次>>>>>",i+"th time>>>>>"); RSATest(true); }
1144 | } else if(inTxt.equals("3")) {
1145 | waitAnyKey=false;
1146 | threadRun();
1147 | } else if(inTxt.equals("4")) {
1148 | waitAnyKey=false;
1149 | setLoadKey();
1150 | } else if(inTxt.equals("5")) {
1151 | waitAnyKey=false;
1152 | setLoadSrcBytes();
1153 | } else if(isSet && inTxt.equals("6")) {
1154 | boolean next=setEncType();
1155 | if(next) {
1156 | execEnc();
1157 | }
1158 | } else if(isSet && inTxt.equals("7")) {
1159 | boolean next=setEncType();
1160 | if(next) {
1161 | execDec();
1162 | }
1163 | } else if(isSet && inTxt.equals("8")) {
1164 | boolean next=setSignType();
1165 | if(next) {
1166 | execSign();
1167 | }
1168 | } else if(isSet && inTxt.equals("9")) {
1169 | boolean next=setSignType();
1170 | if(next) {
1171 | execVerify();
1172 | }
1173 | } else if(inTxt.equals("A")) {
1174 | waitAnyKey=false;
1175 | keyTools();
1176 | } else if(inTxt.equals("B") || inTxt.equals("B2")) {
1177 | testProvider(inTxt.equals("B2"));
1178 | } else if(inTxt.equals("C")) {
1179 | showOpenSSLTips();
1180 | } else if(inTxt.startsWith("LANG=")) {
1181 | waitAnyKey=false; newRun=true;
1182 | if(inTxt.equals("LANG=ZH")) {
1183 | RSA_PEM.SetLang("zh");
1184 | S("已切换语言成简体中文");
1185 | }else if(inTxt.equals("LANG=EN")) {
1186 | RSA_PEM.SetLang("en");
1187 | S("Switched language to English-US");
1188 | }else {
1189 | waitAnyKey=true; newRun=false;
1190 | ST("语言设置命令无效!","Invalid language setting command!");
1191 | }
1192 | } else if(inTxt.equals("EXIT")) {
1193 | S("bye!");
1194 | return;
1195 | } else {
1196 | inTxt="";
1197 | ST("序号无效,请重新输入菜单序号!","The menu number is invalid, please re-enter the menu number!");
1198 | System.out.print("> ");
1199 | continue;
1200 | }
1201 | } catch(Exception e) {
1202 | e.printStackTrace();
1203 | Thread.sleep(100);
1204 | waitAnyKey=true;
1205 | }
1206 | break;
1207 | }
1208 |
1209 | if(waitAnyKey) {
1210 | ST("按回车键继续...","Press Enter to continue...");
1211 | ReadIn();
1212 | }
1213 | S();
1214 | }
1215 | }
1216 |
1217 | }
1218 |
--------------------------------------------------------------------------------
/images/1-en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangyuecn/RSA-java/4aa9814e8e32b79364579a09aa5688bae3133e80/images/1-en.png
--------------------------------------------------------------------------------
/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangyuecn/RSA-java/4aa9814e8e32b79364579a09aa5688bae3133e80/images/1.png
--------------------------------------------------------------------------------
/scripts/Create-jar.bat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangyuecn/RSA-java/4aa9814e8e32b79364579a09aa5688bae3133e80/scripts/Create-jar.bat
--------------------------------------------------------------------------------
/scripts/Create-jar.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #[zh_CN] 在Linux、macOS系统终端中运行这个脚本文件,自动完成java文件编译和打包成jar
3 | #[en_US] Run this script file in the terminal of Linux and macOS system to automatically compile and package java files into jar
4 |
5 |
6 | #[zh_CN] 修改这里指定需要使用的JDK(/结尾bin目录完整路径),否则将使用已安装的默认JDK
7 | #[en_US] Modify here to specify the JDK to be used (full path to the bin directory ending with /), otherwise the installed default JDK will be used
8 | jdkBinDir=""
9 | #jdkBinDir="/home/download/jdk-19.0.1/bin/"
10 |
11 |
12 | clear
13 |
14 | isZh=0
15 | if [ $(echo ${LANG/_/-} | grep -Ei "\\b(zh|cn)\\b") ]; then isZh=1; fi
16 |
17 | function echo2(){
18 | if [ $isZh == 1 ]; then echo $1;
19 | else echo $2; fi
20 | }
21 | cd `dirname $0`
22 | cd ../
23 | echo2 "显示语言:简体中文 `pwd`" "Language: English `pwd`"
24 | echo
25 | function err(){
26 | if [ $isZh == 1 ]; then echo -e "\e[31m$1\e[0m";
27 | else echo -e "\e[31m$2\e[0m"; fi
28 | }
29 | function exit2(){
30 | if [ $isZh == 1 ]; then read -n1 -rp "请按任意键退出..." key;
31 | else read -n1 -rp "Press any key to exit..."; fi
32 | exit
33 | }
34 |
35 |
36 | echo2 "请输入需要生成的jar文件版本号:" "Please enter the version number of the jar file to be generated:"
37 | read -rp "> " jarVer
38 |
39 |
40 | srcDir="target/src"
41 | if [ -e $srcDir ]; then rm -r $srcDir > /dev/null 2>&1; fi
42 | mkdir -p $srcDir
43 | cp RSA_PEM.java $srcDir
44 | cp RSA_Util.java $srcDir
45 | cd $srcDir
46 |
47 |
48 | if [ "$jdkBinDir" == "" ]; then
49 | echo2 "正在读取JDK版本(如需指定JDK为特定版本或目录,请修改本sh文件内jdkBinDir为JDK bin目录):" "Reading the JDK Version (if you need to specify JDK as a specific version or directory, please modify the jdkBinDir in this sh file to the JDK bin directory):"
50 | else
51 | echo2 "正在读取JDK(${jdkBinDir})版本:" "Reading JDK (${jdkBinDir}) Version:"
52 | fi
53 |
54 | ${jdkBinDir}javac -version
55 | [ ! $? -eq 0 ] && {
56 | echo
57 | err "需要安装JDK才能编译运行java文件" "JDK needs to be installed to compile and run java files";
58 | exit2;
59 | }
60 |
61 | echo
62 | echo2 "正在编译Java文件..." "Compiling Java files...";
63 | echo
64 | ${jdkBinDir}javac -encoding utf-8 -cp "./*" RSA_PEM.java RSA_Util.java
65 | [ ! $? -eq 0 ] && {
66 | echo
67 | err "Java文件编译失败" "Java file compilation failed";
68 | exit2;
69 | }
70 | cd ../..
71 |
72 | dir="target/classes/com/github/xiangyuecn/rsajava"
73 | if [ -e target/classes ]; then rm -r target/classes > /dev/null 2>&1; fi
74 | mkdir -p $dir
75 | mv $srcDir/*.class $dir
76 |
77 |
78 | echo2 "编译完成,正在生成jar..." "The compilation is complete, and the jar is being generated..."
79 |
80 |
81 | jarPath="target/rsa-java.lib-${jarVer}.jar"
82 | rm $jarPath > /dev/null 2>&1
83 | [ -e $jarPath ] && {
84 | echo
85 | err "无法删除旧文件:${jarPath}" "Unable to delete old file: ${jarPath}"
86 | exit2;
87 | }
88 |
89 | MANIFEST=target/classes/MANIFEST.MF
90 | echo Manifest-Version: 1.0>$MANIFEST
91 | echo Info-Name: RSA-java>>$MANIFEST
92 | echo Info-Version: ${jarVer}>>$MANIFEST
93 | echo Info-Build-Date: `date '+%Y-%m-%d'`>>$MANIFEST
94 | echo Info-Build-JDK: `javac -version`>>$MANIFEST
95 | echo Info-Copyright: MIT, Copyright `date '+%Y'` xiangyuecn>>$MANIFEST
96 | echo Info-Repository: https://github.com/xiangyuecn/RSA-java>>$MANIFEST
97 |
98 | ${jdkBinDir}jar cfm $jarPath $MANIFEST -C target/classes/ com
99 | [ ! $? -eq 0 ] && {
100 | echo
101 | err "生成jar失败" "Failed to generate jar";
102 | exit2;
103 | }
104 | [ ! -e $jarPath ] && {
105 | echo
106 | err "未找到生成的jar文件:${jarPath}" "Generated jar file not found: ${jarPath}";
107 | exit2;
108 | }
109 | echo
110 | echo2 "已生成jar,文件在源码根目录:${jarPath},请copy这个jar到你的项目中使用。" "The jar has been generated, and the file is in the root directory of the source code: ${jarPath}, please copy this jar to use in your project."
111 | echo
112 |
113 | exit2;
114 |
--------------------------------------------------------------------------------