密码学基本概念古典密码学① 替换法② 移位法③ 古典密码破解方式近代密码学现代密码学① 散列函数② 对称密码③ 非对称密码如何设置密码才安全ASCII编码恺撒加密中国古代加密外国加密凯撒位移加密凯撒加密和解密频度分析法破解恺撒加密Byte和bit获取字符串bytebyte对应bit中文对应的字节英文对应的字节常见加密方式对称加密DES加密DES解密base64补等号测试AES加密解密toString()与new String ()用法区别加密模式ECBCBC填充模式NoPaddingPKCS5PaddingTips消息摘要特点获取字符串消息摘要base64 编码其他数字摘要算法获取文件消息摘要非对称加密生成公钥和私钥私钥加密私钥加密私钥解密私钥加密公钥解密公钥加密和公钥解密保存公钥和私钥读取私钥读取公钥数字签名简单认识基本原理数字证书网页加密代码实现keytool工具使用生成私钥公钥导出公钥第二章 SpringBoot集成Swagger2创建项目 encryptcase创建数据库创建启动类创建javabean创建dao创建servcie创建controller集成 Swagger2swagger介绍swagger的基础注解介绍代码中添加swagger注解2.4 购物功能
密码在我们的生活中有着重要的作用,那么密码究竟来自何方,为何会产生呢?
密码学是网络安全、信息安全、区块链等产品的基础,常见的非对称加密、对称加密、散列函数等,都属于密码学范畴。
密码学有数千年的历史,从最开始的替换法到如今的非对称加密算法,经历了古典密码学,近代密码学和现代密码学三个阶段。密码学不仅仅是数学家们的智慧,更是如今网络空间安全的重要基础。
在古代的战争中,多见使用隐藏信息的方式保护重要的通信资料。比如先把需要保护的信息用化学药水写到纸上,药水干后,纸上看不出任何的信息,需要使用另外的化学药水涂抹后才可以阅读纸上的信息。
这些方法都是在保护重要的信息不被他人获取,但藏信息的方式比较容易被他人识破,例如增加哨兵的排查力度,就会发现其中的猫腻,因而随后发展出了较难破解的古典密码学。
替换法很好理解,就是用固定的信息将原文替换成无法直接阅读的密文信息。例如将 b 替换成 w ,e 替换成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 。
同理替换法,移位法也可以采用多表移位的方式,典型的多表案例是“维尼吉亚密码”(又译维热纳尔密码),属于多表密码的一种形式。
古典密码虽然很简单,但是在密码史上是使用的最久的加密方式,直到“概率论”的数学方法被发现,古典密码就被破解了。
英文单词中字母出现的频率是不同的,e以12.702%的百分比占比最高,z 只占到0.074%,感兴趣的可以去百科查字母频率详细统计数据。如果密文数量足够大,仅仅采用频度分析法就可以破解单表的替换法或移位法。
多表的替换法或移位法虽然难度高一些,但如果数据量足够大的话,也是可以破解的。以维尼吉亚密码算法为例,破解方法就是先找出密文中完全相同的字母串,猜测密钥长度,得到密钥长度后再把同组的密文放在一起,使用频率分析法破解。
古典密码的安全性受到了威胁,外加使用便利性较低,到了工业化时代,近现代密码被广泛应用。
恩尼格玛机
恩尼格玛机是二战时期纳粹德国使用的加密机器,后被英国破译,参与破译的人员有被称为计算机科学之父、人工智能之父的图灵。
恩尼格玛机
恩尼格玛机使用的加密方式本质上还是移位和替代,只不过因为密码表种类极多,破解难度高,同时加密解密机器化,使用便捷,因而在二战时期得以使用。
散列函数,也见杂凑函数、摘要函数或哈希函数,可将任意长度的消息经过运算,变成固定长度数值,常见的有MD5、SHA-1、SHA256,多应用在文件校验,数字签名中。
MD5 可以将任意长度的原文生成一个128位(16字节)的哈希值
SHA-1可以将任意长度的原文生成一个160位(20字节)的哈希值
对称密码应用了相同的加密密钥和解密密钥。对称密码分为:序列密码(流密码),分组密码(块密码)两种。流密码是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密,块密码是先对信息流分块,再对每一块分别加密。
例如原文为1234567890,流加密即先对1进行加密,再对2进行加密,再对3进行加密……最后拼接成密文;块加密先分成不同的块,如1234成块,5678成块,90XX(XX为补位数字)成块,再分别对不同块进行加密,最后拼接成密文。前文提到的古典密码学加密方法,都属于流加密。
对称密码的密钥安全极其重要,加密者和解密者需要提前协商密钥,并各自确保密钥的安全性,一但密钥泄露,即使算法是安全的也无法保障原文信息的私密性。
在实际的使用中,远程的提前协商密钥不容易实现,即使协商好,在远程传输过程中也容易被他人获取,因此非对称密钥此时就凸显出了优势。
非对称密码有两支密钥,公钥(publickey)和私钥(privatekey),加密和解密运算使用的密钥不同。用公钥对原文进行加密后,需要由私钥进行解密;用私钥对原文进行加密后(此时一般称为签名),需要由公钥进行解密(此时一般称为验签)。公钥可以公开的,大家使用公钥对信息进行加密,再发送给私钥的持有者,私钥持有者使用私钥对信息进行解密,获得信息原文。因为私钥只有单一人持有,因此不用担心被他人解密获取信息原文。
密码不要太常见,不要使用类似于123456式的常用密码。
各应用软件密码建议不同,避免出现一个应用数据库被脱库,全部应用密码崩塌,
可在设置密码时增加注册时间、注册地点、应用特性等方法。例如tianjin123456,表示在天津注册的该应用。
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
示例代码
创建maven项目 encrypt-decrypt
添加pom文件
71 <dependencies>2 <dependency>3 <groupId>commons-io</groupId>4 <artifactId>commons-io</artifactId>5 <version>2.6</version>6 </dependency>7 </dependencies>创建类 com.atguigu.ascii.AsciiDemo
字符转换成ascii码
181package com.atguigu.ascii;2
3/**4 * AsciiDemo5 *6 * @Author: 尚硅谷7 * @CreateTime: 2020-03-178 * @Description:9 */10public class AsciiDemo {11
12 public static void main(String[] args) {13 char a = 'A';14 int b = a;15 // 打印ascii码16 System.out.println(b);17 }18}运行程序
字符串转换成ascii码
251package com.atguigu.ascii;2
3/**4 * AsciiDemo5 *6 * @Author: 尚硅谷7 * @CreateTime: 2020-03-178 * @Description:9 */10public class AsciiDemo {11
12 public static void main(String[] args) {13// char a = 'A';14// int b = a;15// System.out.println(b);16 String a = "AaZ";17 // 获取ascii码,需要把字符串转成字符18 char[] chars = a.toCharArray();19 for (char c : chars) {20
21 int asciiCode = c;22 System.out.println(asciiCode);23 }24 }25}运行程序
看一个小故事 , 看看古人如何加密和解密:
公元683年,唐中宗即位。随后,武则天废唐中宗,立第四子李旦为皇帝,但朝政大事均由她自己专断。
裴炎、徐敬业和骆宾王等人对此非常不满。徐敬业聚兵十万,在江苏扬州起兵。裴炎做内应,欲以拆字手段为其传递秘密信息。后因有人告密,裴炎被捕,未发出的密信落到武则天手中。这封密信上只有“青鹅”二字,群臣对此大惑不解。
武则天破解了“青鹅”的秘密:“青”字拆开来就是“十二月”,而“鹅”字拆开来就是“我自与”。密信的意思是让徐敬业、骆宾王等率兵于十二月进发,裴炎在内部接应。“青鹅”破译后,裴炎被杀。接着,武则天派兵击败了徐敬业和骆宾王。
在密码学中,恺撒密码是一种最简单且最广为人知的加密技术。
凯撒密码最早由古罗马军事统帅盖乌斯·尤利乌斯·凯撒在军队中用来传递加密信息,故称凯撒密码。这是一种位移加密方式,只对26个字母进行位移替换加密,规则简单,容易破解。下面是位移1次的对比:
将明文字母表向后移动1位,A变成了B,B变成了C……,Z变成了A。同理,若将明文字母表向后移动3位:
则A变成了D,B变成了E……,Z变成了C。
字母表最多可以移动25位。凯撒密码的明文字母表向后或向前移动都是可以的,通常表述为向后移动,如果要向前移动1位,则等同于向后移动25位,位移选择为25即可。
它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。
例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。
这个加密方法是以恺撒的名字命名的,当年恺撒曾用此方法与其将军们进行联系。
恺撒密码通常被作为其他更复杂的加密方法中的一个步骤。
简单来说就是当秘钥为n,其中一个待加密字符ch,加密之后的字符为ch+n,当ch+n超过’z’时,回到’a’计数。
创建类 KaiserDemo,把 hello world 往右边移动3位
291package com.atguigu.kaiser;23/**4* KaiserDemo5*6* @Author: 尚硅谷7* @CreateTime: 2020-03-178* @Description:9*/10public class KaiserDemo {11public static void main(String[] args) {12String input = "Hello world";13// 往右边移动3位14int key = 3;15// 用来拼接16StringBuilder sb = new StringBuilder();17// 字符串转换成字节数组18char[] chars = input.toCharArray();19for (char c : chars) {20int asciiCode = c;21// 移动3位22asciiCode = asciiCode + key;23char newChar = (char) asciiCode;24sb.append(newChar);25}2627System.out.println(sb.toString());28}29}
运行
671package com.atguigu.kaiser;23/**4* KaiserDemo5*6* @Author: 尚硅谷7* @CreateTime: 2020-03-168* @Description:9*/10public class KaiserDemo {11public static void main(String[] args) {12String orignal = "Hello world";13// 往右边偏移三位14int key = 3;15// 选中我即将抽取的代码,按快捷键Ctrl + Alt + M16String encryptKaiser = encryptKaiser(orignal,key);17System.out.println("加密:" + encryptKaiser);18String decryptKaiser = decryptKaiser(encryptKaiser,key);19System.out.println("解密:" + decryptKaiser);20}21/**22* 使用凯撒加密方式解密数据23*24* @param encryptedData :密文25* @param key :密钥26* @return : 源数据27*/28public static String decryptKaiser(String encryptedData, int key) {29// 将字符串转为字符数组30char[] chars = encryptedData.toCharArray();31StringBuilder sb = new StringBuilder();32for (char aChar : chars) {33// 获取字符的ASCII编码34int asciiCode = aChar;35// 偏移数据36asciiCode -= key;37// 将偏移后的数据转为字符38char result = (char) asciiCode;39// 拼接数据40sb.append(result);41}42return sb.toString();43}44/**45* 使用凯撒加密方式加密数据46*47* @param orignal :原文48* @param key :密钥49* @return :加密后的数据50*/51public static String encryptKaiser(String orignal, int key) {52// 将字符串转为字符数组53char[] chars = orignal.toCharArray();54StringBuilder sb = new StringBuilder();55for (char aChar : chars) {56// 获取字符的ascii编码57int asciiCode = aChar;58// 偏移数据59asciiCode += key;60// 将偏移后的数据转为字符61char result = (char) asciiCode;62// 拼接数据63sb.append(result);64}65return sb.toString();66}67}
密码棒
公元前5世纪的时候,斯巴达人利用一根木棒,缠绕上皮革或者羊皮纸,在上面横向写下信息,解下这条皮带。展开来看,这长串字母没有任何意义。
比如这样:
信差可以将这条皮带当成腰带,系在腰上。
比如这样:
然后收件人将这条皮带缠绕在相同的木棒上,就能恢复信息了。
前404年,一位遍体鳞伤的信差来到斯巴达将领利桑德面前,这趟波斯之旅只有他和四位同伴幸存,利桑德接下腰带,缠绕到他的密码棒上,得知波斯的发那巴祖斯准备侵袭他,多亏密码棒利桑德才能够预先防范,击退敌军。
频率分析解密法
密码棒是不是太简单了些?
加密者选择将组成信息的字母替代成别的字母,比如说将a写成1,这样就不能被解密者直接拿到信息了。
这难不倒解密者,以英文字母为例,为了确定每个英文字母的出现频率,分析一篇或者数篇普通的英文文章,英文字母出现频率最高的是e,接下来是t,然后是a……,然后检查要破解的密文,也将每个字母出现的频率整理出来,假设密文中出现频率最高的字母是j,那么就可能是e的替身,如果密码文中出现频率次高的但是P,那么可能是t的替身,以此类推便就能解开加密信息的内容。这就是频率分析法。
将明文字母的出现频率与密文字母的频率相比较的过程
通过分析每个符号出现的频率而轻易地破译代换式密码
在每种语言中,冗长的文章中的字母表现出一种可对之进行分辨的频率。
e是英语中最常用的字母,其出现频率为八分之一
拷贝资料里面的 Util.java 和 FrequencyAnalysis.java 到项目的 com.atguigu.kaiser包下面 , article.txt 拷贝到项目文件夹的根目录
运行 FrequencyAnalysis.java 用来统计每个字符出现的次数
运行 FrequencyAnalysis.java 里面 main 函数里面的 encryptFile 方法 对程序进行加密
131public static void main(String[] args) throws Exception {2 //测试1,统计字符个数3 //printCharCount("article.txt");4 5 //加密文件6 int key = 3;7 encryptFile("article.txt", "article_en.txt", key);8 9 //读取加密后的文件10 // String artile = Util.file2String("article_en.txt");11 //解密(会生成多个备选文件)12 // decryptCaesarCode(artile, "article_de.txt");13 }在根目录会生成一个 article_en.txt 文件,然后我们统计这个文件当中每个字符出现的次数
131public static void main(String[] args) throws Exception {2 //测试1,统计字符个数3 printCharCount("article_en.txt");4 5 //加密文件6 int key = 3;7 //encryptFile("article.txt", "article_en.txt", key);8 9 //读取加密后的文件10 // String artile = Util.file2String("article_en.txt");11 //解密(会生成多个备选文件)12 // decryptCaesarCode(artile, "article_de.txt");13 }运行程序
我们来看看 频度分析法如何工作的
131public static void main(String[] args) throws Exception {2 //测试1,统计字符个数3 //printCharCount("article_en.txt");4 5 //加密文件6 int key = 3;7 //encryptFile("article.txt", "article_en.txt", key);8 9 //读取加密后的文件10 String artile = Util.file2String("article_en.txt");11 //解密(会生成多个备选文件)12 decryptCaesarCode(artile, "article_de.txt");13 }运行程序
运行结果 # 出现次数最多, 我们知道在英文当中 e 出现的频率是最高的,我们假设现在 # 号,就是 e ,变形而来的 ,我们可以对照 ascii 编码表 ,我们的凯撒加密当中位移是加了一个 key ,所以我们 猜测 两个值直接相差 -66 ,我们现在就以 -66 进行解密 生成一个文件,我们查看第一个文件发现,根本读不懂,所以解密失败,我们在猜测 h 是 e ,h 和 e 之间相差3 ,所以我们在去看第二个解密文件,发现我们可以读懂,解密成功
Byte : 字节. 数据存储的基本单位,比如移动硬盘1T , 单位是byte
bit : 比特, 又叫位. 一个位要么是0要么是1. 数据传输的单位 , 比如家里的宽带100MB,下载速度并没有达到100MB,一般都是12-13MB,那么是因为需要使用 100 / 8
关系: 1Byte = 8bit
201package com.atguigu.bytebit;2
3/**4 * ByteBit5 *6 * @Author: 尚硅谷7 * @CreateTime: 2020-03-178 * @Description:9 */10public class ByteBit {11 public static void main(String[] args) {12 String a = "a";13 byte[] bytes = a.getBytes();14 for (byte b : bytes) {15 int c=b;16 // 打印发现byte实际上就是ascii码17 System.out.println(c);18 }19 }20}运行程序
231package com.atguigu.bytebit;2
3/**4 * ByteBit5 *6 * @Author: 尚硅谷7 * @CreateTime: 2020-03-178 * @Description:9 */10public class ByteBit {11 public static void main(String[] args) {12 String a = "a";13 byte[] bytes = a.getBytes();14 for (byte b : bytes) {15 int c=b;16 // 打印发现byte实际上就是ascii码17 System.out.println(c);18 // 我们在来看看每个byte对应的bit,byte获取对应的bit19 String s = Integer.toBinaryString(c);20 System.out.println(s);21 }22 }23}运行程序
打印出来应该是8个bit,但前面是0,没有打印 ,从打印结果可以看出来,一个英文字符 ,占一个字节
251// 中文在GBK编码下, 占据2个字节2// 中文在UTF-8编码下, 占据3个字节3package com.atguigu;4
5/**6 * ByteBitDemo7 *8 * @Author: 尚硅谷9 * @CreateTime: 2020-03-1610 * @Description:11 */12public class ByteBitDemo {13 public static void main(String[] args) throws Exception{14
15 String a = "尚";16 byte[] bytes = a.getBytes();17 for (byte b : bytes) {18 System.out.print(b + " ");19 String s = Integer.toBinaryString(b);20 System.out.println(s);21 }22 }23 24 25}运行程序:我们发现一个中文是有 3 个字节组成
我们修改 编码格式 , 编码格式改成 GBK ,我们在运行发现变成了 2 个字节
xxxxxxxxxx151public static void main(String[] args) throws Exception{2
3 String a = "尚";4
5 // 在中文情况下,不同的编码格式,对应不同的字节6 //GBK :编码格式占2个字节7 // UTF-8:编码格式占3个字节8 byte[] bytes = a.getBytes("GBK");9 // byte[] bytes = a.getBytes("UTF-8");10 for (byte b : bytes) {11 System.out.print(b + " ");12 String s = Integer.toBinaryString(b);13 System.out.println(s);14 }15 }运行程序
我们在看看英文,在不同的编码格式占用多少字节
231package com.atguigu.bytebit;2
3/**4 * ByteBit5 *6 * @Author: 尚硅谷7 * @CreateTime: 2020-04-128 * @Description:9 */10public class ByteBit {11 public static void main(String[] args) throws Exception{12
13 String a = "A";14 byte[] bytes = a.getBytes();15 // 在中文情况下,不同的编码格式,对应不同的字节16// byte[] bytes = a.getBytes("GBK");17 for (byte b : bytes) {18 System.out.print(b + " ");19 String s = Integer.toBinaryString(b);20 System.out.println(s);21 }22 }23}运行程序
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
示例
我们现在有一个原文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使用
示例代码 des加密算法
Cipher :文档 https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html#getInstance-java.lang.String-
431package com.atguigu.desaes;2
3import javax.crypto.Cipher;4import javax.crypto.spec.SecretKeySpec;5
6/**7 * DesAesDemo8 *9 * @Author: 尚硅谷10 * @CreateTime: 2020-03-1711 * @Description:12 */13public class DesAesDemo {14 public static void main(String[] args) throws Exception{15 // 原文16 String input = "硅谷";17 // des加密必须是8位18 String key = "123456";19 // 算法20 String algorithm = "DES";21
22 String transformation = "DES";23 // Cipher:密码,获取加密对象24 // transformation:参数表示使用什么类型加密25 Cipher cipher = Cipher.getInstance(transformation);26 // 指定秘钥规则27 // 第一个参数表示:密钥,key的字节数组28 // 第二个参数表示:算法29 SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);30 // 对加密进行初始化31 // 第一个参数:表示模式,有加密模式和解密模式32 // 第二个参数:表示秘钥规则33 cipher.init(Cipher.ENCRYPT_MODE,sks);34 // 进行加密35 byte[] bytes = cipher.doFinal(input.getBytes());36 // 打印字节,因为ascii码有负数,解析不出来,所以乱码37// for (byte b : bytes) {38// System.out.println(b);39// }40 // 打印密文41 System.out.println(new String(bytes));42 }43}运行:
修改 密钥 key = “12345678” ,再次运行 ,出现乱码是因为对应的字节出现负数,但负数,没有出现在 ascii 码表里面,所以出现乱码,需要配合base64进行转码
使用 base64 进行编码
base64 导包的时候,需要注意 ,别导错了,需要导入 apache 包
运行程序
使用 ctrl + alt + m 快捷键抽取代码
781package com.atguigu.desaes;2
3import com.sun.org.apache.xml.internal.security.utils.Base64;4
5import javax.crypto.Cipher;6import javax.crypto.spec.IvParameterSpec;7import javax.crypto.spec.SecretKeySpec;8
9public class DesDemo {10 // DES加密算法,key的大小必须是8个字节11
12 public static void main(String[] args) throws Exception {13 String input ="硅谷";14 // DES加密算法,key的大小必须是8个字节15 String key = "12345678";16
17 String transformation = "DES"; // 9PQXVUIhaaQ=18 // 指定获取密钥的算法19 String algorithm = "DES";20 String encryptDES = encryptDES(input, key, transformation, algorithm);21 System.out.println("加密:" + encryptDES);22 String s = decryptDES(encryptDES, key, transformation, algorithm);23 System.out.println("解密:" + s);24
25 }26
27 /**28 * 使用DES加密数据29 *30 * @param input : 原文31 * @param key : 密钥(DES,密钥的长度必须是8个字节)32 * @param transformation : 获取Cipher对象的算法33 * @param algorithm : 获取密钥的算法34 * @return : 密文35 * @throws Exception36 */37 private static String encryptDES(String input, String key, String transformation, String algorithm) throws Exception {38 // 获取加密对象39 Cipher cipher = Cipher.getInstance(transformation);40 // 创建加密规则41 // 第一个参数key的字节42 // 第二个参数表示加密算法43 SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);44 // ENCRYPT_MODE:加密模式45 // DECRYPT_MODE: 解密模式46 // 初始化加密模式和算法47 cipher.init(Cipher.ENCRYPT_MODE,sks);48 // 加密49 byte[] bytes = cipher.doFinal(input.getBytes());50
51 // 输出加密后的数据52 String encode = Base64.encode(bytes);53
54 return encode;55 }56
57 /**58 * 使用DES解密59 *60 * @param input : 密文61 * @param key : 密钥62 * @param transformation : 获取Cipher对象的算法63 * @param algorithm : 获取密钥的算法64 * @throws Exception65 * @return: 原文66 */67 private static String decryptDES(String input, String key, String transformation, String algorithm) throws Exception {68 // 1,获取Cipher对象69 Cipher cipher = Cipher.getInstance(transformation);70 // 指定密钥规则71 SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);72 cipher.init(Cipher.DECRYPT_MODE, sks);73 // 3. 解密,上面使用的base64编码,下面直接用密文74 byte[] bytes = cipher.doFinal(Base64.decode(input));75 // 因为是明文,所以直接返回76 return new String(bytes);77 }78}运行程序:
Base64 算法简介
61Base64是网络上最常见的用于传输8Bit字节码的可读性编码算法之一2可读性编码算法不是为了保护数据的安全性,而是为了可读性3可读性编码不改变信息内容,只改变信息内容的表现形式4所谓Base64,即是说在编码过程中使用了64种字符:大写A到Z、小写a到z、数字0到9、“+”和“/”5Base58是Bitcoin(比特币)中使用的一种编码方式,主要用于产生Bitcoin的钱包地址6相比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,111 111 = 32 + 16 + 8 + 4 + 2 + 1 =
base64 构成原则
① 小写 a - z = 26个字母
② 大写 A - Z = 26个字母
③ 数字 0 - 9 = 10 个数字
④ + / = 2个符号
大家可能发现一个问题,咱们的base64有个 = 号,但是在映射表里面没有发现 = 号 , 这个地方需要注意,等号非常特殊,因为base64是三个字节一组 ,如果当我们的位数不够的时候,会使用等号来补齐
151package com.atguigu;2
3import com.sun.org.apache.xml.internal.security.utils.Base64;4
5
6public class TestBase64 {7 public static void main(String[] args) {8 // 1:MQ== 表示一个字节,不够三个字节,所以需要后面通过 == 号补齐9 System.out.println(Base64.encode("1".getBytes()));10// System.out.println(Base64.encode("12".getBytes()));11// System.out.println(Base64.encode("123".getBytes()));12// // 硅谷:中文占6个字节,6 * 8 = 48 ,刚刚好被整除,所以没有等号13// System.out.println(Base64.encode("硅谷".getBytes()));14 }15}运行:
AES 加密解密和 DES 加密解密代码一样,只需要修改加密算法就行,拷贝 ESC 代码
771package com.atguigu.desaes;2import com.sun.org.apache.xml.internal.security.utils.Base64;3
4import javax.crypto.Cipher;5import javax.crypto.spec.SecretKeySpec;6
7public class AesDemo {8 // DES加密算法,key的大小必须是8个字节9
10 public static void main(String[] args) throws Exception {11 String input ="硅谷";12 // AES加密算法,比较高级,所以key的大小必须是16个字节13 String key = "1234567812345678";14
15 String transformation = "AES"; // 9PQXVUIhaaQ=16 // 指定获取密钥的算法17 String algorithm = "AES";18 // 先测试加密,然后在测试解密19 String encryptDES = encryptDES(input, key, transformation, algorithm);20 System.out.println("加密:" + encryptDES);21 String s = dncryptDES(encryptDES, key, transformation, algorithm);22 System.out.println("解密:" + s);23
24 }25
26 /**27 * 使用DES加密数据28 *29 * @param input : 原文30 * @param key : 密钥(DES,密钥的长度必须是8个字节)31 * @param transformation : 获取Cipher对象的算法32 * @param algorithm : 获取密钥的算法33 * @return : 密文34 * @throws Exception35 */36 private static String encryptDES(String input, String key, String transformation, String algorithm) throws Exception {37 // 获取加密对象38 Cipher cipher = Cipher.getInstance(transformation);39 // 创建加密规则40 // 第一个参数key的字节41 // 第二个参数表示加密算法42 SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);43 // ENCRYPT_MODE:加密模式44 // DECRYPT_MODE: 解密模式45 // 初始化加密模式和算法46 cipher.init(Cipher.ENCRYPT_MODE,sks);47 // 加密48 byte[] bytes = cipher.doFinal(input.getBytes());49
50 // 输出加密后的数据51 String encode = Base64.encode(bytes);52
53 return encode;54 }55
56 /**57 * 使用DES解密58 *59 * @param input : 密文60 * @param key : 密钥61 * @param transformation : 获取Cipher对象的算法62 * @param algorithm : 获取密钥的算法63 * @throws Exception64 * @return: 原文65 */66 private static String dncryptDES(String input, String key, String transformation, String algorithm) throws Exception {67 // 1,获取Cipher对象68 Cipher cipher = Cipher.getInstance(transformation);69 // 指定密钥规则70 SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);71 cipher.init(Cipher.DECRYPT_MODE, sks);72 // 3. 解密73 byte[] bytes = cipher.doFinal(Base64.decode(input));74
75 return new String(bytes);76 }77}运行程序:AES 加密的密钥key , 需要传入16个字节
在运行程序
举例子
201package com.atguigu;2
3
4import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;5
6
7public class TestBase64 {8 public static void main(String[] args) {9 String str="TU0jV0xBTiNVYys5bEdiUjZlNU45aHJ0bTdDQStBPT0jNjQ2NDY1Njk4IzM5OTkwMDAwMzAwMA==";10
11
12 String rlt1=new String(Base64.decode(str));13
14 String rlt2=Base64.decode(str).toString();15
16 System.out.println(rlt1);17
18 System.out.println(rlt2);19 }20}结果是:
31MM#WLAN#Uc+9lGbR6e5N9hrtm7CA+A==#646465698#39990000300023[B@1540e19d
哪一个是正确的?为什么?
这里应该用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()对象打印的时候使用
加密模式:https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html
ECB : Electronic codebook, 电子密码本. 需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密
优点 : 可以并行处理数据
缺点 : 同样的原文生成同样的密文, 不能很好的保护数据
同时加密,原文是一样的,加密出来的密文也是一样的
CBC : Cipher-block chaining, 密码块链接. 每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块
优点 : 同样的原文生成的密文不一样
缺点 : 串行处理数据.
当需要按块处理的数据, 数据长度不符合块处理需求时, 按照一定的方法填充满块长的规则
不填充.
在DES加密算法下, 要求原文长度必须是8byte的整数倍
在AES加密算法下, 要求原文长度必须是16byte的整数倍
数据块的大小为8位, 不够就补足
默认情况下, 加密模式和填充模式为 : ECB/PKCS5Padding
如果使用CBC模式, 在初始化Cipher对象时, 需要增加参数, 初始化向量IV : IvParameterSpec iv = new IvParameterSpec(key.getBytes());
加密模式和填充模式
151AES/CBC/NoPadding (128)2AES/CBC/PKCS5Padding (128)3AES/ECB/NoPadding (128)4AES/ECB/PKCS5Padding (128)5DES/CBC/NoPadding (56)6DES/CBC/PKCS5Padding (56)7DES/ECB/NoPadding (56)8DES/ECB/PKCS5Padding (56)9DESede/CBC/NoPadding (168)10DESede/CBC/PKCS5Padding (168)11DESede/ECB/NoPadding (168)12DESede/ECB/PKCS5Padding (168)13RSA/ECB/PKCS1Padding (1024, 2048)14RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)15RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
加密模式和填充模式例子
861package com.atguigu.desaes;2
3import com.sun.org.apache.xml.internal.security.utils.Base64;4
5import javax.crypto.Cipher;6import javax.crypto.spec.IvParameterSpec;7import javax.crypto.spec.SecretKeySpec;8
9public class DesDemo {10 // DES加密算法,key的大小必须是8个字节11
12 public static void main(String[] args) throws Exception {13 String input ="硅谷";14 // DES加密算法,key的大小必须是8个字节15 String key = "12345678";16 // 指定获取Cipher的算法,如果没有指定加密模式和填充模式,ECB/PKCS5Padding就是默认值17 // String transformation = "DES"; // 9PQXVUIhaaQ=18 //String transformation = "DES/ECB/PKCS5Padding"; // 9PQXVUIhaaQ=19 // CBC模式,必须指定初始向量,初始向量中密钥的长度必须是8个字节20 //String transformation = "DES/CBC/PKCS5Padding"; // 9PQXVUIhaaQ=21 // NoPadding模式,原文的长度必须是8个字节的整倍数 ,所以必须把 硅谷改成硅谷1222 String transformation = "DES/CBC/NoPadding"; // 9PQXVUIhaaQ=23 // 指定获取密钥的算法24 String algorithm = "DES";25 String encryptDES = encryptDES(input, key, transformation, algorithm);26 System.out.println("加密:" + encryptDES);27// String s = dncryptDES(encryptDES, key, transformation, algorithm);28// System.out.println("解密:" + s);29
30 }31
32 /**33 * 使用DES加密数据34 *35 * @param input : 原文36 * @param key : 密钥(DES,密钥的长度必须是8个字节)37 * @param transformation : 获取Cipher对象的算法38 * @param algorithm : 获取密钥的算法39 * @return : 密文40 * @throws Exception41 */42 private static String encryptDES(String input, String key, String transformation, String algorithm) throws Exception {43 // 获取加密对象44 Cipher cipher = Cipher.getInstance(transformation);45 // 创建加密规则46 // 第一个参数key的字节47 // 第二个参数表示加密算法48 SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);49 // ENCRYPT_MODE:加密模式50 // DECRYPT_MODE: 解密模式51 // 初始向量,参数表示跟谁进行异或,初始向量的长度必须是8位52// IvParameterSpec iv = new IvParameterSpec(key.getBytes());53 // 初始化加密模式和算法54 cipher.init(Cipher.ENCRYPT_MODE,sks);55 // 加密56 byte[] bytes = cipher.doFinal(input.getBytes());57
58 // 输出加密后的数据59 String encode = Base64.encode(bytes);60
61 return encode;62 }63
64 /**65 * 使用DES解密66 *67 * @param input : 密文68 * @param key : 密钥69 * @param transformation : 获取Cipher对象的算法70 * @param algorithm : 获取密钥的算法71 * @throws Exception72 * @return: 原文73 */74 private static String dncryptDES(String input, String key, String transformation, String algorithm) throws Exception {75 // 1,获取Cipher对象76 Cipher cipher = Cipher.getInstance(transformation);77 // 指定密钥规则78 SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);79// IvParameterSpec iv = new IvParameterSpec(key.getBytes());80 cipher.init(Cipher.DECRYPT_MODE, sks);81 // 3. 解密82 byte[] bytes = cipher.doFinal(Base64.decode(input));83
84 return new String(bytes);85 }86}运行程序:
修改成 CBC 加密 模式
xxxxxxxxxx11String transformation = "DES/CBC/PKCS5Padding";
运行 ,报错,需要添加一个参数
修改加密代码:
运行程序
修改填充模式
xxxxxxxxxx11String transformation = "DES/CBC/NoPadding";
运行报错 NoPadding 这种填充模式 原文必须是8个字节的整倍数
修改运行
在测试 AES 的时候需要注意,key需要16个字节,加密向量也需要16个字节 ,其他方式跟 DES 一样
消息摘要(Message Digest)又称为数字摘要(Digital Digest)
它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生
使用数字摘要生成的值是不可以篡改的,为了保证文件或者值的安全
无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出
只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出
消息摘要是单向、不可逆的
常见算法 :
xxxxxxxxxx41- MD52- SHA13- SHA2564- SHA512
百度搜索 tomcat ,进入官网下载 ,会经常发现有 sha1,sha512 , 这些都是数字摘要
数字摘要
xxxxxxxxxx261package com.atguigu.digest;2
3import javax.sound.midi.Soundbank;4import java.security.MessageDigest;5
6/**7 * DigestDemo18 *9 * @Author: 尚硅谷10 * @CreateTime: 2020-03-1711 * @Description:12 */13public class DigestDemo1 {14
15 public static void main(String[] args) throws Exception{16 // 原文17 String input = "aa";18 // 算法19 String algorithm = "MD5";20 // 获取数字摘要对象21 MessageDigest messageDigest = MessageDigest.getInstance(algorithm);22 // 获取消息数字摘要的字节数组23 byte[] digest = messageDigest.digest(input.getBytes());24 System.out.println(new String(digest));25 }26}运行
xxxxxxxxxx301package com.atguigu.digest;2
3import com.sun.org.apache.xml.internal.security.utils.Base64;4
5import javax.sound.midi.Soundbank;6import java.security.MessageDigest;7
8/**9 * DigestDemo110 *11 * @Author: 尚硅谷12 * @CreateTime: 2020-03-1713 * @Description:14 */15public class DigestDemo1 {16
17 public static void main(String[] args) throws Exception{18 // 原文19 String input = "aa";20 // 算法21 String algorithm = "MD5";22 // 获取数字摘要对象23 MessageDigest messageDigest = MessageDigest.getInstance(algorithm);24 // 消息数字摘要25 byte[] digest = messageDigest.digest(input.getBytes());26// System.out.println(new String(digest));27 // base64编码28 System.out.println(Base64.encode(digest));29 }30}运行
使用在线 md5 加密 ,发现我们生成的值和代码生成的值不一样,那是因为消息摘要不是使用base64进行编码的,所以我们需要把值转成16进制
数字摘要转换成 16 进制
xxxxxxxxxx21// 4124bc0a9335c27f086f24ba207a4912 md5 在线校验2// QSS8CpM1wn8IbyS6IHpJEg== 消息摘要使用的是16进制
代码转成16进制
xxxxxxxxxx471package com.atguigu.digest;2
3import com.sun.org.apache.xml.internal.security.utils.Base64;4
5import javax.sound.midi.Soundbank;6import java.security.MessageDigest;7
8/**9 * DigestDemo110 *11 * @Author: 尚硅谷12 * @CreateTime: 2020-03-1713 * @Description:14 */15public class DigestDemo1 {16
17 public static void main(String[] args) throws Exception{18 // 4124bc0a9335c27f086f24ba207a4912 md5 在线校验19 // QSS8CpM1wn8IbyS6IHpJEg== 消息摘要使用的是16进制20 // 原文21 String input = "aa";22 // 算法23 String algorithm = "MD5";24 // 获取数字摘要对象25 MessageDigest messageDigest = MessageDigest.getInstance(algorithm);26 // 消息数字摘要27 byte[] digest = messageDigest.digest(input.getBytes());28// System.out.println(new String(digest));29 // base64编码30// System.out.println(Base64.encode(digest));31 // 创建对象用来拼接32 StringBuilder sb = new StringBuilder();33
34 for (byte b : digest) {35 // 转成 16进制36 String s = Integer.toHexString(b & 0xff);37 //System.out.println(s);38 if (s.length() == 1){39 // 如果生成的字符只有一个,前面补040 s = "0"+s;41 }42 sb.append(s);43 }44 System.out.println(sb.toString());45 46 }47}运行
xxxxxxxxxx701package com.atguigu.digest;2
3import com.sun.org.apache.xml.internal.security.utils.Base64;4
5import javax.sound.midi.Soundbank;6import java.security.MessageDigest;7import java.security.NoSuchAlgorithmException;8
9/**10 * DigestDemo111 *12 * @Author: 尚硅谷13 * @CreateTime: 2020-03-1714 * @Description:15 */16public class DigestDemo1 {17
18 public static void main(String[] args) throws Exception{19 // 4124bc0a9335c27f086f24ba207a4912 md5 在线校验20 // QSS8CpM1wn8IbyS6IHpJEg== 消息摘要使用的是16进制21 // 原文22 String input = "aa";23 // 算法24 String algorithm = "MD5";25 // 获取数字摘要对象26 String md5 = getDigest(input, "MD5");27 System.out.println(md5);28
29 String sha1 = getDigest(input, "SHA-1");30 System.out.println(sha1);31
32 String sha256 = getDigest(input, "SHA-256");33 System.out.println(sha256);34
35 String sha512 = getDigest(input, "SHA-512");36 System.out.println(sha512);37
38
39 }40
41 private static String toHex(byte[] digest) throws Exception {42
43// System.out.println(new String(digest));44 // base64编码45// System.out.println(Base64.encode(digest));46 // 创建对象用来拼接47 StringBuilder sb = new StringBuilder();48
49 for (byte b : digest) {50 // 转成 16进制51 String s = Integer.toHexString(b & 0xff);52 if (s.length() == 1){53 // 如果生成的字符只有一个,前面补054 s = "0"+s;55 }56 sb.append(s);57 }58 System.out.println("16进制数据的长度:" + sb.toString().getBytes().length);59 return sb.toString();60 }61
62 private static String getDigest(String input, String algorithm) throws Exception {63 MessageDigest messageDigest = MessageDigest.getInstance(algorithm);64 // 消息数字摘要65 byte[] digest = messageDigest.digest(input.getBytes());66 System.out.println("密文的字节长度:" + digest.length);67
68 return toHex(digest);69 }70}运行
xxxxxxxxxx791package com.atguigu.digest;2
3import com.sun.org.apache.xml.internal.security.utils.Base64;4import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;5import sun.misc.BASE64Decoder;6
7import java.io.ByteArrayOutputStream;8import java.io.FileInputStream;9import java.security.MessageDigest;10
11/**12 * DigestDemo13 *14 * @Author: 尚硅谷15 * @CreateTime: 2020-03-1616 * @Description:17 */18public class DigestDemo {19
20 public static void main(String[] args) throws Exception{21 String input = "aa";22 String algorithm = "MD5";23
24 // sha1 可以实现秒传功能25
26 String sha1 = getDigestFile("apache-tomcat-9.0.10-windows-x64.zip", "SHA-1");27 System.out.println(sha1);28
29 String sha512 = getDigestFile("apache-tomcat-9.0.10-windows-x64.zip", "SHA-512");30 System.out.println(sha512);31
32 String md5 = getDigest("aa", "MD5");33 System.out.println(md5);34
35 String md51 = getDigest("aa ", "MD5");36 System.out.println(md51);37 }38
39 private static String getDigestFile(String filePath, String algorithm) throws Exception{40 FileInputStream fis = new FileInputStream(filePath);41 int len;42 byte[] buffer = new byte[1024];43 ByteArrayOutputStream baos = new ByteArrayOutputStream();44 while ( (len = fis.read(buffer))!=-1){45 baos.write(buffer,0,len);46 }47 // 获取消息摘要对象48 MessageDigest messageDigest = MessageDigest.getInstance(algorithm);49 // 获取消息摘要50 byte[] digest = messageDigest.digest(baos.toByteArray());51 System.out.println("密文的字节长度:"+digest.length);52 return toHex(digest);53 }54
55 private static String getDigest(String input, String algorithm) throws Exception{56 MessageDigest messageDigest = MessageDigest.getInstance(algorithm);57 byte[] digest = messageDigest.digest(input.getBytes());58 System.out.println("密文的字节长度:"+digest.length);59 return toHex(digest);60 }61
62 private static String toHex(byte[] digest) {63 // System.out.println(new String(digest));64 // 消息摘要进行表示的时候,是用16进制进行表示65 StringBuilder sb = new StringBuilder();66 for (byte b : digest) {67 // 转成16进制68
69 String s = Integer.toHexString(b & 0xff);70 // 保持数据的完整性,前面不够的用0补齐71 if (s.length()==1){72 s="0"+s;73 }74 sb.append(s);75 }76 System.out.println("16进制数据的长度:"+ sb.toString().getBytes().length);77 return sb.toString();78 }79}运行程序 ,获取 sha-1 和 sha-512 的值
查看 tomcat 官网上面 sha-1 和 sha-512 的值
使用 sha-1 算法,可以实现秒传功能,不管咱们如何修改文件的名字,最后得到的值是一样的
运行程序 ,获取 sha-1 和 sha-512 的值
如果原文修改了,那么sha-1值 就会不一样
运行结果:
总结
MD5算法 : 摘要结果16个字节, 转16进制后32个字节
SHA1算法 : 摘要结果20个字节, 转16进制后40个字节
SHA256算法 : 摘要结果32个字节, 转16进制后64个字节
SHA512算法 : 摘要结果64个字节, 转16进制后128个字节
简介:
① 非对称加密算法又称现代加密算法。
② 非对称加密是计算机通信安全的基石,保证了加密数据不会被破解。
③ 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(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
xxxxxxxxxx431package com.atguigu.rsa;2import com.sun.org.apache.xml.internal.security.utils.Base64;3import org.apache.commons.io.FileUtils;4
5import javax.crypto.Cipher;6import javax.crypto.spec.SecretKeySpec;7import java.io.File;8import java.nio.charset.Charset;9import java.security.*;10import java.security.spec.PKCS8EncodedKeySpec;11/**12 * RSAdemo13 *14 * @Author: 尚硅谷15 * @CreateTime: 2020-04-1216 * @Description:17 */18public class RSAdemo {19 public static void main(String[] args) throws Exception {20 21 // 加密算法22 String algorithm = "RSA";23 // 创建密钥对生成器对象24 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);25 // 生成密钥对26 KeyPair keyPair = keyPairGenerator.generateKeyPair();27 // 生成私钥28 PrivateKey privateKey = keyPair.getPrivate();29 // 生成公钥30 PublicKey publicKey = keyPair.getPublic();31 // 获取私钥字节数组32 byte[] privateKeyEncoded = privateKey.getEncoded();33 // 获取公钥字节数组34 byte[] publicKeyEncoded = publicKey.getEncoded();35 // 对公私钥进行base64编码36 String privateKeyString = Base64.encode(privateKeyEncoded);37 String publicKeyString = Base64.encode(publicKeyEncoded);38 // 打印私钥39 System.out.println(privateKeyString);40 // 打印公钥41 System.out.println(publicKeyString);42 }43}运行程序:先打印的是私钥 , 后面打印的是公钥
xxxxxxxxxx521package com.atguigu.rsa;2import com.sun.org.apache.xml.internal.security.utils.Base64;3import org.apache.commons.io.FileUtils;4
5import javax.crypto.Cipher;6import javax.crypto.spec.SecretKeySpec;7import java.io.File;8import java.nio.charset.Charset;9import java.security.*;10import java.security.spec.PKCS8EncodedKeySpec;11/**12 * RSAdemo13 *14 * @Author: 尚硅谷15 * @CreateTime: 2020-04-1216 * @Description:17 */18public class RSAdemo {19 public static void main(String[] args) throws Exception {20 String input = "硅谷";21 // 加密算法22 String algorithm = "RSA";23 // 创建密钥对生成器对象24 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);25 // 生成密钥对26 KeyPair keyPair = keyPairGenerator.generateKeyPair();27 // 生成私钥28 PrivateKey privateKey = keyPair.getPrivate();29 // 生成公钥30 PublicKey publicKey = keyPair.getPublic();31 // 获取私钥字节数组32 byte[] privateKeyEncoded = privateKey.getEncoded();33 // 获取公钥字节数组34 byte[] publicKeyEncoded = publicKey.getEncoded();35 // 对公私钥进行base64编码36 String privateKeyString = Base64.encode(privateKeyEncoded);37 String publicKeyString = Base64.encode(publicKeyEncoded);38
39
40 // 创建加密对象41 // 参数表示加密算法42 Cipher cipher = Cipher.getInstance(algorithm);43 // 初始化加密44 // 第一个参数:加密的模式45 // 第二个参数:使用私钥进行加密46 cipher.init(Cipher.ENCRYPT_MODE,privateKey);47 // 私钥加密48 byte[] bytes = cipher.doFinal(input.getBytes());49 System.out.println(Base64.encode(bytes));50 51 }52}运行程序
xxxxxxxxxx571package com.atguigu.rsa;2import com.sun.org.apache.xml.internal.security.utils.Base64;3import org.apache.commons.io.FileUtils;4
5import javax.crypto.Cipher;6import javax.crypto.spec.SecretKeySpec;7import java.io.File;8import java.nio.charset.Charset;9import java.security.*;10import java.security.spec.PKCS8EncodedKeySpec;11/**12 * RSAdemo13 *14 * @Author: 尚硅谷15 * @CreateTime: 2020-04-1216 * @Description:17 */18public class RSAdemo {19 public static void main(String[] args) throws Exception {20 String input = "硅谷";21 // 加密算法22 String algorithm = "RSA";23 // 创建密钥对生成器对象24 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);25 // 生成密钥对26 KeyPair keyPair = keyPairGenerator.generateKeyPair();27 // 生成私钥28 PrivateKey privateKey = keyPair.getPrivate();29 // 生成公钥30 PublicKey publicKey = keyPair.getPublic();31 // 获取私钥字节数组32 byte[] privateKeyEncoded = privateKey.getEncoded();33 // 获取公钥字节数组34 byte[] publicKeyEncoded = publicKey.getEncoded();35 // 对公私钥进行base64编码36 String privateKeyString = Base64.encode(privateKeyEncoded);37 String publicKeyString = Base64.encode(publicKeyEncoded);38
39
40 // 创建加密对象41 // 参数表示加密算法42 Cipher cipher = Cipher.getInstance(algorithm);43 // 初始化加密44 // 第一个参数:加密的模式45 // 第二个参数:使用私钥进行加密46 cipher.init(Cipher.ENCRYPT_MODE,privateKey);47 // 私钥加密48 byte[] bytes = cipher.doFinal(input.getBytes());49 System.out.println(Base64.encode(bytes));50 // 私钥进行解密51 cipher.init(Cipher.DECRYPT_MODE,publicKey);52 // 对密文进行解密,不需要使用base64,因为原文不会乱码53 byte[] bytes1 = cipher.doFinal(bytes);54 System.out.println(new String(bytes1));55
56 }57}运行程序 ,因为私钥加密,只能公钥解密
xxxxxxxxxx571package com.atguigu.rsa;2import com.sun.org.apache.xml.internal.security.utils.Base64;3import org.apache.commons.io.FileUtils;4
5import javax.crypto.Cipher;6import javax.crypto.spec.SecretKeySpec;7import java.io.File;8import java.nio.charset.Charset;9import java.security.*;10import java.security.spec.PKCS8EncodedKeySpec;11/**12 * RSAdemo13 *14 * @Author: 尚硅谷15 * @CreateTime: 2020-04-1216 * @Description:17 */18public class RSAdemo {19 public static void main(String[] args) throws Exception {20 String input = "硅谷";21 // 加密算法22 String algorithm = "RSA";23 // 创建密钥对生成器对象24 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);25 // 生成密钥对26 KeyPair keyPair = keyPairGenerator.generateKeyPair();27 // 生成私钥28 PrivateKey privateKey = keyPair.getPrivate();29 // 生成公钥30 PublicKey publicKey = keyPair.getPublic();31 // 获取私钥字节数组32 byte[] privateKeyEncoded = privateKey.getEncoded();33 // 获取公钥字节数组34 byte[] publicKeyEncoded = publicKey.getEncoded();35 // 对公私钥进行base64编码36 String privateKeyString = Base64.encode(privateKeyEncoded);37 String publicKeyString = Base64.encode(publicKeyEncoded);38
39
40 // 创建加密对象41 // 参数表示加密算法42 Cipher cipher = Cipher.getInstance(algorithm);43 // 初始化加密44 // 第一个参数:加密的模式45 // 第二个参数:使用私钥进行加密46 cipher.init(Cipher.ENCRYPT_MODE,privateKey);47 // 私钥加密48 byte[] bytes = cipher.doFinal(input.getBytes());49 System.out.println(Base64.encode(bytes));50 // 私钥进行解密51 cipher.init(Cipher.DECRYPT_MODE,publicKey);52 // 对密文进行解密,不需要使用base64,因为原文不会乱码53 byte[] bytes1 = cipher.doFinal(bytes);54 System.out.println(new String(bytes1));55
56 }57}运行程序
一样会报错
前面代码每次都会生成 加密和解密 ,咱们需要把加密和解密的方法全部到本地的根目录下面。
xxxxxxxxxx1111package com.atguigu.rsa;2import com.sun.org.apache.xml.internal.security.utils.Base64;3import org.apache.commons.io.FileUtils;4
5import javax.crypto.Cipher;6import javax.crypto.spec.SecretKeySpec;7import java.io.File;8import java.nio.charset.Charset;9import java.security.*;10import java.security.spec.PKCS8EncodedKeySpec;11/**12 * RSAdemo13 *14 * @Author: 尚硅谷15 * @CreateTime: 2020-04-1216 * @Description:17 */18public class RSAdemo {19 public static void main(String[] args) throws Exception {20 String input = "硅谷";21 // 加密算法22 String algorithm = "RSA";23
24 //生成密钥对并保存在本地文件中25 generateKeyToFile(algorithm, "a.pub", "a.pri");26
27 //加密28// String s = encryptRSA(algorithm, privateKey, input);29 // 解密30// String s1 = decryptRSA(algorithm, publicKey, s);31// System.out.println(s1);32
33
34 }35
36 /**37 * 生成密钥对并保存在本地文件中38 *39 * @param algorithm : 算法40 * @param pubPath : 公钥保存路径41 * @param priPath : 私钥保存路径42 * @throws Exception43 */44 private static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {45 // 获取密钥对生成器46 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);47 // 获取密钥对48 KeyPair keyPair = keyPairGenerator.generateKeyPair();49 // 获取公钥50 PublicKey publicKey = keyPair.getPublic();51 // 获取私钥52 PrivateKey privateKey = keyPair.getPrivate();53 // 获取byte数组54 byte[] publicKeyEncoded = publicKey.getEncoded();55 byte[] privateKeyEncoded = privateKey.getEncoded();56 // 进行Base64编码57 String publicKeyString = Base64.encode(publicKeyEncoded);58 String privateKeyString = Base64.encode(privateKeyEncoded);59 // 保存文件60 FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));61 FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));62
63 }64
65 /**66 * 解密数据67 *68 * @param algorithm : 算法69 * @param encrypted : 密文70 * @param key : 密钥71 * @return : 原文72 * @throws Exception73 */74 public static String decryptRSA(String algorithm,Key key,String encrypted) throws Exception{75 // 创建加密对象76 // 参数表示加密算法77 Cipher cipher = Cipher.getInstance(algorithm);78 // 私钥进行解密79 cipher.init(Cipher.DECRYPT_MODE,key);80 // 由于密文进行了Base64编码, 在这里需要进行解码81 byte[] decode = Base64.decode(encrypted);82 // 对密文进行解密,不需要使用base64,因为原文不会乱码83 byte[] bytes1 = cipher.doFinal(decode);84 System.out.println(new String(bytes1));85 return new String(bytes1);86
87 }88 /**89 * 使用密钥加密数据90 *91 * @param algorithm : 算法92 * @param input : 原文93 * @param key : 密钥94 * @return : 密文95 * @throws Exception96 */97 public static String encryptRSA(String algorithm,Key key,String input) throws Exception{98 // 创建加密对象99 // 参数表示加密算法100 Cipher cipher = Cipher.getInstance(algorithm);101 // 初始化加密102 // 第一个参数:加密的模式103 // 第二个参数:使用私钥进行加密104 cipher.init(Cipher.ENCRYPT_MODE,key);105 // 私钥加密106 byte[] bytes = cipher.doFinal(input.getBytes());107 // 对密文进行Base64编码108 System.out.println(Base64.encode(bytes));109 return Base64.encode(bytes);110 }111}运行在项目根目录生成私钥
运行在项目根目录生成公钥
xxxxxxxxxx1151package com.atguigu.rsa;2import com.sun.org.apache.xml.internal.security.utils.Base64;3import org.apache.commons.io.FileUtils;4
5import javax.crypto.Cipher;6import javax.crypto.spec.SecretKeySpec;7import java.io.File;8import java.nio.charset.Charset;9import java.security.*;10import java.security.spec.PKCS8EncodedKeySpec;11/**12 * RSAdemo13 *14 * @Author: 尚硅谷15 * @CreateTime: 2020-04-1216 * @Description:17 */18public class RSAdemo {19 public static void main(String[] args) throws Exception {20 String input = "硅谷";21 // 加密算法22 String algorithm = "RSA";23 PrivateKey privateKey = getPrivateKey("a.pri", algorithm);24
25
26
27 }28
29 public static PrivateKey getPrivateKey(String priPath,String algorithm) throws Exception{30 // 将文件内容转为字符串31 String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());32 // 获取密钥工厂33 KeyFactory keyFactory = KeyFactory.getInstance(algorithm);34 // 构建密钥规范 进行Base64解码35 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));36 // 生成私钥37 return keyFactory.generatePrivate(spec);38 }39
40 /**41 * 生成密钥对并保存在本地文件中42 *43 * @param algorithm : 算法44 * @param pubPath : 公钥保存路径45 * @param priPath : 私钥保存路径46 * @throws Exception47 */48 private static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {49 // 获取密钥对生成器50 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);51 // 获取密钥对52 KeyPair keyPair = keyPairGenerator.generateKeyPair();53 // 获取公钥54 PublicKey publicKey = keyPair.getPublic();55 // 获取私钥56 PrivateKey privateKey = keyPair.getPrivate();57 // 获取byte数组58 byte[] publicKeyEncoded = publicKey.getEncoded();59 byte[] privateKeyEncoded = privateKey.getEncoded();60 // 进行Base64编码61 String publicKeyString = Base64.encode(publicKeyEncoded);62 String privateKeyString = Base64.encode(privateKeyEncoded);63 // 保存文件64 FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));65 FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));66
67 }68
69 /**70 * 解密数据71 *72 * @param algorithm : 算法73 * @param encrypted : 密文74 * @param key : 密钥75 * @return : 原文76 * @throws Exception77 */78 public static String decryptRSA(String algorithm,Key key,String encrypted) throws Exception{79 // 创建加密对象80 // 参数表示加密算法81 Cipher cipher = Cipher.getInstance(algorithm);82 // 私钥进行解密83 cipher.init(Cipher.DECRYPT_MODE,key);84 // 由于密文进行了Base64编码, 在这里需要进行解码85 byte[] decode = Base64.decode(encrypted);86 // 对密文进行解密,不需要使用base64,因为原文不会乱码87 byte[] bytes1 = cipher.doFinal(decode);88 System.out.println(new String(bytes1));89 return new String(bytes1);90
91 }92 /**93 * 使用密钥加密数据94 *95 * @param algorithm : 算法96 * @param input : 原文97 * @param key : 密钥98 * @return : 密文99 * @throws Exception100 */101 public static String encryptRSA(String algorithm,Key key,String input) throws Exception{102 // 创建加密对象103 // 参数表示加密算法104 Cipher cipher = Cipher.getInstance(algorithm);105 // 初始化加密106 // 第一个参数:加密的模式107 // 第二个参数:使用私钥进行加密108 cipher.init(Cipher.ENCRYPT_MODE,key);109 // 私钥加密110 byte[] bytes = cipher.doFinal(input.getBytes());111 // 对密文进行Base64编码112 System.out.println(Base64.encode(bytes));113 return Base64.encode(bytes);114 }115}xxxxxxxxxx1301package com.atguigu.rsa;2import com.sun.org.apache.xml.internal.security.utils.Base64;3import org.apache.commons.io.FileUtils;4
5import javax.crypto.Cipher;6import javax.crypto.spec.SecretKeySpec;7import java.io.File;8import java.nio.charset.Charset;9import java.security.*;10import java.security.spec.PKCS8EncodedKeySpec;11import java.security.spec.X509EncodedKeySpec;12
13/**14 * RSAdemo15 *16 * @Author: 尚硅谷17 * @CreateTime: 2020-04-1218 * @Description:19 */20public class RSAdemo {21 public static void main(String[] args) throws Exception {22 String input = "硅谷";23 // 加密算法24 String algorithm = "RSA";25 PrivateKey privateKey = getPrivateKey("a.pri", algorithm);26 PublicKey publicKey = getPublicKey("a.pub", algorithm);27
28 String s = encryptRSA(algorithm, privateKey, input);29 String s1 = decryptRSA(algorithm, publicKey, s);30 System.out.println(s1);31
32
33 }34
35 public static PublicKey getPublicKey(String pulickPath,String algorithm) throws Exception{36 // 将文件内容转为字符串37 String publicKeyString = FileUtils.readFileToString(new File(pulickPath), Charset.defaultCharset());38 // 获取密钥工厂39 KeyFactory keyFactory = KeyFactory.getInstance(algorithm);40 // 构建密钥规范 进行Base64解码41 X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyString));42 // 生成公钥43 return keyFactory.generatePublic(spec);44 }45
46 public static PrivateKey getPrivateKey(String priPath,String algorithm) throws Exception{47 // 将文件内容转为字符串48 String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());49 // 获取密钥工厂50 KeyFactory keyFactory = KeyFactory.getInstance(algorithm);51 // 构建密钥规范 进行Base64解码52 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));53 // 生成私钥54 return keyFactory.generatePrivate(spec);55 }56
57 /**58 * 生成密钥对并保存在本地文件中59 *60 * @param algorithm : 算法61 * @param pubPath : 公钥保存路径62 * @param priPath : 私钥保存路径63 * @throws Exception64 */65 public static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {66 // 获取密钥对生成器67 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);68 // 获取密钥对69 KeyPair keyPair = keyPairGenerator.generateKeyPair();70 // 获取公钥71 PublicKey publicKey = keyPair.getPublic();72 // 获取私钥73 PrivateKey privateKey = keyPair.getPrivate();74 // 获取byte数组75 byte[] publicKeyEncoded = publicKey.getEncoded();76 byte[] privateKeyEncoded = privateKey.getEncoded();77 // 进行Base64编码78 String publicKeyString = Base64.encode(publicKeyEncoded);79 String privateKeyString = Base64.encode(privateKeyEncoded);80 // 保存文件81 FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));82 FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));83
84 }85
86 /**87 * 解密数据88 *89 * @param algorithm : 算法90 * @param encrypted : 密文91 * @param key : 密钥92 * @return : 原文93 * @throws Exception94 */95 public static String decryptRSA(String algorithm,Key key,String encrypted) throws Exception{96 // 创建加密对象97 // 参数表示加密算法98 Cipher cipher = Cipher.getInstance(algorithm);99 // 私钥进行解密100 cipher.init(Cipher.DECRYPT_MODE,key);101 // 由于密文进行了Base64编码, 在这里需要进行解码102 byte[] decode = Base64.decode(encrypted);103 // 对密文进行解密,不需要使用base64,因为原文不会乱码104 byte[] bytes1 = cipher.doFinal(decode);105 return new String(bytes1);106
107 }108 /**109 * 使用密钥加密数据110 *111 * @param algorithm : 算法112 * @param input : 原文113 * @param key : 密钥114 * @return : 密文115 * @throws Exception116 */117 public static String encryptRSA(String algorithm,Key key,String input) throws Exception{118 // 创建加密对象119 // 参数表示加密算法120 Cipher cipher = Cipher.getInstance(algorithm);121 // 初始化加密122 // 第一个参数:加密的模式123 // 第二个参数:使用私钥进行加密124 cipher.init(Cipher.ENCRYPT_MODE,key);125 // 私钥加密126 byte[] bytes = cipher.doFinal(input.getBytes());127 // 对密文进行Base64编码128 return Base64.encode(bytes);129 }130}运行程序
数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。数字签名是非对称密钥加密技术与数字摘要技术的应用。
相信我们都写过信,在写信的时候落款处总是要留下自己的名字,用来表示写信的人是谁。我们签的这个字就是生活中的签名:
而数字签名呢?其实也是同样的道理,他的含义是:在网络中传输数据时候,给数据添加一个数字签名,表示是谁发的数据,而且还能证明数据没有被篡改。
OK,数字签名的主要作用就是保证了数据的有效性(验证是谁发的)和完整性(证明信息没有被篡改)。下面我们就来好好地看一下他的底层实现原理是什么样子的。
为了理解得清楚,我们通过案例一步一步来讲解。话说张三有俩好哥们A、B。由于工作原因,张三和AB写邮件的时候为了安全都需要加密。于是张三想到了数字签名:
整个思路是这个样子的:
第一步:加密采用非对称加密,张三有三把钥匙,两把公钥,送给朋友。一把私钥留给自己。
第二步:A或者B写邮件给张三:A先用公钥对邮件加密,然后张三收到邮件之后使用私钥解密。
第三步:张三写邮件给A或者B:
(1)张三写完邮件,先用hash函数生成邮件的摘要,附着在文章上面,这就完成了数字签名,然后张三再使用私钥加密。就可以把邮件发出去了。
(2)A或者是B收到邮件之后,先把数字签名取下来,然后使用自己的公钥解密即可。这时候取下来的数字签名中的摘要若和张三的一致,那就认为是张三发来的,再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。
上面的流程我们使用一张图来演示一下:
首先把公钥送给朋友A和B:
还有就是最后一个比较麻烦的,张三给A或者B发邮件:
上面提到我们对签名进行验证时,需要用到公钥。如果公钥是伪造的,那我们无法验证数字签名了,也就根本不可能从数字签名确定对方的合法性了。这时候证书就闪亮登场了。我们可能都有考各种证书的经历,比如说普通话证书,四六级证书等等,但是归根结底,到任何场合我们都能拿出我们的证书来证明自己确实已经考过了普通话,考过了四六级。这里的证书也是同样的道理。
如果不理解证书的作用,我们可以举一个例子,比如说我们的毕业证书,任何公司都会承认。为什么会承认?因为那是国家发得,大家都信任国家。也就是说只要是国家的认证机构,我们都信任它是合法的。
那么这个证书是如何生成的呢?我们再来看一张图:
此时即使张三的朋友A把公钥弄错了,张三也可以通过这个证书验证。
我们看一个应用“数字证书”的实例:https协议。这个协议主要用于网页加密
首先,客户端向服务器发出加密请求。
服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。
客户端(浏览器)的“证书管理器”,有“受信任的根证书颁发机构”列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内。
如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告。
如果这张数字证书不是由受信任的机构颁发的,浏览器会发出另一种警告。
如果数字证书是可靠的,客户端就可以使用证书中的服务器公钥,对信息进行加密,然后与服务器交换加密信息。
xxxxxxxxxx601import java.security.*;2import com.sun.org.apache.xml.internal.security.utils.Base64;3public class SignatureDemo {4public static void main(String[] args) throws Exception {5String a = "123";67PublicKey publicKey = RsaDemo.loadPublicKeyFromFile("RSA", "a.pub");8PrivateKey privateKey = RsaDemo.loadPrivateKeyFromFile("RSA", "a.pri");910String signaturedData = getSignature(a, "sha256withrsa", privateKey);1112boolean b = verifySignature(a, "sha256withrsa", publicKey, signaturedData);1314}1516/**17* 生成签名18*19* @param input : 原文20* @param algorithm : 算法21* @param privateKey : 私钥22* @return : 签名23* @throws Exception24*/25private static String getSignature(String input, String algorithm, PrivateKey privateKey) throws Exception {26// 获取签名对象27Signature signature = Signature.getInstance(algorithm);28// 初始化签名29signature.initSign(privateKey);30// 传入原文31signature.update(input.getBytes());32// 开始签名33byte[] sign = signature.sign();34// 对签名数据进行Base64编码35return Base64.encode(sign);36}3738/**39* 校验签名40*41* @param input : 原文42* @param algorithm : 算法43* @param publicKey : 公钥44* @param signaturedData : 签名45* @return : 数据是否被篡改46* @throws Exception47*/48private static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signaturedData) throws Exception {49// 获取签名对象50Signature signature = Signature.getInstance(algorithm);51// 初始化签名52signature.initVerify(publicKey);53// 传入原文54signature.update(input.getBytes());55// 校验数据56return signature.verify(Base64.decode(signaturedData));5758}5960}
keytool工具路径:C:\Program Files\Java\jre1.8.0_91\bin
常用命令: 生成keypair keytool -genkeypair keytool -genkeypair -alias lisi(后面部分是为证书指定别名,否则采用默认的名称为mykey)
看看keystore中有哪些项目: keytool -list或keytool -list -v keytool -exportcert -alias lisi -file lisi.cer
生成可打印的证书: keytool -exportcert -alias lisi -file lisi.cer –rfc
显示数字证书文件中的证书信息: keytool -printcert -file lisi.cer 直接双击lisi.cer,用window系统的内置程序打开lisi.cer
(1)生成密钥证书 下边命令生成密钥证书,采用RSA 算法每个证书包含公钥和私钥
创建一个文件夹,在该文件夹下执行如下命令行:
xxxxxxxxxx11keytool -genkeypair -alias guigu -keyalg RSA -keypass guigu -keystore guigu.jks -storepass guigu Keytool 是一个java提供的证书管理工具
xxxxxxxxxx51-alias:密钥的别名2-keyalg:使用的hash算法3-keypass:密钥的访问密码4-keystore:密钥库文件名,xc.keystore保存了生成的证书5-storepass:密钥库的访问密码
(2)查询证书信息
xxxxxxxxxx11keytool -list -keystore guigu.jks(3)删除别名
xxxxxxxxxx11keytool -delete -alias guigu -keystore guigu.jskopenssl是一个加解密工具包,这里使用openssl来导出公钥信息。
安装 openssl:http://slproweb.com/products/Win32OpenSSL.html
安装资料目录下的Win64OpenSSL-1_1_0g.exe
配置openssl的path环境变量,如下图:
本教程配置在C:\OpenSSL-Win64\bin
cmd进入guigu.jks文件所在目录执行如下命令(如下命令在windows下执行,会把-变成中文方式,请将它改成英文的-):
xxxxxxxxxx11keytool -list -rfc --keystore guigu.jks | openssl x509 -inform pem -pubkey下面段内容是公钥
xxxxxxxxxx91-----BEGIN PUBLIC KEY-----2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFsEiaLvij9C1Mz+oyAm3t47whAaRkRu/8kePM+X8760UGU0RMwGti6Z9y3LQ0RvK6I0brXmbGB/RsN38PVnh4cP8ZfxGUH26kX0RK+tlrxcrG+HkPYOH4XPAL8Q1lu1n9x3tLcIPxq8ZZtuIyKYEm5oLKyMsvTviG5flTpDprT25unWgE4md1kthRWXOnfWHATVY7Y/r4obiOL1mS5bEa/6iNKotQNnvIAKtjBM4RlIDWMa6dmz+lHtLtqDD2LF1qwoiSIHI75LQZ/CNYaHCfZS7xtOydpNKq8eb1/PGiLNolD4La2zf0/1dlcr5mkesV570NxRmU1tFm8Zd3MZlZmyv89QIDAQAB9-----END PUBLIC KEY-----
将上边的公钥拷贝到文本public.key文件中,合并为一行,可以将它放到需要实现授权认证的工程中。
encryptcase导入pom文件
xxxxxxxxxx421 <parent>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-starter-parent</artifactId>4 <version>2.1.6.RELEASE</version>5 </parent>6
7 <properties>8 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>9 <maven.compiler.source>1.8</maven.compiler.source>10 <maven.compiler.target>1.8</maven.compiler.target>11 </properties>12
13 <dependencies>14 <dependency>15 <groupId>org.springframework.boot</groupId>16 <artifactId>spring-boot-starter-web</artifactId>17 </dependency>18 <dependency>19 <groupId>org.springframework.boot</groupId>20 <artifactId>spring-boot-starter-data-jpa</artifactId>21 </dependency>22 <dependency>23 <groupId>mysql</groupId>24 <artifactId>mysql-connector-java</artifactId>25 </dependency>26 <!--swagger2-->27 <dependency>28 <groupId>io.springfox</groupId>29 <artifactId>springfox-swagger2</artifactId>30 <version>2.8.0</version>31 </dependency>32 <dependency>33 <groupId>io.springfox</groupId>34 <artifactId>springfox-swagger-ui</artifactId>35 <version>2.8.0</version>36 </dependency>37 <dependency>38 <groupId>commons-io</groupId>39 <artifactId>commons-io</artifactId>40 <version>2.6</version>41 </dependency>42 </dependencies>xxxxxxxxxx81CREATE DATABASE USER;2USE USER;3CREATE TABLE USER(4 id INT PRIMARY KEY AUTO_INCREMENT,5 username VARCHAR(20),6 password VARCHAR(50),7
8)xxxxxxxxxx121package com.atguigu.encryptcase;2
3import org.springframework.boot.SpringApplication;4import org.springframework.boot.autoconfigure.SpringBootApplication;5
67public class EncryptcaseApplication {8
9 public static void main(String[] args) {10 SpringApplication.run(EncryptcaseApplication.class, args);11 }12}xxxxxxxxxx481package com.atguigu.encryptcase.bean;2
3import javax.persistence.*;4import java.io.Serializable;5
67(name="user")8public class User implements Serializable {9 10 (strategy= GenerationType.IDENTITY)11 (name="id")12 private Integer id;13 private String username;14 private String password;15 16 public User() {17 }18
19 public User(Integer id, String username, String password) {20 this.id = id;21 this.username = username;22 this.password = password;23 }24
25 public Integer getId() {26 return id;27 }28
29 public void setId(Integer id) {30 this.id = id;31 }32
33 public String getUsername() {34 return username;35 }36
37 public void setUsername(String username) {38 this.username = username;39 }40
41 public String getPassword() {42 return password;43 }44
45 public void setPassword(String password) {46 this.password = password;47 }48}xxxxxxxxxx91package com.atguigu.encryptcase.dao;2
3import com.atguigu.encryptcase.bean.User;4import org.springframework.data.jpa.repository.JpaRepository;5
6public interface UserDao extends JpaRepository<User, Integer> {7
8
9}xxxxxxxxxx581package com.atguigu.encryptcase.service;2
3import com.atguigu.encryptcase.bean.User;4
5import java.util.List;6
7public interface UserService {8 void addUser(User user);9
10 void delUser(Integer id);11
12 void updateUser(User user);13
14 List<User> findAll();15
16}17
18
19
20package com.atguigu.encryptcase.service.impl;21
22import com.atguigu.encryptcase.bean.User;23import com.atguigu.encryptcase.dao.UserDao;24import com.atguigu.encryptcase.service.UserService;25import org.springframework.beans.factory.annotation.Autowired;26import org.springframework.stereotype.Service;27import org.springframework.transaction.annotation.Transactional;28
29import java.util.List;30
313233public class UserServiceImpl implements UserService {34
35 36 private UserDao userDao;37
38 39 public void addUser(User user) {40 userDao.save(user);41 }42
43 44 public void delUser(Integer id) {45 userDao.delete(id);46 }47
48 49 public void updateUser(User user) {50 userDao.save(user);51 }52
53 54 public List<User> findAll() {55 return userDao.findAll();56 }57
58}xxxxxxxxxx551package com.atguigu.encryptcase.controller;2
3import com.atguigu.encryptcase.bean.User;4import com.atguigu.encryptcase.service.UserService;5import io.swagger.annotations.Api;6import io.swagger.annotations.ApiOperation;7import org.springframework.beans.factory.annotation.Autowired;8import org.springframework.web.bind.annotation.RequestMapping;9import org.springframework.web.bind.annotation.RequestMethod;10import org.springframework.web.bind.annotation.RestController;11
12import java.util.List;13
1415public class UserController {16 17 private UserService userService;18
19 (value = "/addUser", method = RequestMethod.POST)20 public String addUser(User user) {21 try {22 userService.addUser(user);23 return "添加用户成功";24 } catch (Exception e) {25 return "添加用户失败:" + e.getMessage();26 }27 }28
29 (value = "/addUser", method = RequestMethod.DELETE)30 public String delUser(int id) {31 try {32 userService.delUser(id);33 return "删除用户成功";34 } catch (Exception e) {35 return "删除用户失败:" + e.getMessage();36 }37 }38
39 (value = "/updateUser", method = RequestMethod.PUT)40 public String updateUser(User user) {41 try {42 userService.updateUser(user);43 return "更新用户成功";44 } catch (Exception e) {45 return "更新用户失败:" + e.getMessage();46 }47 }48
49 (value = "/findAll", method = RequestMethod.GET)50 public List<User> findAll() {51
52 return userService.findAll();53
54 }55}在 resources文件夹下面创建数据库配置文件
xxxxxxxxxx61spring:2 datasource:3 username: root4 password: root5 url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf86 driver-class-name: com.mysql.jdbc.Driver运行项目
现如今,前后端分离已经逐渐成为互联网项目一种标准的开发方式,前端与后端交给不同的人员开发,
但是项目开发中的沟通成本也随之升高,这部分沟通成本主要在于前端开发人员与后端开发人员对WebAPI接口的沟通,Swagger2 就可以很好地解决,它可以动态生成Api接口文档,降低沟通成本,促进项目高效开发。
有时候定义了文档,代码中修改了一点小的东西,总会忘记同步修改文档,时间长了,自己都比较蒙,还需要看一下代码才能发现问题。
OpenAPI规范(OpenAPI Specification 简称OAS)是Linux基金会的一个项目,试图通过定义一种用来描述API格
式或API定义的语言,来规范RESTful服务开发过程,目前版本是V3.0,并且已经发布并开源在github上。
(https://github.com/OAI/OpenAPI-Specification)
Swagger是全球最大的OpenAPI规范(OAS)API开发工具框架,支持从设计和文档到测试和部署的整个API生命周
期的开发。(https://swagger.io/)
Spring Boot 可以集成Swagger,生成Swagger接口,Spring Boot是Java领域的神器,它是Spring项目下快速构建
项目的框架
swagger通过注解生成接口文档,包括接口名、请求方法、参数、返回信息的等等。
xxxxxxxxxx111:修饰整个类,描述Controller的作用2:描述一个类的一个方法,或者说一个接口3:单个参数描述4:用对象实体来作为入参5:用对象接实体收参数时,描述对象的一个字段6:HTTP响应其中1个描述7:HTTP响应整体描述8:使用该注解忽略这个API9 :发生错误返回的信息10:一个请求参数11: 多个请求参数2.1、@Api修饰整个类,描述Controller的作用
xxxxxxxxxx612("/swagger")3(value = "swagger2的demo例子")4public class SwaggerController {5 6}2.2、@ApiOperation
用于描述一个方法或者接口
可以添加的参数形式:@ApiOperation(value = “接口说明”, httpMethod = “接口请求方式”, response = “接口返回参数类型”, notes = “接口发布说明”)
xxxxxxxxxx71("/swagger")23(value = "根据用户名获取用户的信息",notes = "查询数据库中的记录",httpMethod = "POST",response = String.class)4 public String getUserInfo(String userName) {5 return "1234";6 }7}2.3、@ApiImplicitParam 一个请求参数
@ApiImplicitParam(required = “是否必须参数”, name = “参数名称”, value = “参数具体描述”,dateType=“变量类型”,paramType=”请求方式”)
xxxxxxxxxx51(name = "userName",value = "用户名",required = true,dataType = "String",paramType = "query")2 public String getUserInfo(String userName) {3 return "1234";4 }5}2.5、@ApiImplicitParams 多个请求参数
参数和@ApiImplicitParam一致,只是这个注解可以添加多个参数而已
xxxxxxxxxx81
2({3 (name = "nickName",value = "用户的昵称",paramType = "query",dataType = "String",required = true),4 (name = "id",value = "用户的ID",paramType = "query",dataType = "Integer",required = true)5})6public String getUserInfoByNickName(String nickName, Integer id) {7 return "1234";8}xxxxxxxxxx661package com.atguigu.encryptcase.controller;23import com.atguigu.encryptcase.bean.User;4import com.atguigu.encryptcase.service.UserService;5import io.swagger.annotations.Api;6import io.swagger.annotations.ApiOperation;7import org.springframework.beans.factory.annotation.Autowired;8import org.springframework.web.bind.annotation.RequestMapping;9import org.springframework.web.bind.annotation.RequestMethod;10import org.springframework.web.bind.annotation.RestController;1112import java.util.List;13/**14* RestController15*16* @Author: 尚硅谷17* @CreateTime: 2020-03-2618* @Description:19*/20@Api(tags = "提供用户的增删改查的功能")21@RestController22public class UserController {23@Autowired24private UserService userService;2526@ApiOperation(value = "添加用户")27@RequestMapping(value = "/addUser", method = RequestMethod.POST)28public String addUser(User user) {29try {30userService.addUser(user);31return "添加用户成功";32} catch (Exception e) {33return "添加用户失败:" + e.getMessage();34}35}3637@ApiOperation(value = "删除用户")38@RequestMapping(value = "/addUser", method = RequestMethod.DELETE)39public String delUser(int id) {40try {41userService.delUser(id);42return "删除用户成功";43} catch (Exception e) {44return "删除用户失败:" + e.getMessage();45}46}4748@ApiOperation(value = "更新用户")49@RequestMapping(value = "/updateUser", method = RequestMethod.PUT)50public String updateUser(User user) {51try {52userService.updateUser(user);53return "更新用户成功";54} catch (Exception e) {55return "更新用户失败:" + e.getMessage();56}57}5859@ApiOperation(value = "查询用户")60@RequestMapping(value = "/findAll", method = RequestMethod.GET)61public List<User> findAll() {6263return userService.findAll();6465}66}
运行程序 :http://localhost:8080/swagger-ui.html
展开接口内部
点击try it out 输入姓名, Execute执行,返回如下图效果
查看数据库表,数据已经添加到数据库
模拟购物场景,用户点击购物的时候,在前端生成签名信息,传递给后台服务器进行校验,如果价格,数量,签名都正确,购物成功,如果被人修改,购物失败
拷贝今天咱们写的 两个代码 RsaDemo.java 和 SignatureDemo.java
xxxxxxxxxx2791package com.atguigu.encryptcase.utils;2
3import com.sun.org.apache.xml.internal.security.utils.Base64;4import org.apache.commons.io.FileUtils;5
6import javax.crypto.Cipher;7import java.io.ByteArrayOutputStream;8import java.io.File;9import java.nio.charset.Charset;10import java.security.*;11import java.security.spec.PKCS8EncodedKeySpec;12import java.security.spec.X509EncodedKeySpec;13
14public class RsaDemo {15
16 public static void main(String[] args) throws Exception {17
18 generateKeyToFile("RSA", "a.pub", "a.pri");19 }20
21 /**22 * 生成密钥对并保存在本地文件中23 *24 * @param algorithm : 算法25 * @param pubPath : 公钥保存路径26 * @param priPath : 私钥保存路径27 * @throws Exception28 */29 public static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {30 // 获取密钥对生成器31 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);32 // 获取密钥对33 KeyPair keyPair = keyPairGenerator.generateKeyPair();34 // 获取公钥35 PublicKey publicKey = keyPair.getPublic();36 // 获取私钥37 PrivateKey privateKey = keyPair.getPrivate();38 // 获取byte数组39 byte[] publicKeyEncoded = publicKey.getEncoded();40 byte[] privateKeyEncoded = privateKey.getEncoded();41 // 进行Base64编码42 String publicKeyString = Base64.encode(publicKeyEncoded);43 String privateKeyString = Base64.encode(privateKeyEncoded);44 // 保存文件45 FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));46 FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));47
48 }49
50 /**51 * 从文件中加载公钥52 *53 * @param algorithm : 算法54 * @param filePath : 文件路径55 * @return : 公钥56 * @throws Exception57 */58 public static PublicKey loadPublicKeyFromFile(String algorithm, String filePath) throws Exception {59 // 将文件内容转为字符串60 String keyString = FileUtils.readFileToString(new File(filePath), Charset.forName("UTF-8"));61
62 return loadPublicKeyFromString(algorithm, keyString);63
64 }65
66 /**67 * 从字符串中加载公钥68 *69 * @param algorithm : 算法70 * @param keyString : 公钥字符串71 * @return : 公钥72 * @throws Exception73 */74 public static PublicKey loadPublicKeyFromString(String algorithm, String keyString) throws Exception {75 // 进行Base64解码76 byte[] decode = Base64.decode(keyString);77 // 获取密钥工厂78 KeyFactory keyFactory = KeyFactory.getInstance(algorithm);79 // 构建密钥规范80 X509EncodedKeySpec keyspec = new X509EncodedKeySpec(decode);81 // 获取公钥82 return keyFactory.generatePublic(keyspec);83
84 }85
86 /**87 * 从文件中加载私钥88 *89 * @param algorithm : 算法90 * @param filePath : 文件路径91 * @return : 私钥92 * @throws Exception93 */94 public static PrivateKey loadPrivateKeyFromFile(String algorithm, String filePath) throws Exception {95 // 将文件内容转为字符串96 String keyString = FileUtils.readFileToString(new File(filePath), Charset.forName("UTF-8"));97 return loadPrivateKeyFromString(algorithm, keyString);98
99 }100
101 /**102 * 从字符串中加载私钥103 *104 * @param algorithm : 算法105 * @param keyString : 私钥字符串106 * @return : 私钥107 * @throws Exception108 */109 public static PrivateKey loadPrivateKeyFromString(String algorithm, String keyString) throws Exception {110 // 进行Base64解码111 byte[] decode = Base64.decode(keyString);112 // 获取密钥工厂113 KeyFactory keyFactory = KeyFactory.getInstance(algorithm);114 // 构建密钥规范115 PKCS8EncodedKeySpec keyspec = new PKCS8EncodedKeySpec(decode);116 // 生成私钥117 return keyFactory.generatePrivate(keyspec);118
119 }120
121 /**122 * 使用密钥加密数据123 *124 * @param algorithm : 算法125 * @param input : 原文126 * @param key : 密钥127 * @param maxEncryptSize : 最大加密长度(需要根据实际情况进行调整)128 * @return : 密文129 * @throws Exception130 */131 public static String encrypt(String algorithm, String input, Key key, int maxEncryptSize) throws Exception {132 // 获取Cipher对象133 Cipher cipher = Cipher.getInstance(algorithm);134 // 初始化模式(加密)和密钥135 cipher.init(Cipher.ENCRYPT_MODE, key);136 // 将原文转为byte数组137 byte[] data = input.getBytes();138 // 总数据长度139 int total = data.length;140 // 输出流141 ByteArrayOutputStream baos = new ByteArrayOutputStream();142 decodeByte(maxEncryptSize, cipher, data, total, baos);143 // 对密文进行Base64编码144 return Base64.encode(baos.toByteArray());145
146 }147
148 /**149 * 解密数据150 *151 * @param algorithm : 算法152 * @param encrypted : 密文153 * @param key : 密钥154 * @param maxDecryptSize : 最大解密长度(需要根据实际情况进行调整)155 * @return : 原文156 * @throws Exception157 */158 public static String decrypt(String algorithm, String encrypted, Key key, int maxDecryptSize) throws Exception {159 // 获取Cipher对象160 Cipher cipher = Cipher.getInstance(algorithm);161 // 初始化模式(解密)和密钥162 cipher.init(Cipher.DECRYPT_MODE, key);163 // 由于密文进行了Base64编码, 在这里需要进行解码164 byte[] data = Base64.decode(encrypted);165 // 总数据长度166 int total = data.length;167 // 输出流168 ByteArrayOutputStream baos = new ByteArrayOutputStream();169
170 decodeByte(maxDecryptSize, cipher, data, total, baos);171 // 输出原文172 return baos.toString();173
174 }175
176 /**177 * 分段处理数据178 *179 * @param maxSize : 最大处理能力180 * @param cipher : Cipher对象181 * @param data : 要处理的byte数组182 * @param total : 总数据长度183 * @param baos : 输出流184 * @throws Exception185 */186 public static void decodeByte(int maxSize, Cipher cipher, byte[] data, int total, ByteArrayOutputStream baos) throws Exception {187 // 偏移量188 int offset = 0;189 // 缓冲区190 byte[] buffer;191 // 如果数据没有处理完, 就一直继续192 while (total - offset > 0) {193 // 如果剩余的数据 >= 最大处理能力, 就按照最大处理能力来加密数据194 if (total - offset >= maxSize) {195 // 加密数据196 buffer = cipher.doFinal(data, offset, maxSize);197 // 偏移量向右侧偏移最大数据能力个198 offset += maxSize;199 } else {200 // 如果剩余的数据 < 最大处理能力, 就按照剩余的个数来加密数据201 buffer = cipher.doFinal(data, offset, total - offset);202 // 偏移量设置为总数据长度, 这样可以跳出循环203 offset = total;204 }205 // 向输出流写入数据206 baos.write(buffer);207 }208 }209
210}211
212package com.atguigu.encryptcase.utils;213
214import com.sun.org.apache.xml.internal.security.utils.Base64;215
216import java.security.PrivateKey;217import java.security.PublicKey;218import java.security.Signature;219
220public class SignatureDemo {221 public static void main(String[] args) throws Exception {222 // 6999:表示购物的价格223 // 10:表示购物的数量224 String a = "6999" + "10";225
226 PublicKey publicKey = RsaDemo.loadPublicKeyFromFile("RSA", "a.pub");227 PrivateKey privateKey = RsaDemo.loadPrivateKeyFromFile("RSA", "a.pri");228
229 String signaturedData = getSignature(a, "sha256withrsa", privateKey);230
231 System.out.println(signaturedData);232
233 }234
235 /**236 * 生成签名237 *238 * @param input : 原文239 * @param algorithm : 算法240 * @param privateKey : 私钥241 * @return : 签名242 * @throws Exception243 */244 public static String getSignature(String input, String algorithm, PrivateKey privateKey) throws Exception {245 // 获取签名对象246 Signature signature = Signature.getInstance(algorithm);247 // 初始化签名248 signature.initSign(privateKey);249 // 传入原文250 signature.update(input.getBytes());251 // 开始签名252 byte[] sign = signature.sign();253 // 对签名数据进行Base64编码254 return Base64.encode(sign);255 }256
257 /**258 * 校验签名259 *260 * @param input : 原文261 * @param algorithm : 算法262 * @param publicKey : 公钥263 * @param signaturedData : 签名264 * @return : 数据是否被篡改265 * @throws Exception266 */267 public static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signaturedData) throws Exception {268 // 获取签名对象269 Signature signature = Signature.getInstance(algorithm);270 // 初始化签名271 signature.initVerify(publicKey);272 // 传入原文273 signature.update(input.getBytes());274 // 校验数据275 return signature.verify(Base64.decode(signaturedData));276
277 }278
279}运行 RsaDemo.java 生成 公钥和私钥
运行 SignatureDemo.java 生成 签名信息
xxxxxxxxxx412VYC512IQvgb+2jkIkb/tAkRpcG6XOELYx0q/KB7VCupOCUo4qn12VfJhJPqXIV24PT8Y2/WWUUxu3rMDx8xZXtUwSlV4uxPwuKuf/PZk3KyGB91qzoSi2icEIXUiZe2rFd2ZCNbcSvSlZnLuvWvc9/VGE4iMkYmRtwA8wgK2Geg+M=
在 UserController.java 添加如下方法
xxxxxxxxxx211(value = "购物")2 (value = "/buy", method = RequestMethod.GET)3 public String buy(String price, String num, String signature) {4 try {5 // 获取公钥6 PublicKey publicKey = RsaDemo.loadPublicKeyFromFile("RSA", "a.pub");7 // 第一个参数:原文8 // 第二个参数:算法9 // 第三个参数:公钥10 // 第四个参数:签名11 boolean result = SignatureDemo.verifySignature(price + num, "SHA256withRSA", publicKey, signature);12
13 if (result) {14 return "购物成功";15 }16 } catch (Exception e) {17 e.printStackTrace();18 }19
20 return "购物失败";21 }运行程序 http://localhost:8080/swagger-ui.html
如果在请求服务器的时候,程序被人篡改,把价格改成6元,运行就会购物失败
#