JFinal使用技巧-RSA非对称加密RsaKit工具类

应客户要求部分数据需要使用 “非对称加密算法传递数据”。

记得JF里有个加密工具类AesKit,但是它是对称加密。

该客户对第三方包要求比较严格,会进行审核,比较麻烦。决定模仿AesKit手撸一个RsaKit。

什么是非对称加密?
答:
非对称加密算法需要两个密钥:公开密钥(public key)和 私有密钥(private key)。
公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
非对称加密的过程:
A要向B发送信息,A和B都要产生一对用于加密和解密的公钥和私钥。
A的私钥保密,A的公钥告诉B;B的私钥保密,B的公钥告诉A。
A要给B发送信息时,A用B的公钥加密信息,因为A知道B的公钥。
A将这个消息发给B(已经用B的公钥加密消息)。
B收到这个消息后,B用自己的私钥解密A的消息,其他所有收到这个报文的人都无法解密,因为只有B才有B的私钥。
反过来,B向A发送消息也是一样。
常见的非对称加密算法:
RSA,ECC,DSA...
摘自:https://www.bbsmax.com/A/n2d9QXywdD/

想使用ECC加密来着,但是这个需要导包。最后还是选择RSA算法了,JDK有自带。

废话不多说了,上石马!

import com.jfinal.kit.Base64Kit;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA加密方法
 */
public class RsaKit {

    private final static String ALGORITHM = "RSA";

    /**
     * 直接生成公钥、私钥对象
     */
    public static KeyPair getKeyPair() {
        return getKeyPair(2048);
    }

    public static KeyPair getKeyPair(int keySize) {
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM);
            generator.initialize(keySize);
            return generator.generateKeyPair();
        }catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 公钥加密
     */
    public static String encrypt(RSAPublicKey key, String data){
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            int maxSingleSize = key.getModulus().bitLength() / 8 - 11;
            byte[][] dataArray = splitArray(data.getBytes(), maxSingleSize);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            for (byte[] s : dataArray) {
                out.write(cipher.doFinal(s));
            }
            return Base64Kit.encode(out.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static String encrypt(KeyPair key, String data){
        return encrypt((RSAPublicKey) key.getPublic(), data);
    }
    public static String encrypt(String key, String data){
        return encrypt((RSAPublicKey) toKey(key, true), data);
    }

    /**
     * 私钥解密
     */
    public static String decrypt(RSAPrivateKey keyPair, String data){
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, keyPair);
            int modulusSize = keyPair.getModulus().bitLength() / 8;
            byte[] decodeData = Base64Kit.decode(data);
            byte[][] splitArrays = splitArray(decodeData, modulusSize);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            for (byte[] arr : splitArrays) {
                out.write(cipher.doFinal(arr));
            }
            return new String(out.toByteArray());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    public static String decrypt(KeyPair key, String data){
        return decrypt((RSAPrivateKey) key.getPrivate(), data);
    }
    public static String decrypt(String key, String data){
        return decrypt((RSAPrivateKey) toKey(key, false), data);
    }


    /**
     * 钥对象 转换为 字符串 方便数据库存储
     */
    public static String toStr(Key key){
        return Base64Kit.encode(key.getEncoded());
    }
    public static String toStrByPublic(KeyPair key){
        return toStr(key.getPublic());
    }
    public static String toStrByPrivate(KeyPair key){
        return toStr(key.getPrivate());
    }

    /**
     * 字符串钥 转换为 钥对象 方便使用
     */
    public static <T> T toKey(String key, boolean isPublic){
        try {
            KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
            byte[] bytes = Base64Kit.decode(key);
            if (isPublic){
                return (T) factory.generatePublic(new X509EncodedKeySpec(bytes));
            }
            return (T) factory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }

    private static byte[][] splitArray(byte[] data, int len){
        int dataLen = data.length;
        if (dataLen <= len) {
            return new byte[][]{data};
        }
        byte[][] result = new byte[(dataLen-1)/len + 1][];
        int resultLen = result.length;
        for (int i = 0; i < resultLen; i++) {
            if (i == resultLen - 1) {
                int length = dataLen - len * i;
                byte[] single = new byte[length];
                System.arraycopy(data, len * i, single, 0, length);
                result[i] = single;
                break;
            }
            byte[] single = new byte[len];
            System.arraycopy(data, len * i, single, 0, len);
            result[i] = single;
        }
        return result;
    }

}

测试:

public static void main(String[] args) {
    KeyPair key = RsaKit.getKeyPair();
    String s = "Abc 中文 @#";
    System.out.println(s);

    //加密
    String str = RsaKit.encrypt(key, s);
    //解密
    String s1 = RsaKit.decrypt(key, str);
    System.out.println(str);
    System.out.println(s1);
    System.out.println("-----------");

    //公钥字符串
    String publicStr = RsaKit.toStrByPublic(key);
    //私钥字符串
    String privateStr = RsaKit.toStrByPrivate(key);
    System.out.println(publicStr);
    System.out.println(privateStr);
    System.out.println("-----------");

    //加密
    str = RsaKit.encrypt(publicStr, s);
    //解密
    s1 = RsaKit.decrypt(privateStr, str);
    System.out.println(str);
    System.out.println(s1);
    System.out.println("-----------");

}

结果:

image.png

测试结果方法没问题可以加密解密。


再搭配前端JS使用,我选择jsencrypt.min.js 因为够简单:) 

JS下载地址:https://www.bootcdn.cn/jsencrypt/

比如Controller渲染页面的时候,把公钥放在页面中,供JS调取使用:

<script src="assets/jsencrypt/jsencrypt.min.js"></script>
<script>
    let jse = new JSEncrypt();
    jse.setPublicKey("#(publicKey)");
    var str = jse.encrypt("Abc 中文 @#");
    console.log("加密数据:" + str)
</script>

浏览器控制台打印出:

image.png

复制密文到Java这边再跑一下(相当于js提交到Controller接收参数后去解密一样,这里方便测试):

 public static void main(String[] args) {
        //私钥
        String privateStr = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAINpw8mqG2xmf9rpWMd/hNG/s4NDsdx/3UubjTPtrjif9srBtIGxKg+OGPMjmm9wcHzUC+EKTn3SicOGt/GrLKBTdQjFrNPd+CwurWcJkrm3yf3krAbFl0e5ABWQ/ugMm3KaOA+4E9jPXD6lQqO2BcUu/K7nGSe2z9EXL3DkcFEpAgMBAAECgYBJbfPk35RrQer3W6Qr9Wk1+rRICK990kTJVqXT+l97bIbuTMacIW6rOm9ejOpikqWIWsQ4fxXU4KvyAXkPJOLN9lR2jXxLQ4GP8zVZUYSe4kdfydubWpMt/62E4iF3OmKIM7OJ+9f8iSmGM+Nb+Jzhr39hbej2plbbjSsbaOMXwQJBALoS/P+TvQQMhM+JMnweqm/qE9HI9G4gb9xjp93WZiJnHdSefJh3wdtaKLA8fzhVHP0Z07/WnL6FibSbVaTvGhUCQQC0zC/VEPJjmpff9Q5kI37xtI614wHbNSBgoIj+eUheSz4bOVe8E30Jm+n2MAEBnOYvlfOjyi/Q2htJltCWVwPFAkBxzflFC4P4fPuhvyTMeyj85+qVTVYKIPapkZ3y9RVkhzLcfs5vphc/5KWsHGQm/Q+M0YYL9+PINv5hIvw/syl9AkAt3+V/JaarOU2yCOcW557NS6guZKRS778AZZt9Hl8LdgITPFCTq0o9xu7tha6rrxkFGAJTG/lYAA+Oc5MHopqVAkAfokP2BKDtRjSXzRotJcwvrOJgXTocSrPCBB74BrfChE8KDFDkY0aIFGOQ8UKdhI4tEcETu0jiWULQ9QY126EH";
        //密文
        String str = "NAGhtVY+wbETv5J159R14v1zxTGu0CKQmUJbC8XV3NfuFlKYTrd/WQ+/QpMbLX2YKCkCO7yPfD5srPG7oNUppzYW/G1mQNOxlSkia+M0Im5iFa4IAHwXfpxW2WrmlULJ9fKWN2JRKOWMAfzB4Pkx6S9qie8/cazM06yTIwbQA/0=";
        //解密
        String s1 = RsaKit.decrypt(privateStr, str);

        System.out.println(s1);
        System.out.println("-----------");
    }

image.png

OK!能还原内容。莫马达!

如果是单机使用,可以直接static 秘钥方便使用:

private static final KeyPair keyPair = RsaKit.getKeyPair();

KeyPair支持序列化,所以Redis和EhCache等都可以直接使用。
并且RsaKit加了toStr方法,方便数据库等入库业务。


好了,有参考价值就点个赞呗~ 有特殊业务自行改造吧~

评论区

JFinal

2023-04-02 23:46

越玩越深入了 RSA 非对称加密都在玩了,先赞一个

北流家园网

2023-04-03 08:24

非常有参考价值上,收藏先

dkmilk

2023-04-23 10:04

能用aes加密一下就不错了,我系统过审时要求帐号密码不能是明文,就用aes应付了一下。结果又说表单要防重复提交,我也不得不搞了个表单token,每次提交验证。

杜福忠

2023-04-23 15:00

@dkmilk 开始也是明文+HTTPS,奇安信扫描后说要加密,又用对称加密了。后面他们又找到前端存储的那个秘钥了。。。得~ 又改成非对称了。。。

热门分享

扫码入社