目录
一、安全问题
在我们的数据传输过程中有以下几个安全问题:
Q1、中间人篡改过程中的数据,使得接收方接收到错误的数据
Q2、发送方发送数据,被中间人获取,窃取发送方的机密信息。
Q3、中间人模拟发送方发送数据,接收人接收到的是中间人的数据。
二、应用场景
在我们的应用中需要考虑这些问题的场景:
C1、开放平台open接口。
C2、对互联网开放访问的服务端。
三、加密算法
为解决上面几个场景的几个安全问题,有以下几种加密算法。
S1、签名、摘要算法
哈希算法,主要是验证数据完整性。
特点:
1、长度不受限制
2、hash值容易计算
3、过程不可逆
有一种压缩散列, 能够把不同长度的值散列成相同长度的值,当明文不同而hash值相同时,叫做“hash碰撞”
主要有MD5,SHA,MAC3种算法。
MD5
摘要是128位的,即32位16进制的字符串
不可逆,高度离散型。MD5>MD4>MD2
但是文字碰撞率比SHA256高
java实现:
import org.apache.commons.codec.binary.Hex
public static void jdkMD5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD1");// 得到MD5加密的对象
byte[] md5Bytes = md.digest(str.getBytes("UTF-8"));
// Hex.encodeHexString()将byte[]数组转换成十六进制
System.out.println("JDK MD5:" + Hex.encodeHexString(md5Bytes));
BigInteger bi = new BigInteger(1, md5Bytes);
System.out.println(bi.toString(16));
} catch (Exception e) {
e.printStackTrace();
}
}
输出:
JDK MD5:87b9d8e8b87cf7f59ed59852a17dd128
SHA
SHA1 160位的,但是已被破解
SHA-256 256位的,比较靠谱
private static void jdkSHA256(String str) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");// 得到MD5加密的对象
byte[] md5Bytes = md.digest(str.getBytes("UTF-8"));
System.out.println("JDK SHA-256:" + Hex.encodeHexString(md5Bytes));
// Hex.encodeHexString()将byte[]数组转换成十六进制
BigInteger bi = new BigInteger(1, md5Bytes);
System.out.println(bi.toString(16));
} catch (Exception e) {
e.printStackTrace();
}
}
输出:
JDK SHA-256:75927f072a8da41477c4236ad81de37b4bf0c8bfc033a97602aa2733cee83d58
MAC
加入秘钥计算,秘钥经过一系列的计算和元字符串组成一个新的字符串,再MD5或者SHA-256计算得到
public static void jdkHmacMD5(String str) {
try {
// 还原密钥,HmacMD5是算法的名字
SecretKey restoreSecretKey = new SecretKeySpec("deng".getBytes(), "HmacMD5");
// 实例化MAC
Mac mac = Mac.getInstance("HmacMD5");
// 初始化MAC
mac.init(restoreSecretKey);
// 执行消息摘要
byte[] hmacMD5Bytes = mac.doFinal(str.getBytes());
System.out.println("jdk hmacMD5:"
+ Hex.encodeHexString(hmacMD5Bytes));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void jdkHmacSHA256(String str) {
try {
// 还原密钥,HmacMD5是算法的名字
SecretKey restoreSecretKey = new SecretKeySpec("deng".getBytes(), "HmacSHA256");
// 实例化MAC
Mac mac = Mac.getInstance("HmacSHA256");
// 初始化MAC
mac.init(restoreSecretKey);
// 执行消息摘要
byte[] hmacMD5Bytes = mac.doFinal(str.getBytes());
System.out.println("jdk HmacSHA256:"
+ Hex.encodeHexString(hmacMD5Bytes));
} catch (Exception e) {
e.printStackTrace();
}
}
输出:
jdk hmacMD5:fc969a3904490852d6209ffd27cab3e5
jdk HmacSHA256:1d1884f44eeecf818457345159dfcc571160baa68784b5d411cdf96f6e60ee26
S2、对称加密
使用相同的密钥进行加密,解密。
常用:AES(安全) DES
DES
(Data Encryption Standard),数据加密标准,已被破解。
秘钥长度:56位
3DES
经过3轮DES
AES
(Advanced Encryption Standard),高级加密标准,未被破解,比3DES效率高。
秘钥长度:128
public static void jdkAES(String str) {
try {
// 获取KEY生成器
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed("111111".getBytes());//设置加密用的种子,密钥
keyGenerator.init(128, random);
// keyGenerator.init(128);
// 产生KEY
SecretKey secretKey = keyGenerator.generateKey();
// 获取KEY
byte[] keyBytes = secretKey.getEncoded();
// KEY转换
Key key = new SecretKeySpec(keyBytes, "AES");
// 加密
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] result = cipher.doFinal(str.getBytes());
System.out.println("jdk aes encrypt:" + Base64.encodeBase64String(result));
// 解密
cipher.init(Cipher.DECRYPT_MODE, key);
result = cipher.doFinal(result);
System.out.println("jdk aes decrypt:" + new String(result));
} catch (Exception e) {
e.printStackTrace();
}
}
输出:
jdk aes encrypt:td9pqIv9Z9Q1Y4n+LO3GKg==
jdk aes decrypt:denganming
S3、非对称加密
一对加密对,分为公钥和私钥,有RSA ESA等。
公钥加密用作数据加密,私钥解密;
私钥加密用作数据签名,公钥验签。
对明文有限制,不能大于公钥的长度。通常只做ca数字签名和对称密钥加密。
RSA
RSA:基于因子分解,既能用于数字加密也能用于数字签名
秘钥长度:512-65536 默认1024 jdk
public static void jdkRSA(String str) {
try {
// 1.初始化发送方密钥
// KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
// keyPairGenerator.initialize(512);
// KeyPair keyPair = keyPairGenerator.generateKeyPair();
// RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
// RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
// System.out.println("Public Key:"+ Base64.encodeBase64String(rsaPublicKey.getEncoded()));
// System.out.println("Private Key:"+ Base64.encodeBase64String(rsaPrivateKey.getEncoded()));
String publicKeyStr = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAK9m6RcW8OH/HQeL/YVhyKX1v79V6Lgm\n" +
"/+39AcLx0jdt8zPDk57A/8ld9PKpz8RAPXJ3aq2vuSRpWghSJ07v5CcCAwEAAQ==";
String privateKeyStr = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAr2bpFxbw4f8dB4v9\n" +
"hWHIpfW/v1XouCb/7f0BwvHSN23zM8OTnsD/yV308qnPxEA9cndqra+5JGlaCFIn\n" +
"Tu/kJwIDAQABAkEAibMm5oOSFDntllEmdKIxnWhyYkmVa4zievmXem3R9MTF49oz\n" +
"aR0mCAuM0LVe1xL0SeKDPN4vGyKmASc295T0MQIhANRhtLlLVQF67Bb/7DCWHe38\n" +
"Th9IyHrllqSRMwQ344IdAiEA02zw+PCXdJfHYAqsRM8rMz7CANTR2QXm+WUGHl59\n" +
"bBMCIAIQdQ48AjaCeFr839HVE7NleWjqbzBqnN06oBFRbb+tAiEAkhxoj/FdCdNW\n" +
"krXthYQSW9DUK9TnzO56SAIb72LnlIcCIFqEL75aMM+R7LEG0TjXY3oD/jOePVoA\n" +
"+NHO45CI/jaf";
// 2.私钥加密、公钥解密 ---- 加密
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(str.getBytes());
System.out.println("私钥加密、公钥解密 ---- 加密:"+ Base64.encodeBase64String(result) + "--" + result.length+"---"+Base64.decodeBase64(privateKeyStr).length);
// 3.私钥加密、公钥解密 ---- 解密
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr));
keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
result = cipher.doFinal(result);
System.out.println("私钥加密、公钥解密 ---- 解密:" + new String(result));
// 4.公钥加密、私钥解密 ---- 加密
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr));
KeyFactory keyFactory2 = KeyFactory.getInstance("RSA");
PublicKey publicKey2 = keyFactory2.generatePublic(x509EncodedKeySpec2);
Cipher cipher2 = Cipher.getInstance("RSA");
cipher2.init(Cipher.ENCRYPT_MODE, publicKey2);
byte[] result2 = cipher2.doFinal(str.getBytes());
System.out.println("公钥加密、私钥解密 ---- 加密:"+ Base64.encodeBase64String(result2)+ "--" + result2.length+"---"+Base64.decodeBase64(publicKeyStr).length);
// 5.公钥加密、私钥解密 ---- 解密
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
KeyFactory keyFactory5 = KeyFactory.getInstance("RSA");
PrivateKey privateKey5 = keyFactory5.generatePrivate(pkcs8EncodedKeySpec5);
Cipher cipher5 = Cipher.getInstance("RSA");
cipher5.init(Cipher.DECRYPT_MODE, privateKey5);
byte[] result5 = cipher5.doFinal(result2);
System.out.println("公钥加密、私钥解密 ---- 解密:" + new String(result5) );
} catch (Exception e) {
e.printStackTrace();
}
}
输出:
私钥加密、公钥解密 ---- 加密:P3NhKa8y+jTXuOHFkZE9Vkc9dYvZonr1bLT4fj4/tB70EU9JP1fnJWf7xrDczC7+HxVr4GlH6M3/h4Qkb59Vdg==--64---345
私钥加密、公钥解密 ---- 解密:denganming
公钥加密、私钥解密 ---- 加密:I5OB0cwfzKHWwUnIsvw2XoLaomwwlQpIkcFmH8lWgWjMDWqYK6DO17xEUHXVKR1iB/jxROX4Ejbyw/f59nEUew==--64---94
公钥加密、私钥解密 ---- 解密:denganming
S4、数字签名
先签名,再用私钥加密签名得到数字签名
先用MD5,再RSA私钥加密。
public static void jdkRSAMD5(String str) {
try {
// 1.初始化密钥
KeyPairGenerator keyPairGenerator = KeyPairGenerator
.getInstance("RSA");
//设置KEY的长度
keyPairGenerator.initialize(512);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//得到公钥
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
//得到私钥
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
// 2.进行签名
//用私钥进行签名
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
rsaPrivateKey.getEncoded());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//构造一个privateKey
PrivateKey privateKey = keyFactory
.generatePrivate(pkcs8EncodedKeySpec);
//声明签名的对象
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
signature.update(str.getBytes());
//进行签名
byte[] result = signature.sign();
System.out.println("jdk rsa sign:" + Hex.encodeHexString(result));
// 3.验证签名
//用公钥进行验证签名
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
rsaPublicKey.getEncoded());
keyFactory = KeyFactory.getInstance("RSA");
//构造一个publicKey
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
//声明签名对象
signature = Signature.getInstance("MD5withRSA");
signature.initVerify(publicKey);
signature.update(str.getBytes());
//验证签名
boolean bool = signature.verify(result);
System.out.println("jdk rsa verify:" + bool);
} catch (Exception e) {
System.out.println(e.toString());
}
}
输出:
jdk rsa sign:6f2e0a9a028271906c40559c26cebb290c30744066474507df0fb91652ba739139fcccdeff9b8450898e1587afd7647ac8fcc932d44ea736320176609a7f081b
jdk rsa verify:true
S5、数字证书
数字证书就是:服务端的公钥+服务端信息+CA数字签名(第三方机构通过自己的私钥加密的数字证书的签名)/证书编号
浏览器/操作系统存留大的第三方机构的公钥,用于解开数字证书得到服务端公钥
可以使用keyTools生成证书:https://www.cnblogs.com/xdp-gacl/p/3750965.html
四、解决方案
我们就考虑互联网的解决方案
Q1、加签可以解决
Q2、公钥加密,只要服务端有私钥,其他人没有,获取不到数据。
Q3、私钥加密,只有客户端有私钥,其他人没有,不能发送请求。
五、HTTPS的原理
同时使用对称加密和非对称加密。
对称加密是传输过程中的加密(效率高),非对称加密是刚开始建立连接时使用(耗时,安全)。
建立SSL安全连接的全过程:
1、服务端生成一对非对称加密公私钥,pubK1,priK1。
2、服务端把公钥和服务端信息发送到CA(第三方数字证书颁发机构),由CA生成数字证书
3、数字证书内容:服务端公钥pubK1,服务器信息,CA的数字签名(证书编号),签名算法。
数字签名:由CA用自己的私钥priK2做数字签名。
4、服务端获取到证书,把证书库放到服务端下。
5、客户端请求,建立SSL连接。
6、请求获取到证书,根据证书的颁发机构,用浏览器内置的颁发机构的公钥pubK2,进行解密数字签名,再用同样的签名算法对明文签名,对比2个结果,如果相同的话,则认为合法,获取到服务端的公钥pubK1。
7、客户端利用随机数,(客户端和服务端多次交互产生的随机数,由客户端和服务端产生的随机数),在客户端生成一个密钥K,用公钥pubK1加密,传给服务端。
8、服务端接收到秘钥K,和客户端使用秘钥K进行后面的数据交互。
六、tomcat的设置
参考:https://www.cnblogs.com/wanghaoyuhappy/p/5267702.html
七、参考文档和其他问题
1、https://www.cnblogs.com/Caersi/p/6720789.html
2、https://www.cnblogs.com/lipengxiang2009/p/7471752.html
3、https://showme.codes/2017-02-20/understand-https/
4、https://wetest.qq.com/lab/view/110.html
5、加密系列:https://blog.csdn.net/u013991521/article/details/48182081
6、A、B两人分别在两座岛上。B生病了,A有B所需要的药。C有一艘小船和一个可以上锁的箱子。C愿意在A和B之间运东西,但东西只能放在箱子里。只要箱子没被上锁,C都会偷走箱子里的东西,不管箱子里有什么。如果A和B各自有一把锁和只能开自己那把锁的钥匙,A应该如何把东西安全递交给B?
A加锁,B加锁,A解锁,B解锁
7、
分组加密:分成组加密,多用于网络传输
流加密:一个字符一个字符加密
8、服务端私钥如何存储 tomcat设置目录
9、浏览器保存的是CA的公钥和证书信息
10、CA的密钥被污染怎么办:证书链
11、登录密码器的实现
1、60s刷新一次
2、使用相同的hash算法 生成固定的6位数字
3、参数:用户信息,时间,私钥
4、注册时,根据用户名 生成私钥。把私钥,软件(加密算法),密码器发给用户
5、客户端算法:时间(分)+私钥 hash一个6位的数字 30s/60s有效,打开软件时开始计算时间
6、服务端验证:时间(分)+私钥 hash一个6位的数字 验证当前1分钟和上一分钟的code