RSA+AES+Base64实现数据非对称加密解密

前置知识

数据签名:私钥签名,然后公钥进行验签。
私钥签名的过程:先利用摘要算法对明文数据生成数字摘要,然后使用私钥数字摘要进行签名(加密),生成的值称为数字签名,最后把 数字签名+明文数据 发送给对方。
公钥验签的过程:利用公钥数字签名进行验证(解密),拿到解密后的数字摘要,然后利用相同的摘要算法对数据明文生成数字摘要,然后比对解密后的数字摘要和自己生成的数字摘要是否一致,如果一致则说明明文数据没有被篡改过,反之就是被篡改过。

备注1:私钥仅被服务端锁持有,而公钥被多个客户端锁持有。
备注2:常见的摘要算法有MD5、SHA1、SHA256,生成的摘要值长度分别为128位、160位、256位比特。

场景需求

需求1:服务端明文数据进行加密,只有拥有公钥的客户端能对数据进行解密,而且要求具备不可否认的效果,即如果客户端能解密成功,必须代表这些数据是来自服务端(有点签名、验证的味道)。
需求2:客户端加密的数据只能由服务端进行解密,其他客户端无法解密。

解决方案

方案猜想1(RSA)

方案猜想1:利用RSA 私钥、公钥来解决。服务端私钥对明文数据进行加密,客户端公钥进行解密;客户端公钥对明文数据进行加密,服务端私钥对密文数据进行解密。该猜想是否可行?

上面方案猜想理论上可以满足需求,但是实际无法解决。
猜想实际存在问题1:RSA非对称密钥对数据加解密速度很慢,尤其是数据量大的情况。可以参考 Https采用非对称加密+对称加密的原因。
猜想实际存在问题2:RSA加解密对数据明文长度是有限制的,这也是致命问题。

RSA密钥长度和明文长度的关系
在RSA 1024位密钥长度时,最大明文长度限制是117个字节
在RSA 2048位密钥长度时,最大明文长度限制是245个字节
在RSA 4096位密钥长度时,最大明文长度限制是512个字节

综上所述,方案猜想一不成立!

方案猜想2(RSA+AES+Base64)

步骤一:服务端生成随机AES对称密钥,然后利用AES对称密钥明文数据进行加密形成数据密文,然后利用RSA私钥AES对称密钥进行加密,形成对称密钥密文。然后将这两者先进行Base64编码,最后以 “冒号” 进行拼接,然后发给客户端。
步骤二:客户端收到密文后,先用**“冒号”进行分割成两部分,然后别进行进行Base64解码,对称密钥密文和数据密文两部分。客户端先利用公钥对称密钥密文进行解密,拿到对称密钥,然后利用对称密钥数据密文进行解密,拿到数据明文

客户端数据加密只能服务端解密流程也是基本类似。只不过此时是客户端生成随机对称密钥,利用公钥对对称密钥进行加密,服务端使用私钥对对称密钥密文进行解密,拿到对称密钥。

好处1:对称密钥加解密速度比非对称密钥快得多。这里 利用AES对称密钥对较长的明文数据进行加解密,利用RSA对较短的对称密钥进行加解密。
好处2:AES对称密钥长度较短,无需担心 RSA加解密明文数据长度的限制。
好处3:Base64对数据进行编码,可以将一些不可打印字符转为常见的可打印字符。这样的话就无需考虑加密后的数据存在特殊字符之类的问题。

扩展:Base64的64个可打印字符如下:这里是引用

实施过程(RSA+AES256+Base64)

OpenSsl生成RSA私钥和公钥文件

#生成2048位的密钥
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
#从私钥中提取公钥
openssl rsa -pubout -in private_key.pem -out public_key.pem

查看生成的文件
私钥文件:
在这里插入图片描述
公钥文件:
在这里插入图片描述
注:私钥和公钥的数据内容默认都是按Base64进行了编码,所以我们这边才能肉眼查看文件内容。

代码案例

我这里把生成后的私钥和公钥放在D盘根目录下,大家可以根据实际业务来修改。

package com.jxf;

import org.springframework.util.Assert;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;  
  
public class RSASignatureExample {  
  
    // 公钥和私钥文件存放路径
    private static final String PRIVATE_KEY_FILE = "d:/private_key.pem";
    private static final String PUBLIC_KEY_FILE = "d:/public_key.pem";

    private static final String IV = "1234567890123456";

    private static String defaultCharset = "utf-8";

    public static PrivateKey loadPrivateKey() throws Exception {
        byte[] keyBytes = Files.readAllBytes(Paths.get(PRIVATE_KEY_FILE));
        String keyStr = new String(keyBytes)
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
        byte[] encoded = Base64.getDecoder().decode(keyStr);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(keySpec);
    }

    public static PublicKey loadPublicKey() throws Exception {
        byte[] keyBytes = Files.readAllBytes(Paths.get(PUBLIC_KEY_FILE));
        String keyStr = new String(keyBytes)
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s", "");
        X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(keyStr));
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePublic(spec);
    }

    // 使用私钥签名(数据签名,这里没有使用到该方法,可以跳过该方法)
    public static byte[] signData(String data, PrivateKey privateKey) throws Exception {
        Signature signer = Signature.getInstance("SHA256withRSA");
        signer.initSign(privateKey);
        signer.update(data.getBytes());
        return signer.sign();
    }

    // 使用公钥验签(数据验签,这里没有使用到该方法,可以跳过该方法)
    public static boolean verifySignature(String data, byte[] signature, PublicKey publicKey) throws Exception {  
        Signature signer = Signature.getInstance("SHA256withRSA");  
        signer.initVerify(publicKey);  
        signer.update(data.getBytes());
        return signer.verify(signature);  
    }

    public static String encryptData(String rowData, Key key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
        // 生成随机的AES密钥
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(256); // 可以选择128, 192, 或 256位
        SecretKey secretKey = keyGenerator.generateKey();

        // aes对数据加密
        Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // iv参数可以理解为对称加密的盐值
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(defaultCharset));
        aesCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
        byte[] encryptDataBytes = aesCipher.doFinal(rowData.getBytes(defaultCharset));
        String encryptDataBase64 = Base64.getEncoder().encodeToString(encryptDataBytes);

        // rsa对密钥进行加密
        Cipher rsaCipher = Cipher.getInstance("RSA");
        rsaCipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encryptAesSecretBytes = rsaCipher.doFinal(secretKey.getEncoded());
        String encryptAesSecretBase64 = Base64.getEncoder().encodeToString(encryptAesSecretBytes);

        System.out.println("AES length:" + secretKey.getEncoded().length);
        // 将解密后的密钥 和 加密数据进行拼接
        return encryptAesSecretBase64 + ":" + encryptDataBase64;
    }

    public static String decryptData(String encryptedData, Key key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
        String[] split = encryptedData.split(":");
        Assert.isTrue(split.length == 2, "密文格式有误!");

        // 获取加密密钥 和 加密数据
        String encryptedAesSecretBase64 = split[0];
        String encryptedDataBase64 = split[1];

        // rsa对密钥密文进行解密,拿到对称密钥
        Cipher rasCipher = Cipher.getInstance("RSA");
        rasCipher.init(Cipher.DECRYPT_MODE, key);
        byte[] aesSecretBytes = rasCipher.doFinal(Base64.getDecoder().decode(encryptedAesSecretBase64));
        SecretKey aesSecret = new SecretKeySpec(aesSecretBytes, "AES");
        System.out.println("AES length:" + aesSecretBytes.length);

        // 利用对称密钥对数据密文进行解密
        Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // iv参数可以理解为对称加密的盐值
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(defaultCharset));
        aesCipher.init(Cipher.DECRYPT_MODE, aesSecret, ivParameterSpec);
        byte[] rowData = aesCipher.doFinal(Base64.getDecoder().decode(encryptedDataBase64));

        return new String(rowData, defaultCharset);
    }

    public static void main(String[] args) {  
        try {
            // 加载私钥和公钥
            PrivateKey privateKey = loadPrivateKey();
            PublicKey publicKey = loadPublicKey();
            // 数据加密(可以选择私钥加密公钥加密 或者 公钥解密私钥加密)
            String encryptData = encryptData("你好!111111111111111111111111111111111111", privateKey);
            System.out.println(encryptData);
            // 数据解密
            String decryptData = decryptData(encryptData, publicKey);
            System.out.println(decryptData);
        } catch (Exception e) {
            e.printStackTrace();  
        }  
    }  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值