前言
作为一个已经在互联网公司工作了一段时间的咸鱼来说,从当初一脸懵逼到现在似懂非懂的这段期间始终有一些问题困扰着我,由于工作原因又很少有时间去了解这些问题,导致自己欠下的技术债也是越来越多。于是准备趁着五一假期把加密算法这个大坑先给填一填。
先决条件
大家应该都知道网络环境是非常复杂的,在一个请求到达服务器的过程中,通常是要经过很多个中间环节进行转发的(网关、路由、代理),而在任意的一个中间环节中都是可以获取到请求中的数据的。当你用电脑或者是手机连接上隔壁WIFI时也许你的心里正高兴的不得了,殊不知隔壁的黑客大佬已经在磨刀霍霍了。
由于我们连接的是隔壁黑客的WIFI,所以我们通过此WIFI发出去的请求,都需要通过隔壁的路由器进行转发,这样一来黑客就可以做到如下几件事情: 1、在你通过QQ与朋友聊天时,黑客通过抓包工具,截获到你的请求,查看你的聊天记录(数据泄露) 2、在你用美团点了一份外卖时,黑客通过工具将收货地址改成了自己的地址,提交给美团的服务器,于是吃到了一顿免费的午餐。(数据被篡改) 3、在你通过QQ与朋友聊天时,黑客冒充你的朋友跟你聊天,然后再冒充你与你的朋友聊天(中间人攻击)
上面的例子只是用来说明网络环境下的数据安全问题,现实中应该没有这么蠢的黑客吧 哈哈。而且像美团、QQ这种软件都有较为完善的机制。
加密算法
数据加密就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段“密文”,该密文只能在输入相应的密钥之后才能显示出原本的数据,通过这样的途径来达到保护数据不被非法窃取、阅读的目的。
对称加密
采用同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。这种加密方式的特点是加密解密速度快
对称加密主要用来解决网络传输中的数据泄漏问题,其存在的缺点也很明显,通信双方需要约定一个密钥,而在复杂的网络环境下如何安全的约定这个密钥成为了一个问题(需要防止这个密钥不被黑客获取)。
常见的对称加密算法有AES、DES
AES代码实现(别问我为啥要贴代码?为了照顾那些想要白嫖的,我也是操碎了心)
代码语言:javascript复制package com.example.encrypt.util;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
/**
* AES加密工具类
* 加密解密使用同一秘钥、加密速度快
* 应用场景:防止数据泄露
*
* @author hcq
* @date 2020/4/20 19:27
*/
public class AesEncryptUtil {
private static final String CHAR_SET = "UTF-8";
private static final String ENCRYPT_TYPE = "AES";
/**
* 加密
* @param data 数据
* @param securityKey 秘钥
* @return 密文
*/
public static String encrypt(String data, String securityKey) throws Exception {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, securityKey);
byte[] bytes = cipher.doFinal(data.getBytes(CHAR_SET));
return new BASE64Encoder().encode(bytes);
}
/**
* 解密
*
* @param data 密文
* @param securityKey 秘钥
*/
public static String decrypt(String data, String securityKey) throws Exception {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, securityKey);
//8.将加密并编码后的内容解码成字节数组
byte[] decodeBuffer = new BASE64Decoder().decodeBuffer(data);
byte[] bytes = cipher.doFinal(decodeBuffer);
return new String(bytes, CHAR_SET);
}
/**
* @param type 模式
* @param securityKey 秘钥
* @return Cipher
*/
private static Cipher getCipher(int type, String securityKey) throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance(ENCRYPT_TYPE);
keygen.init(128, new SecureRandom(securityKey.getBytes(CHAR_SET)));
SecretKey secretKey = keygen.generateKey();
byte[] raw = secretKey.getEncoded();
SecretKey key = new SecretKeySpec(raw, ENCRYPT_TYPE);
Cipher cipher = Cipher.getInstance(ENCRYPT_TYPE);
cipher.init(type, key);
return cipher;
}
public static void main(String[] args) throws Exception {
String data="hello world";
String key="000d9b262280432e96c2c1b5ca3936e4";
String dataEncode = encrypt(data, key);
System.out.println("密文:" dataEncode);
System.out.println("明文" decrypt(dataEncode, key));
}
}
非对称加密
非对称加密需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
非对称加密存在如下特点:
- 公钥可以暴露给任何人,私钥只能自己保存(私钥相当于自己在网络环境下的身份证)。
- 公钥加密的数据、只有对应的私钥才能解密(比较常用的加密场景) 非对称加密算法实现敏感信息加密的基本过程是:甲方生成一对密钥并将公钥公开传递给乙方,乙方通过甲方的公钥对数据进行加密,然后将加密后的数据传递给甲方,甲方通过自己的私钥对数据进行解密。在这个过程中,即使黑客获取了甲方的公钥和乙方加密后的数据,因为无法掌握甲方的私钥,所以也就无法对数据进行解密。这样一来就解决了网络环境下的密钥传输问题(中间人攻击的问题会在下面讲解数字证书时进行介绍)
- 私钥加密的数据、也同样只有对应的公钥才能解密 看到私钥加密也许你会反驳我,既然我公钥都暴露出去了,那么任何人都可以对我加密后的数据进行解密,那我加密还有什么意义?这里其实是有意义的、没意义我还写在这里,那不是找打么。 虽然任何人都可以拿到公钥进行解密,但是由于私钥是只保存在你这里的,所以只要可以通过你的公钥进行解密的数据就一定是由你进行加密的,这样即使黑客可以用公钥解密出数据,也不能进行修改,因为修改过后因为缺乏私钥无法进行加密,所以私钥也相当于是你个人身份的一个标识
RSA加密算法实现
代码语言:javascript复制package com.example.encrypt.util;
import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* RSA非对称加密
* 私钥签名、公钥验签
* 公钥加密、私钥解密
*
* @author hcq
* @date 2020/4/29 16:33
*/
public class RsaUti {
private static final String RSA = "RSA";
/**
* 公钥加密
* @param data 明文
* @param pubKey BASE64公钥
* @return BASE64 密文
*/
public static String pubKeyEncryptToBase64(String data, String pubKey) throws GeneralSecurityException {
byte[] pubByte = Base64.decodeBase64(pubKey);
PublicKey publicKey = KeyFactory.getInstance(RSA).generatePublic(new X509EncodedKeySpec(pubByte));
byte[] encryptData = encrypt(data, publicKey);
return Base64.encodeBase64String(encryptData);
}
/**
* 私钥解密
* @param data Base64密文
* @param priKey Base64私钥
* @return 明文
*/
public static String priKeyDecryptByBase64(String data,String priKey) throws GeneralSecurityException{
byte[] decode = Base64.decodeBase64(priKey);
PrivateKey privateKey = KeyFactory.getInstance(RSA).generatePrivate(new PKCS8EncodedKeySpec(decode));
return new String(decrypt(Base64.decodeBase64(data),privateKey));
}
/**
* 私钥加密
* @param data 明文
* @param priKey BASE64私钥
* @return Base64密文
*/
public static String priKeyEncryptToBase64(String data, String priKey) throws GeneralSecurityException {
byte[] priByte = Base64.decodeBase64(priKey);
PrivateKey privateKey = KeyFactory.getInstance(RSA).generatePrivate(new PKCS8EncodedKeySpec(priByte));
byte[] encryptData = encrypt(data, privateKey);
return Base64.encodeBase64String(encryptData);
}
/**
* 公钥解密
* @param data Base密文
* @param pubKey Base64公钥
* @return 明文
*/
public static String pubKeyDecryptByBase64(String data, String pubKey) throws GeneralSecurityException {
byte[] pubKeyByte = Base64.decodeBase64(pubKey);
PublicKey publicKey = KeyFactory.getInstance(RSA).generatePublic(new X509EncodedKeySpec(pubKeyByte));
byte[] encryptData = decrypt(Base64.decodeBase64(data), publicKey);
return new String(encryptData);
}
/**
* RSA加密
*
* @param data 消息内容
* @param key rsa公钥/私钥
* @return 密文
*/
public static byte[] encrypt(String data, Key key) throws GeneralSecurityException{
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data.getBytes());
}
/**
* 解密
*
* @param data 密文
* @param key rsa私钥/公钥
* @return 明文
*/
public static byte[] decrypt(byte[] data, Key key) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(data);
}
public static void main(String[] args) throws Exception {
String priKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ/Tf6CL/iN8kNe3"
"QaG wNe/ nFU0i0WM3fg70vzbULPlPYUG00fNdj6U5BvsGzjh7HI7EmVZGMqLmfY"
" ETB 0OpteFOgt0Redr7WXt5OYDu TU4sWHuxNR 7ID0fWFR2k9B36q umj1ROw2"
"71kJ1inmu4n6c7AIwjQuzH1d7ra5AgMBAAECgYBlbbymL5G2BZyKObN KWeKxDv0"
"5maH1HoPTwGuSqsBZmlbjcERvYfXzm1v0WG iNsZubAytotB34gZwXk1cYG6G1BP"
"BvnoVm5INi5mTAzvqttvQX/WaVitLftMQkxwLYAoQuOCHCz8HGeRZcV1oKyWdy7c"
"jWb7ydzwdWAhxtmrvQJBAM7zZ3zz8A6xv2DinHbx0RhFzi6Xo cZFz71B2YDuaeM"
"sABJBv5wICo79ThO15r0wsPRjRgL4PpKZPzA50ZAmjMCQQDFtNcMn7DK7P99ZVUD"
"YV/JI sfstDjZ20E7Fz Kz9Ss15fyxaCnV18ArsYtRR0cxhW1kr6y5CKEM5/fgKX"
"BZdjAkBzjwDjigcq/V/jBsbduCvMxPXbmHtCSQVs9z 5XC0n/OwuTJjmLNAZJT/J"
"wGSuNywmUfXaTo/C0xXO RxrYxl3AkABVh6aBD5SsNVtSJERi8f0 Rwuw6urzdgr"
"z1k1kp9D9Nhvd1T4nw2xt cB3L99pgWFGL 7AENC26g5rmVgFfaXAkAGyHwz5JtV"
"lcZRqh8v9ZDAPJYPzbQS9Cy1HFhZuB748WrLcmeQPi2nVqOXFNJfNskUCck Kc1O"
"iswqNHWMOdTk";
String pubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCf03 gi/4jfJDXt0GhvsDXv/px"
"VNItFjN34O9L821Cz5T2FBtNHzXY lOQb7Bs44exyOxJlWRjKi5n2PhEwftDqbXh"
"ToLdEXna 1l7eTmA7vk1OLFh7sTUfuyA9H1hUdpPQd qvrpo9UTsNu9ZCdYp5ruJ"
" nOwCMI0Lsx9Xe62uQIDAQAB";
String data = "hello world";
String encrypt=pubKeyEncryptToBase64(data,pubKey);
System.out.println("公钥加密:" encrypt);
String decrypt=priKeyDecryptByBase64(encrypt,priKey);
System.out.println("私钥解密:" decrypt);
encrypt=priKeyEncryptToBase64(data,priKey);
System.out.println("私钥加密:" encrypt);
decrypt= pubKeyDecryptByBase64(encrypt,pubKey);
System.out.println("公钥解密:" decrypt);
}
}
签名算法
签名算法是指用于计算数字签名的算法。数字签名,就是只有信息的发送者才能产生别人无法伪造的一段数字串,这段数字串同时也是对信息发送者的一个有效证明。计算数字签名通常是个单向的过程(类似于摘要算法的不可逆性)。目前应用最为广泛的三种签名算法是:RSA签名、Rabin签名、DSS签名。
签名算法存在如下特点
- 不可否认性 这个就不用解释了吧(只有你的私钥才能计算出正确的数字签名,你还想否认?)
- 防止数据被篡改
- 签名算法通常包含一个摘要算法和一个非对称加密算法
数字签名的计算过程一般是先把所有的请求参数拼接为一个字符串,然后通过摘要算法计算出摘要信息,最后再通过我们的私钥对该字符串的摘要信息进行加密从而生成该请求对应的数字签名。(简称:加签)
服务端收到请求后,先通过我们的公钥对我们的签名进行解密得出请求参数的摘要信息。然后再通过同样的方法把我们的请求参数拼接为一个字符串然后进行摘要。最后通过对比摘要信息来判断该请求是否被篡改。(简称:验签)
至于第三个特性,在这里提问一个问题:签名算法为什么不直接用非对称加密计算签名,而是要先进行摘要然后再通过摘要信息计算签名呢?直接使用非对称加密计算签名不也可以保证签名的不可否性与防止数据被篡改的特性吗?
其实这里涉及到非对称加密的一个弊端,那就是加密数据的长度不能大于密钥的长度。所以需要利用摘要算法变长输入、定长输出的这一特性,将加密的数据变为一个固定长度的摘要信息。
数字证书
首先回顾一下我们开头提到的3个问题:数据泄漏、数据被篡改、中间人攻击
根据目前我们所了解到的加密知识,只能避免数据被篡改这一问题,但是数据泄漏和中间人攻击这两个问题依然还是存在的。啥?你说啥?我们不是做了非对称加密吗?数据还会泄漏??
我们来了解一下在加密的网络环境下黑客是怎样获取数据的。首先我们要先把自己的公钥传输给服务器(服务器要通过此公钥验证我们的签名),然后我们再发起一个请求获取服务器的公钥(我们要通过服务器公钥来加密和验证服务器的签名)。假如在我们获取服务器公钥的时候,黑客截获了我们的请求,把自己的公钥给我们。这样一来导致我们会误认为自己获取到的是服务器的公钥。(殊不知,我们获取到的公钥是黑客的公钥,而黑客拿到的才是服务器的公钥),于是我们通过黑客的公钥进行加密,再用自己的私钥进行签名,发给服务器时,再次被黑客截获,由于我们用的是黑客的公钥加密的,所以黑客自然可以用自己的私钥进行解密,这样就读取到了我们的请求数据,然后黑客再用服务器的公钥对请求数据进行加密传输给服务器。服务器通过自己的私钥解密,并且通过我们的公钥验签,验签通过、所以将响应数据给了黑客,黑客用自己的私钥对服务器响应的数据进行签名,然后再将响应数据传递给我们,我们采用服务器的公钥(其实是黑客的公钥)进行验签,发现验签通过。这样一来黑客就可以作为你跟服务器之间的中间人来进行交互(你永远不知道你对面的是服务器还是黑客)。
明白了中间人攻击的步骤后,我们不得不开始做一些防御措施了,目前我们已经了解到了中间人攻击是在交换公钥这一环节出了问题,那么怎样才能判断我们拿到的公钥确实是服务器的公钥呢?我们可以想象一下在现实生活中我们一般是怎样鉴定一个物品是否是属于某个人的呢???
答案是公证处,公证处是依据《中华人民共和国公证法》设立,不以营利为目的,依法独立行使公证职能、承担民事责任的证明机构。
举例说明:马路上有一辆自行车,张三说这辆自行车是自己的,准备把他给卖掉,但是买家不相信这辆车是张三的,担心买了后会产生纠纷,那怎么办呢?于是两个人来到公证处(警察局),结果根据公证处的调查,这辆车果然不是张三的,张三没有权利对其进行买卖。看这就是公证处的好处
那么在网络环境中到底有没有公证处呢?答案当然是有的。网络环境中的公证处叫做CA机构,其职责是承担公钥体系中公钥的合法性检验的责任,同时也承担着数字证书的管理与颁发的工作。
数字证书是指在互联网通讯中标志通讯各方身份信息的一个数字认证,人们可以在网上用它来识别对方的身份。 ps:摘自百度百科,数字证书跟现实中的身份证是相同的作用
这是我从浏览器中扒拉出来的一个证书,可以看到这个证书中包含的信息有很多,我认为其中最为主要的信息有3个
- 使用者信息:用于标识此证书的使用者是谁
- 公钥:使用者公钥
- 颁发机构的签名信息:用于验证该证书是否是合法的
这样一来我们就可以通过CA颁发的数字证书来确认这一公钥是否属于某个用户,从而在复杂的网络中进行安全的数据传输。
HTTPS原理探究
HTTPS 是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 。HTTPS 在HTTP 的基础下加入SSL层,因此HTTPS 的安全基础是 SSL。 HTTPS协议=HTTP协议 SSL协议
服务端如果要开放HTTPS协议,那么必须向CA机构申请数字证书,然后将数字证书内置在自己的服务器中。
客户端发起HTTPS请求的流程如下
- 客户端向服务器发送HTTPS请求,连接到服务器的443端口
- 服务器将含有公钥的数字证书发送给客户端。
- 客户端用浏览器或操作系统中内置的证书,对服务器证书进行检查。若证书检查未通过则说明此证书不是由CA颁发的证书,很有可能是伪造的,此时浏览器会弹出风险提示
- 若客户端检查服务器证书通过,则会在本地随机生成一个对称加密的密钥,然后通过证书中的公钥进行加密,传输给服务器
- 最后客户端与服务器之间通过对称加密进行数据的这然后客户端与服务器之间通过对称加密进行交互
至此、一个安全的网络环境被构建了出来。