├── LICENSE ├── README-English.md ├── README.md ├── RSA_PEM.java ├── RSA_Util.java ├── Test-Build-Run.bat ├── Test-Build-Run.sh ├── Test.java ├── images ├── 1-en.png └── 1.png └── scripts ├── Create-jar.bat └── Create-jar.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 高坚果 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-English.md: -------------------------------------------------------------------------------- 1 | # :open_book:RSA-java Usage Documentation 2 | 3 | > This document is translated from Chinese to English using Google Translate. 4 | 5 | **Functions of this project: support `PEM` (`PKCS#1`, `PKCS#8`) format RSA key generation, import, and export in `Java` environments; a variety of common RSA encryption signatures padding algorithm support.** 6 | 7 | - Support Java8 (1.8) and above 8 | - RSA can be created through `PEM`, `XML` format keys 9 | - RSA can be created by specifying key digits and key parameters 10 | - Can export `PEM`, `XML` format public key, private key; format mutual conversion 11 | - Public key encryption, private key decryption: `NoPadding`, `PKCS1Padding`, `OAEP+MD5`, `OAEP+SHA1 ... SHA3-512` 12 | - Private key signature, public key verification: `PKCS1+SHA1 ... SHA3-512`, `PKCS1+MD5`, `PSS+SHA1 ... SHA3-512` 13 | - Unconventional: private key encryption, public key decryption, public key signature, private key verification 14 | - Multilingual support: provide Chinese and English language support 15 | - There is also a C# version [RSA-csharp](https://github.com/xiangyuecn/RSA-csharp), all encrypted signature algorithms are interoperable in `Java`, `.NET`, `OpenSSL` 16 | - The source code is simple, and compile and test `.bat|.sh` scripts are provided. The source code can be modified and run without IDE, and can be used by copying 17 | 18 | [​](?) 19 | 20 | You can just copy the `RSA_PEM.java` and `RSA_Util.java` files to your project to use all the functions. You can also clone the entire project code and double-click `Test-Build-Run.bat` to run the test directly (macOS, linux use the terminal to run `.sh`), through the `scripts/Create-jar.bat(sh)` script can generate jar files for project reference. 21 | 22 | The underlying implementation of the `RSA_PEM` class uses bytecode parsing of the PEM file at the binary level, which is simple, lightweight, and zero-dependent; `RSA_Util` is a class that encapsulates RSA operations, it supports most encryption signature modes under higher versions of Java. In addition, it can be used with the jar encryption enhancement package of `BouncyCastle` to obtain richer encryption signature mode support. 23 | 24 | 25 | [​](?) 26 | 27 | **Screenshot of Test-Build-Run.bat test compilation and operation:** 28 | 29 | ![console test](images/1-en.png) 30 | 31 | 32 | [​](?) 33 | 34 | [​](?) 35 | 36 | ## Quick Start: Encryption, Decryption, Signature, Verification 37 | 38 | ### Step 1: Reference RSA-java 39 | - Method 1: Copy the `RSA_PEM.java` and `RSA_Util.java` files directly to your project and use them. 40 | - Method 2: Use the `scripts/Create-jar.bat(sh)` script to package and generate a jar, and add this jar package to the project to use it. 41 | - Method 3: Download the jar file in Releases (that is, the jar packaged by the method 2 script), and add this jar package to the project to use it. 42 | 43 | 44 | ### Step 2: Write the code 45 | ``` java 46 | //Parse pem or xml first, and both public and private keys can be parsed 47 | //RSA_PEM pem=RSA_PEM.FromPEM("-----BEGIN XXX KEY-----....-----END XXX KEY-----"); 48 | //RSA_PEM pem=RSA_PEM.FromXML("...."); 49 | 50 | //Directly create RSA operation classes, which can be created as global objects, and both encryption and decryption signatures support concurrent calls 51 | //RSA_Util rsa=new RSA_Util(pem); 52 | RSA_Util rsa=new RSA_Util(2048); //You can also directly generate a new key, rsa.ToPEM() gets the pem object 53 | 54 | //Optionally register BouncyCastle's jar encryption enhancement package (just register once when the program starts), which is used to be compatible with lower versions of Java, and to implement encrypted signature padding methods that Java does not support; go to https://www.bouncycastle.org/latest_releases.html Download bcprov-jdk**-**.jar 55 | //Security.addProvider(new BouncyCastleProvider()); 56 | //RSA_Util.UseBouncyCastle(BouncyCastleProvider.PROVIDER_NAME); 57 | 58 | //Encrypt with public key, padding mode: PKCS1, you can use OAEP+SHA256 and other padding modes 59 | String enTxt=rsa.Encrypt("PKCS1", "test123"); 60 | //Decrypt with private key 61 | String deTxt=rsa.Decrypt("PKCS1", enTxt); 62 | 63 | //Sign with private key, padding mode: PKCS1+SHA1, PSS+SHA256 and other padding modes can be used 64 | String sign=rsa.Sign("PKCS1+SHA1", "test123"); 65 | //Verify with public key 66 | boolean isVerify=rsa.Verify("PKCS1+SHA1", sign, "test123"); 67 | 68 | //Export PEM text 69 | String pemTxt=rsa.ToPEM(false).ToPEM_PKCS8(false); 70 | 71 | //Unconventional (unsafe, not recommended): private key encryption, public key decryption, public key signature, private key verification 72 | RSA_Util rsaS_Private=rsa.SwapKey_Exponent_D__Unsafe(); 73 | RSA_Util rsaS_Public=new RSA_Util(rsa.ToPEM(true)).SwapKey_Exponent_D__Unsafe(); 74 | //... rsaS_Private.Encrypt rsaS_Public.Decrypt 75 | //... rsaS_Public.Sign rsaS_Private.Verify 76 | 77 | System.out.println(pemTxt+"\n"+enTxt+"\n"+deTxt+"\n"+sign+"\n"+isVerify); 78 | //****For more examples, please read Test.java**** 79 | //****For more functional methods, please read the detailed documentation below**** 80 | ``` 81 | 82 | **If you need function customization, website, app, small program development, etc., please add the QQ group below and contact the group owner (ie the author), thank you~** 83 | 84 | 85 | 86 | [​](?) 87 | 88 | ## [QQ group] communication and support 89 | 90 | Welcome to join QQ group: 421882406, pure lowercase password: `xiangyuecn` 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | [​](?) 100 | 101 | [​](?) 102 | 103 | [​](?) 104 | 105 | [​](?) 106 | 107 | [​](?) 108 | 109 | [​](?) 110 | 111 | # :open_book:Documentation 112 | 113 | ## Encryption Paddings 114 | 115 | > In the table below, BC is the support of BouncyCastle's jar encryption enhancement package (can be registered through the RSA_Util.UseBouncyCastle method); √ means support, × means no support, and other values ​​are supported by a certain version (Java9); Among them, the mask generation function MGF1 of OAEP uses the same Hash algorithm as OAEP, and the encryption and decryption implementation code uniformly adopts: "RSA/ECB/OAEPPadding" mode + configuration parameters makes Java underlying calls; Java's RSA defaults to PKCS1 padding way (Android defaults to NoPadding?). 116 | 117 | Padding|Algorithm|Java|BC 118 | :-|:-|:-:|:-: 119 | NO|RSA/ECB/NoPadding|√|√ 120 | PKCS1 |RSA/ECB/PKCS1Padding|√|√ 121 | OAEP+SHA1 |RSA/ECB/OAEPwithSHA-1andMGF1Padding|√|√ 122 | OAEP+SHA256|RSA/ECB/OAEPwithSHA-256andMGF1Padding|√|√ 123 | OAEP+SHA224|RSA/ECB/OAEPwithSHA-224andMGF1Padding|√|√ 124 | OAEP+SHA384|RSA/ECB/OAEPwithSHA-384andMGF1Padding|√|√ 125 | OAEP+SHA512|RSA/ECB/OAEPwithSHA-512andMGF1Padding|√|√ 126 | OAEP+SHA-512/224|RSA/ECB/OAEPwithSHA-512/224andMGF1Padding|9+|√ 127 | OAEP+SHA-512/256|RSA/ECB/OAEPwithSHA-512/256andMGF1Padding|9+|√ 128 | OAEP+SHA3-256|RSA/ECB/OAEPwithSHA3-256andMGF1Padding|9+|√ 129 | OAEP+SHA3-224|RSA/ECB/OAEPwithSHA3-224andMGF1Padding|9+|√ 130 | OAEP+SHA3-384|RSA/ECB/OAEPwithSHA3-384andMGF1Padding|9+|√ 131 | OAEP+SHA3-512|RSA/ECB/OAEPwithSHA3-512andMGF1Padding|9+|√ 132 | OAEP+MD5 |RSA/ECB/OAEPwithMD5andMGF1Padding|√|√ 133 | 134 | 135 | 136 | ## Signature Paddings 137 | 138 | > In the table below, BC is the support of BouncyCastle's jar encryption enhancement package (can be registered through the RSA_Util.UseBouncyCastle method); √ means support, × means no support, and other values ​​are supported by a certain version (Java11); Among them, the number of salt bytes of PSS is equal to the number of bytes of the Hash algorithm used, the mask generation function MGF1 of PSS uses the same Hash algorithm as PSS, the value of the tracking attribute TrailerField is fixed at 0xBC, and the signature implementation code uniformly adopts: "RSASSA-PSS" mode + configuration parameters makes Java underlying calls. 139 | 140 | Padding|Algorithm|Java|BC 141 | :-|:-|:-:|:-: 142 | SHA1 ... SHA3-512|Same as PKCS1+SHA***|| 143 | PKCS1+SHA1 |SHA1withRSA|√|√ 144 | PKCS1+SHA256|SHA256withRSA|√|√ 145 | PKCS1+SHA224|SHA224withRSA|√|√ 146 | PKCS1+SHA384|SHA384withRSA|√|√ 147 | PKCS1+SHA512|SHA512withRSA|√|√ 148 | PKCS1+SHA-512/224|SHA512/224withRSA|11+|√ 149 | PKCS1+SHA-512/256|SHA512/256withRSA|11+|√ 150 | PKCS1+SHA3-256|SHA3-256withRSA|16+|√ 151 | PKCS1+SHA3-224|SHA3-224withRSA|16+|√ 152 | PKCS1+SHA3-384|SHA3-384withRSA|16+|√ 153 | PKCS1+SHA3-512|SHA3-512withRSA|16+|√ 154 | PKCS1+MD5 |MD5withRSA|√|√ 155 | PSS+SHA1 |SHA1withRSA/PSS|11+|√ 156 | PSS+SHA256|SHA256withRSA/PSS|11+|√ 157 | PSS+SHA224|SHA224withRSA/PSS|11+|√ 158 | PSS+SHA384|SHA384withRSA/PSS|11+|√ 159 | PSS+SHA512|SHA512withRSA/PSS|11+|√ 160 | PSS+SHA-512/224|SHA512/224withRSA/PSS|11+|√ 161 | PSS+SHA-512/256|SHA512/256withRSA/PSS|11+|√ 162 | PSS+SHA3-256|SHA3-256withRSA/PSS|16+|√ 163 | PSS+SHA3-224|SHA3-224withRSA/PSS|16+|√ 164 | PSS+SHA3-384|SHA3-384withRSA/PSS|16+|√ 165 | PSS+SHA3-512|SHA3-512withRSA/PSS|16+|√ 166 | PSS+MD5 |MD5withRSA/PSS|×|√ 167 | 168 | 169 | 170 | [​](?) 171 | 172 | [​](?) 173 | 174 | ## RSA_PEM Class Documentation 175 | The `RSA_PEM.java` file does not depend on any files, you can directly copy this file to use in your project; through `FromPEM`, `ToPEM` and `FromXML`, `ToXML` two pairs of methods, you can implement PEM `PKCS#1`, `PKCS#8` mutual conversion, PEM, XML mutual conversion. 176 | 177 | Note: `openssl rsa -in privateKey -pubout` exports PKCS#8 format public key (used more), `openssl rsa -pubin -in PKCS#8 publicKey -RSAPublicKey_out` exports PKCS#1 format public key (rarely used). 178 | 179 | 180 | ### Static Attributes and Methods 181 | 182 | `RSA_PEM` **FromPEM(String pem)**: Create RSA with PEM format key, support PKCS#1, PKCS#8 format PEM, error will throw an exception. pem format such as: `-----BEGIN XXX KEY-----....-----END XXX KEY-----`. 183 | 184 | `RSA_PEM` **FromXML(String xml)**: Convert the key in XML format to PEM, support public key xml, private key xml, and an exception will be thrown if an error occurs. xml format such as: `....`. 185 | 186 | `String` **T(String zh, String en)**: Simplified multi-language support, returns Chinese or English according to the current language `Lang()` value. 187 | 188 | `String` **Lang()**, **SetLang(String lang)**: Simplified multi-language support, value: `zh` (Simplified Chinese), `en` (English-US), the default value is based on the system, and can be set to the specified language. 189 | 190 | 191 | ### Construction Methods 192 | 193 | **RSA_PEM(RSAPublicKey publicKey, RSAPrivateKey privateKeyOrNull)**: Construct a PEM using the public and private keys in RSA. The private key may not be provided, and the exported PEM only contains the public key. 194 | 195 | **RSA_PEM(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ)**: Construct a PEM through the full amount of PEM field parameters. Except for the modulus and public key exponent that must be provided, other private key exponent information must be provided or not provided at all (the exported PEM only contains the public key) Note: all If the first byte of the parameter is 0, it must be removed first. 196 | 197 | **RSA_PEM(byte[] modulus, byte[] exponent, byte[] dOrNull)**: Construct a PEM through the public key exponent and the private key exponent, and P and Q will be calculated in reverse, but they are extremely unlikely to be the same as the P and Q of the original generated key. Note: If the first byte of all parameters is 0, it must be removed first. Errors will throw exceptions. The private key exponent may not be provided, and the exported PEM only contains the public key. 198 | 199 | 200 | ### Instance Attributes 201 | 202 | `byte[]`: **Key_Modulus**(Modulus n, both public key and private key), **Key_Exponent**(Public key exponent e, both public key and private key), **Key_D**(Private key exponent d, only available when private key); These 3 are enough for encryption and decryption. 203 | 204 | `byte[]`: **Val_P**(prime1), **Val_Q**(prime2), **Val_DP**(exponent1), **Val_DQ**(exponent2), **Val_InverseQ**(coefficient); The private key in PEM has more values; these values can be deduced through n, e, and d (only the effective value is deduced, which is different from the original value with high probability). 205 | 206 | `int` **keySize()**: Key digits. 207 | 208 | `boolean` **hasPrivate()**: Whether to include the private key. 209 | 210 | 211 | ### Instance Methods 212 | 213 | `RSAPublicKey` **getRSAPublicKey()**: Get the public key Java object. 214 | 215 | `RSAPrivateKey` **getRSAPrivateKey()**: Get the private key Java object. If the PEM does not contain the private key, an error will be reported directly. 216 | 217 | `RSA_PEM` **CopyToNew(boolean convertToPublic)**: Copy the key in the current PEM to a new PEM object. convertToPublic: When equal to true, the PEM containing the private key will only return the public key, and the PEM containing only the public key will not be affected. 218 | 219 | `RSA_PEM` **SwapKey_Exponent_D__Unsafe()**: [Unsafe and not recommended] Swap the public key exponent (Key_Exponent) and the private key exponent (Key_D): use the public key as the private key (new.Key_D=this.Key_Exponent) and the private key as the public key (new.Key_Exponent=this.Key_D), returns a new PEM object; for example, used for: private key encryption, public key decryption, this is an unconventional usage. The current object must contain a private key, otherwise an exception will be thrown if it cannot be swapped. Note: It is very insecure to use the public key as a private key, because the public key exponent of most generated keys is 0x10001 (AQAB), which is too easy to guess and cannot be used as a real private key. 220 | 221 | `byte[]` **ToDER(boolean convertToPublic, boolean privateUsePKCS8, boolean publicUsePKCS8)**: Convert the key pair in RSA to DER format. The DER format is binary data before Base64 text encoding in PEM. Refer to the ToPEM method for parameter meanings. 222 | 223 | `String` **ToPEM(boolean convertToPublic, boolean privateUsePKCS8, boolean publicUsePKCS8)**: Convert the key in RSA to PEM format. convertToPublic: When it is equal to true, the RSA containing the private key will only return the public key, and the RSA containing only the public key will not be affected. **privateUsePKCS8**: The return format of the private key, when it is equal to true, it returns the PKCS#8 format (`-----BEGIN PRIVATE KEY-----`), otherwise returns PKCS#1 format (`-----BEGIN RSA PRIVATE KEY-----`), this parameter is invalid when returning a public key; Both formats are used more commonly. **publicUsePKCS8**: The return format of the public key, when it is equal to true, it returns the PKCS#8 format (`-----BEGIN PUBLIC KEY-----`), otherwise returns PKCS#1 format (`-----BEGIN RSA PUBLIC KEY-----`), this parameter is invalid when returning the private key; Generally, the true PKCS#8 format public key is mostly used, and the PKCS#1 format public key seems to be relatively rare. 224 | 225 | `String` **ToPEM_PKCS1(boolean convertToPublic)**: Simplified writing of the ToPEM method, regardless of the public key or the private key, it returns the PKCS#1 format; it seems that the export of the PKCS#1 public key is less used, and the PKCS#8 public key is used more, and the private key #1#8 is almost. 226 | 227 | `String` **ToPEM_PKCS8(boolean convertToPublic)**: Simplified writing of the ToPEM method, regardless of whether the public key or the private key returns the PKCS#8 format. 228 | 229 | `String` **ToXML(boolean convertToPublic)**: Convert the key in RSA to XML format. If convertToPublic, the RSA containing the private key will only return the public key, and the RSA containing only the public key will not be affected. 230 | 231 | 232 | 233 | 234 | [​](?) 235 | 236 | [​](?) 237 | 238 | ## RSA_Util Class Documentation 239 | The `RSA_Util.java` file depends on `RSA_PEM.java`, which encapsulates encryption, decryption, signature, verification, and key import and export operations. 240 | 241 | 242 | ### Static Attributes and Methods 243 | 244 | `String` **RSAPadding_Enc(String padding)**: Convert the encryption padding into the corresponding Algorithm string, such as: `PKCS1 -> RSA/ECB/PKCS1Padding`. 245 | 246 | `String` **RSAPadding_Sign(String hash)**: Convert the signature padding into the corresponding Algorithm string, such as: `PKCS1+SHA1 -> SHA1withRSA`. 247 | 248 | `boolean` **IsJavaLowVerSupportError(Throwable err)**: Determine whether the exception message is an error caused by low version Java compatibility. 249 | 250 | `void` **UseBouncyCastle(String providerName)**: Forces the use of BouncyCastle's jar encryption enhancement package for RSA operations. You only need to call it once after the program starts, you need to call `Security.addProvider(new BouncyCastleProvider())` to register globally first, and then call this method to enable: `UseBouncyCastle(BouncyCastleProvider.PROVIDER_NAME)`, pass in null to cancel enabled. Introduce the BouncyCastle encryption enhancement package into the project to expand the Java encryption function, first go to https://www.bouncycastle.org/latest_releases.html to download bcprov-jdk**-**.jar, and call this method to register when the program starts And enable it to get support for all encrypted signature padding methods. 251 | 252 | `boolean` **IsUseBouncyCastle()**: Whether to force the use of BouncyCastle's jar encryption enhancement package for RSA operations. When true, Java's RSA implementation will not be used. 253 | 254 | 255 | ### Construction Methods 256 | 257 | **RSA_Util(int keySize)**: Create a new RSA with the specified key size, a new key will be generated, and an exception will be thrown if an error occurs. 258 | 259 | **RSA_Util(String pemOrXML)**: Create an RSA with a key in `PEM` or `XML` format, which can be a public key or a private key, and throws an exception if an error occurs. XML format such as: `...`. pem supports `PKCS#1`, `PKCS#8` format, the format is as follows: `-----BEGIN XXX KEY-----....-----END XXX KEY-----`. 260 | 261 | **RSA_Util(RSA_PEM pem)**: Create RSA through a pem object, where pem is a public key or private key, and an exception is thrown if an error occurs. 262 | 263 | 264 | ### Instance Attributes 265 | 266 | `RSAPublicKey` **publicKey**: RSA public key. 267 | 268 | `RSAPrivateKey` **privateKey**: RSA private key, null if there is only public key. 269 | 270 | `int` **keySize()**: Key digits. 271 | 272 | `boolean` **hasPrivate()**: Whether to include the private key. 273 | 274 | 275 | ### Instance Methods 276 | 277 | `String` **ToXML(boolean convertToPublic)**: Export the secret key in XML format. If the RSA contains a private key, the private key will be exported by default. When only the public key is set, only the public key will be exported; if the private key is not included, only the public key will be exported. 278 | 279 | `RSA_PEM` **ToPEM(boolean convertToPublic)**: Export RSA_PEM object (then you can export PEM text by RSA_PEM.ToPEM method), if convertToPublic RSA containing private key will only return public key, RSA containing only public key will not be affected. 280 | 281 | `RSA_Util` **SwapKey_Exponent_D__Unsafe()**: [Unsafe and not recommended] Swap the public key exponent (Key_Exponent) and the private key exponent (Key_D): use the public key as the private key (new.Key_D=this.Key_Exponent) and the private key as the public key (new.Key_Exponent=this.Key_D), returns a new RSA object; for example, used for: private key encryption, public key decryption, this is an unconventional usage. If the current key is a public key, the swap will not occur, and the new RSA returned will allow decryption and signing operations with the public key. Note: It is very unsafe to use a public key as a private key, because the public key exponent of most generated keys is 0x10001 (AQAB), which is too easy to guess and cannot be used as a true private key. In some private key encryption implementations, such as Java's own RSA, when using non-NoPadding padding, encryption with private key objects may use EMSA-PKCS1-v1_5 padding (using the private key exponent to construct a public key object does not have this problem ), so when interoperating between different programs, you may need to use the corresponding padding algorithm to first fill the data, and then use NoPadding padding to encrypt (decryption also uses NoPadding padding to decrypt, and then remove the padding data). 282 | 283 | `String` **Encrypt(String padding, String str)**: Encrypt arbitrary length string (utf-8) returns base64, and an exception is thrown if an error occurs. This method is thread safe. padding specifies the encryption padding, such as: PKCS1, OAEP+SHA256 uppercase, refer to the encryption padding table above, and the default is PKCS1 when using a null value. 284 | 285 | `byte[]` **Encrypt(String padding, byte[] data)**: Encrypt arbitrary length data, and throw an exception if an error occurs. This method is thread safe. 286 | 287 | `String` **Decrypt(String padding, String str)**: Decrypt arbitrary length ciphertext (base64) to get string (utf-8), and throw an exception if an error occurs. This method is thread safe. padding specifies the encryption padding, such as: PKCS1, OAEP+SHA256 uppercase, refer to the encryption padding table above, and the default is PKCS1 when using a null value. 288 | 289 | `byte[]` **Decrypt(String padding, byte[] data)**: Decrypt arbitrary length data, and throw an exception if an error occurs. This method is thread safe. 290 | 291 | `String` **Sign(String hash, String str)**: Sign the string str, return the base64 result, and throw an exception if an error occurs. This method is thread safe. hash specifies the signature digest algorithm and signature padding, such as: SHA256, PSS+SHA1 uppercase, refer to the signature padding table above. 292 | 293 | `byte[]` **Sign(String hash, byte[] data)**: Sign the data, and throw an exception if an error occurs. This method is thread safe. 294 | 295 | `boolean` **Verify(String hash, String sign, String str)**: Verify whether the signature of the string str is sign (base64), and throw an exception if an error occurs. This method is thread safe. hash specifies the signature digest algorithm and signature padding, such as: SHA256, PSS+SHA1 uppercase, refer to the signature padding table above. 296 | 297 | `boolean` **Verify(String hash, byte[] sign, byte[] data)**: Verify whether the signature of data is sign, and throw an exception if an error occurs. This method is thread safe. 298 | 299 | 300 | 301 | 302 | 303 | 304 | [​](?) 305 | 306 | [​](?) 307 | 308 | ## OpenSSL RSA common command line reference 309 | ``` bat 310 | ::First prepare a test file test.txt and fill in a small amount of content, openssl does not support automatic segmentation encryption 311 | 312 | ::Generate new key 313 | openssl genrsa -out private.pem 1024 314 | 315 | ::Extract public key PKCS#8 316 | openssl rsa -in private.pem -pubout -out public.pem 317 | 318 | ::Convert to RSAPublicKey PKCS#1 319 | openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public.pem.rsakey 320 | ::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 321 | echo abcd123 | openssl rsautl -encrypt -inkey public.pem.rsakey -pubin 322 | 323 | 324 | 325 | ::Encryption and decryption, padding mode: PKCS1 326 | openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin 327 | openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt 328 | 329 | ::Encryption and decryption, padding mode: OAEP+SHA256, mask generation function MGF1 uses the same hash algorithm 330 | 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 331 | 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 332 | 333 | 334 | ::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 335 | 336 | 337 | ::Signature and verification, padding mode: PKCS1+SHA256 338 | openssl dgst -sha256 -binary -sign private.pem -out test.txt.sign.bin test.txt 339 | openssl dgst -sha256 -binary -verify public.pem -signature test.txt.sign.bin test.txt 340 | 341 | ::Signature and verification, padding mode: PSS+SHA256, salt=-1 use hash length=256/8, mask generation function MGF1 uses the same hash algorithm 342 | openssl dgst -sha256 -binary -out test.txt.hash test.txt 343 | 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 344 | 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 345 | ``` 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | [​](?) 354 | 355 | [​](?) 356 | 357 | [​](?) 358 | 359 | # :star:Donate 360 | If this library is helpful to you, please star it. 361 | 362 | You can also use Alipay or WeChat to donate to the author: 363 | 364 | ![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/donate-alipay.png) ![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/donate-weixin.png) 365 | 366 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **【[源GitHub仓库](https://github.com/xiangyuecn/RSA-java)】 | 【[Gitee镜像库](https://gitee.com/xiangyuecn/RSA-java)】如果本文档图片没有显示,请手动切换到Gitee镜像库阅读文档。** 2 | 3 | # :open_book:RSA-java使用文档 ( [English Documentation](README-English.md) ) 4 | 5 | **本项目核心功能:支持`Java`环境下`PEM`(`PKCS#1`、`PKCS#8`)格式RSA密钥生成、导入、导出,多种常见RSA加密、签名填充算法支持。** 6 | 7 | - 支持Java8(1.8)及以上版本 8 | - 可通过`PEM`、`XML`格式密钥创建RSA 9 | - 可通过指定密钥位数、密钥参数创建RSA 10 | - 可导出`PEM`、`XML`格式公钥、私钥,格式相互转换 11 | - 公钥加密、私钥解密:`NoPadding`、`PKCS1Padding`、`OAEP+MD5`、`OAEP+SHA1 ... SHA3-512` 12 | - 私钥签名、公钥验证:`PKCS1+SHA1 ... SHA3-512`、`PKCS1+MD5`、`PSS+SHA1 ... SHA3-512` 13 | - 非常规的:私钥加密、公钥解密,公钥签名、私钥验证 14 | - 多语言支持:提供中文、英文两种语言支持 15 | - 另有C#版 [RSA-csharp](https://github.com/xiangyuecn/RSA-csharp),所有加密签名算法在`Java`、`.NET`、`OpenSSL`中均可互通 16 | - 源码简单,提供编译测试`.bat|.sh`脚本,无需IDE即可修改和运行,copy即用 17 | 18 | [​](?) 19 | 20 | 你可以只copy `RSA_PEM.java`、`RSA_Util.java` 文件到你的项目中使用,即可使用上所有的功能。也可以clone整个项目代码双击 `Test-Build-Run.bat` 即可直接运行测试(macOS、linux用终端运行`.sh`的),通过`scripts/Create-jar.bat(sh)`脚本可打包成jar文件供项目引用。 21 | 22 | `RSA_PEM`类底层实现采用PEM文件二进制层面上进行字节码解析,简单轻巧0依赖;`RSA_Util`为封装RSA操作类,在高版本Java下支持大部分加密签名模式,另可选搭配使用`BouncyCastle`的jar加密增强包可获得更丰富的加密签名模式支持。 23 | 24 | 25 | [​](?) 26 | 27 | **Test-Build-Run.bat 测试编译运行截图:** 28 | 29 | ![控制台测试](images/1.png) 30 | 31 | 32 | [​](?) 33 | 34 | [​](?) 35 | 36 | ## 快速使用:加密、解密、签名、校验 37 | 38 | ### 步骤一:引入RSA-java 39 | - 方法1:直接复制 `RSA_PEM.java`、`RSA_Util.java` 文件到你的项目中使用。 40 | - 方法2:使用`scripts/Create-jar.bat(sh)`脚本打包生成jar,项目里添加这个jar包即可使用。 41 | - 方法3:下载Releases中的jar文件(就是方法2脚本打包出的jar),项目里添加这个jar包即可使用。 42 | 43 | 44 | ### 步骤二:编写代码 45 | ``` java 46 | //先解析pem或xml,公钥私钥均可解析 47 | //RSA_PEM pem=RSA_PEM.FromPEM("-----BEGIN XXX KEY-----....-----END XXX KEY-----"); 48 | //RSA_PEM pem=RSA_PEM.FromXML("...."); 49 | 50 | //直接创建RSA操作类,可创建成全局对象,加密解密签名均支持并发调用 51 | //RSA_Util rsa=new RSA_Util(pem); 52 | RSA_Util rsa=new RSA_Util(2048); //也可以直接生成新密钥,rsa.ToPEM()得到pem对象 53 | 54 | //可选注册BouncyCastle的jar加密增强包(程序启动时注册一次即可),用来兼容低版本Java,和实现Java不支持的加密签名填充方式;可到 https://www.bouncycastle.org/latest_releases.html 下载 bcprov-jdk**-**.jar 55 | //Security.addProvider(new BouncyCastleProvider()); 56 | //RSA_Util.UseBouncyCastle(BouncyCastleProvider.PROVIDER_NAME); 57 | 58 | //公钥加密,填充方式:PKCS1,可以使用 OAEP+SHA256 等填充方式 59 | String enTxt=rsa.Encrypt("PKCS1", "测试123"); 60 | //私钥解密 61 | String deTxt=rsa.Decrypt("PKCS1", enTxt); 62 | 63 | //私钥签名,填充方式:PKCS1+SHA1,可以使用 PSS+SHA256 等填充方式 64 | String sign=rsa.Sign("PKCS1+SHA1", "测试123"); 65 | //公钥校验签名 66 | boolean isVerify=rsa.Verify("PKCS1+SHA1", sign, "测试123"); 67 | 68 | //导出pem文本 69 | String pemTxt=rsa.ToPEM(false).ToPEM_PKCS8(false); 70 | 71 | //非常规的(不安全、不建议使用):私钥加密、公钥解密,公钥签名、私钥验证 72 | RSA_Util rsaS_Private=rsa.SwapKey_Exponent_D__Unsafe(); 73 | RSA_Util rsaS_Public=new RSA_Util(rsa.ToPEM(true)).SwapKey_Exponent_D__Unsafe(); 74 | //... rsaS_Private.Encrypt rsaS_Public.Decrypt 75 | //... rsaS_Public.Sign rsaS_Private.Verify 76 | 77 | System.out.println(pemTxt+"\n"+enTxt+"\n"+deTxt+"\n"+sign+"\n"+isVerify); 78 | //****更多的实例,请阅读 Test.java**** 79 | //****更多功能方法,请阅读下面的详细文档**** 80 | ``` 81 | 82 | **如需功能定制,网站、App、小程序开发等需求,请加下面的QQ群,联系群主(即作者),谢谢~** 83 | 84 | 85 | 86 | [​](?) 87 | 88 | ## 【QQ群】交流与支持 89 | 90 | 欢迎加QQ群:421882406,纯小写口令:`xiangyuecn` 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | [​](?) 100 | 101 | [​](?) 102 | 103 | [​](?) 104 | 105 | [​](?) 106 | 107 | [​](?) 108 | 109 | [​](?) 110 | 111 | # :open_book:文档 112 | 113 | ## 加密填充方式 114 | 115 | > 下表中BC为BouncyCastle的jar加密增强包支持情况(可通过RSA_Util.UseBouncyCastle方法注册);√为支持,×为不支持,其他值为某版本开始支持(Java9);其中OAEP的掩码生成函数MGF1使用和OAEP相同的Hash算法,加密解密实现代码中统一采用:"RSA/ECB/OAEPPadding"模式+配置参数 这种形式进行Java底层调用;Java的RSA默认是PKCS1填充方式(Android默认是NoPadding?)。 116 | 117 | 加密填充方式|Algorithm|Java|BC 118 | :-|:-|:-:|:-: 119 | NO|RSA/ECB/NoPadding|√|√ 120 | PKCS1 |RSA/ECB/PKCS1Padding|√|√ 121 | OAEP+SHA1 |RSA/ECB/OAEPwithSHA-1andMGF1Padding|√|√ 122 | OAEP+SHA256|RSA/ECB/OAEPwithSHA-256andMGF1Padding|√|√ 123 | OAEP+SHA224|RSA/ECB/OAEPwithSHA-224andMGF1Padding|√|√ 124 | OAEP+SHA384|RSA/ECB/OAEPwithSHA-384andMGF1Padding|√|√ 125 | OAEP+SHA512|RSA/ECB/OAEPwithSHA-512andMGF1Padding|√|√ 126 | OAEP+SHA-512/224|RSA/ECB/OAEPwithSHA-512/224andMGF1Padding|9+|√ 127 | OAEP+SHA-512/256|RSA/ECB/OAEPwithSHA-512/256andMGF1Padding|9+|√ 128 | OAEP+SHA3-256|RSA/ECB/OAEPwithSHA3-256andMGF1Padding|9+|√ 129 | OAEP+SHA3-224|RSA/ECB/OAEPwithSHA3-224andMGF1Padding|9+|√ 130 | OAEP+SHA3-384|RSA/ECB/OAEPwithSHA3-384andMGF1Padding|9+|√ 131 | OAEP+SHA3-512|RSA/ECB/OAEPwithSHA3-512andMGF1Padding|9+|√ 132 | OAEP+MD5 |RSA/ECB/OAEPwithMD5andMGF1Padding|√|√ 133 | 134 | 135 | 136 | ## 签名填充方式 137 | 138 | > 下表中BC为BouncyCastle的jar加密增强包支持情况(可通过RSA_Util.UseBouncyCastle方法注册);√为支持,×为不支持,其他值为某版本开始支持(Java11);其中PSS的salt字节数等于使用的Hash算法字节数,PSS的掩码生成函数MGF1使用和PSS相同的Hash算法,跟踪属性TrailerField取值固定为0xBC,签名实现代码中统一采用:"RSASSA-PSS"模式+配置参数 这种形式进行Java底层调用。 139 | 140 | 签名填充方式|Algorithm|Java|BC 141 | :-|:-|:-:|:-: 142 | SHA1 ... SHA3-512|等同于PKCS1+SHA***|| 143 | PKCS1+SHA1 |SHA1withRSA|√|√ 144 | PKCS1+SHA256|SHA256withRSA|√|√ 145 | PKCS1+SHA224|SHA224withRSA|√|√ 146 | PKCS1+SHA384|SHA384withRSA|√|√ 147 | PKCS1+SHA512|SHA512withRSA|√|√ 148 | PKCS1+SHA-512/224|SHA512/224withRSA|11+|√ 149 | PKCS1+SHA-512/256|SHA512/256withRSA|11+|√ 150 | PKCS1+SHA3-256|SHA3-256withRSA|16+|√ 151 | PKCS1+SHA3-224|SHA3-224withRSA|16+|√ 152 | PKCS1+SHA3-384|SHA3-384withRSA|16+|√ 153 | PKCS1+SHA3-512|SHA3-512withRSA|16+|√ 154 | PKCS1+MD5 |MD5withRSA|√|√ 155 | PSS+SHA1 |SHA1withRSA/PSS|11+|√ 156 | PSS+SHA256|SHA256withRSA/PSS|11+|√ 157 | PSS+SHA224|SHA224withRSA/PSS|11+|√ 158 | PSS+SHA384|SHA384withRSA/PSS|11+|√ 159 | PSS+SHA512|SHA512withRSA/PSS|11+|√ 160 | PSS+SHA-512/224|SHA512/224withRSA/PSS|11+|√ 161 | PSS+SHA-512/256|SHA512/256withRSA/PSS|11+|√ 162 | PSS+SHA3-256|SHA3-256withRSA/PSS|16+|√ 163 | PSS+SHA3-224|SHA3-224withRSA/PSS|16+|√ 164 | PSS+SHA3-384|SHA3-384withRSA/PSS|16+|√ 165 | PSS+SHA3-512|SHA3-512withRSA/PSS|16+|√ 166 | PSS+MD5 |MD5withRSA/PSS|×|√ 167 | 168 | 169 | 170 | [​](?) 171 | 172 | [​](?) 173 | 174 | ## RSA_PEM 类文档 175 | `RSA_PEM.java`文件不依赖任何文件,可以直接copy这个文件到你项目中用;通过`FromPEM`、`ToPEM` 和`FromXML`、`ToXML`这两对方法,可以实现PEM`PKCS#1`、`PKCS#8`相互转换,PEM、XML的相互转换。 176 | 177 | 注:`openssl rsa -in 私钥文件 -pubout`导出的是PKCS#8格式公钥(用的比较多),`openssl rsa -pubin -in PKCS#8公钥文件 -RSAPublicKey_out`导出的是PKCS#1格式公钥(用的比较少)。 178 | 179 | 180 | ### 静态属性和方法 181 | 182 | `RSA_PEM` **FromPEM(String pem)**:用PEM格式密钥对创建RSA,支持PKCS#1、PKCS#8格式的PEM,出错将会抛出异常。pem格式如:`-----BEGIN XXX KEY-----....-----END XXX KEY-----`。 183 | 184 | `RSA_PEM` **FromXML(String xml)**:将XML格式密钥转成PEM,支持公钥xml、私钥xml,出错将会抛出异常。xml格式如:`....`。 185 | 186 | `String` **T(String zh, String en)**:简版多语言支持,根据当前语言`Lang()`值返回中文或英文。 187 | 188 | `String` **Lang()**、**SetLang(String lang)**:简版多语言支持,取值:`zh`(简体中文)、`en`(English-US),默认根据系统取值,可设为指定的语言。 189 | 190 | 191 | ### 构造方法 192 | 193 | **RSA_PEM(RSAPublicKey publicKey, RSAPrivateKey privateKeyOrNull)**:通过RSA中的公钥和私钥构造一个PEM,私钥可以不提供,导出的PEM就只包含公钥。 194 | 195 | **RSA_PEM(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ)**:通过全量的PEM字段数据构造一个PEM,除了模数modulus和公钥指数exponent必须提供外,其他私钥指数信息要么全部提供,要么全部不提供(导出的PEM就只包含公钥)注意:所有参数首字节如果是0,必须先去掉。 196 | 197 | **RSA_PEM(byte[] modulus, byte[] exponent, byte[] dOrNull)**:通过公钥指数和私钥指数构造一个PEM,会反推计算出P、Q但和原始生成密钥的P、Q极小可能相同。注意:所有参数首字节如果是0,必须先去掉。出错将会抛出异常。私钥指数可以不提供,导出的PEM就只包含公钥。 198 | 199 | 200 | ### 实例属性 201 | 202 | `byte[]`:**Key_Modulus**(模数n,公钥、私钥都有)、**Key_Exponent**(公钥指数e,公钥、私钥都有)、**Key_D**(私钥指数d,只有私钥的时候才有);有这3个足够用来加密解密。 203 | 204 | `byte[]`:**Val_P**(prime1)、**Val_Q**(prime2)、**Val_DP**(exponent1)、**Val_DQ**(exponent2)、**Val_InverseQ**(coefficient); (PEM中的私钥才有的更多的数值;可通过n、e、d反推出这些值(只是反推出有效值,和原始的值大概率不同))。 205 | 206 | `int` **keySize()**:密钥位数。 207 | 208 | `boolean` **hasPrivate()**:是否包含私钥。 209 | 210 | 211 | ### 实例方法 212 | 213 | `RSAPublicKey` **getRSAPublicKey()**:得到公钥Java对象。 214 | 215 | `RSAPrivateKey` **getRSAPrivateKey()**:得到私钥Java对象,如果此PEM不含私钥会直接报错。 216 | 217 | `RSA_PEM` **CopyToNew(boolean convertToPublic)**:将当前PEM中的密钥对复制出一个新的PEM对象。convertToPublic:等于true时含私钥的PEM将只返回公钥,仅含公钥的PEM不受影响。 218 | 219 | `RSA_PEM` **SwapKey_Exponent_D__Unsafe()**:【不安全、不建议使用】对调交换公钥指数(Key_Exponent)和私钥指数(Key_D):把公钥当私钥使用(new.Key_D=this.Key_Exponent)、私钥当公钥使用(new.Key_Exponent=this.Key_D),返回一个新PEM对象;比如用于:私钥加密、公钥解密,这是非常规的用法。当前对象必须含私钥,否则无法交换会直接抛异常。注意:把公钥当私钥使用是非常不安全的,因为绝大部分生成的密钥的公钥指数为 0x10001(AQAB),太容易被猜测到,无法作为真正意义上的私钥。 220 | 221 | `byte[]` **ToDER(boolean convertToPublic, boolean privateUsePKCS8, boolean publicUsePKCS8)**:将RSA中的密钥对转换成DER格式,DER格式为PEM中的Base64文本编码前的二进制数据,参数含义参考ToPEM方法。 222 | 223 | `String` **ToPEM(boolean convertToPublic, boolean privateUsePKCS8, boolean publicUsePKCS8)**:将RSA中的密钥对转换成PEM格式。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 。**privateUsePKCS8**:私钥的返回格式,等于true时返回PKCS#8格式(`-----BEGIN PRIVATE KEY-----`),否则返回PKCS#1格式(`-----BEGIN RSA PRIVATE KEY-----`),返回公钥时此参数无效;两种格式使用都比较常见。**publicUsePKCS8**:公钥的返回格式,等于true时返回PKCS#8格式(`-----BEGIN PUBLIC KEY-----`),否则返回PKCS#1格式(`-----BEGIN RSA PUBLIC KEY-----`),返回私钥时此参数无效;一般用的多的是true PKCS#8格式公钥,PKCS#1格式公钥似乎比较少见。 224 | 225 | `String` **ToPEM_PKCS1(boolean convertToPublic)**:ToPEM方法的简化写法,不管公钥还是私钥都返回PKCS#1格式;似乎导出PKCS#1公钥用的比较少,PKCS#8的公钥用的多些,私钥#1#8都差不多。 226 | 227 | `String` **ToPEM_PKCS8(boolean convertToPublic)**:ToPEM方法的简化写法,不管公钥还是私钥都返回PKCS#8格式。 228 | 229 | `String` **ToXML(boolean convertToPublic)**:将RSA中的密钥对转换成XML格式,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响。 230 | 231 | 232 | 233 | 234 | [​](?) 235 | 236 | [​](?) 237 | 238 | ## RSA_Util 类文档 239 | `RSA_Util.java`文件依赖`RSA_PEM.java`,封装了加密、解密、签名、验证、秘钥导入导出操作。 240 | 241 | 242 | ### 静态属性和方法 243 | 244 | `String` **RSAPadding_Enc(String padding)**:将加密填充方式转换成对应的Algorithm字符串,比如`PKCS1 -> RSA/ECB/PKCS1Padding`。 245 | 246 | `String` **RSAPadding_Sign(String hash)**:将签名填充方式转换成对应的Algorithm字符串,比如`PKCS1+SHA1 -> SHA1withRSA`。 247 | 248 | `boolean` **IsJavaLowVerSupportError(Throwable err)**:判断异常消息是否是因为低版本Java兼容性产生的错误。 249 | 250 | `void` **UseBouncyCastle(String providerName)**:强制使用BouncyCastle的jar加密增强包进行RSA操作。只需在程序启动后调用一次即可,需先调用 `Security.addProvider(new BouncyCastleProvider())` 进行全局注册,然后再调用本方法进行启用:`UseBouncyCastle(BouncyCastleProvider.PROVIDER_NAME)`,传入null取消启用。项目中引入BouncyCastle加密增强包来扩充Java加密功能,先到 https://www.bouncycastle.org/latest_releases.html 下载 bcprov-jdk**-**.jar,在程序启动时调用本方法进行注册和启用即可得到全部的加密签名填充方式支持。 251 | 252 | `boolean` **IsUseBouncyCastle()**:是否强制使用BouncyCastle的jar加密增强包进行RSA操作,为true时将不会使用Java的RSA实现。 253 | 254 | 255 | ### 构造方法 256 | 257 | **RSA_Util(int keySize)**:用指定密钥大小创建一个新的RSA,会生成新密钥,出错抛异常。 258 | 259 | **RSA_Util(String pemOrXML)**:通过`PEM格式`或`XML格式`密钥,创建一个RSA,pem或xml内可以只包含一个公钥或私钥,或都包含,出错抛异常。`XML格式`如:`...`。pem支持`PKCS#1`、`PKCS#8`格式,格式如:`-----BEGIN XXX KEY-----....-----END XXX KEY-----`。 260 | 261 | **RSA_Util(RSA_PEM pem)**:通过一个pem对象创建RSA,pem为公钥或私钥,出错抛异常。 262 | 263 | 264 | ### 实例属性 265 | 266 | `RSAPublicKey` **publicKey**:RSA公钥。 267 | 268 | `RSAPrivateKey` **privateKey**:RSA私钥,仅有公钥时为null。 269 | 270 | `int` **keySize()**:密钥位数。 271 | 272 | `boolean` **hasPrivate()**:是否包含私钥。 273 | 274 | 275 | ### 实例方法 276 | 277 | `String` **ToXML(boolean convertToPublic)**:导出`XML格式`秘钥对,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响。 278 | 279 | `RSA_PEM` **ToPEM(boolean convertToPublic)**:导出RSA_PEM对象(然后可以通过RSA_PEM.ToPEM方法导出PEM文本),如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响。 280 | 281 | `RSA_Util` **SwapKey_Exponent_D__Unsafe()**:【不安全、不建议使用】对调交换公钥指数(Key_Exponent)和私钥指数(Key_D):把公钥当私钥使用(new.Key_D=this.Key_Exponent)、私钥当公钥使用(new.Key_Exponent=this.Key_D),返回一个新RSA对象;比如用于:私钥加密、公钥解密,这是非常规的用法。当前密钥如果是公钥,将不会发生对调,返回的新RSA将允许用公钥进行解密和签名操作。注意:把公钥当私钥使用是非常不安全的,因为绝大部分生成的密钥的公钥指数为 0x10001(AQAB),太容易被猜测到,无法作为真正意义上的私钥。部分私钥加密实现中,比如Java自带的RSA,使用非NoPadding填充方式时,用私钥对象进行加密可能会采用EMSA-PKCS1-v1_5填充方式(用私钥指数构造成公钥对象无此问题),因此在不同程序之间互通时,可能需要自行使用对应填充算法先对数据进行填充,然后再用NoPadding填充方式进行加密(解密也按NoPadding填充进行解密,然后去除填充数据)。 282 | 283 | `String` **Encrypt(String padding, String str)**:加密任意长度字符串(utf-8)返回base64,出错抛异常。本方法线程安全。padding指定填充方式,如:PKCS1、OAEP+SHA256大写,参考上面的加密填充方式表格,使用空值时默认为PKCS1。 284 | 285 | `byte[]` **Encrypt(String padding, byte[] data)**:加密任意长度数据,出错抛异常。本方法线程安全。 286 | 287 | `String` **Decrypt(String padding, String str)**:解密任意长度密文(base64)得到字符串(utf-8),出错抛异常。本方法线程安全。padding指定填充方式,如:PKCS1、OAEP+SHA256大写,参考上面的加密填充方式表格,使用空值时默认为PKCS1。 288 | 289 | `byte[]` **Decrypt(String padding, byte[] data)**:解密任意长度数据,出错抛异常。本方法线程安全。 290 | 291 | `String` **Sign(String hash, String str)**:对字符串str进行签名,返回base64结果,出错抛异常。本方法线程安全。hash指定签名摘要算法和填充方式,如:SHA256、PSS+SHA1大写,参考上面的签名填充方式表格。 292 | 293 | `byte[]` **Sign(String hash, byte[] data)**:对data进行签名,出错抛异常。本方法线程安全。 294 | 295 | `boolean` **Verify(String hash, String sign, String str)**:验证字符串str的签名是否是sign(base64),出错抛异常。本方法线程安全。hash指定签名摘要算法和填充方式,如:SHA256、PSS+SHA1大写,参考上面的签名填充方式表格。 296 | 297 | `boolean` **Verify(String hash, byte[] sign, byte[] data)**:验证data的签名是否是sign,出错抛异常。本方法线程安全。 298 | 299 | 300 | 301 | 302 | 303 | 304 | [​](?) 305 | 306 | [​](?) 307 | 308 | ## OpenSSL RSA常用命令行参考 309 | ``` bat 310 | ::先准备一个测试文件 test.txt 里面填少量内容,openssl不支持自动分段加密 311 | 312 | ::生成新密钥 313 | openssl genrsa -out private.pem 1024 314 | 315 | ::提取公钥PKCS#8 316 | openssl rsa -in private.pem -pubout -out public.pem 317 | 318 | ::转换成RSAPublicKey PKCS#1 319 | openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public.pem.rsakey 320 | ::测试RSAPublicKey PKCS#1,不出意外会出错。因为这个公钥里面没有OID,通过RSA_PEM转换成PKCS#8自动带上OID就能正常加密 321 | echo abcd123 | openssl rsautl -encrypt -inkey public.pem.rsakey -pubin 322 | 323 | 324 | 325 | ::加密和解密,填充方式:PKCS1 326 | openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin 327 | openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt 328 | 329 | ::加密和解密,填充方式:OAEP+SHA256,掩码生成函数MGF1使用相同的hash算法 330 | 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 331 | 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 332 | 333 | 334 | ::命令行参数中的sha256可以换成md5、sha1等;如需sha3系列,就换成sha3-256即可 335 | 336 | 337 | ::签名和验证,填充方式:PKCS1+SHA256 338 | openssl dgst -sha256 -binary -sign private.pem -out test.txt.sign.bin test.txt 339 | openssl dgst -sha256 -binary -verify public.pem -signature test.txt.sign.bin test.txt 340 | 341 | ::签名和验证,填充方式:PSS+SHA256 ,salt=-1使用hash长度=256/8,掩码生成函数MGF1使用相同的hash算法 342 | openssl dgst -sha256 -binary -out test.txt.hash test.txt 343 | 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 344 | 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 345 | ``` 346 | 347 | 348 | 349 | 350 | 351 | 352 | [​](?) 353 | 354 | [​](?) 355 | 356 | [​](?) 357 | 358 | [​](?) 359 | 360 | [​](?) 361 | 362 | [​](?) 363 | 364 | # :open_book:知识库 365 | 366 | 请移步到[RSA-csharp](https://github.com/xiangyuecn/RSA-csharp)阅读知识库部分,知识库内包含了详细的PEM格式解析,和部分ASN.1语法;然后逐字节分解PEM字节码教程。 367 | 368 | 本库的诞生是由于微信付款到银行卡的功能,然后微信提供的RSA公钥接口返回的公钥和openssl -RSAPublicKey_out生成的一样,公钥 PEM 字节码内没有OID(目测是因为不带 OID 所以openssl 自己都不支持用这个公钥来加密数据),这种是不是PKCS#1 格式不清楚(目测是,大部分文章也说是),正反都是难用,所以就撸了一个java版转换代码,也不是难事以前撸过C#的,copy C#的代码过来改改就上线使用了。 369 | 370 | 本库的代码整理未使用IDE,RSA_PEM.java copy过来的,Test.java直接用的文本编辑器编写,*.java文件全部丢到根目录,没有创建包名目录,源码直接根目录裸奔,简单粗暴;这样的项目结构肉眼看去也算是简洁,也方便copy文件使用。 371 | 372 | 373 | [​](?) 374 | 375 | [​](?) 376 | 377 | [​](?) 378 | 379 | # :star:捐赠 380 | 如果这个库有帮助到您,请 Star 一下。 381 | 382 | 您也可以使用支付宝或微信打赏作者: 383 | 384 | ![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/donate-alipay.png) ![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/donate-weixin.png) 385 | -------------------------------------------------------------------------------- /RSA_PEM.java: -------------------------------------------------------------------------------- 1 | package com.github.xiangyuecn.rsajava; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.math.BigInteger; 5 | import java.security.KeyFactory; 6 | import java.security.interfaces.RSAPrivateKey; 7 | import java.security.interfaces.RSAPublicKey; 8 | import java.security.spec.RSAPrivateKeySpec; 9 | import java.security.spec.RSAPublicKeySpec; 10 | import java.util.Base64; 11 | import java.util.Locale; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | /** 16 | * RSA PEM格式秘钥对的解析和导出 17 | * 18 | * GitHub:https://github.com/xiangyuecn/RSA-java 19 | * 20 | * https://github.com/xiangyuecn/RSA-java/blob/master/RSA_PEM.java 21 | * 移植自:https://github.com/xiangyuecn/RSA-csharp/blob/master/RSA_PEM.cs 22 | */ 23 | public class RSA_PEM { 24 | /**modulus 模数,公钥、私钥都有**/ 25 | public byte[] Key_Modulus; 26 | /**publicExponent 公钥指数,公钥、私钥都有**/ 27 | public byte[] Key_Exponent; 28 | /**privateExponent 私钥指数,只有私钥的时候才有**/ 29 | public byte[] Key_D; 30 | 31 | //以下参数只有私钥才有 https://docs.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.rsaparameters?redirectedfrom=MSDN&view=netframework-4.8 32 | /**prime1,只有私钥的时候才有**/ 33 | public byte[] Val_P; 34 | /**prime2,只有私钥的时候才有**/ 35 | public byte[] Val_Q; 36 | /**exponent1,只有私钥的时候才有**/ 37 | public byte[] Val_DP; 38 | /**exponent2,只有私钥的时候才有**/ 39 | public byte[] Val_DQ; 40 | /**coefficient,只有私钥的时候才有**/ 41 | public byte[] Val_InverseQ; 42 | 43 | private RSA_PEM() {} 44 | /*** 45 | * 通过公钥和私钥构造一个PEM 46 | * @param publicKey 必须提供公钥 47 | * @param privateKeyOrNull 私钥可以不提供,导出的PEM就只包含公钥 48 | **/ 49 | public RSA_PEM(RSAPublicKey publicKey, RSAPrivateKey privateKeyOrNull) { 50 | this( 51 | BigB(publicKey.getModulus()) 52 | , BigB(publicKey.getPublicExponent()) 53 | , privateKeyOrNull==null?null:BigB(privateKeyOrNull.getPrivateExponent()) 54 | ); 55 | } 56 | /*** 57 | * 通过全量的PEM字段数据构造一个PEM,除了模数modulus和公钥指数exponent必须提供外,其他私钥指数信息要么全部提供,要么全部不提供(导出的PEM就只包含公钥) 58 | * 注意:所有参数首字节如果是0,必须先去掉 59 | */ 60 | public RSA_PEM(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ) { 61 | Key_Modulus=modulus; 62 | Key_Exponent=exponent; 63 | 64 | if(d!=null){ 65 | Key_D=BigL(d, modulus.length); 66 | 67 | int keyLen = modulus.length / 2; 68 | Val_P=BigL(p, keyLen); 69 | Val_Q=BigL(q, keyLen); 70 | Val_DP=BigL(dp, keyLen); 71 | Val_DQ=BigL(dq, keyLen); 72 | Val_InverseQ=BigL(inverseQ, keyLen); 73 | } 74 | } 75 | /*** 76 | * 通过公钥指数和私钥指数构造一个PEM,会反推计算出P、Q但和原始生成密钥的P、Q极小可能相同 77 | * 注意:所有参数首字节如果是0,必须先去掉 78 | * @param modulus 必须提供模数 79 | * @param exponent 必须提供公钥指数 80 | * @param dOrNull 私钥指数可以不提供,导出的PEM就只包含公钥 81 | **/ 82 | public RSA_PEM(byte[] modulus, byte[] exponent, byte[] dOrNull) { 83 | Key_Modulus=modulus;//modulus 84 | Key_Exponent=exponent;//publicExponent 85 | 86 | if(dOrNull!=null) { 87 | Key_D=BigL(dOrNull, modulus.length);//privateExponent 88 | 89 | //反推P、Q 90 | BigInteger n = BigX(modulus); 91 | BigInteger e = BigX(exponent); 92 | BigInteger d = BigX(dOrNull); 93 | BigInteger p = findFactor(e, d, n); 94 | BigInteger q = n.divide(p); 95 | if (p.compareTo(q) > 0) { 96 | BigInteger t = p; 97 | p = q; 98 | q = t; 99 | } 100 | BigInteger exp1 = d.mod(p.subtract(BigInteger.ONE)); 101 | BigInteger exp2 = d.mod(q.subtract(BigInteger.ONE)); 102 | BigInteger coeff = q.modInverse(p); 103 | 104 | int keyLen = modulus.length / 2; 105 | Val_P=BigL(BigB(p), keyLen);//prime1 106 | Val_Q=BigL(BigB(q), keyLen);//prime2 107 | Val_DP=BigL(BigB(exp1), keyLen);//exponent1 108 | Val_DQ=BigL(BigB(exp2), keyLen);//exponent2 109 | Val_InverseQ=BigL(BigB(coeff), keyLen);//coefficient 110 | } 111 | } 112 | 113 | /**秘钥位数**/ 114 | public int keySize(){ 115 | return Key_Modulus.length*8; 116 | } 117 | /**是否包含私钥**/ 118 | public boolean hasPrivate(){ 119 | return Key_D!=null; 120 | } 121 | /**得到公钥Java对象**/ 122 | public RSAPublicKey getRSAPublicKey() throws Exception { 123 | RSAPublicKeySpec spec=new RSAPublicKeySpec(BigX(Key_Modulus), BigX(Key_Exponent)); 124 | KeyFactory factory=KeyFactory.getInstance("RSA"); 125 | return (RSAPublicKey)factory.generatePublic(spec); 126 | } 127 | /**得到私钥Java对象**/ 128 | public RSAPrivateKey getRSAPrivateKey() throws Exception { 129 | if(Key_D==null) { 130 | throw new Exception("当前为公钥,无法获得私钥"); 131 | } 132 | RSAPrivateKeySpec spec=new RSAPrivateKeySpec(BigX(Key_Modulus), BigX(Key_D)); 133 | KeyFactory factory=KeyFactory.getInstance("RSA"); 134 | return (RSAPrivateKey)factory.generatePrivate(spec); 135 | } 136 | /**转成正整数,如果是负数,需要加前导0转成正整数**/ 137 | static public BigInteger BigX(byte[] bigb) { 138 | if(bigb[0]<0) { 139 | byte[] c=new byte[bigb.length+1]; 140 | System.arraycopy(bigb,0,c,1,bigb.length); 141 | bigb=c; 142 | } 143 | return new BigInteger(bigb); 144 | } 145 | /**BigInt导出byte整数首字节>0x7F的会加0前导,保证正整数,因此需要去掉0**/ 146 | static public byte[] BigB(BigInteger bigx) { 147 | byte[] val=bigx.toByteArray(); 148 | if(val[0]==0) { 149 | byte[] c=new byte[val.length-1]; 150 | System.arraycopy(val,1,c,0,c.length); 151 | val=c; 152 | } 153 | return val; 154 | } 155 | /**某些密钥参数可能会少一位(32个byte只有31个,目测是密钥生成器的问题,只在c#生成的密钥中发现这种参数,java中生成的密钥没有这种现象),直接修正一下就行;这个问题与BigB有本质区别,不能动BigB**/ 156 | static public byte[] BigL(byte[] bytes, int keyLen) { 157 | if (keyLen - bytes.length == 1) { 158 | byte[] c = new byte[bytes.length + 1]; 159 | System.arraycopy(bytes, 0, c, 1, bytes.length); 160 | bytes = c; 161 | } 162 | return bytes; 163 | } 164 | /** 165 | * 由n e d 反推 P Q 166 | * 资料: https://stackoverflow.com/questions/43136036/how-to-get-a-rsaprivatecrtkey-from-a-rsaprivatekey 167 | * https://v2ex.com/t/661736 168 | ***/ 169 | private static BigInteger findFactor(BigInteger e, BigInteger d, BigInteger n) { 170 | BigInteger edMinus1 = e.multiply(d).subtract(BigInteger.ONE); 171 | int s = edMinus1.getLowestSetBit(); 172 | BigInteger t = edMinus1.shiftRight(s); 173 | 174 | long now=System.currentTimeMillis(); 175 | for (int aInt = 2; true; aInt++) { 176 | if(aInt%10==0 && System.currentTimeMillis()-now>3000) { 177 | throw new RuntimeException(T("推算RSA.P超时", "Estimated RSA.P timeout"));//测试最多循环2次,1024位的速度很快 8ms 178 | } 179 | 180 | BigInteger aPow = BigInteger.valueOf(aInt).modPow(t, n); 181 | for (int i = 1; i <= s; i++) { 182 | if (aPow.equals(BigInteger.ONE)) { 183 | break; 184 | } 185 | if (aPow.equals(n.subtract(BigInteger.ONE))) { 186 | break; 187 | } 188 | BigInteger aPowSquared = aPow.multiply(aPow).mod(n); 189 | if (aPowSquared.equals(BigInteger.ONE)) { 190 | return aPow.subtract(BigInteger.ONE).gcd(n); 191 | } 192 | aPow = aPowSquared; 193 | } 194 | } 195 | } 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | /** 209 | * 用PEM格式密钥对创建RSA,支持PKCS#1、PKCS#8格式的PEM 210 | */ 211 | static public RSA_PEM FromPEM(String pem) throws Exception { 212 | RSA_PEM param=new RSA_PEM(); 213 | 214 | String base64 = _PEMCode.matcher(pem).replaceAll(""); 215 | byte[] dataX = Base64.getDecoder().decode(base64);//java byte是正负数 216 | if (dataX == null) { 217 | throw new Exception(T("PEM内容无效", "Invalid PEM content")); 218 | } 219 | short[] data=new short[dataX.length];//转成正整数的bytes数组,不然byte是负数难搞 220 | for(int i=0;i= 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*"); 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 | --------------------------------------------------------------------------------