> :key: 密码学:研究编制密码和破译密码的科学。 #### 1.密码学的发展 密码学:密码学有数千年的历史,从最开始的替换法到如今的非对称加密算法,经历了古典密码学,近代密码学和现代密码学三个阶段。密码学不仅仅是数学家们的智慧,更是如今网络空间安全的重要基础。 **古典密码学** 古典密码学的核心原理为替换法和移位法,通常用来在战争中保护重要的通信资料。 - 替换法:替换法就是用固定的信息将原文替换成无法直接阅读的密文信息。例如将 `b` 替换成 `w` ,`e` 替换成 `p` ,这样 `bee` 单词就变换成了 `wpp`,不知道替换规则的人就无法阅读出原文的含义。 替换法又分为单表替换和多表替换: - 单表替换:原文和密文使用同一张替换表,相对比较简单。 - 多表替换:多表替换有多张原文密文对照表单,按照约定顺序进行加密解密。 > :books: 多表替换实现:例如制定好 3 张替换表单(1:abcde-swtrp、2:abcde-chfhk、3:abcde-jftou),同时约定第 1 个字母用第 3 张表单,第 2 个字母用第 1 张表单,第 3 个字母用第 2 张表单;此时 `bee` 单词就变成了 `fpk`(312),破解难度更高,其中 312 又叫做密钥,密钥可以事先约定好,也可以在传输过程中标记出来。 - 移位法:移位法就是将原文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后得出密文,例如约定好向后移动 2 位 `abcde` -> `cdefg`,这样 `bee` 单词就变换成了 `dgg`;与替换法一样,移位法也分为单表移位和多表移位,典型的单表移位法有 `恺撒加密`;而典型的多表移位案例有 `维尼吉亚密码`(维热纳尔密码)。 **频率分析法** 频率分析法是古典密码的一种破译方式,基于概率论分析研究字母或者字母组合在文本中出现的频率,以此破解古典密码。 > 频率分析法破译原理: > > 在英文单词中字母出现的频率是不同的,e 以 12.702% 的百分比占比最高,而 z 只占到了 0.074%,如果密文的数量足够大,仅仅采用频率分析法就可以破解单表的替换法或移位法;而多表的替换法或移位法虽然难度较高,但如果数据量足够大的话,也是可以破解的。 **近代密码学** 近代密码学的核心原理仍然是替换法和移位法,只不过因为密码表种类极多,同时加密解密机器化,所以破解难度较高; [恩尼格玛机](https://baike.baidu.com/item/%E6%81%A9%E5%B0%BC%E6%A0%BC%E7%8E%9B%E5%AF%86%E7%A0%81%E6%9C%BA/5691350):恩尼格玛机是二战时期纳粹德国使用的加密机器,它是一系列相似的转子机械加解密机器的统称,包括了许多不同的型号。 > :bulb: 恩尼格玛机的密码虽然复杂,后来仍被英国破译,参与破译的人员中包括后来的被称作计算机科学之父、人工智能之父的 [图灵](https://baike.baidu.com/item/%E8%89%BE%E4%BC%A6%C2%B7%E9%BA%A6%E5%B8%AD%E6%A3%AE%C2%B7%E5%9B%BE%E7%81%B5/3940576)。 **现代密码学** 现代密码学:在现代密码学中,主要分为了 3 中加密(解密)方式 - 散列函数:如 MD5,SHA-1,SHA-256 等,多应用在文件校验,数字签名中。 - 对称加密(解密):对称加密分为流加密和块加密两种,常见的如 DES 加密解密,AES 加密解密。 - 非对称加密:非对称加密有两支密钥(公钥和私钥),加密和解密运算使用不同的密钥,常见的算法如 RSA,ECC 等。 我们如何设置密码才安全? 1. 密码不能太简单常见,如 123456 这种。 2. 不同的应用软件尽量设置不同的密码,防止一个应用的密码泄露而造成全部的应用密码崩塌。 3. 在设置密码时可以增加注册时间、注册地点、应用特性等特殊标记,比如 jdxxxx1234(京东)密码。 --- #### 2.密码学基础 ASCII 编码:American Standard Code for Information Interchange,美国信息交换标准代码,它是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言,它是现今最通用的单字节编码系统,并等同于国际标准 ISO/IEC 646。 > :anger: 在 ASCII 码中,字母 A 对应的十进制数字为 65,B-Z 在 65 后依次增加;字母 a 对应的十进制数字为 97,b-z 在 97 后依次增加。 在 Java 中,我们可以基于 ASCII 编码实现凯撒加密: ~~~java /** * 加密方法 * @param input 原文 * @param key 秘钥(位移位数) * @return 密文 */ private static String encrypt(String input, int key) { StringBuilder secret = new StringBuilder(); for (char c : input.toCharArray()) { // 位移 key 个字符 secret.append((char) (c + key)); } return secret.toString(); } ~~~ 在加密时,将字母移动指定位数;在解密时,只需要将字母向反方向位移指定位数即可。~注:此处仅为实现原理,并未严格按照字母进行位移。~ :question: ​Byte 和 Bit Byte:字节,数据存储的基本单位,简称 B; > 常见的数据存储单位及大小: > > B ~x1024~ KB ~x1024~ MB ~x1024~ GB ~x1024~ TB ~x1024~ PB ~x1024~ EB ... Bit:比特,又叫位,是计算机运算(传输)的基本单位,1 个 bit 就是 1 位二进制数。 Byte 和 Bit 的联系:8 个 Bit 组成一个 Byte,将 8 个 0 或者 1 组合在一起,就可以说它们是 8 个比特或者 1 个字节。 > :eyes: 宽带带宽与下载速度: > > 1M 的带宽指的是 1M(1024*1024)的 Bit,而下载速度一般是以 Byte 计算的,所以一般来说下载速度约为带宽的 1/8,并且不会超过此值。 中英文与字节: - 在英文中,字母(符号)对应的 Byte 就是其对应的 ASCII 编码。 - 在中文中,根据编码的格式不一样,一个汉字对应的 Byte 个数也不一样;UTF-8 编码为 3 个字节,而 GBK 为 2 个字节。 ~~~java public static void main(String[] args) throws UnsupportedEncodingException { String en = "A"; System.out.println(en + " 的 UTF-8 编码:" + Arrays.toString(en.getBytes())); System.out.println(en + " 的 GBK 编码:" + Arrays.toString(en.getBytes("GBK"))); String zh = "中"; System.out.println(zh + " 的 UTF-8 编码:" + Arrays.toString(zh.getBytes())); System.out.println(zh + " 的 GBK 编码:" + Arrays.toString(zh.getBytes("GBK"))); } ----------------------------------------------------------------------------------- 输出: A 的 UTF-8 编码:[65] A 的 GBK 编码:[65] 中 的 UTF-8 编码:[-28, -72, -83] 中 的 GBK 编码:[-42, -48] ~~~ 由此可见:英文字母的 Byte 数组就是其对应那一个的 ASCII 编码,而中文则根据不同的编码集由多个 Byte 组成。 --- #### 3.对称加密 对称加密的加密和解密用的都是同一个秘钥。 流加密和块加密: - 流加密:使用秘钥生成一个伪随机的加密数据流如 `1001010110101...`,再使用原文数据的数据流按顺序进行加密(一般为异或)操作得到密文。 - 块加密:又称分组加密,将信息分割成大小相等的数据块(如果最后一个块的大小不够,则会按照特定规则进行补全),然后对每一个数据块分别进行加密运算,块加密的允许使用同一个密钥对多于一块的数据进行加密,所以一般来说比流加密更安全一些。 > 流密码与块密码,各自有各自的优缺点,出于安全考量,在现代密码学中一般都优先考虑分组加密算法。 对称加密的特点: 1. 加密速度快, 可以加密大文件。 2. 密文可逆, 一旦密钥文件泄漏, 就会导致数据暴露。 3. 加密后如果在编码表找不到对应字符, 则会出现乱码。 4. 一般需要结合 **Base64** 进行使用。 常见的加密算法: - **DES**:*Data Encryption Standard*,数据加密标准。是一种使用密钥加密的块算法,1977 年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。 - **AES**:*Advanced Encryption Standard*,高级加密标准。在密码学中又称 Rijndael 加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的 DES,已经被多方分析且广为全世界所使用。 ==Java 中的加密解密类:**javax.crypto.Cipher**,该类提供加密和解密的功能,时 Java 加密扩展框架(JCE)的核心。== 使用 Java 实现 DES 加密: ~~~java public class FileDemo { public static void main(String[] args) throws Exception { // 原文 String input = "今天是个好日子"; // 秘钥(DES 加密的秘钥必须为 8 个字节) String key = "12354678"; // 创建加密对象,传入加密算法 Cipher cipher = Cipher.getInstance("DES"); // 创建加密规则:参数1(秘钥的字节数组),参数二(加密类型) SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "DES"); // 初始化加密对象:参数1(模式:加密/解密),参数二(加密规则) cipher.init(Cipher.ENCRYPT_MODE, keySpec); // 进行加密 byte[] encrypt = cipher.doFinal(input.getBytes()); System.out.println(new String(encrypt)); } } --------------------------------------------------- 输出:�^�i��Pۺi�c���قL ~~~ 由于在加密后,byte 数组可能会出现 ASCII 编码表之外的值,此时会出现乱码,此时一般使用 Base64 进行转码。 使用 `com.sun.org.apache.xerces.internal.impl.dv.util.Base64` 类调用 `encode` 方法即可进行 Base64 编码。 ~~~java // 进行加密 byte[] encrypt = cipher.doFinal(input.getBytes()); String encode = Base64.encode(encrypt); System.out.println(encode); --------------------------------------------------- 此时输出:lF6LaY67UNu6uQjCCGmtY6qN2e8I2YJM ~~~ 使用 Java 实现 DES 解密: ~~~java private static String decrypt(String secret, String key) throws Exception { // 创建解密对象,传入加密算法 Cipher cipher = Cipher.getInstance("DES"); // 创建解密规则:参数1(秘钥的字节数组),参数二(解密类型) SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "DES"); // 初始化加密对象:参数1(模式:加密/解密),参数二(加密规则) cipher.init(Cipher.DECRYPT_MODE, keySpec); // 将密文进行 Base64 解码 byte[] decode = Base64.decode(secret); // 进行解密 byte[] bytes = cipher.doFinal(decode); // 返回原文 return new String(bytes); } ~~~ 注:由于前面的密文经过了 Base64 编码,所以拿到密文后第一步就应该进行 Base64 解码。 > :aerial_tramway: AES 加密解密与 DES 加密解密的 Java 代码一致(参数传入 AES),唯一不同的是 AES 的秘钥必须为 16 个字符。 Base64 是一种基于 64 个可打印字符来表示二进制数据的方法,是网络上最常见的用于传输 8Bit(Byte) 字节码的编码方式之一。 Base64 不是加密算法而是一种可读性算法,它由 64 个字符组成,分别是 `A-Z、a-z、0-9、+、/`。 Base64 原理:它将每 3 个字节分为一组,每个字节是 8 位共 24 位,然后将它们转为 4 组,每组 6 位,在每组的高位补 2 个 0 构成 4 个字节,这样将每个字节的数字控制在 0-63 之内,然后按照 Base64 编码表进行字符转换(就是上面的 64 个字符顺序)。 在编码过程中,有时候原文的字节数组不一定是 3 的倍数,这时 Base64 会进行补等号: - 当最后一组仅有 1 个字节时:将字节分为 8 个 Bit,前 6 个 Bit 正常编码,后 2 个 Bit 追加 4 个 0 组成 6 位然后进行正常编码得到 2 个字符的 Base64 编码,然后在字符后方补 2 个 `=` 组成 4 个字符。 - 当最后一组有 2 个字节时:将 2 个字节分为 16 个 Bit,前 12 个 Bit 正常编码,后 4 个 Bit 追加 2 个 0 组成 6 位然后进行正常编码得到 3 个字符的 Base64 编码,然后在字符后方补 1 个 `=` 组成 4 个字符。 --- #### 4.加密模式和填充模式 在分组加密中,存在着多种加密模式(算法),常见的加密模式有:ECB、CBC 等。 - **ECB**:*Electronic Code Book*,电子密码本;将需要加密的消息按照块密码的块大小被分为数个块,每个块使用同一个 key 进行独立加密。 - 优点:可以并行处理数据。 - 缺点:安全性较低。 - **CBC**:*Cipher-block chaining*, 密码块链接;通过初始化向量(VI)和 key 生成第一个密文块,后续的每个明文块先与前一个密文块进行异或后,再进行加密。 - 优点:同样的原文生成的密文不一样,安全性较高。 - 缺点:只能够串行的处理数据。 在分组加密中,当数据长度不符合块处理需求时, 则会按照一定的方法填充块长的规则。 - **NoPadding**:不填充,此时 DES 要求原文长度是 8Byte 的整数倍,AES 要求原文长度是 16Byte 的整数倍。 - **PKCS5Padding**:数据块的大小为 8Byte,不够就进行补足,补足的填充值算法:value = k - (d % k) ~k:块大小,d:数据长度~。 指定加密模式和填充模式使用 Cipher: ~~~java Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); ~~~ >:deer: 默认情况下,javax.crypto.Cipher 中的加密模式和填充模式分别为 ECB 和 PKCS5Padding。 如果使用 CBC 加密模式,需要初始化一个 IV 向量: ~~~java private static String cbcDecrypt(String plain, String key) throws Exception { // 创建解密对象,传入加密算法 DES、加密模式 CBC、填充模式 PKCS5Padding Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); // 创建 CBC 加密的初始化向量 IvParameterSpec iv = new IvParameterSpec(key.getBytes()); // 创建解密规则:参数1(秘钥的字节数组),参数二(解密类型) SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "DES"); // 初始化加密对象:参数1(模式:加密/解密),参数二(加密规则),参数三(初始化向量) cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); // 进行解密 byte[] encrypt = cipher.doFinal(plain.getBytes()); // 返回原文 return Base64.encode(encrypt); } ~~~ 同理,解密 CBC 加密模式的密文时,也需要初始化一个 IV 向量: ~~~java // 创建 CBC 解密的初始化向量 IvParameterSpec iv = new IvParameterSpec(key.getBytes()); // 创建解密规则:参数1(秘钥的字节数组),参数二(解密类型) SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "DES"); // 初始化加密对象:参数1(模式:加密/解密),参数二(加密规则) cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); ~~~ --- #### 5.消息摘要 消息摘要:*Message Digest*,又称数字摘要,它可以将任意长度的消息变成固定长度的短消息。 - 它由一个单向的 Hash 加密函数对消息进行作用而产生,所以消息摘要是单向、不可逆的。 - 使用消息摘要生成的值是不可以篡改的,所以它被广泛用来验证文件的安全性。 - 无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。 - 只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同,但相同的输入必定会产生相同的输出(改变文件名不会影响消息摘要)。 > :zap: 在很多文件下载网站都会给出文件的 **SHA512**、**MD5** 等消息摘要值,我们可以使用下载下来的文件生成消息摘要进行比对以确保文件没有被篡改。 常见的消息摘要算法有:**MD5**、**SHA1**、**SHA256**、**SHA512**。 在 Java 中,消息摘要算法由 **java.security.MessageDigest** 提供。 使用 Java 获取字符串的消息摘要: ~~~java // 创建消息摘要对象 MessageDigest md5 = MessageDigest.getInstance("MD5"); // 原文 String plain = "今天是个好日子"; // 获取消息摘要数组 byte[] digest = md5.digest(plain.getBytes()); ~~~ 得到消息摘要字节数组后,还可能会出现乱码,此时一般的做法不是采用 Base64 转码,而是将字节数组的每一个 Byte 转为 16 进制的字符串(如果转换为16进制的字符串的长度为 1 则在前面补 0): ~~~java // 转换消息摘要 StringBuilder builder = new StringBuilder(); for (byte b : digest) { String s = Integer.toHexString(b & 0xff); builder.append(s.length() == 1 ? "0" : "").append(s); } System.out.println(builder.toString()); ~~~ > 为什么要使用 Byte & 0xff :question: > > 1. 0xff 是 16 进制的表达方式,f 指 15,0xff 的十进制是 255,二进制是 1111 1111,int 为 0000 0000 0000 0000 0000 0000 1111 1111。 > 2. 在计算机中,负数以其正值的补码(二进制取反 + 1)形式进行存储,正数的补码就是其原码。 > 3. 数据由 Byte 转换为 int 就是在最高位补符号数(正数补 0,负数补 1)。 > > 例如: > > 1. Byte -5 在计算机中存储为 0000 0101 -> 1111 1010 + 1 -> 1111 1011,转换为 int 则为 1111 1111 1111 1111 1111 1111 1111 1011。 > 2. 我们直接对 -5 转 16 进制的结果为 fffffffb(多了很多的符号位)。 > 3. 在转换消息摘要这里我们不关心数值的具体大小,而只需要字节的低 8 位转换而成的十六进制字符串(两位长度),所以使用 0xff 与 -5 相与得 0000 0000 0000 0000 0000 0000 1111 1011,其低 8 位 的补码(1111 1011)与 Byte 的 8 位 补码一致,转换后的十六进制也只有 2 位。 ==如果需要对文件求 MD5 值,则先需要将文件转为 Byte数组。== --- #### 6.非对称加密 非对称加密算法是现代加密算法最流行的加密方式,它是计算机通信安全的基石,保证了加密数据不会被破解。 - 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。 - 如果用 publickey 对数据进行加密,只有用对应的 privatekey 才能解密。 - 如果用 privatekey 对数据进行加密,只有用对应的 publickey 才能解密。 - 非对称加密处理数据的速度较慢, 但是安全级别高。 目前市面上最常见的非对称加密算法有:**RSA** 和 **ECC**。 在 Java 中,我们使用 **java.security.KeyPairGenerator** 生成公钥和私钥。 使用 Java 生成 RSA 公钥和私钥: ~~~java public class RSADemo { public static void main(String[] args) throws NoSuchAlgorithmException { // 获取 key 生成器 KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA"); // 获取密钥对 KeyPair keyPair = rsa.generateKeyPair(); // 获取公钥 PublicKey publicKey = keyPair.getPublic(); // 获取私钥 PrivateKey privateKey = keyPair.getPrivate(); //获取秘钥对应字节数组 byte[] publicKeyEncoded = publicKey.getEncoded(); byte[] privateKeyEncoded = privateKey.getEncoded(); //打印秘钥 System.out.println(Base64.encode(publicKeyEncoded)); System.out.println(Base64.encode(privateKeyEncoded)); } } ~~~ 使用 Java 实现 RSA 加密解密: ~~~java public class RSADemo { public static void main(String[] args) throws Exception { String algorithm = "RSA"; String plain = "今天是个好日子"; // 获取 key 生成器 KeyPairGenerator rsa = KeyPairGenerator.getInstance(algorithm); // 获取密钥对 KeyPair keyPair = rsa.generateKeyPair(); // 生成 RSA 加密对象 Cipher cipher = Cipher.getInstance(algorithm); // 使用私钥初始化加密对象(key 为 PublicKey 或 PrivateKey) cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); // 进行加密 byte[] bytes = cipher.doFinal(plain.getBytes()); String secret = Base64.encode(bytes); System.out.println(secret); // 使用公钥初始化加密对象(key 为 PublicKey 或 PrivateKey) cipher.init(Cipher.DECRYPT_MODE, keyPair.getPublic()); // 进行解密 byte[] plainBytes = cipher.doFinal(Base64.decode(secret)); System.out.println(new String(plainBytes)); } } ~~~ 在实际使用过程中,我们一般讲公钥和私钥保存至文件中,然后在每次加密解密时直接读取字符串进行使用。 保存秘钥字符串: ~~~java private static void saveKey2File() throws Exception { // 获取 key 生成器 KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA"); // 获取密钥对 KeyPair keyPair = rsa.generateKeyPair(); // 指定秘钥保存文件(当前项目目录下) File publicKeyFile = new File("public.txt"); File privateKeyFile = new File("private.txt"); try (FileWriter publicWriter = new FileWriter(publicKeyFile); FileWriter privateWriter = new FileWriter(privateKeyFile)){ // 保存 Base64 编码过后的字符串到文件中 publicWriter.write(Base64.encode(keyPair.getPublic().getEncoded())); privateWriter.write(Base64.encode(keyPair.getPrivate().getEncoded())); } } ~~~ 使用秘钥字符串生成私钥: ~~~java private static PrivateKey getPrivateKey(String keyEncode) throws Exception { // 获取 RSA 秘钥生成工厂 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); // 获取满足私钥 key 规范的 KeySpec PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(keyEncode)); // 生成私钥并返回 return keyFactory.generatePrivate(keySpec); } ~~~ 使用秘钥字符串生成公钥: ~~~java private static PublicKey getPublicKey(String keyEncode) throws Exception { // 获取 RSA 秘钥生成工厂 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); // 获取满足公钥 key 规范的 KeySpec X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(keyEncode)); // 生成公钥并返回 return keyFactory.generatePublic(keySpec); } ~~~ 公钥或私钥生成后,就可以使用其进行加密或解密。 > :smiling_imp: 在非对称加密中,公钥一般是公钥是公开的,所有人都可以认领;而私钥绝对保密,一般不参与数据传输。 --- #### 7.数字签名和数字证书 数字签名:指公钥数字签名,只有信息的发送者才能产生别人无法伪造的一段数字串。它的含义是:在网络中传输数据时候,给数据添加一个数字签名,表示是谁发的数据,而且还能证明数据没有被篡改。 数字签名原理: 1. A 发送一段信息给 B,在写完信息后,A 对信息进行消息摘要,然后对消息摘要使用私钥加密得到数字签名。 2. A 将数字签名附着在信息下方然后发出(如果需要防止信息被窃听,还可以对信息本身使用私钥加密)。 3. B 收到信息后取下数字签名,对数字签名使用公钥解密,如果解密成功,则证明信息确实是由 A 发出的。 4. B 对信息本身(解密后)进行散列,如若散列结果与信息签名一致,则信息未被修改。 使用 Java 生成数字签名和校验数字签名: ~~~java // 原文 String plain = "今天是个好日子"; // 获取 key 生成器 KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA"); // 获取密钥对 KeyPair keyPair = rsa.generateKeyPair(); // 获取签名生成器 Signature signature = Signature.getInstance("SHA256withRSA"); // 使用私钥初始化签名生成器 signature.initSign(keyPair.getPrivate()); // 传入原文 signature.update(plain.getBytes()); // 生成数字签名 String signature = Base64.encode(signature.sign()); System.out.println(signature); // 使用公钥初始化签名校验器 verify.initVerify(keyPair.getPublic()); // 传入原文 verify.update(plain.getBytes()); // 校验数字签名 boolean valid = verify.verify(Base64.decode(signature)); System.out.println(valid); ~~~ > :artificial_satellite: 数字签名存在的问题:如果 B 保存的公钥被人篡改,然后用私钥给 B 发送信息,此时虽然 B 接收的消息是冒充的但是却无法察觉。 数字证书:在我们对签名进行验证时,需要用到公钥。如果公钥是伪造的或者丢失了公钥,那我们就无法验证数字签名,也就不可能从数字签名确定对方的合法性,这时候就需要用到数字证书(公钥 + 数字签名打包)。 > :tiger: 数字证书被广泛用于 **HTTPS** 协议。 数字证书原理: 1. A 去找 CA 证书中心做公钥认证,证书中心用自己的私钥,对 A 的公钥和一些相关信息一起加密,生成数字证书。 2. A 向 B 发送信息时,除了数字签名,还要附加上这张数字证书。 3. B 收到信息后使用 CA 的公钥解密这份数字证书得到 A 的公钥,然后使用 A 的公钥进行验证数字签名流程。 > :joystick: 既然公钥可以被伪造,那么数字证书也有可能是伪造的,如何保证数字证书的可靠性呢? > > B 从 CA 证书中心获取一份根证书,里面存储 CA 公钥来验证所有 CA 分中心颁发的数字证书,只要 A 的公钥在 CA 任意一个分支中心做了认证,那么 B 的根证书就可以验证 A 的数字证书的合法性。 > > 根证书是自验证证书,CA 机构是获得社会绝对认可和有绝对权威的第三方机构,这一点保证了根证书的绝对可靠,如果根证书都有问题那么整个加密体系毫无意义。 > > 在我们的 Windows 系统中,预留了很多 CA 根证书,使得我们访问的大多数资源时都能够得到有效认证。 HTTPS 原理: 1. 客户端向服务器发出加密请求(reques)。 2. 服务器用自己的私钥加密网页数据后,连同本身的数字证书,一起发送给客户端。 3. 客户端(浏览器)内置了一个证书管理器,里面包含了「受信任的根证书颁发机构」的列表,客户端(浏览器)会根据这张列表,查看数字证书的发送机构是否在根证书颁发机构列表之内。 4. 如果数字证书是可靠的,客户端就可以使用 CA 中心的公钥,对证书进行解密得到服务器的公钥,然后与服务器交换信息。 5. 如果证书存在问题,浏览器则会发出警告「此网站的安全证书有问题」。 --- #### 8.keytool **keytool** 是 JDK 自带的一个用来管理公钥、私钥和数字证书的工具,其中包含一系列的命令,能够帮助我们快速的生成证书、导出证书等;此工具位于 jre 下的 bin 目录中。 keytool 用法: ~~~bash P:\Java\jre1.8.0_251\bin>keytool -help 密钥和证书管理工具 命令: -certreq 生成证书请求 -changealias 更改条目的别名 -delete 删除条目 -exportcert 导出证书 -genkeypair 生成密钥对 -genseckey 生成密钥 -gencert 根据证书请求生成证书 -importcert 导入证书或证书链 -importpass 导入口令 -importkeystore 从其他密钥库导入一个或所有条目 -keypasswd 更改条目的密钥口令 -list 列出密钥库中的条目 -printcert 打印证书内容 -printcertreq 打印证书请求的内容 -printcrl 打印 CRL 文件的内容 -storepasswd 更改密钥库的存储口令 ~~~ 使用 genseckey 命令生成用户的密钥(生成的文件默认在家目录下的 .keystore 中): ~~~bash P:\Java\jre1.8.0_251\bin>keytool -genkey -keypass 951112 -keyalg RSA -storepass 951112 您的名字与姓氏是什么? [Unknown]: 何鑫 您的组织单位名称是什么? [Unknown]: 小星星集团 您的组织名称是什么? [Unknown]: star 您所在的城市或区域名称是什么? [Unknown]: 成都市 您所在的省/市/自治区名称是什么? [Unknown]: 四川省 该单位的双字母国家/地区代码是什么? [Unknown]: 86 CN=何鑫, OU=小星星集团, O=star, L=成都市, ST=四川省, C=86是否正确? [否]: y Warning: JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore C:\Users\star\.keystore -destkeystore C:\Users\star\.keystore -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。 ~~~ 查看 keystore 详细信息: ~~~bash P:\Java\jre1.8.0_251\bin>keytool -list -v 输入密钥库口令: 密钥库类型: jks 密钥库提供方: SUN 您的密钥库包含 1 个条目 别名: mykey 创建日期: 2021-4-19 条目类型: PrivateKeyEntry 证书链长度: 1 证书[1]: 所有者: CN=何鑫, OU=小星星集团, O=star, L=成都市, ST=四川省, C=86 发布者: CN=何鑫, OU=小星星集团, O=star, L=成都市, ST=四川省, C=86 序列号: 5a5cd516 有效期为 Mon Apr 19 00:22:16 CST 2021 至 Sun Jul 18 00:22:16 CST 2021 证书指纹: MD5: 87:8A:12:A3:8D:D3:CE:47:24:F0:70:2A:AD:1E:85:A5 SHA1: 3F:3B:BE:5D:96:79:BB:67:FD:DB:8B:87:BC:F0:D4:45:AA:1C:3C:72 SHA256: 6A:D9:3B:16:C0:6D:A4:87:DA:A9:7F:99:41:F6:B9:BB:B7:3D:ED:1C:E2:D4:49:06:CE:AB:44:8C:67:3D:C0:4C 签名算法名称: SHA256withRSA 主体公共密钥算法: 2048 位 RSA 密钥 版本: 3 扩展: #1: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 81 1D F4 5F 63 B0 9D 20 45 F7 63 01 D9 CE 1A 8C ..._c.. E.c..... 0010: 64 3B 91 84 d;.. ] ] ******************************************* ******************************************* ~~~ 将证书导出为 crt 文件: ~~~bash P:\Java\jre1.8.0_251\bin>keytool -export -file C:\Users\star\Desktop\star.crt 输入密钥库口令: 存储在文件 中的证书 ~~~ 删除证书: ~~~bash P:\Java\jre1.8.0_251\bin>keytool -delete -keystore C:\Users\star\.keystore -storepass 951112 ~~~