1、非对称加密算法的加密方式

        对称加密算法使用相同的密钥进行加密,安全性依赖于密钥的保密性,而非对称加密算法则是使用一对密钥(公钥 和 私钥)来进行加解密,公钥(publicKey)加密,私钥(privateKey)解密,通常用于两个系统之间的交互。

常见的非对称加密算法:RSA、ECC(椭圆曲线加密)、DSA(数字签名算法)等

       

A系统生成一对密钥(公钥A 和 私钥A),B系统生成一对密钥(公钥B 和 私钥B)。然后,两个系统分别提供给对方,自己系统的公钥。这样做,即使公钥被捕获也没用,因为解密是通过私钥解密的。而私钥只有自己系统才知道。

交换结果如下:

A系统 B系统
公钥A 公钥B
私钥A 私钥B
公钥B(publicKey-B) 公钥A(publicKey-A)

        B系统要发送加密信息给A系统时,必须使用公钥A进行加密,然后把加密信息发送给A系统。A系统再通过私钥A将获取到的加密信息进行解密。

       A系统回复B系统消息,A系统需使用公钥B将返回信息进行加密,B系统拿到返回的加密信息之后,通过私钥B将返回信息进行解密。

1.0  位(bit)、字节(byte)、字符之间的关系

1字节(byte) = 8bit,但1字符并不一定占用1字节,取决于字符编码。

ASCII编码方式下,每个字符占用 1 字节,包括英文字母、数字、标点符号及一些控制字符。

UTF-8编码方式下,字符长度不固定。ASCII 范围内的字符仍占用1字节,中文字符占用3字节。

GBK编码方式下,中文字符占2字节

2、定义RSA加密工具类

该工具类主要用途:

①生成一对密钥(公钥和私钥)

②提供加解密的方法

2.1  初版RSA工具类(存在数据量过大时,加解密失败)

import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RSAUtil {
    private static String pubKey;

    private static String priKey;


    /**
     * 生成RSA密钥对
     */
    public static void generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        //可以选择1024, 2048, 4096 等
        keyGen.initialize(2048);
        //生成一对密钥,保存在KeyPair中
        KeyPair pair = keyGen.generateKeyPair();

        RSAPublicKey publicKey = (RSAPublicKey) pair.getPublic();
        pubKey = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        System.out.println("随机生成的公钥:\n" + pubKey);

        RSAPrivateKey privateKey = (RSAPrivateKey) pair.getPrivate();
        priKey = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        System.out.println("随机生成的私钥:\n" + priKey);


    }


    /**
     * 使用公钥加密
     *
     * @param data      明文信息
     * @param publicKey 公钥
     * @return
     * @throws Exception
     */
    public static String encrypt(String data, String publicKey) throws Exception {
        byte[] decode = Base64.getDecoder().decode(publicKey);
        //需要以字节数组的形式,导入已有公钥
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decode);
        RSAPublicKey rsaPub = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpec);
        Cipher cipher = Cipher.getInstance("RSA");

        cipher.init(Cipher.ENCRYPT_MODE, rsaPub);
        //执行加密操作
        byte[] encryptedBytes = cipher.doFinal(data.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    /**
     * 使用私钥解密
     *
     * @param encryptData 加密信息
     * @return
     * @throws Exception
     */
    public static String decrypt(String encryptData, String privateKey) throws Exception {
        byte[] decode = Base64.getDecoder().decode(privateKey);
        //导入已有私钥
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decode);
        RSAPrivateKey rsaPri = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keySpec);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, rsaPri);
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptData));
        return new String(decryptedBytes,"UTF-8");
    }

    public static void main(String[] args) {
        RSAUtil rsaUtil = new RSAUtil();
        try {
            //生成密钥对
            generateKeyPair();

            StringBuilder sb = new StringBuilder();
            //构建一个大于245字节的测试数据
            for (int i = 0; i < 200; i++) {
                sb.append(i);
            }

            String data = sb.toString();
            String encryptedData = rsaUtil.encrypt(data, pubKey);
            System.out.println("加密信息: \n" + encryptedData);
            String decryptedText = rsaUtil.decrypt(encryptedData, priKey);
            System.out.println("解密信息: \n" + decryptedText);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

启动运行会提示:加密数据不能超过245字节

 原因是使用RSA加密算法的时候,密钥长度为2048位,默认的填充方案为PKCS1,而明文的字节长度超过了245字节就会报错,至于为什么,代码的注释上会解释。

2.2  工具类2.0版(采用分段加密,解决数据量过大,加解密失败问题)

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.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RSAUtil {
    /**
     * RSA分段加密明文大小
     * 密钥为2048位,对应256字节
     * <p>
     * Cipher.getInstance("RSA")  等价于  RSA/ECB/PKCS1Padding
     * 填充模式:PKCS#1 v1.5 占用 11个字节
     * <p>
     * 分段加密明文 应小于 256 - 11,即为245字节
     * <p>
     * 而分段加密明文大小设置为117字节,是为了兼容曾经密钥长度为1024位的情况
     * <p>
     * 1024位 对应 128字节,减去填充模式占用字节(11字节),就等于117字节
     */
    private static int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA密文块大小(MAX_DECRYPT_BLOCK = 密钥长度/8)
     * <p>
     * 密钥长度(keySize)
     * 2048位(bit) = 256字节(byte)
     * <p>
     * 1024位(bit) = 128字节(byte)
     * <p>
     * <p>
     * RSA的规范,密文大小 必须严格等于 密钥长度,如果不等于这个值,会导致分片失败
     */
    private static int MAX_DECRYPT_BLOCK = 256;

    private static String pubKey;

    private static String priKey;


    /**
     * 生成RSA密钥对
     */
    public static void generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        //密钥长度可以选择 1024位、2048位、4096位
        keyGen.initialize(2048);
        //生成一对密钥,保存在KeyPair中
        KeyPair pair = keyGen.generateKeyPair();

        RSAPublicKey publicKey = (RSAPublicKey) pair.getPublic();
        pubKey = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        System.out.println("随机生成的公钥:\n" + pubKey);

        RSAPrivateKey privateKey = (RSAPrivateKey) pair.getPrivate();
        priKey = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        System.out.println("随机生成的私钥:\n" + priKey);

    }


    /**
     * 使用公钥加密
     *
     * @param data      明文信息
     * @param publicKey 公钥
     * @return
     * @throws Exception
     */
    public static String encrypt(String data, String publicKey) throws Exception {
        byte[] decode = Base64.getDecoder().decode(publicKey);
        //需要以字节数组的形式,导入已有公钥
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decode);
        RSAPublicKey rsaPub = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpec);
        Cipher cipher = Cipher.getInstance("RSA");
        System.out.println("实际算法:" + cipher.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, rsaPub);
        int dataLength = data.getBytes().length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        //如果不想写死,也可以用这种方式获取【分段加密明文大小】
//        MAX_ENCRYPT_BLOCK = rsaPub.getModulus().bitLength() / 8 - 11;
//        System.out.println("明文最大字节数:" + (rsaPub.getModulus().bitLength() / 8 - 11));
        int offset = 0;
        byte[] cache;
        //对数据分段加密,当偏移量offset等于明文字节长度,就说明已经全部分段加密完成,退出循环
        while (offset < dataLength) {
            //min方法取最小值,如果最小值是 MAX_ENCRYPT_BLOCK,说明需要进行分组
            int inputLen = Math.min(MAX_ENCRYPT_BLOCK, dataLength - offset);
            //分割执行加密
            cache = cipher.doFinal(data.getBytes(), offset, inputLen);
            //合并加密块(每个加密块cache肯定等于当前的密钥长度,即256字节)
            out.write(cache, 0, cache.length);
            offset += inputLen;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return Base64.getEncoder().encodeToString(encryptedData);
    }

    /**
     * 使用私钥解密
     *
     * @param encryptData 加密信息
     * @return
     * @throws Exception
     */
    public static String decrypt(String encryptData, String privateKey) throws Exception {
        //将私钥通过base64编码转为字节数组
        byte[] privateKeyDecode = Base64.getDecoder().decode(privateKey);
        //导入已有私钥
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyDecode);
        RSAPrivateKey rsaPri = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keySpec);

        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, rsaPri);

        //将加密信息通过base64编码转为字节数组
        byte[] dataBytes = Base64.getDecoder().decode(encryptData.getBytes("UTF-8"));
        int dataLength = dataBytes.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        // 对加密块进行解密
        while (offset < dataLength) {
        
            cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
            out.write(cache, 0, cache.length);
            offset += MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return new String(decryptedData, "UTF-8");
    }

    public static void main(String[] args) {
        RSAUtil rsaUtil = new RSAUtil();
        try {
            //生成密钥对
            generateKeyPair();

            StringBuilder sb = new StringBuilder();
            //构建一个大于245字节的测试数据
            for (int i = 0; i < 1111; i++) {
                sb.append(i);
            }

            String data = sb.toString();
            String encryptedData = rsaUtil.encrypt(data, pubKey);
            System.out.println("加密信息: \n" + encryptedData);
            String decryptedText = rsaUtil.decrypt(encryptedData, priKey);
            System.out.println("解密信息: \n" + decryptedText);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

以2048位密钥长度(即256字节)为例:

操作阶段 填充模式 长度约束 是否受填充模式影响
加密之前

PSCK#1

(11字节)

明文的字节长度 <= 密钥字节数(256字节) - 填充开销(11字节) 受影响
解密输入

PSCK#1

(11字节)

密文块 = 密钥的字节长度 不受影响

不同的填充模式,占用的字节不同,这里不花过多篇幅解释。

由于每个密文块都是256字节长度,所以合并起来的密文字节长度肯定是256的整数倍。

3、关键方法解释

Cipher的doFinal(byte[] input, int inputOffset, int inputLen) 方法

input: 输入字节数组,表示要进行最终转换的数据源。

inputOffset: 整数类型的偏移量,指定了输入数组中开始读取的位置。这意味着实际参与运算的是从该位置起始的一段子数组。

inputLen: 表示需要被处理的有效长度,即从inputOffset开始往后数inputLen个字节长度,这部分的连续字节,要参与到加/解密的操作中。

4、为什么AES不需要分段进行加解密?

因为AES对称加密算法,属于分组加密算法,本身就支持数据分块处理,其工作模式如CBC,会自动进行分组迭代加密,配合PKCS7填充模式,保证数据完整性。

RSA属于块加密算法,要求需要加密的明文必须作为整体进行处理,但明文的字节长度又受到密钥长度的影响(2048位密钥一次最多只能支持加密245字节长度的明文),所以需要分段加密。

AES设计目标就是针对大数据,数据长度通过分组迭代处理,也就没有限制。

RSA设计是用于小数据安全(如加密AES的密钥),直接加密大数据,其耗时会大大增加。

5、拓展--混合加密

如果觉得RSA分段加密麻烦,还可以使用AES+RSA进行混合加密。

首先,用AES将数据进行加密,然后RSA只需要加密AES的Key就行了,而AES的Key就相对较短,AES128只有16字节、AES192只有24字节、AES256只有32字节。

然后,解密就用RSA的私钥解密,拿到AES的Key,最后,在用AES解密,拿到真正的数据。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐