概念
# 概述
# 概念
密码学(英语:Cryptography)可分为古典密码学和现代密码学。
- 古典密码学主要关注信息的保密书写和传递,以及与其相对应的破译方法。而现代密码学不只关注信息保密问题,还同时涉及信息完整性验证(消息验证码)、信息发布的不可抵赖性(数字签名)、以及在分布式计算中产生的来源于内部和外部的攻击的所有信息安全问题。
- 古典密码学与现代密码学的重要区别:
- 古典密码学的编码和破译通常依赖于设计者和敌手的创造力与技巧,作为一种实用性艺术存在,并没有对于密码学原件的清晰定义。
- 现代密码学则起源于 20 世纪末出现的大量相关理论,这些理论使得现代密码学成为了一种可以系统而严格地学习的科学。
这里将的密码学(Cryptography)是指密码学、密码技术的使用、加密、安全认证,而非日常生活中讲的密码口令(Password)。
| 阶段 | 处理内容 | 加密方式 | 加密技术 |
|---|---|---|---|
| 古典密码 | 字符 | 手工、机械 | 移位法、替换法 |
| 现代密码 | 二进制 | 计算机 | 对称加密、非对称加密 |
# 符号表达与含义
| 元素 | 英文 | 含义 |
|---|---|---|
| 明文 P/M | Plaintext | 需要加密的原始信息 |
| 密文 C | Ciphertext | 明文经过变换或伪装,形成密文 |
| 密码算法 | Algorithm | 加密变换与解密变换的具体规则 |
| 加密 E | Encryption | 对明文实施的一系列变换过程 |
| 解密 D | Decrption | 对密文施加的一系列的逆变还原明文的过程 |
| 密钥 | Key | 加密解密的参数 |
# 古典密码学
# 替代法
# 单表代换加密
用固定的信息将原文替换成无法直接阅读的密文信息。例如将 b 替换成 w , e 替换成 p ,这样 bee 单词就变换成了 wpp ,不知道替换规则的人就无法阅读出原文的含义。
笔记
存在一张密码表,知道里密码表就被破解了。
# 多表代换加密
多表替换即有多张原文密文对照表单,不同字母可以用不同表单的内容替换。
例如约定好表单为:表单 1: abcde-swtrp 、表单 2: abcde-chfhk 、表单 3: abcde-jftou 。
规定第一个字母用第三张表单,第二个字母用第一张表单,第三个字母用第二张表单,这时 bee 单词就变成了
(312) fpk ,破解难度更高,其中 312 又叫做密钥,密钥可以事先约定好,也可以在传输过程中标记出来。
笔记
存在多张密码表和一个密钥,只有同时知道全部密码表和密钥信息才可以被破解。
# 移位法
凯撒密码(Caesar) 加密时会将明文中的每个字母都按照其在字母表中的顺序向后(或向前)移动固定数目(循环移动)作为密文。例如,当偏移量是左移 3 的时候(解密时的密钥就是 3):
明文字母表:ABCDEFGHIJKLMNOPQRSTUVWXYZ
密文字母表:DEFGHIJKLMNOPQRSTUVWXYZABC
使用时,加密者查找明文字母表中需要加密的消息中的每一个字母所在位置,并且写下密文字母表中对应的字母。需要解密的人则根据事先已知的密钥反过来操作,得到原来的明文。例如:
明文:THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
密文:WKH TXLFN EURZQ IRA MXPSV RYHU WKH ODCB GRJ
# 破解
# 频率分析法
频率分析 - 维基百科,自由的百科全书 (opens new window)
在密码学中,频率分析是指研究字母或者字母组合在文本中出现的频率。应用频率分析可以破解古典密码。
在英语中,字母 E 出现的频率很高,而 Z 则出现得较少。类似地,ST、NG、TH,以及 QU 等双字母组合出现的频率非常高,NZ、QJ 组合则极少。英语中出现频率最高的 12 个字母可以简记为 “ETAOIN SHRDLU”。
在现代标准汉语中,汉字 “的”、“不”、“是” 出现的频率很高,而汉字 “翊”、“谧”、“觑” 则出现的较少,见常用国字标准字体表,后三个字属于次常用字。
# 现代密码学
# 摘要算法 - 散列 (Hash) 函数
- 散列函数,也见杂凑函数、摘要函数或哈希函数
- 可将任意长度的消息经过运算,变成固定长度数值,常见的有 MD5、SHA-1、SHA256
- 多应用在文件校验,数字签名中
- 单向,不可逆
# 常见算法
| 算法 | Hash 长度 | 说明 |
|---|---|---|
| MD5 | 128b(16B) | |
| SHA-1 | 160b(20B) | |
| SHA-256 | 256b(32B) | |
| SHA-512 | 512b(64B) |
# 示例:校验文件哈希值
# 以tomcat为例,在官网https://tomcat.apache.org/download-90.cgi找到要下载的文件
# 如https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.111/bin/apache-tomcat-9.0.111.tar.gz
wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.111/bin/apache-tomcat-9.0.111.tar.gz
# 在文件连接隔壁会有一个sha512校验文件,可下载,也可以直接浏览器打开
wget https://downloads.apache.org/tomcat/tomcat-9/v9.0.111/bin/apache-tomcat-9.0.111.tar.gz.sha512
# 校验方法1: 根据.sha512校验文件校验
sha512sum -c apache-tomcat-9.0.111.tar.gz.sha512
# apache-tomcat-9.0.111.tar.gz: 成功 会输出校验成功字样
# 校验方法2: 打印文件哈希值,与浏览器中显示的值比较
sha512sum apache-tomcat-9.0.111.tar.gz
# 2a955d97c6ed7d01fbf0392f3e2920129bcd541b259e894f441e411bac3bbe65576bcb3a314f06d624c9d70040828d26aa8a2c4f39d225d73f6a3db7523aa3ba apache-tomcat-9.0.111.tar.gz
# 示例代码
public static void main(String[] args) throws NoSuchAlgorithmException {
String input = "Hello World";
String algorithm = "MD5"; // MD5、SHA-1、SHA-256、SHA-512
MessageDigest digest = MessageDigest.getInstance(algorithm);
byte[] digest1 = digest.digest(input.getBytes());
// base64 编码
System.out.println(Base64.encode(digest1)); // sQqNsWTgdUEFt6mb5y4/5Q==
// 十六进制编码
StringBuilder sb = new StringBuilder();
for (byte b : digest1) {
String hex = Integer.toHexString(b & 0xff);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
System.out.println(sb); // b10a8db164e0754105b7a99be72e3fe5
}
哈希函数多用 16 进制编码阅读,而非 Base64 编码
# 对称加密
只有一个密钥,加密是它,解密也是它。
对称密码应用了相同的加密密钥和解密密钥。对称密码分为:序列密码 (流密码),分组密码 (块密码) 两种。
- 流密码:是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密
- 块密码:是先对信息流分块,再对每一块分别加密。
例如原文为 1234567890,流加密即先对 1 进行加密,再对 2 进行加密,再对 3 进行加密…… 最后拼接成密文;块加密先分成不同的块,如 1234 成块,5678 成块,90XX (XX 为补位数字) 成块,再分别对不同块进行加密,最后拼接成密文。前文提到的古典密码学加密方法,都属于流加密。
# 流加密,是按每个字母 or bit加密
1 2 3 4 5 6 7 8 9 0
# 块加密,分组加密
1234 5678 90XX
# 原理示例
- A 将原文
3发送给 B - 设置密钥
108,3 * 108 = 324,得到加密后的324作为密文发送给 B - B 收到密文
324后,使用相同的密钥108,324 / 108 = 3得到原文3
# 常见算法
- DES : Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977 年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。【老一代的加密算法,安全性不足(密钥太短)】
- AES(主流) : Advanced Encryption Standard, 高级加密标准。在密码学中又称 Rijndael 加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的 DES,已经被多方分析且广为全世界所使用。【又快(硬件加速)又安全】
# 特点
- 加密速度快,可以加密大文件
- 密文可逆,一旦密钥文件泄漏,就会导致数据暴露
- 加密后编码表找不到对应字符,出现乱码
- 一般结合 Base64 使用,解决乱码的问题
# 加密模式
| 加密模式 | 全称 | 特点 | 优点 | 缺点 |
|---|---|---|---|---|
| ECB | Electronic codebook 电子密码本 | 需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密;同原文输出的密文永远相同。 | 速度快,并行处理 | 具备幂等性,较不安全 |
| CBC | Cipher- block chaining 密码块链接 | 每个明文块先与前一个密文块进行异或后,再进行加密。每个密文块都依赖于它前面的所有明文块 | 安全,同样的明文生成的密文不一样 | 速度慢,串行处理 |
# 填充模式
NoPadding
- 不填充.
- 在 DES 加密算法下,要求原文长度必须是 8byte 的整数倍
- 在 AES 加密算法下,要求原文长度必须是 16byte 的整数倍
PKCS5Padding 数据块的大小为 8 位,不够就补足
- 默认情况下,加密模式和填充模式为 :
ECB/PKCS5Padding - 如果使用 CBC 模式,在初始化 Cipher 对象时,需要增加参数,初始化向量 IV :
IvParameterSpec iv = new IvParameterSpec(key.getBytes());
# 示例代码
# DES
public static void main(String[] args) throws Exception {
// 原文
String input = "Hello World";
// 加密算法类型
String transformation = "DES";
// 加密对象
Cipher cipher = Cipher.getInstance(transformation);
// 密钥:DES 只支持 8 字节(64 位)长度的密钥
String key = "12345678";
String algorithm = "DES"; // 算法
// 指定密钥规则
// 参数1: 密钥字节数组
// 参数2: 加密算法
SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
// 对加密进行初始化
// 参数1: 加密模式 or 解密模式
// 参数2: 密钥
cipher.init(Cipher.ENCRYPT_MODE, sks);
// 进行加密
byte[] bytes = cipher.doFinal(input.getBytes());
// 打印密文:密文以字节为单位,不具备字符属性,因此直接打印是乱码的。
System.out.println(new String(bytes));
// 使用Base64使密文具备阅读性 01Jk6AecyqldguNoHIO7dw==
System.out.println(Base64.encode(bytes));
// 解密
cipher.init(Cipher.DECRYPT_MODE, sks);
byte[] decryptBytes = cipher.doFinal(bytes);
System.out.println(new String(decryptBytes)); // Hello World
}
DES des = new DES(key.getBytes());
// 加密
byte [] encrypt = des.encrypt (input);
System.out.println(Base64.encode(encrypt)); // 01Jk6AecyqldguNoHIO7dw==
// 解密
byte [] decrypt = des.decrypt (encrypt);
System.out.println(new String(decrypt)); // Hello World
在将密文解码问明文时,需要将 Byte 转换为字符串,这里需要用到 new String (),而非 toString (),
toString()方法是调用 Byte 对象中 Object.toString () 方法,Byte 对象并未重写实现转字符串的逻辑,因此输出为 Byte 是地址哈希值。new String(byte[])方法是 String 对象构造函数,使用 Java 虚拟机默认编码格式将 Byte [] 编码成字符串。
# AES
- Java 代码实现与 DES 类似,只需将
DES更改为AES即可。 - 密钥仅支持:
128b(16B)、192b(24B)、256b(32B)
# 非对称加密
对称加密的缺点:对称密码的密钥安全极其重要,加密者和解密者需要提前协商密钥,并各自确保密钥的安全性,一但密钥泄露,即使算法是安全的也无法保障原文信息的私密性。
在实际的使用中,远程的提前协商密钥不容易实现,即使协商好,在远程传输过程中也容易被他人获取,因此非对称密钥此时就凸显出了优势。
非对称密码有两支密钥,公钥(publickey)和私钥(privatekey),合称私钥对,加密和解密运算使用的密钥不同。
- 用公钥对原文进行加密后,必须由私钥进行解密;
- 用私钥对原文进行加密后(此时一般称为签名),必须由公钥进行解密(此时一般称为验签)。 公钥可以公开的,大家使用公钥对信息进行加密,再发送给私钥的持有者,私钥持有者使用私钥对信息进行解密,获得信息原文。因为私钥只有单一人持有,因此不用担心被他人解密获取信息原文。
# 常见算法
- RSA:“老一代密码学”,靠 “把一个超大数字拆成两个质数” 难度来保证安全;
- ECC:“新一代密码学”,靠 “在椭圆曲线图上找到点乘关系” 难度来保证安全;
| 特点 | RSA | ECC |
|---|---|---|
| 数学原理 | 大整数分解 | 椭圆曲线离散对数 |
| 密钥长度 | 长(2048+ 位) | 短(256 位即可) |
| 性能 | 慢、占空间大 | 快、占空间小 |
| 安全性 | 较强 | 同等长度下更强 |
| 应用 | 老系统、证书兼容性强 | 区块链、现代加密协议 |
| 公钥丢失 | 无法找回 | 可通过私钥计算 |
# 示例代码
public static void main(String[] args) throws Exception {
// 加密算法
String algorithm = "RSA";
// 1. 生成密钥对
// 创建密钥对生成器对象
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();
// 对公私钥进行base64编码
String privateKeyString = Base64.encode(privateKeyEncoded);
String publicKeyString = Base64.encode(publicKeyEncoded);
// 打印私钥
System.out.println(privateKeyString);
// 打印公钥
System.out.println(publicKeyString);
// 2. 使用私钥加密,公钥解密
String input = "Hello World";
// 创建加密对象
// 参数表示加密算法
Cipher cipher = Cipher.getInstance(algorithm);
// 初始化加密
// 第一个参数:加密的模式
// 第二个参数:使用私钥进行加密
cipher.init(Cipher.ENCRYPT_MODE,privateKey);
// 私钥加密
byte[] bytes = cipher.doFinal(input.getBytes());
System.out.println(Base64.encode(bytes));
// 公钥解密
cipher.init(Cipher.DECRYPT_MODE,publicKey);
byte[] decryptBytes = cipher.doFinal(bytes);
System.out.println(new String(decryptBytes)); // Hello World
// 3. 使用公钥加密,私钥解密
String input2 = "Hi World";
// 公钥加密
cipher.init(Cipher.ENCRYPT_MODE,publicKey);
byte[] bytes2 = cipher.doFinal(input2.getBytes());
System.out.println(Base64.encode(bytes2));
// 私钥解密
cipher.init(Cipher.DECRYPT_MODE,privateKey);
byte[] decryptBytes2 = cipher.doFinal(bytes2);
System.out.println(new String(decryptBytes2)); // Hi World
}
# 加密过程
Alice 与 Bob 事先互不认识,也没有可靠安全的沟通渠道,但 Alice 现在却要透过不安全的互联网向 Bob 发送信息。
| Alice | Eve (窃听者) | Bob |
|---|---|---|
生成密钥对 B私 和 B公 | ||
<- 发送 B公 给 Alice | ||
收到 B公 <- | 窃取 B公 <- | |
使用 B公 加密 原文 ,发送密文给 Bob-> | ||
-> 窃取 密文 | -> 收到 密文 ,使用 B私 解密得到 原文 |
- 在这个过程中,Eve 由于没有 Bob 的私钥,因此无法解密 Alice 使用 Bob 的公钥加密的原文。
- 但这并不意味着就是安全的,因为存在 Eve 偷换密钥的情况:
| Alice | Eve | Bob |
|---|---|---|
生成密钥对 B私 和 B公 | ||
<- 发送 B公 给 Alice | ||
窃取 B公 ,并拦截 <- | ||
生成密钥对 E私 和 E公 | ||
<- 篡改 Bob 的消息,发送 E公 给 Alice | ||
使用 E公 加密 原文 ,发送密文给 Bob | ||
窃取 密文 ,并拦截 | ||
使用 E私 解密密文,篡改内容,再使用 B公 加密,发送给 Bob-> | ||
收到 密文 ,使用 B私 解密得到被 Eve 篡改过的 原文 |
# 数字签名
又称公钥数字签名
场景:
- Alice 想将文件 A 发送给 Bob,想要确保 Bob 收到的文件是未被篡改的原版文件,则可以使用数字签名。
- Alice 将公钥 A 发送给 CA(认证机构)注册,注册成功后,CA 会发送数字证书(内包含公钥 A)给 Alice
- Alice 计算文件的哈希函数值,并使用私钥 A 对哈希值进行加密得到
文件签名 - Alice 将
文件、文件签名、数字证书发送给 Bob - Bob 收到后,检查数字证书,是否是信任的 CA 颁发,如果是则使用内含的公钥 A 对文件签名进行解码,得到文件哈希值,进而比较收到的文件哈希值是否与签名中的哈希值一致,如果一致则认为是 Alice 发来的原版文件。
# 数字证书(证书链)
- 怎么样的证书才值得信任?
- 在上面的例子中,Alice 会将公钥 A 发送给 CA(下面称中间 CA)注册,并返回数字证书
- 实际上,中间 CA 返回的数字证书有 2 个:
- 一个是对 Alice 的公钥 A 认证的服务器证书
- 一个是对服务器证书签名的中间证书
- 由于客户端通常不直接信任中间 CA,而是信任根 CA,因此申请证书过程为:
验证流程:
- Bob 收到服务器证书、中间证书
- 使用内置在本地的对应根证书,解密中间证书签名,校验中间证书真伪
- 中间证书验证成功后,使用中间证书中的中间公钥,对服务器证书签名验证
- 服务器证书验证通过后,可认为服务器证书中的公钥是可信任的,来自 Alice 的。
- 随后,Bob 生成一个随机的会话密钥,使用服务器公钥加密,发送给 Alice
- 之后便可以使用会话密钥对称加密通讯
# 扩展
# Base64 编码
- Base64 不是加密算法,是可读性算法。
- 由 64 个字符组成:
A-Z(26)、a-z(26)、0-9(10)、+、/ - 原理:
- 3 个 Byte 为一组,即 24Bit
- 再将 3 个 Byte 拆分成 4 组,每组 6Bit,高位补 0:
11111111 00000000 11111111->(00)111111 (00)110000 (00)000011 (00)111111 - 由于每个 Byte 只有 6bit 有效,因此范围是
000000(0) - 111111(63) - 根据重新分组后的 Byte 映射到对应的字符即得到了 Base64 字符串
- 当用于分组的 Byte 不足 3 个时,输出的 Base64 字符串将用
=补充,每缺 1Byte 补 1 个=
# 1 Byte
"1" => MQ==
# 2 Byte
"12" => MTI=
# 3 Byte
"123" => MTIz
# Base64 映射表
| Index | Binary | Char. | Index | Binary | Char. | Index | Binary | Char. | Index | Binary | Char. |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 000000 | A | 16 | 010000 | Q | 32 | 100000 | g | 48 | 110000 | w |
| 1 | 000001 | B | 17 | 010001 | R | 33 | 100001 | h | 49 | 110001 | x |
| 2 | 000010 | C | 18 | 010010 | S | 34 | 100010 | i | 50 | 110010 | y |
| 3 | 000011 | D | 19 | 010011 | T | 35 | 100011 | j | 51 | 110011 | z |
| 4 | 000100 | E | 20 | 010100 | U | 36 | 100100 | k | 52 | 110100 | 0 |
| 5 | 000101 | F | 21 | 010101 | V | 37 | 100101 | l | 53 | 110101 | 1 |
| 6 | 000110 | G | 22 | 010110 | W | 38 | 100110 | m | 54 | 110110 | 2 |
| 7 | 000111 | H | 23 | 010111 | X | 39 | 100111 | n | 55 | 110111 | 3 |
| 8 | 001000 | I | 24 | 011000 | Y | 40 | 101000 | o | 56 | 111000 | 4 |
| 9 | 001001 | J | 25 | 011001 | Z | 41 | 101001 | p | 57 | 111001 | 5 |
| 10 | 001010 | K | 26 | 011010 | a | 42 | 101010 | q | 58 | 111010 | 6 |
| 11 | 001011 | L | 27 | 011011 | b | 43 | 101011 | r | 59 | 111011 | 7 |
| 12 | 001100 | M | 28 | 011100 | c | 44 | 101100 | s | 60 | 111100 | 8 |
| 13 | 001101 | N | 29 | 011101 | d | 45 | 101101 | t | 61 | 111101 | 9 |
| 14 | 001110 | O | 30 | 011110 | e | 46 | 101110 | u | 62 | 111110 | + |
| 15 | 001111 | P | 31 | 011111 | f | 47 | 101111 | v | 63 | 111111 | / |
# Base58 编码
- 一般用在区块链中
- 相较于 Base64,少了
0(数字)、O(大写)、I、i、+、/
| Decimal | Character | Decimal | Character |
|---|---|---|---|
| 0 | 1 | 29 | W |
| 1 | 2 | 30 | X |
| 2 | 3 | 31 | Y |
| 3 | 4 | 32 | Z |
| 4 | 5 | 33 | a |
| 5 | 6 | 34 | b |
| 6 | 7 | 35 | c |
| 7 | 8 | 36 | d |
| 8 | 9 | 37 | e |
| 9 | A | 38 | f |
| 10 | B | 39 | g |
| 11 | C | 40 | h |
| 12 | D | 41 | i |
| 13 | E | 42 | j |
| 14 | F | 43 | k |
| 15 | G | 44 | m |
| 16 | H | 45 | n |
| 17 | J | 46 | o |
| 18 | K | 47 | p |
| 19 | L | 48 | q |
| 20 | M | 49 | r |
| 21 | N | 50 | s |
| 22 | P | 51 | t |
| 23 | Q | 52 | u |
| 24 | R | 53 | v |
| 25 | S | 54 | w |
| 26 | T | 55 | x |
| 27 | U | 56 | y |
| 28 | V | 57 | z |