Java密码学

密码学

1.1 密码学基本概念

密码在我们的生活中有着重要的作用,那么密码究竟来自何方,为何会产生呢?

密码学是网络安全、信息安全、区块链等产品的基础,常见的非对称加密、对称加密、散列函数等,都属于密码学范畴。

密码学有数千年的历史,从最开始的替换法到如今的非对称加密算法,经历了古典密码学,近代密码学和现代密码学三个阶段。密码学不仅仅是数学家们的智慧,更是如今网络空间安全的重要基础。

1.1.1 古典密码学

在古代的战争中,多见使用隐藏信息的方式保护重要的通信资料。比如先把需要保护的信息用化学药水写到纸上,药水干后,纸上看不出任何的信息,需要使用另外的化学药水涂抹后才可以阅读纸上的信息。

https://www.iqiyi.com/v_19rt6ab1hg.html 1分05秒

这些方法都是在保护重要的信息不被他人获取,但藏信息的方式比较容易被他人识破,例如增加哨兵的排查力度,就会发现其中的猫腻,因而随后发展出了较难破解的古典密码学。

① 替换法

替换法很好理解,就是用固定的信息将原文替换成无法直接阅读的密文信息。例如将 b 替换成 we 替换成p ,这样bee 单词就变换成了wpp,不知道替换规则的人就无法阅读出原文的含义。

替换法有**单表替换多表替换**两种形式。单表替换即只有一张原文密文对照表单,发送者和接收者用这张表单来加密解密。在上述例子中,表单即为:a b c d e - s w t r p

多表替换即有多张原文密文对照表单,不同字母可以用不同表单的内容替换。

例如约定好表单为:表单 1:abcde-swtrp 、表单2:abcde-chfhk 、表单 3:abcde-jftou

规定第一个字母用第三张表单,第二个字母用第一张表单,第三个字母用第二张表单,这时 bee单词就变成了

(312)fpk ,破解难度更高,其中 312 又叫做密钥,密钥可以事先约定好,也可以在传输过程中标记出来。

② 移位法

移位法就是将原文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后得出密文,典型的移位法应用有 “ 恺撒密码 ”。

例如约定好向后移动2位(abcde - cdefg),这样 bee 单词就变换成了dgg

同理替换法,移位法也可以采用多表移位的方式,典型的多表案例是“维尼吉亚密码”(又译维热纳尔密码),属于多表密码的一种形式。

image-20230510163148080

③ 古典密码破解方式

古典密码虽然很简单,但是在密码史上是使用的最久的加密方式,直到“概率论”的数学方法被发现,古典密码就被破解了。

image-20230510163306833

英文单词中字母出现的频率是不同的,e以12.702%的百分比占比最高,z 只占到0.074%,感兴趣的可以去百科查字母频率详细统计数据。如果密文数量足够大,仅仅采用频度分析法就可以破解单表的替换法或移位法。

image-20230510163328000

多表的替换法或移位法虽然难度高一些,但如果数据量足够大的话,也是可以破解的。以维尼吉亚密码算法为例,破解方法就是先找出密文中完全相同的字母串,猜测密钥长度,得到密钥长度后再把同组的密文放在一起,使用频率分析法破解。

1.1.2 近代密码学

古典密码的安全性受到了威胁,外加使用便利性较低,到了工业化时代,近现代密码被广泛应用。

恩尼格玛机

恩尼格玛机是二战时期纳粹德国使用的加密机器,后被英国破译,参与破译的人员有被称为计算机科学之父、人工智能之父的图灵。

image-20230510163455972

恩尼格玛机

恩尼格玛机使用的加密方式本质上还是移位和替代,只不过因为密码表种类极多,破解难度高,同时加密解密机器化,使用便捷,因而在二战时期得以使用。

1.1.3 现代密码学

① 散列函数

散列函数,也见杂凑函数、摘要函数或哈希函数,可将任意长度的消息经过运算,变成固定长度数值,常见的有MD5SHA-1SHA256,多应用在文件校验,数字签名中。

MD5 可以将任意长度的原文生成一个128位(16字节)的哈希值

SHA-1可以将任意长度的原文生成一个160位(20字节)的哈希值

② 对称加密

对称加密应用了相同的加密密钥和解密密钥。对称加密分为:序列加密(流密码),分组加密(块密码)两种。流密码是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密,块密码是先对信息流分块,再对每一块分别加密。

例如原文为1234567890,流加密即先对1进行加密,再对2进行加密,再对3进行加密……最后拼接成密文;块加密先分成不同的块,如1234成块,5678成块,90XX(XX为补位数字)成块,再分别对不同块进行加密,最后拼接成密文。前文提到的古典密码学加密方法,都属于流加密。

③ 非对称加密

对称加密的密钥安全极其重要,加密者和解密者需要提前协商密钥,并各自确保密钥的安全性,一但密钥泄露,即使算法是安全的也无法保障原文信息的私密性。

在实际的使用中,远程的提前协商密钥不容易实现,即使协商好,在远程传输过程中也容易被他人获取,因此非对称密钥此时就凸显出了优势。

非对称加密有两支密钥,公钥(publickey)和私钥(privatekey),加密和解密运算使用的密钥不同。用公钥对原文进行加密后,需要由私钥进行解密用私钥对原文进行加密后(此时一般称为签名),需要由公钥进行解密(此时一般称为验签)。公钥可以公开的,大家使用公钥对信息进行加密,再发送给私钥的持有者,私钥持有者使用私钥对信息进行解密,获得信息原文。因为私钥只有单一人持有,因此不用担心被他人解密获取信息原文。

1.1.4 如何设置密码才安全

  • 密码不要太常见,不要使用类似于123456式的常用密码。
  • 各应用软件密码建议不同,避免出现一个应用数据库被脱库,全部应用密码崩塌,
  • 可在设置密码时增加注册时间、注册地点、应用特性等方法。例如tianjin123456,表示在天津注册的该应用。

如果全部软件的密码都设置成相同的,那么不法公司可以利用撞库的方式进行破解。

1.2 ASCII编码

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。

ascll(

创建maven项目 encrypt-decrypt

添加pom文件

<dependencies><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency>
</dependencies>

创建类 com.ynu.ascii.AsciiDemo

字符转换成ascii码

package com.ynu.E1_Ascll;import org.junit.Test;/*** @author ybh* @date 2023.05.10 16:53*/
public class AsciiDemo {@Testpublic void test1() {char a = 'a';int as = (int)a;// 打印字符a的ascii码System.out.println(as);}
}

image-20230510165705222

字符串转换成ascii码

@Test
public void test2(){String a = "AaZ";// 获取ascii码,需要把字符串转成字符char[] chars = a.toCharArray();for (char c : chars) {System.out.println((int) c);}
}

image-20230510165944182

1.3 恺撒加密

1.3.1 中国古代加密

看一个小故事 , 看看古人如何加密和解密:

公元683年,唐中宗即位。随后,武则天废唐中宗,立第四子李旦为皇帝,但朝政大事均由她自己专断。

裴炎、徐敬业和骆宾王等人对此非常不满。徐敬业聚兵十万,在江苏扬州起兵。裴炎做内应,欲以拆字手段为其传递秘密信息。后因有人告密,裴炎被捕,未发出的密信落到武则天手中。这封密信上只有“青鹅”二字,群臣对此大惑不解。

武则天破解了“青鹅”的秘密:“青”字拆开来就是“十二月”,而“鹅”字拆开来就是“我自与”。密信的意思是让徐敬业、骆宾王等率兵于十二月进发,裴炎在内部接应。“青鹅”破译后,裴炎被杀。接着,武则天派兵击败了徐敬业和骆宾王。

image-20230510214549047

1.3.2 外国加密

在密码学中,恺撒密码是一种最简单且最广为人知的加密技术。

凯撒密码最早由古罗马军事统帅盖乌斯·尤利乌斯·凯撒在军队中用来传递加密信息,故称凯撒密码。这是一种位移加密方式,只对26个字母进行位移替换加密,规则简单,容易破解。下面是位移1次的对比:

image-20230510214645300

将明文字母表向后移动1位,A变成了B,B变成了C……,Z变成了A。同理,若将明文字母表向后移动3位:

image-20230510214702764

则A变成了D,B变成了E……,Z变成了C。

字母表最多可以移动25位。凯撒密码的明文字母表向后或向前移动都是可以的,通常表述为向后移动,如果要向前移动1位,则等同于向后移动25位,位移选择为25即可。

它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。这个加密方法是以恺撒的名字命名的,当年恺撒曾用此方法与其将军们进行联系。

恺撒密码通常被作为其他更复杂的加密方法中的一个步骤。

image-20230510214915972

简单来说就是当秘钥为n,其中一个待加密字符ch,加密之后的字符为ch+n,当ch+n超过’z’时,回到’a’计数。

1.3.3 凯撒位移加密

创建类 KaiserDemo,把 hello world 往右边移动3位

@Test
public void test(){String s = "hello world";// 秘钥int key = 3;// 字符串转为字符数组char[] chars = s.toCharArray();// 每个字符右移3位for (int i = 0; i < chars.length; i++) {chars[i] += key;}System.out.println(new String(chars));}

image-20230510215455111

1.3.4 凯撒加密和解密

package com.ynu.E2_kaiser;import org.junit.Test;import java.util.ArrayList;
import java.util.List;/*** @author ybh* @date 2023.05.10 21:49*/
public class KaiserDemo {public static void main(String[] args) {String orignal = "Hello world";// 往右边偏移三位int key = 3;String encryptKaiser =  encryptKaiser(orignal,key);System.out.println("加密:" + encryptKaiser);String decryptKaiser =  decryptKaiser(encryptKaiser,key);System.out.println("解密:" + decryptKaiser);}/*** 使用凯撒加密方式解密数据** @param encryptedData :密文* @param key           :密钥* @return : 源数据*/public static String decryptKaiser(String encryptedData, int key) {// 将字符串转为字符数组char[] chars = encryptedData.toCharArray();StringBuilder sb = new StringBuilder();for (char aChar : chars) {// 获取字符的ASCII编码int asciiCode = aChar;// 偏移数据asciiCode -= key;// 将偏移后的数据转为字符char result = (char) asciiCode;// 拼接数据sb.append(result);}return sb.toString();}/*** 使用凯撒加密方式加密数据** @param orignal :原文* @param key     :密钥* @return :加密后的数据*/public static String encryptKaiser(String orignal, int key) {// 将字符串转为字符数组char[] chars = orignal.toCharArray();StringBuilder sb = new StringBuilder();for (char aChar : chars) {// 获取字符的ascii编码int asciiCode = aChar;// 偏移数据asciiCode += key;// 将偏移后的数据转为字符char result = (char) asciiCode;// 拼接数据sb.append(result);}return sb.toString();}}

1.4 频度分析法破解恺撒加密

频率分析解密法

加密者选择将组成信息的字母替代成别的字母,比如说将a写成1,这样就不能被解密者直接拿到信息了。

这难不倒解密者,以英文字母为例,为了确定每个英文字母的出现频率,分析一篇或者数篇普通的英文文章,英文字母出现频率最高的是e,接下来是t,然后是a……,然后检查要破解的密文,也将每个字母出现的频率整理出来,假设密文中出现频率最高的字母是j,那么就可能是e的替身,如果密码文中出现频率次高的但是P,那么可能是t的替身,以此类推便就能解开加密信息的内容。这就是频率分析法。

  • 将明文字母的出现频率与密文字母的频率相比较的过程
  • 通过分析每个符号出现的频率而轻易地破译代换式密码
  • 在每种语言中,冗长的文章中的字母表现出一种可对之进行分辨的频率。
  • e是英语中最常用的字母,其出现频率为八分之一

拷贝资料里面的 Util.javaFrequencyAnalysis.java 到项目的 com.ynu.kaiser包下面 , article.txt 拷贝到项目文件夹的根目录

运行 FrequencyAnalysis.java 用来统计每个字符出现的次数

package com.ynu.E2_kaiser;import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;/*** 频率分析法破解凯撒密码*/
public class FrequencyAnalysis {//英文里出现次数最多的字符private static final char MAGIC_CHAR = 'e';//破解生成的最大文件数private static final int DE_MAX_FILE = 4;public static void main(String[] args) throws Exception {//测试1,统计字符个数printCharCount("article.txt");//加密文件int key = 3;encryptFile("article.txt", "article_en.txt", key);//读取加密后的文件String artile = Util.file2String("article_en.txt");//解密(会生成多个备选文件)decryptCaesarCode(artile, "article_de.txt");}public static void printCharCount(String path) throws IOException{String data = Util.file2String(path);List<Entry<Character, Integer>> mapList = getMaxCountChar(data);for (Entry<Character, Integer> entry : mapList) {//输出前几位的统计信息System.out.println("字符'" + entry.getKey() + "'出现" + entry.getValue() + "次");}}public static void encryptFile(String srcFile, String destFile, int key) throws IOException {String artile = Util.file2String(srcFile);//加密文件String encryptData = KaiserDemo.encryptKaiser(artile, key);//保存加密后的文件Util.string2File(encryptData, destFile);}/*** 破解凯撒密码* @param input 数据源* @return 返回解密后的数据*/public static void decryptCaesarCode(String input, String destPath) {int deCount = 0;//当前解密生成的备选文件数//获取出现频率最高的字符信息(出现次数越多越靠前)List<Entry<Character, Integer>> mapList = getMaxCountChar(input);for (Entry<Character, Integer> entry : mapList) {//限制解密文件备选数if (deCount >= DE_MAX_FILE) {break;}//输出前几位的统计信息System.out.println("字符'" + entry.getKey() + "'出现" + entry.getValue() + "次");++deCount;//出现次数最高的字符跟MAGIC_CHAR的偏移量即为秘钥int key = entry.getKey() - MAGIC_CHAR;System.out.println("猜测key = " + key + ", 解密生成第" + deCount + "个备选文件" + "\n");String decrypt = KaiserDemo.decryptKaiser(input, key);String fileName = "de_" + deCount + destPath;Util.string2File(decrypt, fileName);}}//统计String里出现最多的字符public static List<Entry<Character, Integer>> getMaxCountChar(String data) {Map<Character, Integer> map = new HashMap<Character, Integer>();char[] array = data.toCharArray();for (char c : array) {if(!map.containsKey(c)) {map.put(c, 1);}else{Integer count = map.get(c);map.put(c, count + 1);}}//输出统计信息/*for (Entry<Character, Integer> entry : map.entrySet()) {System.out.println(entry.getKey() + "出现" + entry.getValue() +  "次");}*///获取获取最大值int maxCount = 0;for (Entry<Character, Integer> entry : map.entrySet()) {//不统计空格if (/*entry.getKey() != ' ' && */entry.getValue() > maxCount) { maxCount = entry.getValue();}}//map转换成list便于排序List<Entry<Character, Integer>> mapList = new ArrayList<Entry<Character,Integer>>(map.entrySet());//根据字符出现次数排序Collections.sort(mapList, new Comparator<Entry<Character, Integer>>(){@Overridepublic int compare(Entry<Character, Integer> o1,Entry<Character, Integer> o2) {return o2.getValue().compareTo(o1.getValue());}});return mapList;}
}

1.5 Byte和bit

Byte : 字节. 数据存储的基本单位,比如移动硬盘1T , 单位是byte

bit : 比特, 又叫位. 一个位要么是0要么是1. 数据传输的单位 , 比如家里的宽带100MB,下载速度并没有达到100MB,一般都是12-13MB,那么是因为需要使用 100 / 8

关系: 1Byte = 8bit

1.5.1 获取字符串byte

package com.ynu.E3_bytebit;public class ByteBit{public static void main(String[] args) {String a = "a";byte[] bytes = a.getBytes();for (byte b : bytes) {int c=b;// 打印发现byte实际上就是ascii码System.out.println(c);}}}

运行程序
在这里插入图片描述

1.5.2 byte对应二进制

package com.ynu.E3_bytebit;public class ByteBit{public static void main(String[] args) {String a = "a";byte[] bytes = a.getBytes();for (byte b : bytes) {int c=b;// 打印发现byte实际上就是ascii码System.out.println(c);// 我们在来看看每个byte对应的bit,byte获取对应的bitString s = Integer.toBinaryString(c);System.out.println(s);}}}

运行程序

打印出来应该是8个bit,但前面是0,没有打印 ,从打印结果可以看出来,一个英文字符 ,占一个字节

image-20230512111517403

1.5.3 中文对应的字节

package com.ynu.E3_bytebit;/*** @author ybh* @date 2023.05.12 11:16*/
public class ByteBitDemo {public static void main(String[] args) {String a = "你";byte[] bytes = a.getBytes(); // 一个中文对应三个字节for (byte b : bytes) {System.out.print(b + "    ");String s = Integer.toBinaryString(b);System.out.println(s);}}
}

运行程序:我们发现一个中文是有 3 个字节组成

image-20230512112001436

我们修改编码格式 , 编码格式改成 GBK ,我们在运行发现变成了 2 个字节。

package com.ynu.E3_bytebit;import java.io.UnsupportedEncodingException;/*** @author ybh* @date 2023.05.12 11:16*/
public class ByteBitDemo {public static void main(String[] args) throws UnsupportedEncodingException {String a = "你";// 在中文情况下,不同的编码格式,对应不同的字节// GBK :编码格式占2个字节// UTF-8:编码格式占3个字节byte[] bytes = a.getBytes("GBK"); // 一个中文对应三个字节for (byte b : bytes) {System.out.print(b + "    ");String s = Integer.toBinaryString(b);System.out.println(s);}}
}

image-20230512112328660

1.5.4 英文对应的字节

我们在看看英文,在不同的编码格式占用多少字节

package com.ynu.E3_bytebit;import java.io.UnsupportedEncodingException;public class ByteBit{public static void main(String[] args) throws UnsupportedEncodingException {String a = "a";byte[] bytes = a.getBytes("GBK");for (byte b : bytes) {int c=b;// 打印发现byte实际上就是ascii码System.out.println(c);// 我们在来看看每个byte对应的bit,byte获取对应的bitString s = Integer.toBinaryString(c);System.out.println(s);}}}

运行程序:发现英文不管什么编码,一个字母都是一个字节

image-20230512112513230

1.6 常见加密方式

image-20230512112833469

1.6.1 对称加密

  • 采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
  • 示例
    • 我们现在有一个原文3要发送给B
    • 设置密钥为108, 3 * 108 = 324, 将324作为密文发送给B
    • B拿到密文324后, 使用324/108 = 3 得到原文
  • 常见加密算法
    • DES : Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。
    • AES : Advanced Encryption Standard, 高级加密标准 .在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。
  • 特点
    • 加密速度快, 可以加密大文件
    • 密文可逆, 一旦密钥文件泄漏, 就会导致数据暴露
    • 加密后编码表找不到对应字符, 出现乱码
    • 一般结合Base64使用

1.6.2 DES加密

示例代码 des加密算法

Cipher :文档 https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html#getInstance-java.lang.String-

package com.ynu.E4_desaes;import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.Key;/*** @author ybh* @date 2023.05.12 11:49*/
public class DesAesDemo {public static void main(String[] args) throws Exception {// 原文String input = "加密";// des加密秘钥String key = "123456";// 算法String algorithm = "DES";String transformation = "DES";// Cipher:密码,获取加密对象// transformation:参数表示使用什么类型加密Cipher cipher = Cipher.getInstance(transformation);// 指定秘钥规则// 第一个参数表示:密钥,key的字节数组// 第二个参数表示:算法SecretKeySpec sks = new SecretKeySpec(key.getBytes(),algorithm);// 对加密进行初始化// 第一个参数:表示模式,有加密模式和解密模式// 第二个参数:表示秘钥规则cipher.init(Cipher.ENCRYPT_MODE,sks);// 进行加密byte[] bytes = cipher.doFinal(input.getBytes());// 打印字节,因为ascii码没有负数,解析不出来,所以乱码for (byte b : bytes) {System.out.println(b);}// 打印密文System.out.println(new String(bytes));}}

运行:

image-20230512120529313

修改 密钥 key = “12345678” ,再次运行 ,出现乱码是因为对应的字节出现负数,但负数,没有出现在 ascii 码表里面,所以出现乱码,需要配合base64进行转码。

image-20230512120627468

使用 base64 进行编码

base64 导包的时候,需要注意 ,别导错了,需要导入 apache

image-20230512121040746

运行程序

image-20230512121151731

1.6.3 DES解密

使用 ctrl + alt + m 快捷键抽取代码

package com.ynu.E4_desaes;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;/*** @author ybh* @date 2023.05.12 12:13*/
public class DesDemo {public static void main(String[] args) throws Exception{String input = "秘密";// DES加密算法,key的大小必须是8个字节String key = "12345678";// 指定获取密钥的算法String transgormation = "DES";String algorithm = "DES";String encodeStr = encryptDES(input, key, transgormation, algorithm); //加密密文System.out.println("加密:" + encodeStr);String decodeStr = decryptDES(encodeStr, key, transgormation, algorithm); // 解密后的原文System.out.println(decodeStr);}/*** 使用DES加密数据** @param input          : 原文* @param key            : 密钥(DES,密钥的长度必须是8个字节)* @param transformation : 获取Cipher对象的算法* @param algorithm      : 获取密钥的算法* @return : 密文* @throws Exception*/private static String encryptDES(String input, String key, String transformation, String algorithm) throws Exception {// 获取加密对象Cipher cipher = Cipher.getInstance(transformation);// 创建加密规则// 第一个参数key的字节// 第二个参数表示加密算法SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);// ENCRYPT_MODE:加密模式// DECRYPT_MODE: 解密模式// 初始化加密模式和算法cipher.init(Cipher.ENCRYPT_MODE,sks);// 加密byte[] bytes = cipher.doFinal(input.getBytes());// 输出加密后的数据String encode = Base64.encode(bytes);return encode;}/*** 使用DES解密** @param input          : 密文* @param key            : 密钥* @param transformation : 获取Cipher对象的算法* @param algorithm      : 获取密钥的算法* @throws Exception* @return: 原文*/private static String decryptDES(String input, String key, String transformation, String algorithm) throws Exception {// 1,获取Cipher对象Cipher cipher = Cipher.getInstance(transformation);// 指定密钥规则SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);cipher.init(Cipher.DECRYPT_MODE, sks);// 3. 解密,上面使用的base64编码,下面直接用密文byte[] bytes = cipher.doFinal(Base64.decode(input));//  因为是明文,所以直接返回return new String(bytes);}}

运行程序:

image-20230512123038424

1.6.4 Base64 编码

① Base64 算法简介

Base64是网络上最常见的用于传输8Bit字节码的可读性编码算法之一
可读性编码算法不是为了保护数据的安全性,而是为了可读性
可读性编码不改变信息内容,只改变信息内容的表现形式
所谓Base64,即是说在编码过程中使用了64种字符:大写A到Z、小写a到z、数字0到9、“+”和“/”
Base58是Bitcoin(比特币)中使用的一种编码方式,主要用于产生Bitcoin的钱包地址
相比Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"i",以及"+“和”/"符号

② Base64 算法原理

base64取3个字节为一组,一个字节8位,一共就是24位。然后,把这个3个字节重新分成4组,每组6位,

3 * 8 = 4 * 6 = 24 ,每组6位,缺少的2位,会在高位进行补0 ,这样做的好处在于 ,base取的是后面6位,去掉高2位 ,那么base64的取值就可以控制在0-63位了,所以就叫base64,0011 1111 = 32 + 16 + 8 + 4 + 2 + 1 = 63

③ base64 构成原则

  • 小写 a - z = 26个字母

  • 大写 A - Z = 26个字母

  • 数字 0 - 9 = 10 个数字

  • / = 2个符号

大家可能发现一个问题,咱们的base64有个 = 号,但是在映射表里面没有发现 = 号 , 这个地方需要注意,等号非常特殊,因为base64是三个字节一组 ,如果当我们的位数不够的时候,会使用等号来补齐

image-20230512125033090

1.6.5 base64补等号测试

package com.ynu.E5_base64;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;/*** @author ybh* @date 2023.05.12 12:52*/
public class TestBase64 {public static void main(String[] args) {//  1:MQ== 表示一个字节,不够三个字节,所以需要后面通过 == 号补齐System.out.println(Base64.encode("1".getBytes()));System.out.println(Base64.encode("12".getBytes()));System.out.println(Base64.encode("123".getBytes()));// 密文: 两个中文占6个字节  刚刚好被3整除,所以没有等号System.out.println(Base64.encode("密文".getBytes()));}}

运行:

image-20230512131549375

1.6.6 AES加密解密

AES 加密解密和 DES 加密解密代码一样,只需要修改加密算法就行,拷贝 DES 代码

package com.ynu.E4_desaes;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;public class AesDemo {// DES加密算法,key的大小必须是8个字节public static void main(String[] args) throws Exception {String input ="你好";// AES加密算法,比较高级,所以key的大小必须是16个字节String key = "1234567812345678";String transformation = "AES"; // 9PQXVUIhaaQ=// 指定获取密钥的算法String algorithm = "AES";// 先测试加密,然后在测试解密String encryptDES = encryptDES(input, key, transformation, algorithm);System.out.println("加密:" + encryptDES);String s = dncryptDES(encryptDES, key, transformation, algorithm);System.out.println("解密:" + s);}/*** 使用DES加密数据** @param input          : 原文* @param key            : 密钥(DES,密钥的长度必须是8个字节)* @param transformation : 获取Cipher对象的算法* @param algorithm      : 获取密钥的算法* @return : 密文* @throws Exception*/private static String encryptDES(String input, String key, String transformation, String algorithm) throws Exception {// 获取加密对象Cipher cipher = Cipher.getInstance(transformation);// 创建加密规则// 第一个参数key的字节// 第二个参数表示加密算法SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);// ENCRYPT_MODE:加密模式// DECRYPT_MODE: 解密模式// 初始化加密模式和算法cipher.init(Cipher.ENCRYPT_MODE,sks);// 加密byte[] bytes = cipher.doFinal(input.getBytes());// 输出加密后的数据String encode = Base64.encode(bytes);return encode;}/*** 使用DES解密** @param input          : 密文* @param key            : 密钥* @param transformation : 获取Cipher对象的算法* @param algorithm      : 获取密钥的算法* @throws Exception* @return: 原文*/private static String dncryptDES(String input, String key, String transformation, String algorithm) throws Exception {// 1,获取Cipher对象Cipher cipher = Cipher.getInstance(transformation);// 指定密钥规则SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);cipher.init(Cipher.DECRYPT_MODE, sks);// 3. 解密byte[] bytes = cipher.doFinal(Base64.decode(input));return new String(bytes);}
}

image-20230512150129656

1.6.7 toString()与new String ()用法区别

举例子

package com.ynu.E5_base64;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;/*** @author ybh* @date 2023.05.12 15:03*/
public class TestStringAndNewString {public static void main(String[] args) {String str="TU0jV0xBTiNVYys5bEdiUjZlNU45aHJ0bTdDQStBPT0jNjQ2NDY1Njk4IzM5OTkwMDAwMzAwMA==";String rlt1=new String(Base64.decode(str));String rlt2=Base64.decode(str).toString();System.out.println(rlt1);System.out.println(rlt2);}
}

结果是:

image-20230512150435281

哪一个是正确的?为什么?

这里应该用new String()的方法,因为Base64加解密是一种转换编码格式的原理

toString()与new String ()用法区别

str.toString是调用了这个object对象的类的toString方法。一般是返回这么一个String:[class name]@[hashCode]

new String(str)是根据parameter是一个字节数组,使用java虚拟机默认的编码格式,将这个字节数组decode为对应的字符。若虚拟机默认的编码格式是ISO-8859-1,按照ascii编码表即可得到字节对应的字符。

什么时候用什么方法呢?

new String()一般使用字符转码的时候,byte[]数组的时候

toString()对象打印的时候使用

1.7 加密模式

加密模式:https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html

ECB

ECB : Electronic codebook, 电子密码本. 需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密

image-20230512152343935

  • 优点 : 可以并行处理数据
  • 缺点 : 同样的原文生成同样的密文, 不能很好的保护数据
  • 同时加密,原文是一样的,加密出来的密文也是一样的

CBC

CBC : Cipher-block chaining, 密码块链接. 每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块

image-20230512152519746

  • 当加密第一个明文分组时,由于不存在“前一个密文分组”,因此需要事先准备一个长度为一个分组的比特序列来代替“前一个密文分组”,这个比特序列称为**初始化向量**(Initialization Vector),缩写为IV。一般每次加密时都会随机产生一个不同的比特序列来作为初始化向量。

  • 优点 : 同样的原文生成的密文不一样

  • 缺点 : 串行处理数据.

1.8 填充模式

  • 当需要按块处理的数据, 数据长度不符合块处理需求时, 按照一定的方法填充满块长的规则

NoPadding

  • 不填充
  • 在DES加密算法下, 要求原文长度必须是8byte的整数倍
  • 在AES加密算法下, 要求原文长度必须是16byte的整数倍

PKCS5Padding

数据块的大小为8位, 不够就补足

默认情况

  • 默认情况下, 加密模式和填充模式为 : ECB/PKCS5Padding
  • 如果使用CBC模式, 在初始化Cipher对象时, 需要增加参数, 初始化向量IV : IvParameterSpec iv = new IvParameterSpec(key.getBytes());

加密模式和填充模式

transformation参数

AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
DES/CBC/NoPadding (56)
DES/CBC/PKCS5Padding (56)
DES/ECB/NoPadding (56)
DES/ECB/PKCS5Padding (56)
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
RSA/ECB/PKCS1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)

加密模式和填充模式例子

1.9 消息摘要

  • 消息摘要(Message Digest)又称为数字摘要(Digital Digest)。
  • 它是一个唯一对应一个消息或文本的固定长度的值,它由一个**单向Hash加密函数**对消息进行作用而产生。
  • 使用数字摘要生成的值是不可以篡改的,为了保证文件或者值的安全。

1.9.1 特点

无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出

只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。

消息摘要是单向、不可逆的。

常见算法 :

  • MD5
  • SHA1
  • SHA256
  • SHA512

在线获取消息摘要

百度搜索 tomcat ,进入官网下载 ,会经常发现有 sha1sha512 , 这些都是数字摘要

image-20230512154415295

数字摘要

image-20230512154435422

1.9.2 获取字符串消息摘要

package com.ynu.E6_digest;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;/*** @author ybh* @date 2023.05.12 15:48*/
public class DigestDemo1 {public static void main(String[] args) throws Exception {// 原文String input = "aa";// 算法String algorithm = "MD5";// 获取数字摘要算法MessageDigest messageDigest =  MessageDigest.getInstance(algorithm);// 获取消息数字摘要的字节数组byte[] digest = messageDigest.digest(input.getBytes());System.out.println(new String(digest));}
}

运行

image-20230512160034980

看不懂,当然。我们要进行base64编码。

1.9.3 base64 编码

package com.ynu.E6_digest;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;/*** @author ybh* @date 2023.05.12 15:48*/
public class DigestDemo1 {public static void main(String[] args) throws Exception {// 原文String input = "aa";// 算法String algorithm = "MD5";// 获取数字摘要算法MessageDigest messageDigest =  MessageDigest.getInstance(algorithm);// 获取消息数字摘要的字节数组byte[] digest = messageDigest.digest(input.getBytes());// 使用base64编码String base64Encode = Base64.encode(digest);System.out.println(new String(base64Encode));}
}

运行

image-20230512160320942

使用在线 md5 加密 ,发现我们生成的值和代码生成的值不一样,那是因为消息摘要不是使用base64进行编码的,所以我们需要把值转成16进制

image-20230512160402090

代码转成16进制

package com.ynu.E6_digest;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;/*** @author ybh* @date 2023.05.12 15:48*/
public class DigestDemo1 {public static void main(String[] args) throws Exception {// 原文String input = "aa";// 算法String algorithm = "MD5";// 获取数字摘要算法MessageDigest messageDigest =  MessageDigest.getInstance(algorithm);// 获取消息数字摘要的字节数组byte[] digest = messageDigest.digest(input.getBytes());// 使用base64编码String base64Encode = Base64.encode(digest);System.out.println(new String(base64Encode));StringBuilder sb = new StringBuilder();for (byte b : digest) {String s = Integer.toHexString(b & 0xff);if (s.length()==1) sb.append("0"+s);sb.append(s);}System.out.println(sb.toString());}
}

image-20230512161740868

1.9.4 其他数字摘要算法

package com.ynu.E6_digest;import java.security.MessageDigest;/*** @author ybh* @date 2023.05.12 16:20*/
public class DigestDemo2 {public static void main(String[] args) throws Exception{// 4124bc0a9335c27f086f24ba207a4912     md5 在线校验// QSS8CpM1wn8IbyS6IHpJEg==             消息摘要使用的是16进制// 原文String input = "aa";// 算法String algorithm = "MD5";// 获取数字摘要对象String md5 = getDigest(input, "MD5");System.out.println(md5);String sha1 = getDigest(input, "SHA-1");System.out.println(sha1);String sha256 = getDigest(input, "SHA-256");System.out.println(sha256);String sha512 = getDigest(input, "SHA-512");System.out.println(sha512);}private static String getDigest(String input, String algorithm) throws Exception {MessageDigest messageDigest = MessageDigest.getInstance(algorithm);// 消息数字摘要byte[] digest = messageDigest.digest(input.getBytes());System.out.println("密文的字节长度:" + digest.length);return toHex(digest);}private static String toHex(byte[] digest) throws Exception {//        System.out.println(new String(digest));// base64编码
//        System.out.println(Base64.encode(digest));// 创建对象用来拼接StringBuilder sb = new StringBuilder();for (byte b : digest) {// 转成 16进制String s = Integer.toHexString(b & 0xff);if (s.length() == 1){// 如果生成的字符只有一个,前面补0s = "0"+s;}sb.append(s);}System.out.println("16进制数据的长度:" + sb.toString().getBytes().length);return sb.toString();}}

运行

image-20230512164354929

总结

  • MD5算法 : 摘要结果128位,16个字节, 转16进制后32个字节
  • SHA1算法 : 摘要结果160位,20个字节, 转16进制后40个字节
  • SHA256算法 : 摘要结果256位,32个字节, 转16进制后64个字节
  • SHA512算法 : 摘要结果512位,64个字节, 转16进制后128个字节

1.10 非对称加密

简介:

① 非对称加密算法又称现代加密算法

② 非对称加密是计算机通信安全的基石,保证了加密数据不会被破解

③ 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)私有密(privatekey)

④ 公开密钥和私有密钥是一对

⑤ 如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密

⑥ 如果用私有密钥对数据进行加密,只有用对应的公开密钥才能解密

⑦ 因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法

  • 示例
    • 首先生成密钥对, 公钥为(5,14), 私钥为(11,14)
    • 现在A希望将原文2发送给B
    • A使用公钥加密数据. 2的5次方mod 14 = 4 , 将密文4发送给B
    • B使用私钥解密数据. 4的11次方mod14 = 2, 得到原文2
  • 特点
    • 加密和解密使用不同的密钥
    • 如果使用私钥加密, 只能使用公钥解密
    • 如果使用公钥加密, 只能使用私钥解密
    • 处理数据的速度较慢, 因为安全级别高
  • 常见算法
    • RSA
    • ECC

1.10.1 生成公钥和私钥

package com.ynu.E6_RSA;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import java.security.*;/*** @author ybh* @date 2023.05.21 16:38*/
public class RSAdemo {public static void main(String[] args) throws NoSuchAlgorithmException {// 加密算法String algorithm = "RSA";// 创建秘钥对生成器对象KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);// 生成秘钥对KeyPair keyPair = keyPairGenerator.generateKeyPair();// 生成公钥PublicKey publicKey = keyPair.getPublic();// 生成私钥PrivateKey privateKey = keyPair.getPrivate();// 获取公钥字节数组byte[] publicKeyEncoded = publicKey.getEncoded();// 获取私钥字节数组byte[] privateKeyEncoded = privateKey.getEncoded();// 用base64进行编码String publicKeyString = Base64.encode(publicKeyEncoded);  // 公钥String privateKeyString = Base64.encode(privateKeyEncoded); // 私钥// 打印公钥System.out.println("公钥:" + publicKeyString);// 打印私钥System.out.println("私钥:" + privateKeyString);}}

运行程序:先打印的是私钥 , 后面打印的是公钥

image-20230521164707461

1.10.2 私钥加密

package com.ynu.E6_RSA;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.security.*;/*** @author ybh* @date 2023.05.21 16:38*/
public class RSAdemo {public static void main(String[] args) throws Exception {String input = "你好我要加密";// 加密算法String algorithm = "RSA";// 创建秘钥对生成器对象KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);// 生成秘钥对KeyPair keyPair = keyPairGenerator.generateKeyPair();// 生成公钥PublicKey publicKey = keyPair.getPublic();// 生成私钥PrivateKey privateKey = keyPair.getPrivate();// 获取公钥字节数组byte[] publicKeyEncoded = publicKey.getEncoded();// 获取私钥字节数组byte[] privateKeyEncoded = privateKey.getEncoded();// 用base64进行编码String publicKeyString = Base64.encode(publicKeyEncoded);  // 公钥String privateKeyString = Base64.encode(privateKeyEncoded); // 私钥// 打印公钥
//        System.out.println("公钥:" + publicKeyString);
//        // 打印私钥
//        System.out.println("私钥:" + privateKeyString);// 私钥加密// 创建加密对象// 参数表示加密算法Cipher cipher = Cipher.getInstance(algorithm);// 初始化加密// 第一个参数:加密的模式// 第二个参数cipher.init(Cipher.ENCRYPT_MODE,privateKey);// 私钥加密byte[] encodeBytes = cipher.doFinal(input.getBytes());System.out.println(Base64.encode(encodeBytes)); //输出加密后的密文}}

1.10.3 私钥加密私钥解密(解不了)

package com.ynu.E6_RSA;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.security.*;/*** @author ybh* @date 2023.05.21 16:38*/
public class RSAdemo {public static void main(String[] args) throws Exception {String input = "你好我要加密";// 加密算法String algorithm = "RSA";// 创建秘钥对生成器对象KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);// 生成秘钥对KeyPair keyPair = keyPairGenerator.generateKeyPair();// 生成公钥PublicKey publicKey = keyPair.getPublic();// 生成私钥PrivateKey privateKey = keyPair.getPrivate();// 获取公钥字节数组byte[] publicKeyEncoded = publicKey.getEncoded();// 获取私钥字节数组byte[] privateKeyEncoded = privateKey.getEncoded();// 用base64进行编码String publicKeyString = Base64.encode(publicKeyEncoded);  // 公钥String privateKeyString = Base64.encode(privateKeyEncoded); // 私钥// 打印公钥
//        System.out.println("公钥:" + publicKeyString);
//        // 打印私钥
//        System.out.println("私钥:" + privateKeyString);// 私钥加密// 创建加密对象// 参数表示加密算法Cipher cipher = Cipher.getInstance(algorithm);// 初始化加密// 第一个参数:加密的模式// 第二个参数cipher.init(Cipher.ENCRYPT_MODE,privateKey);// 私钥加密byte[] encodeBytes = cipher.doFinal(input.getBytes());System.out.println("密文:"+Base64.encode(encodeBytes)); //输出加密后的密文// 用私钥解密// 变成解密模式cipher.init(Cipher.DECRYPT_MODE,privateKey);// 对密文进行解密,不需要使用base64,因为原文不会乱码byte[] decodeBytes = cipher.doFinal(encodeBytes);System.out.println("原文:" + new String(decodeBytes));}}

运行程序 ,因为私钥加密,只能公钥解密。

image-20230521175432699

1.10.4 私钥加密公钥解密

package com.ynu.E6_RSA;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.security.*;/*** @author ybh* @date 2023.05.21 16:38*/
public class RSAdemo {public static void main(String[] args) throws Exception {String input = "你好我要加密";// 加密算法String algorithm = "RSA";// 创建秘钥对生成器对象KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);// 生成秘钥对KeyPair keyPair = keyPairGenerator.generateKeyPair();// 生成公钥PublicKey publicKey = keyPair.getPublic();// 生成私钥PrivateKey privateKey = keyPair.getPrivate();// 获取公钥字节数组byte[] publicKeyEncoded = publicKey.getEncoded();// 获取私钥字节数组byte[] privateKeyEncoded = privateKey.getEncoded();// 用base64进行编码String publicKeyString = Base64.encode(publicKeyEncoded);  // 公钥String privateKeyString = Base64.encode(privateKeyEncoded); // 私钥// 打印公钥
//        System.out.println("公钥:" + publicKeyString);
//        // 打印私钥
//        System.out.println("私钥:" + privateKeyString);// 私钥加密// 创建加密对象// 参数表示加密算法Cipher cipher = Cipher.getInstance(algorithm);// 初始化加密// 第一个参数:加密的模式// 第二个参数cipher.init(Cipher.ENCRYPT_MODE,privateKey);// 私钥加密byte[] encodeBytes = cipher.doFinal(input.getBytes());System.out.println("密文:"+Base64.encode(encodeBytes)); //输出加密后的密文// 用私钥解密// 变成解密模式cipher.init(Cipher.DECRYPT_MODE,publicKey);// 对密文进行解密,不需要使用base64,因为原文不会乱码byte[] decodeBytes = cipher.doFinal(encodeBytes);System.out.println("原文:" + new String(decodeBytes));}}

image-20230521175551892

1.10.5 公钥加密和公钥解密

一样会报错

1.10.6 保存公钥和私钥

前面代码每次都会生成 加密和解密 ,咱们需要把加密和解密的方法全部到本地的根目录下面。在实际项目公钥可以配置到数据库中,私钥必须保护好。

package com.ynu.E6_RSA;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.apache.commons.io.FileUtils;import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;/*** @author ybh* @date 2023.05.21 16:38*/
public class RSAdemo {public static void main(String[] args) throws Exception {// 原文String input = "我在加密啥呢?";// 加密算法String algorithm = "RSA";// 生成秘钥并且保存// generateKeyToFile(algorithm,"pub.rsa","pri.rsa");// 读取私钥 获得私钥对象PrivateKey privateKey = getPrivateKey("pri.rsa", algorithm);// 读取公钥 获得公钥对象PublicKey publicKey = getPublicKey("pub.rsa", algorithm);// 利用私钥加密String encodeStr = encryptRSA(algorithm, privateKey, input);System.out.println("密文:" + encodeStr);// 利用公钥解密String decodeStr = decryptRSA(algorithm, publicKey, encodeStr);System.out.println("原文:" + decodeStr);}/*** 读取公钥* @param publicPath 公钥路径* @param algorithm  算法* @return*/public static PublicKey getPublicKey(String publicPath, String algorithm) throws Exception{String publicKeyString = FileUtils.readFileToString(new File(publicPath), Charset.defaultCharset());// 创建key的工厂KeyFactory keyFactory = KeyFactory.getInstance(algorithm);// 创建公钥规则X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(publicKeyString));return keyFactory.generatePublic(keySpec);}/***  读取私钥* @param priPath 私钥的路径* @param algorithm 算法* @return 返回私钥的key对象*/public static PrivateKey getPrivateKey(String priPath, String algorithm) throws Exception{String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());// 创建key的工厂KeyFactory keyFactory = KeyFactory.getInstance(algorithm);// 创建私钥key的规则PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));// 返回私钥对象return keyFactory.generatePrivate(keySpec);}/*** 解密数据* @param algorithm  算法* @param publicKey  公钥* @param encryptStr 密文* @return 原文* @throws Exception*/private static String decryptRSA(String algorithm, PublicKey publicKey, String encryptStr) throws Exception {Cipher cipher = Cipher.getInstance(algorithm);// 解密模式cipher.init(Cipher.DECRYPT_MODE,publicKey);byte[] decode = Base64.decode(encryptStr);byte[] inputBytes = cipher.doFinal(decode);return new String(inputBytes);}/*** 使用密钥加密数据** @param algorithm      : 算法* @param input          : 原文* @param privateKey            : 密钥* @return : 密文* @throws Exception*/private static String encryptRSA(String algorithm,Key privateKey,String input) throws Exception{Cipher cipher = Cipher.getInstance(algorithm);cipher.init(Cipher.ENCRYPT_MODE,privateKey);byte[] encodeBytes = cipher.doFinal(input.getBytes());return new String(Base64.encode(encodeBytes));}/*** 保存公钥和私钥,把公钥和私钥保存到根目录* @param algorithm 算法* @param pubPath 公钥路径* @param priPath 私钥路径*/private static void generateKeyToFile(String algorithm, String pubPath, String priPath) 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();// 使用base64进行编码String privateEncodeString = Base64.encode(privateKeyEncoded);String publicEncodeString = Base64.encode(publicKeyEncoded);// 把公钥和私钥保存到根目录FileUtils.writeStringToFile(new File(pubPath),publicEncodeString, Charset.forName("UTF-8"));FileUtils.writeStringToFile(new File(priPath),privateEncodeString, Charset.forName("UTF-8"));}}

1.11 数字签名

数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。

数字签名是非对称密钥加密技术与数字摘要技术的应用。

1.11.1 简单认识

相信我们都写过信,在写信的时候落款处总是要留下自己的名字,用来表示写信的人是谁。我们签的这个字就是生活中的签名:

image-20230521182926291

而数字签名呢?其实也是同样的道理,他的含义是:在网络中传输数据时候,给数据添加一个数字签名,表示是谁发的数据,而且还能证明数据没有被篡改。

OK,数字签名的主要作用就是保证了数据的有效性(验证是谁发的)和完整性(证明信息没有被篡改),比如jwt算法的签名。下面我们就来好好地看一下他的底层实现原理是什么样子的。

1.11.2 基本原理

为了理解得清楚,我们通过案例一步一步来讲解。话说张三有俩好哥们A、B。由于工作原因,张三和AB写邮件的时候为了安全都需要加密。于是张三想到了数字签名:

整个思路是这个样子的:

第一步:加密采用非对称加密,张三有三把钥匙,两把公钥,送给朋友。一把私钥留给自己。

第二步:A或者B写邮件给张三:A先用公钥对邮件加密,然后张三收到邮件之后使用私钥解密。

第三步:张三写邮件给A或者B:

  • 张三写完邮件,先用hash函数生成邮件的摘要,附着在文章上面,这就完成了数字签名,然后张三再使用私钥加密。就可以把邮件发出去了。

  • A或者是B收到邮件之后,先把数字签名取下来,然后使用自己的公钥解密即可。这时候取下来的数字签名中的摘要若和张三的一致,那就认为是张三发来的,再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。

上面的流程我们使用一张图来演示一下:

首先把公钥送给朋友A和B:

image-20230521183858421

还有就是最后一个比较麻烦的,张三给A或者B发邮件:

image-20230521184435613

1.11.3 数字证书

上面提到我们对签名进行验证时,需要用到公钥。如果公钥是伪造的,那我们无法验证数字签名了,也就根本不可能从数字签名确定对方的合法性了。这时候证书就闪亮登场了。我们可能都有考各种证书的经历,比如说普通话证书,四六级证书等等,但是归根结底,到任何场合我们都能拿出我们的证书来证明自己确实已经考过了普通话,考过了四六级。这里的证书也是同样的道理。

如果不理解证书的作用,我们可以举一个例子,比如说我们的毕业证书,任何公司都会承认。为什么会承认?因为那是国家发得,大家都信任国家。也就是说只要是国家的认证机构,我们都信任它是合法的。

https协议中ssl证书就类似这个。https协议监听的是443端口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/83036.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ARM Cache 系列文章 9 -- ARM big.LITTLE技术】

文章目录 big.LITTLE 技术背景big.LITTLE 技术详解big.LITTLE 硬件要求 big.LITTLE 软件模型CPU MigrationGlobal Task SchedulingGlobal Task Scheduling比CPU Migration的优势 转自&#xff1a;https://zhuanlan.zhihu.com/p/630981648 如有侵权&#xff0c;请联系删除 big.L…

MySQL的查询方法

单表查询 素材&#xff1a; 表名&#xff1a;worker-- 表中字段均为中文&#xff0c;比如 部门号 工资 职工号 参加工作 要求&#xff1a; 1、显示所有职工的基本信息。 2、查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 3、求出所有职工的人数。 4、…

php后端实现调用高德地图进行POI搜索

对于当前位置或者选定省市位置进行查询 接口实现 /*** 查询地址* ApiTitle (查询地址)* ApiSummary (查询地址)* ApiMethod (POST)* ApiRoute (/api/demo/address)* ApiParams (name"dart", type"integer", requiredtrue, description"省…

TCP/IP四层模型对比OSI七层网络模型的区别是啥?数据传输过程原来是这样的

一、TCP/IP四层模型对比OSI七层模型 它们两个定义的一些功能和协议都是差不多的。TCP/IP四层协议模型比我们的七层少了三层&#xff0c;把我们的数据链路层和物理层放在一层里面了&#xff0c;叫做数据链路层&#xff08;网络接口层&#xff09;&#xff0c;对应网络协议也没有…

openGauss学习笔记-35 openGauss 高级数据管理-ALTER TABLE语句

文章目录 openGauss学习笔记-35 openGauss 高级数据管理-ALTER TABLE语句35.1 语法格式35.2 参数说明35.3 示例 openGauss学习笔记-35 openGauss 高级数据管理-ALTER TABLE语句 修改表&#xff0c;包括修改表的定义、重命名表、重命名表中指定的列、重命名表的约束、设置表的所…

基于应用值迭代的马尔可夫决策过程(MDP)的策略的机器人研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

如何在 Spring Boot 中集成日志框架 SLF4J、Log4j

文章目录 具体步骤附录 笔者的操作环境&#xff1a; Spring Cloud Alibaba&#xff1a;2022.0.0.0-RC2 Spring Cloud&#xff1a;2022.0.0 Spring Boot&#xff1a;3.0.2 Nacos 2.2.3 Maven 3.8.3 JDK 17.0.7 IntelliJ IDEA 2022.3.1 (Ultimate Edition) 具体步骤 因为 …

Three.js光源

目录 Three.js入门 Three.js光源 Three.js阴影 Three.js纹理贴图 本文我们将研究three.js中的灯光类型和JavaScript中的光源&#xff0c;探索不同的光源设置。我们的目标是全面理解光源设置和类型&#xff0c;比如环境光、半球光、矩形光、方向光、点光源和聚光灯。我们将…

智能安防监控:基于Java+SpringBoot实现人脸识别搜索

目录 引言背景介绍目的和重要性 人脸识别技术的基本原理图像采集和预处理特征提取与表示人脸匹配算法 人脸识别搜索的应用领域公告安全和监控社交网络和照片管理 参考实现步骤数据收集与预处理人脸特征提取查询处理 引言 背景介绍 结合人脸识别技术&#xff0c;在工厂、学校、…

Java后台生成ECharts图片

前言 通过echarts的jar包&#xff0c;Java后台生成一张图片&#xff0c;并把图片插入到word中。关于word插图片的代码在下一章。 需要用到的工具PhantomJS,Echarts-convert.js,jquery.js,echarts.js。 1.PhantomJS 介绍 PhantomJS是一个不需要浏览器的富客户端。 官方介绍&…

gitlab-Runner搭建

root wget https://packages.gitlab.com/runner/gitlab-runner/packages/fedora/29/gitlab-runner-12.6.0-1.x86_64.rpm/download.rpm rpm -ivh download.rpm ---- 安装 rpm -Uvh download.rpm -----更新升级 然后运行&#xff1a; gitlab-runner register --url https://git…

泊松损坏图像的快速尺度间小波去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Rocky Linux更换为国内源

Rocky Linux提供的可供切换的源列表&#xff1a;Mirrors - Mirror Manager 其中以 COUNTRY 列为 CN 的是国内源。 选择其中一个Rocky Linux 源使用帮助 — USTC Mirror Help 文档 操作前请做好备份 对于 Rocky Linux 8&#xff0c;使用以下命令替换默认的配置 sed -e s|^mirr…

实现静态资源访问的几种方法

什么是静态资源&#xff1f; 静态资源是指在服务器端存储的不会变化的文件&#xff0c;如HTML、CSS、JavaScript、图片、音频、视频等文件。这些文件一般不包含动态内容&#xff0c;每次请求时返回的内容都是固定的。 为什么要使用静态资源&#xff1f; 提升网站性能&#xf…

Docker学习(二十四)报错速查手册

目录 一、This error may indicate that the docker daemon is not running 报错docker login 报错截图&#xff1a;原因分析&#xff1a;解决方案&#xff1a; 二、Get "https://harbor.xxx.cn/v2/": EOF 报错docker login 报错截图&#xff1a;原因分析&#xff1a…

谷歌推出AI模型机器人RT2 将文本和图像输出为机器人动作

去年年底&#xff0c;ChatGPT火遍全球&#xff0c;全世界都见识了大语言模型的强大力量。人们对大模型不再陌生&#xff0c;开始使用基于大模型的应用绘画、作图、搜索资料、设计剧情等&#xff0c;而妙用不止于此。谷歌推出了Robotics Transformer 2(RT2)&#xff0c;这是一个…

Python中enumerate用法详解

目录 1.简介 2.语法 3.参数 4.返回值 5.详解 6.实例 7.补充 1.简介 enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列&#xff0c;同时列出数据和数据下标&#xff0c;一般用在 for 循环当中。 2.语法 以下是 enumerate() 方法的语…

Sencha Ext.NET Crack 快速应用程序的正确工具集

Sencha Ext.NET Crack 快速应用程序的正确工具集 Sencha Ext.NET是一个高级的ASP.NET核心组件框架&#xff0c;它包含了强大的跨浏览器Sencha Ext JS库。通过140多个预构建和专业测试的UI组件实现企业级性能和生产效率。Sencha Ext.NET使用尖端的Web技术创建功能强大的Web应用程…

Transformer理论学习

Transformer出自于论文《attention is all you need》。 一些主流的序列模型主要依赖于复杂的循环结构或者CNN&#xff0c;这里面包含了编解码器等。而Transformer主要的结构是基于注意力机制&#xff0c;而且是用多头注意力机制去替换网络中的循环或者CNN(换言之就是transfor…