Https通信之RSA加密签名

Https通信的数字证书中一般采用RSA(公钥密码体制)。认知RSA之前补充点其他知识。

公钥密码体制(public-key cryptography)

公钥密码体制分为三个部分,公钥、私钥、加密解密算法,它的加密解密过程如下:

  1. 加密:通过加密算法和公钥对内容(或者说明文)进行加密,得到密文。加密过程需要用到公钥。
  2. 解密:通过解密算法和私钥对密文进行解密,得到明文。解密过程需要用到解密算法和私钥。
    公钥密码体制的公钥和算法都是公开的(这是为什么叫公钥密码体制的原因),私钥是保密的。两者都可以加解密数据,公钥加密的内容只能私钥解密,私钥加密的内容只能公钥解密

对称加密算法(symmetric key algorithms)

对称加密算法就是加密使用的密钥和解密使用的密钥是相同的。因此对称加密算法要保证安全性的话,密钥要做好保密,只能让使用的人知道。安全系数相对较低

非对称加密算法(asymmetric key algorithms)

非对称加密算法就是加密使用的密钥和解密使用的密钥是不相同的。RSA就是一种非对称加密算法。

秘钥

密钥,一般就是一个字符串或数字,在加密或者解密时传递给加密/解密算法。加解密双方可以自行约定加解密方法,所以就出现了多种加解密方法。

数据加解密

加密就是是指对某个内容经过一定的加密算法及秘钥转化成base64、hex等格式。解密就是通过特定的解密算法及秘钥将加密信息转换成原始信息。数据的加解密是为了数据传输的安全,防止被他人截取。加解密算法一般就是对称加密和非对称加密。

数据签名

签名就是在信息的后面再加上一段内容,可以证明信息没有被修改过。签名一般使用的方案:

  1. 是对信息做一个hash计算得到一个hash值,然后把这个hash值(加密后)作为一个签名和信息一起发出去。
  2. 接收方在收到信息后,会重新计算信息的hash值,并和信息所附带的hash值(解密后)进行对比,如果一致,就说明信息的内容没有被修改过。
    1. 这个过程是不可逆的,也就是说无法通过hash值得出原来的信息内容。
    2.不同的内容一定会得到不同的hash值,hash的加解密是为了防止传输过程中被更改,造成信息是否被篡改无法准确验证。

RSA

RSA采用的就是一种公钥密码体制。RSA是三位数学家Rivest、Shamir 和 Adleman 设计的一种算法,所以叫做RSA。它是计算机通信安全的基石,也是最重要的加密算法。

这种算法非常可靠,密钥越长,它就越难破解。根据已经披露的文献,目前被破解的最长RSA密钥是768个二进制位。也就是说长度超过768位的密钥,还无法破解(至少没人公开宣布)。因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全(网络通信中一般都是2048位的)。
对于RSA的算法原理推荐查看RSA算法原理

在实际应用中我们以Https通信为例来分析RSA的使用过程,现在模拟https通信中client和server的常见对话:

  • client >>server:你好,我是clientA
  • server>>client:你好,我是server。
  • client >>server:请证明你是Server,str [str是随机字符串]
  • server>>client:str{XXX-hash} [{}中是私钥RSA加密后内容,hash为str的has,后面都如此表示]
    [client 收到str原文,计算str的hash1,通过证书中RSA公钥解密XXX-hash,获得密文中的内容与hash2,比较解密内容与原内容是否一致,解密的hash和原文计算的hash是否一致,都一致说明服务器是真的,可以进行下一步]
  • client >>server:你确实是server,这是x加密算法的秘钥{XXXX},以后就用X加密算法通信吧
  • server>>client:{可以,我做好通信准备了}[内容经x算法加密]
  • client >>server:{查询一下我的账户余额}[内容经x算法加密]

**注意**
1.验证双方身份的真实性后,双方可以约定使用新的对称加密算法通信,这个比较自由,扩张性很强,可能不同的client与server的通信加密方法是不一样的。
2.在验证双方身份后,也可以client将自己生成的非对称公钥传递给server,通过非对称加密进行双方的通信。

通信前提:client已经拿到了server的数字证书。证书中包含有服务器RSA公钥。关于数字证书原理和其他信息,请阅读该篇博客数字证书
一般我们开发应用,第一次是在程序中放了数字证书的,在快过期时,可以通过server下载新的证书进行保存并投入使用。

疑问:

1. 为什么要使用RSA加解密通信内容?
答:为了数据安全,client与server直接的通信在多个层面都能被黑客攻击,通过伪装成server或者client进行数据的窃取和数据篡改。

2. 为什么不在第一次通话中获取server的证书?或者放在某个网站下载?
答:你一开始访问的网站未必是真的网站,下载或传输的证书也可以是假的证书。

3. 为什么要对通信内容同时使用加密和签名?
答:虽然“黑客”无法获知加密内容,但是可以修改加密内容,如给它首位加一段内容、信息部分内容被替换,进行信息干扰。信息通过加密和内容hash值得签名,即可判断通信内容是否被修改破坏,是否完整。

4. client验证server签名为什么是通过随机字符串的hash,而不是直接通过加解密的方式?
答:因为”黑客”可以通过发送简单有规律的字符串,如”0,1,2,3,4”等找到加密规律,破解加密方法,这样是不安全的。通过对字符串的hash值进行加密,client收到内容后,解密字符串的hash值并与传过来的字符串计算出来的hash进行比较,即可验证server的签名。

5. 如何解决”黑客”截取加密内容,多次重复发送信息?
答:给通信内容添加序号和随机号,client或者server收到同样的信息,则说明通信有被干扰,应停止通信进行其他处理。

总结:
1.信息在通信中加密可看为是防止数据泄漏被解密窃取
2.数据签名是为了判断收到的数据是否完整
保证client和server能收到完整、无关方无法解密的加密数据,才是网络通信数据安全的核心点。

附贴一个RSA工具类

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
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;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;
/**
 * 时间:2020/4/20 0020    星期一
 * 邮件:[email protected]
 * 说明:RSA非对称加密
 */
class RSAUtils {
    
    
    private final String CHARSET = "UTF-8";
    private final String RSA_ALGORITHM = "RSA";

    private Map<String, String> createKeys(int keySize) {
    
    
        //为RSA算法创建一个KeyPairGenerator对象
        KeyPairGenerator kpg;
        try {
    
    
            kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
    
    
            throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
        }

        //初始化KeyPairGenerator对象,密钥长度
        kpg.initialize(keySize);
        //生成密匙对
        KeyPair keyPair = kpg.generateKeyPair();
        //得到公钥
        Key publicKey = keyPair.getPublic();
        String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());

        //得到私钥
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        Map<String, String> keyPairMap = new HashMap<>();
        keyPairMap.put("publicKey", publicKeyStr);
        keyPairMap.put("privateKey", privateKeyStr);
        return keyPairMap;
    }

    /**
     * 得到公钥
     *
     * @param publicKey 密钥字符串(经过base64编码)
     */
    private RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
    
    
        //通过X509编码的Key指令获得公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
        return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
    }

    /**
     * 得到私钥
     * @param privateKey 密钥字符串(经过base64编码)
     */
    private RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
    
    
        //通过PKCS#8编码的Key指令获得私钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
        return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
    }

    /**
     * 公钥加密
     */
    private String publicEncrypt(String data, RSAPublicKey publicKey) {
    
    
        try {
    
    
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength());
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
    
    
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥解密
     *
     * @param data    待解密数据
     * @param privateKey    私钥
     */
    private String privateDecrypt(String data, RSAPrivateKey privateKey) {
    
    
        try {
    
    
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] bytes = Base64.getDecoder().decode(data);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, bytes, privateKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
    
    
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥加密
     *
     */
    public String privateEncrypt(String data, RSAPrivateKey privateKey) {
    
    
        try {
    
    
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength());
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
    
    
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 公钥解密
     */
    public String publicDecrypt(String data, RSAPublicKey publicKey) {
    
    
        try {
    
    
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            byte[] bytes = Base64.getDecoder().decode(data);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, bytes, publicKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
    
    
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    private byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
    
    
        int maxBlock;
        if (opmode == Cipher.DECRYPT_MODE) {
    
    
            maxBlock = keySize / 8;
        } else {
    
    
            maxBlock = keySize / 8 - 11;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        int i = 0;
        try {
    
    
            while (datas.length > offSet) {
    
    
                if (datas.length - offSet > maxBlock) {
    
    
                    buff = cipher.doFinal(datas, offSet, maxBlock);
                } else {
    
    
                    buff = cipher.doFinal(datas, offSet, datas.length - offSet);
                }
                out.write(buff, 0, buff.length);
                i++;
                offSet = i * maxBlock;
            }
        } catch (Exception e) {
    
    
            throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
        }
        byte[] resultDatas = out.toByteArray();
        try {
    
    
            out.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return resultDatas;
    }

    public static void main(String[] args) throws Exception {
    
    
        RSAUtils rsaUtils = new RSAUtils();
        Map<String, String> keyMap = rsaUtils.createKeys(1024);
        String publicKey = keyMap.get("publicKey");
        String privateKey = keyMap.get("privateKey");
        System.out.println("公钥: \n\r" + publicKey);
        System.out.println("私钥: \n\r" + privateKey);

        System.out.println("公钥加密——私钥解密");
        String str = "孙子,我是你爸爸";
        System.out.println("\r明文:\r\n" + str);
        System.out.println("\r明文大小:\r\n" + str.getBytes().length);
        String encodedData = rsaUtils.publicEncrypt(str, rsaUtils.getPublicKey(publicKey));
        System.out.println("密文:\r\n" + encodedData);
        String decodedData = rsaUtils.privateDecrypt(encodedData, rsaUtils.getPrivateKey(privateKey));
        System.out.println("解密后文字: \r\n" + decodedData);
    }
}

该篇博客纯属个人观点和见解,如有错误恳请留言指正,万分感激!

相关链接:

  1. RSA算法原理
  2. Https通信之数字证书

猜你喜欢

转载自blog.csdn.net/luo_boke/article/details/106013674
今日推荐