Https通信之RSA加密签名

2020-05-11 00:08:15 8292
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    星期一
* 邮件:424533553@qq.com
* 说明: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); } } 

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

相关链接:
RSA算法原理

转载:https://blog.csdn.net/luo_boke/article/details/106013674

随缘而来,乘风而去,山高海阔,自有我风采!
所属分类:网络安全