From ab645264dfe02186d86a208c150f82f82753c70c Mon Sep 17 00:00:00 2001 From: Yaser Hsueh Date: Wed, 13 Jul 2022 15:03:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9ERSA=E5=8A=A0=E8=A7=A3?= =?UTF-8?q?=E5=AF=86=E5=92=8C=E7=AD=BE=E5=90=8D=E6=A0=A1=E9=AA=8C=E5=B7=A5?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 9 + src/main/java/com/simaek/util/RSAUtil.java | 173 ++++++++++++++++++ .../java/com/simaek/util/RSAUtilTest.java | 50 +++++ 3 files changed, 232 insertions(+) create mode 100644 src/main/java/com/simaek/util/RSAUtil.java create mode 100644 src/test/java/com/simaek/util/RSAUtilTest.java diff --git a/pom.xml b/pom.xml index 911682f..3a357eb 100644 --- a/pom.xml +++ b/pom.xml @@ -8,6 +8,15 @@ utils 1.0-SNAPSHOT + + + org.junit.jupiter + junit-jupiter + 5.8.2 + test + + + 8 8 diff --git a/src/main/java/com/simaek/util/RSAUtil.java b/src/main/java/com/simaek/util/RSAUtil.java new file mode 100644 index 0000000..5190fa7 --- /dev/null +++ b/src/main/java/com/simaek/util/RSAUtil.java @@ -0,0 +1,173 @@ +package com.simaek.util; + +import javax.crypto.Cipher; +import java.io.ByteArrayOutputStream; +import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * RSA加密解密工具类 + * + * @author Yaser Hsueh + */ +public final class RSAUtil { + private RSAUtil() { + throw new AssertionError(); + } + + private static final int defaultBits = 1024; + /** + * 最大解密明文长度 + */ + private static final int MAX_DECRYPT_BLOCK = defaultBits >> 3; + /** + * 最大加密明文长度 + */ + private static final int MAX_ENCRYPT_BLOCK = (defaultBits >> 3) - 11; + + + /** + * 生成秘钥对(默认长度1024Bits) + */ + public static KeyPair getKeyPair() throws Exception { + return getKeyPair(defaultBits); + } + + /** + * 生成秘钥对 + */ + public static KeyPair getKeyPair(int keySize) throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(keySize); + return keyPairGenerator.generateKeyPair(); + } + + /** + * 获取公钥(Base64编码) + */ + public static String getPublicKey(KeyPair keyPair) { + PublicKey publicKey = keyPair.getPublic(); + byte[] bytes = publicKey.getEncoded(); + return byte2Base64(bytes); + } + + /** + * 获取私钥(Base64编码) + */ + public static String getPrivateKey(KeyPair keyPair) { + PrivateKey privateKey = keyPair.getPrivate(); + byte[] bytes = privateKey.getEncoded(); + return byte2Base64(bytes); + } + + /** + * 将Base64编码后的公钥转换成PublicKey对象 + */ + public static PublicKey string2PublicKey(String pubStr) throws Exception { + byte[] keyBytes = base642Byte(pubStr); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(keySpec); + } + + /** + * 将Base64编码后的私钥转换成PrivateKey对象 + */ + public static PrivateKey string2PrivateKey(String priStr) throws Exception { + byte[] keyBytes = base642Byte(priStr); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(keySpec); + } + + /** + * 公钥加密 + */ + public static byte[] publicEncrypt(byte[] content, PublicKey publicKey) throws Exception { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + return fragment(content, cipher, MAX_ENCRYPT_BLOCK); + } + + /** + * 私钥解密 + */ + public static byte[] privateDecrypt(byte[] content, PrivateKey privateKey) throws Exception { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + return fragment(content, cipher, MAX_DECRYPT_BLOCK); + } + + /** + * 私钥签名 + */ + public static String privateSign(byte[] content, String privateKey) throws Exception { + byte[] keyBytes = base642Byte(privateKey); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec); + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(privateK); + signature.update(content); + return byte2Base64(signature.sign()); + } + + /** + * 公钥验签 + */ + public static boolean publicVerify(byte[] content, String publicKey, String sign) throws Exception { + byte[] keyBytes = base642Byte(publicKey); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicK = keyFactory.generatePublic(keySpec); + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initVerify(publicK); + signature.update(content); + return signature.verify(base642Byte(sign)); + } + + /** + * 字节数组转Base64编码 + */ + public static String byte2Base64(byte[] bytes) { + Base64.Encoder encoder = Base64.getEncoder(); + return encoder.encodeToString(bytes); + } + + /** + * Base64编码转字节数组 + */ + public static byte[] base642Byte(String base64Key) { + Base64.Decoder decoder = Base64.getDecoder(); + return decoder.decode(base64Key); + } + + /** + * 分片处理长度超过RSA支持范文的明文数据 + * + * @param content 加密/解密内容 + * @param cipher 加密/解密算法 + * @param slice 分片长度 + * @return 分片加密/解密后整合的数据 + */ + private static byte[] fragment(byte[] content, Cipher cipher, int slice) throws Exception { + int inputLength = content.length; + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] cache; + int i = 0, offset = 0; + while (inputLength - offset > 0) { + if (inputLength - offset > slice) { + cache = cipher.doFinal(content, offset, slice); + } else { + cache = cipher.doFinal(content, offset, inputLength - offset); + } + byteArrayOutputStream.write(cache, 0, cache.length); + offset = slice * ++i; + } + byte[] resultBytes = byteArrayOutputStream.toByteArray(); + byteArrayOutputStream.close(); + return resultBytes; + } +} \ No newline at end of file diff --git a/src/test/java/com/simaek/util/RSAUtilTest.java b/src/test/java/com/simaek/util/RSAUtilTest.java new file mode 100644 index 0000000..7405d49 --- /dev/null +++ b/src/test/java/com/simaek/util/RSAUtilTest.java @@ -0,0 +1,50 @@ +package com.simaek.util; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.security.KeyPair; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * 测试RSA加密工具类的加密和解密功能,签名和校验功能 + * + * @author Yaser Hsueh + * @see com.simaek.util.RSAUtil + */ +class RSAUtilTest { + private static String publicKeyString; + private static String privateKeyString; + + @BeforeAll + static void before() throws Exception { + final KeyPair keyPair = RSAUtil.getKeyPair(); + publicKeyString = RSAUtil.getPublicKey(keyPair); + privateKeyString = RSAUtil.getPrivateKey(keyPair); + } + + @Test + void encryptAndDecrypt() throws Exception { + final String origin = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + final byte[] bytes = RSAUtil.publicEncrypt(origin.getBytes(), RSAUtil.string2PublicKey(publicKeyString)); + final String base64 = RSAUtil.byte2Base64(bytes); + final byte[] decrypt = RSAUtil.privateDecrypt(RSAUtil.base642Byte(base64), RSAUtil.string2PrivateKey(privateKeyString)); + final String result = new String(decrypt); + System.out.println("Original: " + origin); + System.out.println("Encrypted: " + base64); + System.out.println("Decrypted: " + result); + assertEquals(origin, result); + } + + @Test + void signAndVerify() throws Exception { + final String origin = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + final String sign = RSAUtil.privateSign(origin.getBytes(), privateKeyString); + final boolean verify = RSAUtil.publicVerify(origin.getBytes(), publicKeyString, sign); + System.out.println("Original: " + origin); + System.out.println("Signature: " + sign); + assertTrue(verify); + } +} \ No newline at end of file