文章目录
- 古代密码学
- 替换法
- 移位法
- 频率分析破解
- 近代密码学
- 现代密码学
- 散列函数
- MD5
- SHA1
- SHA256
- SHA512
- HMAC
- 转成16进制
- 获取文件信息摘要
- 消息认证码
- 重放攻击
- 预防重放攻击
- 挑战一应答机制
- 一次性口令机制
- DDoS
- 对称加密
- DES
- AES
- 加密模式
- ==ECB==
- ==CBC==
- CTR
- CFB
- OFB
- 填充模式
- NoPadding
- PKCS5Padding
- Base64
- 使用场景
- 什么是可打印字符?
- 编码规则
- 为什么http是文本传输协议还能传图片和文件?
- 非对称加密
- 公钥加密私钥解密
- 保存共钥、私钥 读取公钥、私钥
- 混合加密
- 认证
- 数字签名代码实现
- Keytool的使用
- 撞库
- URLENCODE
网络安全,信息安全、区块链学科的基础
古代密码学
替换法
例如:bee,b替换为w,e替换为p,单词就变为了wpp
单表替换:原文和密文使用的是同一张表
多表替换:多张表,原文和密文对比
例子:
表1:abcde - swtrp,表2:abcde-chfhk,表3:abcde-jftou
原文:bee
密钥:312
密文:b从表3获取,第二个e从表1获取,第三个3e从表2获取,即fpk
移位法
恺撒加密
例如:移动3位,原文:abc,密文:def
频率分析破解
英文 频率从高到低 e> t > a
近代密码学
恩尼格玛密码机 核心:使用移位法和替换法
被图灵破解了
电影《模仿游戏》
现代密码学
散列函数
MD5
MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的 密码散列函数,可以产生出一个128
位(16字节)的散列值(hash value)可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等
public static void main(String[] args) throws Exception {
MessageDigest md5 = MessageDigest.getInstance("MD5");
// 可以使用 DigestUtils.md5Digest("abc".getBytes());
byte[] digest = md5.digest("123".getBytes());
System.out.println(digest.length);
}
SHA1
SHA-1(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦数据处理标准(FIPS)。SHA-1可以生成一个被称为消息摘要的160位
(20字节)散列值,散列值通常的呈现形式为40个 十六进制数
正式名称为 SHA 的家族第一个成员发布于 1993年。然而人们给它取了一个非正式的名称 SHA-0 以避免与它的后继者混淆。两年之后, SHA-1,第一个 SHA 的后继者发布了。 另外还有四种变体,曾经发布以提升输出的范围和变更一些细微设计: SHA-224, SHA-256, SHA-384 和 SHA-512 (这些有时候也被称做 SHA-2):
public static void main(String[] args) Exception {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
byte[] digest = sha1.digest("123".getBytes());
System.out.println(digest.length);
}
SHA256
256
位
public static void main(String[] args) throws Exception {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] digest = sha256.digest("123".getBytes());
System.out.println(digest.length);
}
SHA512
512
位
public static void main(String[] args) throws Exception {
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
byte[] digest = sha512.digest("123".getBytes());
System.out.println(digest.length);
}
安全性更高的加密方式
HMAC
MAC
message authentication code,
HMAC
是 MAC
值 + hash值,
下载的软件的md5和官网的md5对比,如果一样,则是安全的,即没有被修改
String input = "琉衣";
String algorithm = "MD5";
MessageDigest md5 = MessageDigest.getInstance(algorithm);
byte[] digest = md5.digest();
// 因为ascii是0-127,但是此数组里有负数,解析不出来
/*
for (byte b : digest) {
System.out.println(b);
}
System.out.println(new String(digest));// 乱码
*/
String encode = Base64.encode(digest);
System.out.println(encode);
// 1B2M2Y8AsgTpgAmY7PhCfg==
// 发现和 网上的在线md5加密,得出的结果不一样,那是因为消息摘要不是使用base64进行编码的,所以我们需要把值转成16进制
转成16进制
要注意,要有补0操作,原因在Integer.toHexString(b)
这个语句,如果字节是00001111
只得到了F
String s = Integer.toHexString(0x00001111);
System.out.println(s);
// 1111
0xff,表示十六进制的两个f,一个f即1111,所以也就是八位,一个字节
toHexString方法传入的参数是int类型32位,此处传入的是byte类型8位,所以需要在前面补24个0,然后 &ff,与上11111111就是把前面的24个0去掉,只要最后8位,int是由4组byte组成的,并且java中本身就以byte读取
String input = "琉衣";
String algorithm = "MD5";
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
byte[] digest = messageDigest.digest(input.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
String s = Integer.toHexString(b & 0xff);
if (s.length() == 1){
s = "0" + s;
}
sb.append(s);
}
System.out.println(sb.toString());
获取文件信息摘要
import com.sun.org.apache.xml.internal.security.utils.Base64;
public static void main(String[] args) throws Exception {
String sha1 = getDigestFile("D:\\study.txt", "SHA1");
// String sha1 = getDigestFile("D:\\study.txt", "SHA-256");
System.out.println(sha1);
}
private static String getDigestFile(String filePath, String algorithm) throws Exception {
FileInputStream fis = new FileInputStream(filePath);
int len;
byte[] bytes = new byte[1024];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = fis.read(bytes)) != -1) {
baos.write(bytes, 0, len);
}
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
byte[] digest = messageDigest.digest(baos.toByteArray());
System.out.println("密文长度: " + digest.length);
return toHex(digest);
}
private static String toHex(byte[] digest) {
StringBuilder stringBuilder = new StringBuilder();
for (byte b : digest) {
String s = Integer.toHexString(b & 0xff);
if (s.length() == 1) {
s = "0" + s;
}
stringBuilder.append(s);
}
return stringBuilder.toString();
}
消息认证码
typora ctrl + . 即可中文符号输出
- 首先A 将加解密的密钥
k1
和生成消息验证码的密钥k2
都发送给B(安全的方式:比如 非对称加密) - A将明文通过k1加密得到密文
s
,然后将密文和k2
通过hash得到 消息认证码m
- A将
s
和m
一起发送给B,然后B将s
和k2
通过hash得到m'
,m
和m'
进行比对,一样则,则用k1进行解密
重放攻击
将A发送给B的消息认证码和密文被X截取,但是不做任何修改,而是重新发送,并且是多次。比如A向B借100,但是X发10次给B
预防重放攻击
- 加随机数
该方法优点是认证双方不需要时间同步,双方记住使用过的随机数,如发现报文中有以前使用过的随机数,就认为是重放攻击。缺点是需要额外保存使用过的随机数,若记录的时间段较长,则保存和查询的开销较大 - 加时间戳
该方法优点是不用额外保存其他信息。缺点是认证双方需要准确的时间同步,同步越好,受攻击的可能性就越小。但当系统很庞大,跨越的区域较广时,要做到精确的时间同步并不是很容易。 - 加流水号
就是双方在报文中添加一个逐步递增的整数,只要接收到一个不连续的流水号报文(太大或太小),就认定有重放威胁。该方法优点是不需要时间同步,保存的信息量比随机数方式小。缺点是一旦攻击者对报文解密成功,就可以获得流水号,从而每次将流水号递增欺骗认证端
在实际中,常将方法(1)和方法(2)组合使用,这样就只需保存某个很短时间段内的所有随机数,而且时间戳的同步也不需要太精确。对付重放攻击除了使用本以上方法外,还可以使用挑战一应答机制和一次性口令机制,而且似乎后面两种方法在实际中使用得更广泛
挑战一应答机制
一次性口令机制
DDoS
我开了一家有五十个座位的重庆火锅店,由于用料上等,童叟无欺。平时门庭若市,生意特别红火,而对面二狗家的火锅店却无人问津。二狗为了对付我,想了一个办法,叫了五十个人来我的火锅店坐着却不点菜,让别的客人无法吃饭。
上面这个例子讲的就是典型的 DDoS 攻击,全称是 Distributed Denial of Service,翻译成中文就是分布式拒绝服务。一般来说是指攻击者利用“肉鸡”对目标网站在较短的时间内发起大量请求,大规模消耗目标网站的主机资源,让它无法正常服务。在线游戏、互联网金融等领域是 DDoS 攻击的高发行业
对称加密
加密和解密的方式,使用同一把密钥
假设A手握一把密钥 key1,那么A需要克隆一把相同的密钥 key1’
在第一次通信中,A将报文连同 key1’一起发送给B
例如:需要发送原文3
- 设置密钥为108,则发送 3 * 108 = 324,发送给对方
- 对方收到之后,使用304 / 108 = 3得到原文
特点:
- 机密速度快,可以加密大文件
- 密文可逆,一旦密钥泄露,会导致数据泄露
- 加密后编码表找不到对应的字符,出现乱码
- 一般结合base64使用
核心原理:
- 流加密:对每一个字母或比特加密,然后组成一起
- 块加密:对信息流分块,不够可以补位,然后再对每一块分别加密,最后拼到一起
DES
高级加密标准
以64
位为分组对数据加密,它的密钥长度64
位,实际参与运算56
位,加密解密用同一算法
数据加密标准,是一种使用密钥加密的块算法,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准,并授权在非密级政府通信中使用
private static String strDefaultKey = "hc";
private Key key;
public DES() throws Exception {
this(strDefaultKey);
}
public DES(String strKey) throws Exception {
DESKeySpec dks = new DESKeySpec(strKey.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
this.key = keyFactory.generateSecret(dks);
}
public String encrypt(String strIn) throws Exception {
Cipher encryptCipher = Cipher.getInstance("DES");
encryptCipher.init(Cipher.ENCRYPT_MODE, this.key);
byte[] bytes = encryptCipher.doFinal(strIn.getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(bytes);
}
public String decrypt(String strIn) throws Exception {
Cipher decryptCipher = Cipher.getInstance("DES");
decryptCipher.init(Cipher.DECRYPT_MODE, this.key);
byte[] bytes = decryptCipher.doFinal(Hex.decodeHex(strIn.toCharArray()));
return new String(bytes);
}
public static void main(String[] args) throws Exception {
String newPassword = (new DES()).encrypt("sa");
System.out.println(newPassword);
System.out.print((new DES()).decrypt(newPassword));
}
AES
密码学中的高级加密标准(Advanced Encryption Standard,AES)
,又称Rijndael加密法,是美国联邦政府 采用的一种区块加密标准
密钥128
位
private static String strDefaultKey = "hc";
private Key key;
public AES(String strKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
// 生成一个128位的随机源,根据传入的字节数组
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(strKey.getBytes());
kgen.init(128, secureRandom);
this.key = new SecretKeySpec(kgen.generateKey().getEncoded(), "AES");
}
public AES() throws Exception {
this(strDefaultKey);
}
public String encrypt(String content) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, this.key);
byte[] decryptBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(decryptBytes);
}
public String decrypt(String strIn) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, this.key);
byte[] decryptBytes = cipher.doFinal(Hex.decodeHex(strIn.toCharArray()));
return new String(decryptBytes);
}
public static void main(String[] args) throws Exception {
String newPassword = (new AES()).encrypt("marconi123");
System.out.println(newPassword);
System.out.print((new AES()).decrypt("38998bba637d32decbbb7064e2233194"));
}
使用CBC加密模式
import com.sun.org.apache.xml.internal.security.utils.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class T {
public static void main(String[] args) throws Exception {
String input = "琉衣";
String key = "0123456789123456";
String transformation = "AES/CBC/PKCS5Padding";
String algorithm = "AES";
String encryptAES = encryptAES(input, key, transformation, algorithm);
System.out.println("加密:" + encryptAES);
String s = decryptAES(encryptAES, key, transformation, algorithm);
System.out.println("解密:" + s);
}
private static String encryptAES(String input, String key, String transformation, String algorithm) throws Exception {
Cipher cipher = Cipher.getInstance(transformation);
SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
// 创建iv向量,iv向量,是使用到CBC加密模式
IvParameterSpec iv = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.ENCRYPT_MODE,sks,iv);
byte[] bytes = cipher.doFinal(input.getBytes());
return Base64.encode(bytes);
}
private static String decryptAES(String encryptAES, String key, String transformation, String algorithm) throws Exception{
Cipher cipher = Cipher.getInstance(transformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),algorithm);
// 创建iv向量 //CBC加密模式需要有IV向量
IvParameterSpec iv = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,iv);
byte[] bytes = cipher.doFinal(Base64.decode(encryptAES));
return new String(bytes);
}
}
加密:CCEsmebPjdSoUoZ+hMFLJg==
解密:琉衣
加密模式
ECB
Electronic codebook,电子密码本
,按照块密码的块大小被分为数个块,并对每个块进行独立加密
- 优点: 可以并行处理数据
- 缺点:同样的原文生成同样的密文,不能很好保护数据
CBC
Cipher-block chaining,密码块链接
,每个明文块先与前一个密文块进行异或后,在进行加密,第一个明文和IV
异或再加密
- 优点:同样的原文生成的密文不一样
- 缺点:串行处理数据
CTR
…
CFB
…
OFB
…
填充模式
当需要按块处理的数据,数据长度不合符块处理需求时,按照一定的方法填充满块长 的规则
NoPadding
- 不填充
- 在DES加密算法下, 要求原文长度必须是8byte的整数倍
- 在AES加密算法下, 要求原文长度必须是16byte的整数倍
PKCS5Padding
数据块的大小为8位,不足就补位
Base64
-
Base64是一种用64个字符来表示任意二进制数据的方法,完成了数据在HTTP协议上的传输
-
它是一种编码方式,而非加密方式
-
数据编码之后,数据量会
变大
,变大1/3
左右
使用场景
简单说,因为某些场合并不能传输或者储存二进制流。
比如,如果一个传输协议是基于ASCII文本的,那么它就不能传输二进制流,那你要将二进制流传输就得编码。常见的诸如 http 协议的 url 就是纯文本的,不能直接放二进制流。
特别的,大多数现代语言的 String 类型,都不能直接储存二进制流,但可以储存BASE64编码的字符串。如果你希望用 String 类型操作一切数据,那就没法直接用二进制流。
当然,另外还有一个原因,就是某些协议会对二进制流中的特定字符进行特殊处理(比如ASCII的0~32编码的字符在某些传输介质中,是会被当作特殊含义处理的),这种时候就需要通过编码来避开这些特定字符了。
什么是可打印字符?
在ASCII码中规定,0-31、128这33个字符属于控制字符,32-127这95个字符属于可打印字符,也就是说网络传输只能传输这95个字符,不在这个范围内的字符无法传输。那么该怎么才能传输其他字符呢?其中一种方式就是使用Base64。
既然可打印的只有95,而比95小的整数最大的2的次方的数字就是64了。2的6次方
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAb4AAABYCAYAAACK55LCAAAgAElEQVR4Xuy9aXNc2ZEleN4e+wIgsO8ACXAnk8mUUqoqldQ9Yz3W/an/wsxvnA/TM2NWizKVmczkTgIg9h0IxL6+fez4DbCo7JQqpZLZfGBARjEJBOK95/de9+PHj3toO//7f40x/BpaYGiBoQWGFhha4BOxgDYMfJ/ISg8fc2iBoQWGFhhaQCwwDHzDjTC0wNACQwsMLfBJWWAY
这就是 base64 格式的数据,代表着一张图片的数据,专业术语叫:Data URI scheme。
编码规则
-
首先将待编码的内容转换成8位二进制,每3个字符为一组
-
如果编码前的长度是
3n+1
,编码后的内容最后面补上2个=
,如果编码前的长度是3n+2
,内容最后面补上1个=
。3的倍数则不用 -
再将每一组的二进制内容拆分成
6
位的二进制,不足6位的后面补足0
-
每个6进制的数字前面补足
0
,保证变成8
位二进制 -
将补足后的内容根据base64编码表转换成base64内容输出
(不足四个字符的时候会用 =
来补足,下面会说明)
编码前 “hb”
**1.**根据ascii码转换成8位二进制,3个为一组:
01101000,01100010
**2.**编码前长度是3n+2,所以后面补1个 ‘=’:
01101000,01100010,=
**3.**拆分成6位二进制,不足6位的在后面补足0,0010补足变成001000:
011010,000110,001000,=
**4.**每个6进制的数字前面补足0:
0011010,00000110,00000010,=
**5.**根据base64编码表输出:
aGI=
The Base64 Alphabet
索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 |
---|---|---|---|---|---|---|---|
0 | A | 17 | R | 34 | i | 51 | z |
1 | B | 18 | S | 35 | j | 52 | 0 |
2 | C | 19 | T | 36 | k | 53 | 1 |
3 | D | 20 | U | 37 | l | 54 | 2 |
4 | E | 21 | V | 38 | m | 55 | 3 |
5 | F | 22 | W | 39 | n | 56 | 4 |
6 | G | 23 | X | 40 | o | 57 | 5 |
7 | H | 24 | Y | 41 | p | 58 | 6 |
8 | I | 25 | Z | 42 | q | 59 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | L | 28 | c | 45 | t | 62 | + |
12 | M | 29 | d | 46 | u | 63 | / |
13 | N | 30 | e | 47 | v | ||
14 | O | 31 | f | 48 | w | ||
15 | P | 32 | g | 49 | x | ||
16 | Q | 33 | h | 50 | y |
public class Base64Test {
public static void main(String[] args) {
// 获取编码器
Base64.Encoder encoder = Base64.getEncoder();
byte[] encode = encoder.encode("abc".getBytes());
System.out.println(new String(encode));
// 获取解码器
Base64.Decoder decoder = Base64.getDecoder();
byte[] decode = decoder.decode(encode);
System.out.println(new String(decode));
// 或者使用
byte[] encode2 = BaseUtils.encode("abc".getBytes());
}
}
base58 一般用在比特币里面的一种编码方式,没有数字0
,字母O
,大写I
,小写i
,+
,/
http_626">为什么http是文本传输协议还能传图片和文件?
参考:https://segmentfault.com/q/1010000006670932
HTTP传输的是字符串,到了TCP层以二进制传输。
因为其增加了MIME。http/1.0之后版本在传输非文本文件的时候,比如图片,其实是使用MIME转换成ASCII传输的
具体参考 MIME和计算机网络
非对称加密
假设A手握公钥 key2 和私钥 key2’
首先,A将报文主体连同 key2 一起发送给B
此后,B向A发送的报文都将使用 key2 加密
而被 key2 上锁的报文 只有A手上的 key2’ 才能解密
同理:当A想要回复B时,正好相反
此时A就使用B的公钥对数据进行加密
而B就用自己的私钥进行解密
非对称加密可以解决密钥交换
问题
也称公钥加密
。使用公钥加密的文本只能用私钥解密,使用私钥加密的文本也可以使用公钥解密
公钥不需要具有安全性,因为公钥需要在网络间传输,非对称加密可以解决密钥交换
的问题
非对称加密算法,常见的比如RSA
、ECC
、DH,DSA等
-
RSA加密算法:RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的
-
RSA
是目前最有影响力的公钥加密算法,该算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但那时想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用 -
解密者拥有私钥,并且将由私钥计算生成的公钥发布给加密者。加密都使用公钥进行加密,并将密文发送到解密者,解密者用私钥解密将密文解码为明文。
公钥加密私钥解密
public class T {
public static void main(String[] args) throws Exception {
String input = "琉衣";
String algorithm = "RSA";
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(input.getBytes());
String encode = Base64.encode(bytes);
System.out.println("密文: " + encode);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes1 = cipher.doFinal(bytes);
System.out.println("原文: " + new String(bytes1));
}
}
保存共钥、私钥 读取公钥、私钥
package com.example.demo;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.apache.commons.io.FileUtils;
import javax.crypto.Cipher;
import java.io.File;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Objects;
public class T {
private static final String PUBLIC_KEY = "publicKey";
private static final String PRIVATE_KEY = "privateKey";
private static final String RSA = "RSA";
public static void main(String[] args) throws Exception {
String input = "琉衣";
String algorithm = "RSA";
generateKeyToFile(algorithm, "D:/a.pub", "D:/a.pri");
Key key = getKey("D:/a.pub", RSA, PUBLIC_KEY);
System.out.println(Base64.encode(key.getEncoded()));
Key key1 = getKey("D:/a.pri", RSA, PRIVATE_KEY);
System.out.println(Base64.encode(key1.getEncoded()));
}
private static void generateKeyToFile(String algorithm, String publicPath, String privatePath) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
byte[] privateKeyEncoded = privateKey.getEncoded();
byte[] publicKeyEncoded = publicKey.getEncoded();
String encode = Base64.encode(privateKeyEncoded);
String encode1 = Base64.encode(publicKeyEncoded);
// 保存文件
FileUtils.writeStringToFile(new File(publicPath), encode1, Charset.defaultCharset());
FileUtils.writeStringToFile(new File(privatePath), encode, Charset.defaultCharset());
}
// 读取公钥/私钥
private static Key getKey(String keyPath, String algorithm, String keyType) throws Exception {
String s = FileUtils.readFileToString(new File(keyPath), Charset.defaultCharset());
// 密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
if (Objects.equals(keyType, PUBLIC_KEY)) {
// 密钥规范
// 此类表示根据 ASN.1 类型 SubjectPublicKeyInfo 进行编码的公用密钥的 ASN.1 编码。X.509 标准中定义的 SubjectPublicKeyInfo 语法
X509EncodedKeySpec specification = new X509EncodedKeySpec(Base64.decode(s));
return keyFactory.generatePublic(specification);
}
// 该类代表私有密钥的ASN.1编码,根据ASN.1类型PrivateKeyInfo进行编码。PrivateKeyInfo语法在PKCS#8标准中
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(s));
return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
}
private static String encryptRSA(String algorithm, Key key, String input) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] bytes = cipher.doFinal(input.getBytes());
return Base64.encode(bytes);
}
private static String decryptRSA(String algorithm, Key key, String encode) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decode = cipher.doFinal(Base64.decode(encode));
return new String(decode);
}
}
混合加密
TLS
是使用对称加密和非对称加密的混合加密方式来实现的
完整性通过信息摘要算法实现
在交换密钥的环节使用非对称加密,之后的通信则使用对称加密
例如:A手握一对非对称密钥(公钥key3、私钥key3’),B手握一对对称密钥(key4、key4’)
阶段1:交换密钥-使用非对称加密
1、A将公钥 key3 发送给B
2、B将下一阶段加密/解密用的 key4’ 放在报文中,并使用公钥 key3 对报文进行加密
3、A使用私钥 key3’ 解密报文,得到 key4’
阶段2:数据通信-使用对称加密
A和B分别使用 key4’ 和 key4对接收/发送的报文进行解密/加密
一方面,第一阶段的非对称加密 保证了对称密钥的安全性
另一方面,第二阶段的对称加密 可以提高加密/解密处理的速度,提高数据传输的效率
加密和完整性都搞定之后就差认证了
认证
认证中心 CA(Certificate Authority)
是认证中心,全世界具有认证的CA就几家,分别颁布了DV
,OV
,EV
,区别在于可信度,DV是最低的,只是域名级别的可信,EV是最高的,经过了法律和审计的严格核查,可以证明网络拥有者的身份(在浏览器地址会显示出公司的名字,比如Apple,Github的网站)。不同的信任等级的机构一起形成了层级关系
形成 证书链
,最高的是RCA(root CA)
,RSA
还会相互给对方签名
可以访问https的网站查看证书的路径
,就能看到层级关系
发布认证过的公钥,才能解决公钥的信任问题
-
根据RSA算法生成公钥和私钥,然后将文件进行hash,得到哈希值.然后将哈希值和私钥解密运算->得到
数字签名
-
公钥,文件,数字签名一起发送给另外一个人,然后使用公钥根据RSA对数字签名通过加密算法 得到哈希值,
-
然后再对文件哈希运算得到哈希值,看是否和上面的哈希值一样
https://img-blog.csdnimg.cn/f9a0c6af217c43a3bad8166c331992fa.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAQ2h1YW5nLTI=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center" alt="在这里插入图片描述" />
-
如果被冒充了,
https://img-blog.csdnimg.cn/29ce121a71374a37aba10394a12e9b48.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAQ2h1YW5nLTI=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center" alt="在这里插入图片描述" />
-
那么该如何确定就是小明发送的呢?这就涉及到了
数字证书
,小明将公钥和个人身份发送给权威公证的证书颁发机构CA,核实了小明的身份之后,将颁发一个数字证书,该证书包含了小明的身份信息和公钥,就可以保证了 -
但如何保证数字证书不会被伪造呢?因为CA机构自己也生成一套公钥和私钥,使用私钥 对小明的身份和公钥 生成数字签名,然后将该数字签名放到小明的数字证书中。每个人的电脑是有安装默认的根证书,根证书记录了可以信赖的CA机构信息及其公钥
https://img-blog.csdnimg.cn/87884cddf9ae4e1590a20c21118fb0e7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAQ2h1YW5nLTI=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center" alt="在这里插入图片描述" />
通过以上方式就可以确保某一文件被谁签署并且没有被篡改
运行中输入certlm.msc
,打开证书管理器
数字签名代码实现
public class T {
private static final String PUBLIC_KEY = "publicKey";
private static final String PRIVATE_KEY = "privateKey";
private static final String RSA = "RSA";
public static void main(String[] args) throws Exception {
String input = "琉衣";
String algorithm = "RSA";
generateKeyToFile(algorithm, "D:/a.pub", "D:/a.pri");
Key key = getKey("D:/a.pub", RSA, PUBLIC_KEY);
System.out.println(Base64.encode(key.getEncoded()));
Key key1 = getKey("D:/a.pri", RSA, PRIVATE_KEY);
System.out.println(Base64.encode(key1.getEncoded()));
String signaturedData = generateSignature(input, "sha256withrsa", (PrivateKey) key1);
boolean isTrue = verifySignature(input, "sha256withrsa", (PublicKey)key, signaturedData);
System.out.println(isTrue);
}
private static void generateKeyToFile(String algorithm, String publicPath, String privatePath) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
byte[] privateKeyEncoded = privateKey.getEncoded();
byte[] publicKeyEncoded = publicKey.getEncoded();
String encode = Base64.encode(privateKeyEncoded);
String encode1 = Base64.encode(publicKeyEncoded);
// 保存文件
FileUtils.writeStringToFile(new File(publicPath), encode1, Charset.defaultCharset());
FileUtils.writeStringToFile(new File(privatePath), encode, Charset.defaultCharset());
}
// 读取公钥/私钥
private static Key getKey(String keyPath, String algorithm, String keyType) throws Exception {
String s = FileUtils.readFileToString(new File(keyPath), Charset.defaultCharset());
// 密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
if (Objects.equals(keyType, PUBLIC_KEY)) {
// 密钥规范
X509EncodedKeySpec specification = new X509EncodedKeySpec(Base64.decode(s));
return keyFactory.generatePublic(specification);
}
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(s));
return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
}
// RSA加密
private static String encryptRSA(String algorithm, Key key, String input) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] bytes = cipher.doFinal(input.getBytes());
return Base64.encode(bytes);
}
// RSA解密
private static String decryptRSA(String algorithm, Key key, String encode) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decode = cipher.doFinal(Base64.decode(encode));
return new String(decode);
}
// 生成签名
private static String generateSignature(String input, String algorithm, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance(algorithm);
// 初始化签名
signature.initSign(privateKey);
// 传入原文
signature.update(input.getBytes());
// 生成签名
byte[] sign = signature.sign();
return Base64.encode(sign);
}
// 校验签名
private static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signaturedData) throws Exception {
// 获取签名对象
Signature signature = Signature.getInstance(algorithm);
// 初始化签名
signature.initVerify(publicKey);
// 传入原文
signature.update(input.getBytes());
// 校验数据,验证自己活得的签名和传入的签名是否匹配
return signature.verify(Base64.decode(signaturedData));
}
}
Keytool的使用
撞库
撞库是黑客通过收集互联网已泄露的用户和密码信息,生成对于的字典表,尝试批量登录其他网站后,得到一系列可以登录的用户。因为很多用户在不同网站使用的账号密码大多是相同的,因此黑客可以通过获取用户在A网站的账户从而尝试登录B网站
不要每个网站的密码都设置成一样的
URLENCODE
为什么使用
- url发送给服务器,不允许出现空格和特殊字符
- url转义其实也只是为了符合url的规范而已。因为在标准的url规范中中文和很多的字符是不允许出现在url中的
除了 杠-
,下划线_
,点 .
,数字,字母 之外字符都将被替换成%
后跟两位十六进制数
需要转换:
- ASCII中,
0-31
、128
这33
个字符属于控制字符,32-127
这95
个字符属于可打印字符,网络传输只能传输这95个字符 - 一些非ASCII字符
- 一些保留字符 比如
&
- 不安全的字符,比如 空格,会变为
+
加号
public class UrlEncode {
public static void main(String[] args) throws UnsupportedEncodingException {
String encode = URLEncoder.encode("http://xx.cn/中文 , . ? + ", String.valueOf(StandardCharsets.UTF_8));
System.out.println(encode);
String decode = URLDecoder.decode(encode, "UTF-8");
System.out.println(decode);
}
}
对于get请求
浏览器会先把空格变成%20 而+号还保留,然后接收到请求之后decode的时候,会把+号变成空格,然后这个字符串中原来的空格(%20)解码之后还是空格
因为get请求是只能是ascii编码,显示的只有95个可打印字符
对于post请求,urlencode,会把空格
变成+
号,+
变成%2B
,然后解码的时候+号变为空格,%2B变为+号