RSA加密 — 详解

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/120316606
本文出自【赵彦军的博客】

推荐

AES加密 — 详解
RSA 加密 — 详解

RSA 简介

RSA——非对称加密,会产生公钥和私钥,公钥在客户端,私钥在服务端。公钥用于加密,私钥用于解密。

RSA 其实是三位数学家名字的缩写,1977年,三位数学家 Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字命名,叫做RSA算法。从那时直到现在,RSA算法一直是最广为使用的"非对称加密算法"。毫不夸张地说,只要有计算机网络的地方,就有RSA算法。

在1977年的RSA论文里,提到分解一个75位十进制数字大约需要104天。人类的技术进步是如此惊人!

当攻方的矛越来越锋利时,守方的盾就必须越来越厚重。所以,1024比特RSA已经不安全,应用系统不应该使用少于2048比特的公钥 值。而当需要高安全性时,选择4096比特RSA。

RSA 常用的加密填充模式

  • RSA/None/PKCS1Padding
  • RSA/ECB/PKCS1Padding

Java 默认的 RSA 实现是 RSA/None/PKCS1Padding , 默认实现如下:

Cipher cipher = Cipher.getInstance("RSA");

使用模式方式的 Cipher 生成的密文总是不一致的 ,Bouncy Castle的默认 RSA 实现是 RSA/None/NoPadding

为什么 Java 默认的 RSA 实现每次生成的密文都不一致呢,即使每次使用同一个明文、同一个公钥?这是因为 RSA 的 PKCS #1 padding 方案在加密前对明文信息进行了随机数填充。

实际运用注意事项

  • 1、一般由服务器创建秘钥对,私钥保存在服务器,公钥下发至客户端

  • 2、公钥是二进制数据,怎么下发给客户端呢?
    第一种方式:服务器把二进制数据写入文件,然后把文件传给客户端。由客户端从文件读取二进制数据。
    第二种方式:服务器把二进制数据转成 base64 字符串,客户端获取到 base64 字符串后,再转码为二进制数据。

  • 3、使用相同的公钥加密后的数据,每次都不一样,这是因为 RSA 的填充方案在加密前对明文信息进行了随机数填充。但是不并不影响解密的结果

  • 4、在创建RSA秘钥对时,长度最好选择 2048的整数倍,长度为1024在今天的互联网已经不安全了。

实战

RSA/ECB/PKCS1Padding 封装类

我们封存一个 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;

public class RSA {

    /**
     * 使用公钥加密
     *
     * @param data
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
        // 得到公钥对象
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey pubKey = keyFactory.generatePublic(keySpec);
        // 加密数据
        Cipher cp = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cp.init(Cipher.ENCRYPT_MODE, pubKey);
        return cp.doFinal(data);
    }

    /**
     * 使用私钥解密
     *
     * @param encrypted
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] encrypted, byte[] privateKey) throws Exception {
        // 得到私钥对象
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey keyPrivate = kf.generatePrivate(keySpec);
        // 解密数据
        Cipher cp = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cp.init(Cipher.DECRYPT_MODE, keyPrivate);
        byte[] arr = cp.doFinal(encrypted);
        return arr;
    }

    /**
     * 创建非对称加密RSA秘钥对
     * @param keyLength
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static KeyPair generateRSAKeyPair(int keyLength) throws NoSuchAlgorithmException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(keyLength);
        return kpg.genKeyPair();
    }

    /**
     * 获取公钥,打印为48-12613448136942-12272-122-913111503-126115048-12...等等一长串用-拼接的数字
     */
    public static byte[] getPublicKey(KeyPair keyPair) {
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        return rsaPublicKey.getEncoded();
    }

    /**
     * 获取私钥,同上
     */
    public static byte[] getPrivateKey(KeyPair keyPair) {
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
        return rsaPrivateKey.getEncoded();
    }

}

测试代码

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Base64;

public class Test {

    public static void main(String[] args) {
        String message = "星期二,我好开心哦";

        try {
            //创建秘钥对
            KeyPair keyPair = RSA.generateRSAKeyPair(1024);
            
            //获取公钥
            byte[] publicKey = RSA.getPublicKey(keyPair);
            //获取私钥
            byte[] privateKey = RSA.getPrivateKey(keyPair);
            //公钥base64编码
            String publicBase64 = Base64.getEncoder().encodeToString(publicKey);
            System.out.println("publicBase64 = " + publicBase64);

            //加密
            byte[] enResult = RSA.encryptByPublicKey(message.getBytes(), publicKey);
            //解密
            byte[] deResult = RSA.decryptByPrivateKey(enResult, privateKey);

            System.out.println("解密=" + new String(deResult, StandardCharsets.UTF_8));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试结果:

解密=星期二,我好开心哦

RSA 默认实现

主要就是下面这一句代码:

Cipher cp = Cipher.getInstance("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;

public class RSA {

    /**
     * 使用公钥加密
     *
     * @param data
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
        // 得到公钥对象
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey pubKey = keyFactory.generatePublic(keySpec);
        // 加密数据
        Cipher cp = Cipher.getInstance("RSA");
        cp.init(Cipher.ENCRYPT_MODE, pubKey);
        return cp.doFinal(data);
    }

    /**
     * 使用私钥解密
     *
     * @param encrypted
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] encrypted, byte[] privateKey) throws Exception {
        // 得到私钥对象
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey keyPrivate = kf.generatePrivate(keySpec);
        // 解密数据
        Cipher cp = Cipher.getInstance("RSA");
        cp.init(Cipher.DECRYPT_MODE, keyPrivate);
        byte[] arr = cp.doFinal(encrypted);
        return arr;
    }

    /**
     * 创建非对称加密RSA秘钥对
     *
     * @param keyLength
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static KeyPair generateRSAKeyPair(int keyLength) throws NoSuchAlgorithmException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(keyLength);
        return kpg.genKeyPair();
    }

    /**
     * 获取公钥,打印为48-12613448136942-12272-122-913111503-126115048-12...等等一长串用-拼接的数字
     */
    public static byte[] getPublicKey(KeyPair keyPair) {
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        return rsaPublicKey.getEncoded();
    }

    /**
     * 获取私钥,同上
     */
    public static byte[] getPrivateKey(KeyPair keyPair) {
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
        return rsaPrivateKey.getEncoded();
    }

}

Https 证书

我们看一下百度的 https 证书信息
在这里插入图片描述
公钥信息是 RSA 算法。

Https 协议是结合了非对称加密和对称加密一起工作,从而保证数据传输的安全性的。

非对称加密用于确保客户端可以安全地获取到服务器的真实公钥。
对称加密用于确保客户端和服务器之间的数据传输不会被任何人监听和解密,因为对称加密使用的密钥只有客户端和服务器这两者知道,其他任何人在不知道密钥的情况下都无法对传输数据进行解密。

数字签名是什么?

为什么如此安全的https协议却仍然可以被抓包呢?

写一篇最好懂的HTTPS讲解

签名认证过程

为什么会有签名认证?

在这里插入图片描述