diff --git a/cspell.json b/cspell.json index 6482cd7..1e81bb8 100644 --- a/cspell.json +++ b/cspell.json @@ -31,6 +31,7 @@ "mybatis", "Nonnull", "NOSONAR", + "OAEP", "okhttp", "okio", "ooxml", diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/security/RSA.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/security/RSA.java new file mode 100644 index 0000000..7213909 --- /dev/null +++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/security/RSA.java @@ -0,0 +1,115 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.zhouxy.plusone.commons.security; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; + +import javax.crypto.Cipher; + +import com.google.common.annotations.Beta; + +// TODO [优化] 优化实现 +// TODO [测试] 添加单元测试 +// TODO [doc] javadoc、README.md、模块介绍、package-info.java + +/** + * RSA 非对称加密 + * + * @author ZhouXY108 + * @since 1.1.0 + */ +@Beta +public class RSA { + private static final int DEFAULT_KEY_SIZE = 2048; + public static final String NAME = "RSA"; + private static final String TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; + + private final PublicKey publicKey; + private final PrivateKey privateKey; + + private RSA(final PublicKey publicKey, final PrivateKey privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public static RSA of(final PublicKey publicKey, final PrivateKey privateKey) { + return new RSA(publicKey, privateKey); + } + + public static RSA of(final KeyPair keyPair) { + return new RSA(keyPair.getPublic(), keyPair.getPrivate()); + } + + public static RSA withKeySize(int keySize) { + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(NAME); + keyPairGenerator.initialize(keySize); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + return new RSA(keyPair.getPublic(), keyPair.getPrivate()); + } + catch (NoSuchAlgorithmException e) { + throw new SecurityException(e); + } + } + + public static RSA withDefaultKeySize() { + return withKeySize(DEFAULT_KEY_SIZE); + } + + public PrivateKey getPrivateKey() { + return privateKey; + } + + public PublicKey getPublicKey() { + return publicKey; + } + + public byte[] encrypt(byte[] input) throws GeneralSecurityException { + Cipher encryptModeCipher = Cipher.getInstance(TRANSFORMATION); + encryptModeCipher.init(Cipher.ENCRYPT_MODE, publicKey); + return encryptModeCipher.doFinal(input); + } + + public byte[] encrypt(String input) throws GeneralSecurityException { + return encrypt(input.getBytes(StandardCharsets.UTF_8)); + } + + public String encryptToString(String input) throws GeneralSecurityException { + return new BigInteger(1, encrypt(input)).toString(16); + } + + public byte[] decrypt(byte[] input) throws GeneralSecurityException { + Cipher decryptModeCipher = Cipher.getInstance(TRANSFORMATION); + decryptModeCipher.init(Cipher.DECRYPT_MODE, privateKey); + return decryptModeCipher.doFinal(input); + } + + public byte[] decrypt(String input) throws GeneralSecurityException { + return decrypt(new BigInteger(input, 16).toByteArray()); + } + + public String decryptToString(String input) throws GeneralSecurityException { + return new String(decrypt(input)); + } +}