一、前言

1.1 问题思考

  1. 为什么需要加密 / 解密?
  2. 信息泄露可能造成什么影响?

二、 基础回顾

2.1 加密技术

加密技术是最常用的安全保密手段,利用技术手段把重要的数据变为乱码(加密)传送,到达目的地后再用相同或不同的手段还原(解密)。

加密技术包括两个元素:算法和密钥。算法是将普通的信息或者可以理解的信息与一串数字(密钥)结合,产生不可理解的密文的步骤,密钥是用来对数据进行编码和解密的一种算法。在安全保密中,可通过适当的钥加密技术和管理机制来保证网络的信息通信安全。简言之:

  • 算法:加密 / 解密所使用的转换规则
  • 密钥:加密 / 解密所使用的指令或代码

2.2 加密的目的与方式

加密目的:就是为了保护数据在存储状态下和在传输过程中,不被窃取、解读和利用。简单的说:确保数据的机密性和保护信息的完整性;
加密的方式:包括单向散列加密、对称加密、非对称加密三种。

2.3 加密方式简述

方式一:单向散列加密

根据输入长度信息进行散列计算,得到固定长度输出,常用于密码保存,常见的是MD5,SHA等,通常会加盐处理;

java pem格式 rsa 公钥 java rsa 解密_java


特点: 加密效率高、单方向加密

安全性:不安全(相对于对称加密)

使用情况:比较主流的加密方式

方式二:对称加密

采用单钥密码系统加密方法,同一个密钥可以同时用作信息的加密和解密。常见有AES

java pem格式 rsa 公钥 java rsa 解密_RSA_02


特点:加密效率高、双方使用的密钥相同

安全性:不安全(相对于非对称加密)

使用情况:比较主流的加密方式

方式三:非对称加密

加密和解密使用的是不同的秘钥,其中一个对外公开,称为公钥,另一个被称为私钥。若使用公钥对数据进行加密,则只有使用对应的私钥才能解密,反之亦然。常见的有RSA。

java pem格式 rsa 公钥 java rsa 解密_java pem格式 rsa 公钥_03


关于密钥

  • 公钥:任何人都可以持有,一般用于加密作用
  • 私钥:只有自己持有,一般用于数字签名,签名的数据,可以证明是私钥持有人发送的数据,私钥签名的数据,私钥持有人无法否认自己发送这个消息

特点

  1. 公钥加密的只有对应的私钥能解开
  2. 加密解密效率很低,一般不做大量数据加解密使用

安全性较高,使用的时候,一般配合对称机密使用,建立之初先使用非对称加密,协商好对称加密的算法和密钥,然后使用对称加密,进行后续加解密。

比如:使用对称加密加密很大的数据,非对称加密可以加密对称加密的密钥,报文一起传输,这样既保证了安全性,又保证了加密效率。

三、RSA加密实践

3.1 RSA 加解密工具类

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @contract: 公众号:技术能量站
 * @desc: RSA加密和解密工具-普通版
 */
public class RSAUtil01 {

    /**
     * 数字签名,密钥算法
     */
    private static final String RSA_KEY_ALGORITHM = "RSA";

    /**
     * 数字签名签名/验证算法
     */
    private static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    /**
     * 公钥 key
     */
    private static final String PUBLIC_KEY = "RSAPublicKey";

    /**
     * 私钥 key
     */
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * RSA密钥长度,RSA算法的默认密钥长度是1024密钥长度必须是64的倍数,在512到65536位之间
     */
    private static final int KEY_SIZE = 1024;

    /**
     * 生成密钥对
     */
    private static Map<String, String> initKey() throws Exception {
        KeyPairGenerator keygen = KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM);
        SecureRandom secrand = new SecureRandom();

        /**
         * 初始化随机产生器
         */
        secrand.setSeed("initSeed".getBytes());

        /**
         * 初始化密钥生成器
         * 设置密钥对的bit数 越大越安全
         */
        keygen.initialize(KEY_SIZE, secrand);

        KeyPair keys = keygen.genKeyPair();
        // 获取公钥
        byte[] pub_key = keys.getPublic().getEncoded();
        String publicKeyString = Base64.encodeBase64String(pub_key);

        // 获取私钥
        byte[] pri_key = keys.getPrivate().getEncoded();
        String privateKeyString = Base64.encodeBase64String(pri_key);

        Map<String, String> keyPairMap = new HashMap<>();
        keyPairMap.put(PUBLIC_KEY, publicKeyString);
        keyPairMap.put(PRIVATE_KEY, privateKeyString);

        return keyPairMap;
    }

    /**
     * 密钥转成字符串
     *
     * @param key
     * @return
     */
    public static String encodeBase64String(byte[] key) {
        return Base64.encodeBase64String(key);
    }

    /**
     * 密钥转成byte[]
     *
     * @param key
     * @return
     */
    public static byte[] decodeBase64(String key) {
        return Base64.decodeBase64(key);
    }

    /**
     * 公钥加密
     *
     * @param data      加密前的字符串
     * @param publicKey 公钥
     * @return 加密后的字符串
     * @throws Exception
     */
    public static String encryptByPubKey(String data, String publicKey) throws Exception {
        byte[] pubKey = RSAUtil01.decodeBase64(publicKey);
        byte[] enSign = encryptByPubKey(data.getBytes(), pubKey);
        return Base64.encodeBase64String(enSign);
    }

    /**
     * 公钥加密
     *
     * @param data   待加密数据
     * @param pubKey 公钥
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPubKey(byte[] data, byte[] pubKey) throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥加密
     *
     * @param text       加密前的字符串
     * @param privateKey 私钥
     * @return 加密后的字符串
     * @throws Exception
     */
    public static String encryptByPriKey(String text, String privateKey) {
        try {
            byte[] priKey = RSAUtil01.decodeBase64(privateKey);
            byte[] enSign = encryptByPriKey(text.getBytes(), priKey);
            return Base64.encodeBase64String(enSign);
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + text + "]时遇到异常", e);
        }
    }

    /**
     * 私钥加密
     *
     * @param data   待加密的数据
     * @param priKey 私钥
     * @return 加密后的数据
     * @throws Exception
     */
    public static byte[] encryptByPriKey(byte[] data, byte[] priKey) throws Exception {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     *
     * @param data   待解密的数据
     * @param pubKey 公钥
     * @return 解密后的数据
     * @throws Exception
     */
    public static byte[] decryptByPubKey(byte[] data, byte[] pubKey) throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     *
     * @param data      解密前的字符串
     * @param publicKey 公钥
     * @return 解密后的字符串
     * @throws Exception
     */
    public static String decryptByPubKey(String data, String publicKey) throws Exception {
        byte[] pubKey = RSAUtil01.decodeBase64(publicKey);
        byte[] design = decryptByPubKey(Base64.decodeBase64(data), pubKey);
        return new String(design);
    }

    /**
     * 私钥解密
     *
     * @param data   待解密的数据
     * @param priKey 私钥
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPriKey(byte[] data, byte[] priKey) throws Exception {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥解密
     *
     * @param secretText 解密前的字符串
     * @param privateKey 私钥
     * @return 解密后的字符串
     * @throws Exception
     */
    public static String decryptByPriKey(String secretText, String privateKey) {
        try {
            byte[] priKey = RSAUtil01.decodeBase64(privateKey);
            byte[] design = decryptByPriKey(Base64.decodeBase64(secretText), priKey);
            return new String(design);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + secretText + "]时遇到异常", e);
        }
    }

    /**
     * RSA签名
     *
     * @param data   待签名数据
     * @param priKey 私钥
     * @return 签名
     * @throws Exception
     */
    public static String sign(byte[] data, byte[] priKey) throws Exception {
        // 取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        // 生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        // 实例化Signature
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 初始化Signature
        signature.initSign(privateKey);
        // 更新
        signature.update(data);
        return Base64.encodeBase64String(signature.sign());
    }

    /**
     * RSA校验数字签名
     *
     * @param data   待校验数据
     * @param sign   数字签名
     * @param pubKey 公钥
     * @return boolean 校验成功返回true,失败返回false
     */
    public boolean verify(byte[] data, byte[] sign, byte[] pubKey) throws Exception {
        // 实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        // 初始化公钥
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
        // 产生公钥
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        // 实例化Signature
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 初始化Signature
        signature.initVerify(publicKey);
        // 更新
        signature.update(data);
        // 验证
        return signature.verify(sign);
    }

    public static void main(String[] args) {
        try {
            Map<String, String> keyMap = initKey();
            String publicKeyString = keyMap.get(PUBLIC_KEY);
            String privateKeyString = keyMap.get(PRIVATE_KEY);
            System.out.println("公钥:" + publicKeyString);
            System.out.println("length: " + publicKeyString.length());
            System.out.println("私钥:" + privateKeyString);
            System.out.println("length: " + privateKeyString.length());

            // 待加密数据
            String data = "admin123";

            // 公钥加密
            String encrypt = RSAUtil01.encryptByPubKey(data, publicKeyString);

            // 私钥解密
            String decrypt = RSAUtil01.decryptByPriKey(encrypt, privateKeyString);

            System.out.println("加密前:" + data);
            System.out.println("明文length:" + data.length());
            System.out.println("加密后:" + encrypt);
            System.out.println("解密后:" + decrypt);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

如图所示, 生成一对1024 bit 的 RSA 密钥对,并加解密成功。

java pem格式 rsa 公钥 java rsa 解密_学习_04

3.2 深入实践案例探索

案例一:报文长度过长加解密失败

测试发现当明文过长时,加密异常,返回如下报错

java pem格式 rsa 公钥 java rsa 解密_算法_05


原因分析: RSA 加解密时,对加密的数据大小有限制,最大不大于密钥长度。

在使用 1024 位的密钥时,最大可以加密 1024/8 = 128字节的数据,此时需要对数据进行分组加密,分组加密后的加密串拼接成一个字符串返回给客户端。如果 Padding 方式使用默认的 OPENSSL_PKCS1_PADDING(需要占用11字节用于填充),则明文长度最多为 128 - 11 = 117 Bytes。

同理,当解密的密文超过128Byte时,也需要进行分组解密

案例二:分段加解密的的实现

实现代码:

/**
* 分段加密
*/
public static String encrypt(String plainText, String publicKeyStr) throws Exception {
        System.out.println("明文lenth为"+plainText.length());
        byte[] plainTextArray = plainText.getBytes();
        PublicKey publicKey = getPublicKey(publicKeyStr);
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        int inputLen = plainTextArray.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        int i = 0;
        byte[] cache;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(plainTextArray, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(plainTextArray, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptText = out.toByteArray();
        out.close();
        return Base64.getEncoder().encodeToString(encryptText);
    }
    /**
     * 分段解密
     */
    public static String decrypt(String encryptTextHex, String privateKeyStr) throws Exception{
        byte[] encryptText = Base64.getDecoder().decode(encryptTextHex);
        PrivateKey privateKey = getPrivateKey(privateKeyStr);
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        int inputLen = encryptText.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(encryptText, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptText, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] plainText = out.toByteArray();
        out.close();
        return new String(plainText);
}

案例三:当密钥长度非默认的1024,改为2048bit时,如何生成密钥、如何分段

如上文提到, 当密钥对改为 2048 位时, 最大加密明文大小 = 2048(bit) / 8 - 11(byte) = 245 byte

/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 245;
    
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 256;
 
 
/**
* 生成密钥对
 */
public static Map<String, Object> initKey() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        //设置密钥对的bit数 越大越安全
        keyPairGen.initialize(2048);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        //获取公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //获取私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
}
四、总结

4.1 核心点简述

  1. RSA加密默认密钥长度是1024,但是密钥长度必须是64的倍数,在512到65536位之间即可。
  2. RSA加密数据有长度限制,如果加密数据太长(大于密钥长度)会报错,此时的解决方案是 可以分段加密。
  3. RSA如果采用分段加密,当密钥对改为2048位时,RSA最大加解密文大小也需要调整:
    • RSA密钥长度=1024时, 最大加密明文长度是117,解密明文长度是128;
    • RSA密钥长度=2048时, 最大加密明文长度是245,解密明文长度是256;

4.2 PKCS1 和 PKCS8 的区别

PKCS#1格式
以-----BEGIN RSA PRIVATE KEY-----开头
以-----END RSA PRIVATE KEY-----结束

PKCS#8格式
以-----BEGIN PRIVATE KEY-----开头
以-----END PRIVATE KEY-----结束