【尚硅谷密码学】B站:https://www.bilibili.com/video/BV1tz4y197hm
基本概念
密码学:主要是研究编制密码和破译密码的学科。是网络安全、信息安全、区块链等学科的基础。常见的非对称加密、对称加密、散列函数等,都属于密码学范畴。
密码学有数千年的历史,从最开始的替换法到如今的非对称加密算法,经历了古典密码学,近代密码学和现代密码学三个阶段。密码学不仅仅是数学家们的智慧,更是如今网络空间安全的重要基础。
密码学的主要作用:研究如何隐藏信息并且把消息传递出去的一个学科。
古典密码学
在古代的战争中,多见使用隐藏信息的方式保护重要的通信资料。比如先把需要保护的信息用化学药水写到纸上,药水干后,纸上看不出任何的信息,需要使用另外的化学药水涂抹后才可以阅读纸上的信息。
核心原理:替换法、移位法。
替换法
就是使用固定的信息,将原文替换成密文。
例如:bee,将b替换成w,e替换p,单词就变成 wpp。
替换法的加密方式:
-
第一种:单表替换(原文和密文使用的是同一张表);
-
第二种:多表替换(表示有多张表,原文和密文进行对比)。
多表替换
表单1:abcde-swtrp
表单2:abcde-chfhk
表单3:abcde-jftou
原文:bee
密钥:312(第一个用第三张表,第二个用第一张表,第三个用第二张表)
密文:fpk
移位法
移位法就是将原文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后得出密文,典型的移位法应用有 “ 恺撒密码 ”。
例如约定好向后移动2位(abcde - cdefg)
,这样 bee
单词就变换成了dgg
。
同理替换法,移位法也可以采用多表移位的方式,典型的多表案例是“维尼吉亚密码”(又译维热纳尔密码),属于多表密码的一种形式。
凯撒加密
凯撒密码最早由古罗马军事统帅盖乌斯·尤利乌斯·凯撒在军队中用来传递加密信息,故称凯撒密码。这是一种位移加密方式,只对26个字母进行位移替换加密,规则简单,容易破解。下面是位移1次的对比:
字母表最多可以移动25位。凯撒密码的明文字母表向后或向前移动都是可以的,通常表述为向后移动,如果要向前移动1位,则等同于向后移动25位,位移选择为25即可。
它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。
例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。
这个加密方法是以恺撒的名字命名的,当年恺撒曾用此方法与其将军们进行联系。
恺撒密码通常被作为其他更复杂的加密方法中的一个步骤。
简单来说就是当秘钥为n,其中一个待加密字符ch,加密之后的字符为ch+n,当ch+n超过’z’时,回到’a’计数。
/**
* 凯撒加密方式加密数据
*
* @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();
}
/**
* 凯撒加密方式解密数据
*
* @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();
}
@Test
public void kaiserTest() {
//加密 把hello world往右边移动3位
System.out.println(encryptKaiser("Hello World", 3));//Khoor#Zruog
//解密
System.out.println(decryptKaiser("Khoor#Zruog", 3));
}
破解古典密码
古典密码虽然很简单,但是在密码史上是使用的最久的加密方式,直到“概率论”的数学方法被发现,古典密码就被破解了。
英文单词中字母出现的频率是不同的,e以12.702%的百分比占比最高,z 只占到0.074%,如果密文数量足够大,仅仅采用频度分析法就可以破解单表的替换法或移位法。
多表的替换法或移位法虽然难度高一些,但如果数据量足够大的话,也是可以破解的。以维尼吉亚密码算法为例,破解方法就是先找出密文中完全相同的字母串,猜测密钥长度,得到密钥长度后再把同组的密文放在一起,使用频率分析法破解。
频率分析法原理:比如以英文字母e,出现的评率是最高的,第二个评率最高的是t,然后就是a。当我们拿到密文的时候,密文里面也会出现一个评率最高的字母,假设密文里面出现评率最高的是j,可以假设密文里面的j,就是明文里面的e。
频度分析法破解恺撒加密
拷贝工具类 Util.java
、 FrequencyAnalysis.java
和Kaiser.java
到项目的 com.coydone.kaiser
包下面 , article.txt
拷贝到项目文件夹的根目录。
运行 FrequencyAnalysis.java
用来统计每个字符出现的次数。
运行 FrequencyAnalysis.java
里面 main 函数里面的 encryptFile
方法对程序进行加密。
在根目录会生成一个 article_en.txt
文件,然后统计这个文件当中每个字符出现的次数。
近代密码学
古典密码的安全性受到了威胁,外加使用便利性较低,到了工业化时代,近现代密码被广泛应用。
恩尼格玛机:恩尼格玛机是二战时期纳粹德国使用的加密机器,后被英国破译,参与破译的人员有被称为计算机科学之父、人工智能之父的图灵。恩尼格玛机使用的加密方式本质上还是移位和替代,只不过因为密码表种类极多,破解难度高,同时加密解密机器化,使用便捷,因而在二战时期得以使用。
现代密码学
散列函数
散列函数,也叫杂凑函数、摘要函数或哈希函数,可将任意长度的消息经过运算,变成固定长度数值,常见的有MD5
、SHA-1
、SHA256
,多应用在文件校验,数字签名中。
MD5可以将任意长度的原文生成一个128位(16字节)的哈希值。
SHA-1可以将任意长度的原文生成一个160位(20字节)的哈希值。
对称密码
对称密码应用了相同的加密密钥和解密密钥。对称密码分为:序列密码(流密码),分组密码(块密码)两种。流密码是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密,块密码是先对信息流分块,再对每一块分别加密。
例如原文为1234567890,流加密即先对1进行加密,再对2进行加密,再对3进行加密……最后拼接成密文;块加密先分成不同的块,如1234成块,5678成块,90XX(XX为补位数字)成块,再分别对不同块进行加密,最后拼接成密文。前文提到的古典密码学加密方法,都属于流加密。
非对称密码
对称密码的密钥安全极其重要,加密者和解密者需要提前协商密钥,并各自确保密钥的安全性,一但密钥泄露,即使算法是安全的也无法保障原文信息的私密性。
在实际的使用中,远程的提前协商密钥不容易实现,即使协商好,在远程传输过程中也容易被他人获取,因此非对称密钥此时就凸显出了优势。
非对称密码有两支密钥,公钥(publickey)和私钥(privatekey),加密和解密运算使用的密钥不同。用公钥对原文进行加密后,需要由私钥进行解密;用私钥对原文进行加密后(此时一般称为签名),需要由公钥进行解密(此时一般称为验签)。公钥可以公开的,大家使用公钥对信息进行加密,再发送给私钥的持有者,私钥持有者使用私钥对信息进行解密,获得信息原文。因为私钥只有单一人持有,因此不用担心被他人解密获取信息原文。
ASCII编码
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
//字符转换成ascii码
@Test
public void charToASCII() {
char a = 'A';
int b = a;
// 打印ascii码
System.out.println(b);//65
}
//字符串转换成ascii码
@Test
public void strToASCII() {
String a = "AaZ";
// 获取ascii码,需要把字符串转成字符
char[] chars = a.toCharArray();
for (char c : chars) {
int asciiCode = c;
System.out.println(asciiCode);//65 97 90
}
}
Byte和bit
Byte : 字节,数据存储的基本单位,比如移动硬盘1T , 单位是byte。
bit:比特, 又叫位。 一个位要么是0要么是1,数据传输的单位 , 比如家里的宽带100MB,下载速度并没有达到100MB,一般都是12-13MB,那么是因为需要使用 100 / 8(1Byte = 8bit)。
//byte对应bit
@Test
public void byte2Bit() {
String a = "a";
byte[] bytes = a.getBytes();
for (byte b : bytes) {
int c = b;
//获取字符串byte
// 打印发现byte实际上就是ascii码
System.out.println(c);
// 每个byte对应的bit,byte获取对应的bit
String s = Integer.toBinaryString(c);
System.out.println(s);//1100001
}
}
打印出来应该是8个bit,但前面是0,没有打印 ,从打印结果可以看出来,一个英文字符 ,占一个字节。
中文对应的字节
// 中文在GBK编码下,占据2个字节
// 中文在UTF-8编码下,占据3个字节
@Test
public void testChinese() {
String a = "中";
byte[] bytes = a.getBytes();
for (byte b : bytes) {
System.out.print(b + " ");
String s = Integer.toBinaryString(b);
System.out.println(s);
}
//-28 11111111111111111111111111100100
//-72 11111111111111111111111110111000
//-83 11111111111111111111111110101101
}
常见加密方式
对称加密
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。对称加密分为流加密和块加密。
例如:流加密123456789,先加密1,再加密2,再在加密3;块加密123456789,相当于分组加密,先加密12345,再加密6789。
常见加密算法:
-
DES:Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。
-
AES:Advanced Encryption Standard,高级加密标准。在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。
特点:
-
加密速度快,可以加密大文件。
-
密文可逆,一旦密钥文件泄漏,就会导致数据暴露。
-
加密后编码表找不到对应字符,出现乱码。
-
一般结合Base64使用。
DES加密解密
//DES加密
@Test
public void testDes() throws Exception {
// 原文
String input = "中国";
// des加密必须是8位,DES加密算法规定,密钥key必须是8个字节
String key = "12345678";
// 加密算法
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));//Fo���
//对数据进行Base64编码
//使用 base64 进行编码,base64 导包的时候,需要注意导入apache的包
System.out.println(Base64.encode(bytes));//Rm+8trB4CBQ=
}
抽取方法
/**
* 使用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);
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);
// 2、指定密钥规则
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);
}
@Test
public void testDes1() throws Exception {
System.out.println(encryptDES("中国", "12345678", "DES", "DES"));
System.out.println(decryptDES("Rm+8trB4CBQ=", "12345678", "DES", "DES"));
}
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 ,这样做的好处在于base64取的是后面6位,去掉高2位 ,那么base64的取值就可以控制在0-63位了,所以就叫base64,111111 = 32 + 16 + 8 + 4 + 2 + 1 = 63
问题,Base64有个 = 号,但是在映射表里面没有发现 = 号 , 等号非常特殊,因为Base64是三个字节一组,如果当位数不够的时候,会使用等号来补齐。
Base64补等号测试
@Test
//import com.sun.org.apache.xml.internal.security.utils.Base64;
public void testBase64() {
// 1:MQ== 表示一个字节,不够三个字节,所以需要后面通过==号补齐
System.out.println(Base64.encode("1".getBytes()));//MQ==
System.out.println(Base64.encode("12".getBytes()));//MTI=
System.out.println(Base64.encode("123".getBytes()));//MTIz
// 中国:中文占6个字节,6 * 8 = 48 ,刚刚好被整除,所以没有等号
System.out.println(Base64.encode("中国".getBytes()));//5Lit5Zu9
}
toString()与new String()
@Test
public void testStr() throws Exception {
String str="TU0jV0xBTiNVYys5bEdiUjZlNU45aHJ0bTdDQStBPT0jNjQ2NDY1Njk4IzM5OTkwMDAwMzAwMA==";
String rlt1=new String(Base64.decode(str));
String rlt2=Base64.decode(str).toString();
System.out.println(rlt1);//MM#WLAN#Uc+9lGbR6e5N9hrtm7CA+A==#646465698#399900003000
System.out.println(rlt2);//[B@5e8c92f4
}
//这里应该用new String()的方法,因为Base64加解密是一种转换编码格式的原理
str.toString() 是调用了这个Object对象的类的toString()方法。一般是返回这么一个String:[class name]@[hashCode]
。对象打印的时候使用。
new String(str) 是根据parameter是一个字节数组,使用Java虚拟机默认的编码格式,将这个字节数组decode为对应的字符。若虚拟机默认的编码格式是ISO-8859-1,按照ascii编码表即可得到字节对应的字符。一般用于字符转码的时候,byte[]
数组的时候。
AES加密解密
AES加密解密和DES加密解密代码一样,只需要修改加密算法就行。
@Test
public void testAes() throws Exception {
// DES加密算法,key的大小必须是16个字节
System.out.println(encryptDES("中国", "1234567812345678", "AES", "AES"));
System.out.println(decryptDES("xwDk6ldAgRgDxCNaQmo7NQ==", "1234567812345678", "AES", "AES"));
}
加密模式
ECB
ECB : Electronic codebook,电子密码本。需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密。
优点:可以并行处理数据。
缺点:同样的原文生成同样的密文,不能很好的保护数据。
同时加密,原文是一样的,加密出来的密文也是一样的。
CBC
CBC:Cipher-block chaining,密码块链接。每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。
优点:同样的原文生成的密文不一样。
缺点:串行处理数据。
填充模式
当需要按块处理的数据,数据长度不符合块处理需求时,按照一定的方法填充满块长的规则。
-
NoPadding:不填充。在DES加密算法下,要求原文长度必须是8byte的整数倍;在AES加密算法下,要求原文长度必须是16byte的整数倍。
-
PKCS5Padding:数据块的大小为8位,不够就补足。
默认情况下,加密模式和填充模式为:ECB/PKCS5Padding。如果使用CBC模式,在初始化Cipher对象时,需要增加参数,初始化向量IV : IvParameterSpec iv = new IvParameterSpec(key.getBytes());
加密模式和填充模式:
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)
//加密算法/加密模式/填充模式
String transformation = "DES/CBC/NoPadding";
// 初始化加密模式和算法
IvParameterSpec iv = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.ENCRYPT_MODE,sks,iv);
消息摘要
消息摘要(Message Digest)又称为数字摘要(Digital Digest)。它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生。使用数字摘要生成的值是不可以篡改的,为了保证文件或者值的安全。
特点:
-
无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出。
-
只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。
-
消息摘要是单向、不可逆的。
常见算法:MD5、SHA1、SHA256、SHA512。
-
MD5算法:摘要结果16个字节,转16进制后32个字节。
-
SHA1算法:摘要结果20个字节,转16进制后40个字节。
-
SHA256算法:摘要结果32个字节,转16进制后64个字节。
-
SHA512算法:摘要结果64个字节,转16进制后128个字节。
百度搜索tomcat,进入官网下载 ,会经常发现有sha1、sha512,这些都是数字摘要。
数字摘要
@Test
//消息摘要算法,为了防止篡改
public void testDigest() throws Exception {
// 原文
String input = "aa";
// 算法
String algorithm = "MD5";
// 获取消息摘要对象
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
// 获取消息摘要的字节数组
byte[] digest = messageDigest.digest(input.getBytes());
//使用Base64进行转码
System.out.println(Base64.encode(digest));//QSS8CpM1wn8IbyS6IHpJEg==
}
使用在线md5加密 ,发现我们生成的值和代码生成的值不一样,那是因为消息摘要不是使用base64进行编码的,所以我们需要把值转成16进制。
@Test
//消息摘要算法,为了防止篡改
public void testDigest() throws Exception {
System.out.println(getDigest("aa","MD5"));//4124bc0a9335c27f086f24ba207a4912
System.out.println(getDigest("aa","SHA-1"));//e0c9035898dd52fc65c41454cec9c4d2611bfb37
}
private String getDigest(String input, String algorithm) throws Exception {
// 获取消息摘要对象
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
// 获取消息摘要的字节数组
byte[] digest = messageDigest.digest(input.getBytes());
return toHex(digest);
}
private String toHex(byte[] digest) {
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
// 转成 16进制
String s = Integer.toHexString(b & 0xff);
//System.out.println(s);
if (s.length() == 1){
// 如果生成的字符只有一个,前面补0
s = "0"+s;
}
sb.append(s);
}
return sb.toString();
}
非对称加密
非对称加密算法又称现代加密算法。是计算机通信安全的基石,保证了加密数据不会被破解。
与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey), 公开密钥和私有密钥是一对。
如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
特点
-
加密和解密使用不同的密钥;
-
如果使用私钥加密,只能使用公钥解密;
-
如果使用公钥加密,只能使用私钥解密;
-
处理数据的速度较慢,因为安全级别高。
常见算法:RSA算法、ECC算法
生成公钥和私钥
@Test
public void test1() throws Exception {
// 加密算法
String algorithm = "RSA";
// 创建密钥对生成器对象
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm);
// 生成密钥对
KeyPair keyPair = generator.genKeyPair();
// 生成私钥
PrivateKey privateKey = keyPair.getPrivate();
// 生成公钥
PublicKey publicKey = keyPair.getPublic();
// 获取私钥字节数组
byte[] privateKeyEncoded = privateKey.getEncoded();
// 获取公钥字节数组
byte[] publicKeyEncoded = publicKey.getEncoded();
// 对公私钥进行base64编码
String privateKeyString = Base64.encode(privateKeyEncoded);
String publicKeyString = Base64.encode(publicKeyEncoded);
System.out.println(privateKeyString);
System.out.println(publicKeyString);
}
私钥加密公钥解密
@Test
public void test1() throws Exception {
String input = "中国";
// 加密算法
String algorithm = "RSA";
// 创建密钥对生成器对象
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm);
// 生成密钥对
KeyPair keyPair = generator.genKeyPair();
// 生成私钥
PrivateKey privateKey = keyPair.getPrivate();
// 生成公钥
PublicKey publicKey = keyPair.getPublic();
// 创建加密对象
// 参数表示加密算法
Cipher cipher = Cipher.getInstance(algorithm);
// 初始化加密
// 第一个参数:加密的模式
// 第二个参数:使用私钥进行加密
cipher.init(Cipher.ENCRYPT_MODE,privateKey);
// 私钥加密
byte[] bytes = cipher.doFinal(input.getBytes());
System.out.println(Base64.encode(bytes));
//公钥解密
cipher.init(Cipher.DECRYPT_MODE,publicKey);
System.out.println(new String(cipher.doFinal(bytes)));
}
保存公钥和私钥
将生成的公钥和私钥保存到文件中,从文件中读取公钥和私钥进行加解密操作。
1、添加依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
2、编写代码
/**
* 生成密钥对并保存在本地文件中
*
* @param algorithm : 算法
* @param pubPath : 公钥保存路径
* @param priPath : 私钥保存路径
* @throws Exception
*/
private static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {
// 获取密钥对生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
// 获取密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 获取公钥
PublicKey publicKey = keyPair.getPublic();
// 获取私钥
PrivateKey privateKey = keyPair.getPrivate();
// 获取byte数组
byte[] publicKeyEncoded = publicKey.getEncoded();
byte[] privateKeyEncoded = privateKey.getEncoded();
// 进行Base64编码
String publicKeyString = Base64.encode(publicKeyEncoded);
String privateKeyString = Base64.encode(privateKeyEncoded);
// 保存文件
//org.apache.commons.io.FileUtils
FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));
FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));
}
/**
* 解密数据
*
* @param algorithm : 算法
* @param encrypted : 密文
* @param key : 密钥
* @return : 原文
* @throws Exception
*/
public static String decryptRSA(String algorithm,Key key,String encrypted) throws Exception{
// 创建加密对象
// 参数表示加密算法
Cipher cipher = Cipher.getInstance(algorithm);
// 公钥进行解密
cipher.init(Cipher.DECRYPT_MODE,key);
// 由于密文进行了Base64编码, 在这里需要进行解码
byte[] decode = Base64.decode(encrypted);
// 对密文进行解密,不需要使用base64,因为原文不会乱码
byte[] bytes1 = cipher.doFinal(decode);
return new String(bytes1);
}
/**
* 使用密钥加密数据
*
* @param algorithm : 算法
* @param input : 原文
* @param key : 密钥
* @return : 密文
* @throws Exception
*/
public static String encryptRSA(String algorithm,Key key,String input) throws Exception{
// 创建加密对象
// 参数表示加密算法
Cipher cipher = Cipher.getInstance(algorithm);
// 初始化加密
// 第一个参数:加密的模式
// 第二个参数:使用私钥进行加密
cipher.init(Cipher.ENCRYPT_MODE,key);
// 私钥加密
byte[] bytes = cipher.doFinal(input.getBytes());
// 对密文进行Base64编码
return Base64.encode(bytes);
}
/**
* 读取私钥
* @param priPath 私钥路径
* @param algorithm 算法
* @return 私钥
* @throws Exception
*/
public static PrivateKey getPrivateKey(String priPath,String algorithm) throws Exception{
// 将文件内容转为字符串
String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 构建密钥规范 进行Base64解码
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));
// 生成私钥
return keyFactory.generatePrivate(spec);
}
/**
* 读取公钥
* @param pulickPath 公钥路径
* @param algorithm 算法
* @return 公钥
* @throws Exception
*/
public static PublicKey getPublicKey(String pulickPath,String algorithm) throws Exception{
// 将文件内容转为字符串
String publicKeyString = FileUtils.readFileToString(new File(pulickPath), Charset.defaultCharset());
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 构建密钥规范 进行Base64解码
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyString));
// 生成公钥
return keyFactory.generatePublic(spec);
}
@Test
public void test2() throws Exception {
String input = "中国";
// 加密算法
String algorithm = "RSA";
//生成密钥对并保存在本地文件中
//generateKeyToFile(algorithm, "a.pub", "a.pri");
//加密
Key privateKey = getPrivateKey("a.pri",algorithm);
String s = encryptRSA(algorithm, privateKey, input);
// 解密
Key publicKey = getPublicKey("a.pub",algorithm);
String s1 = decryptRSA(algorithm, publicKey, s);
System.out.println(s1);
}
数字签名
简介
数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。数字签名是非对称密钥加密技术与数字摘要技术的应用。
数字签名的含义是:在网络中传输数据时候,给数据添加一个数字签名,表示是谁发的数据,而且还能证明数据没有被篡改。
数字签名的主要作用就是保证了数据的有效性(验证是谁发的)和完整性(证明信息没有被篡改)。
数字证书
对签名进行验证时,需要用到公钥。如果公钥是伪造的,那我们无法验证数字签名了,也就根本不可能从数字签名确定对方的合法性了。这时候证书就闪亮登场了。
举一个例子,比如说我们的毕业证书,任何公司都会承认。为什么会承认?因为那是国家发得,大家都信任国家。也就是说只要是国家的认证机构,我们都信任它是合法的。
CA认证中心:所谓CA(Certificate Authority)认证中心,它是采用PKI(Public Key Infrastructure)公开密钥基础架构技术,专门提供网络身份认证服务,CA可以是民间团体,也可以是政府机构。负责签发和管理数字证书,且具有权威性和公正性的第三方信任机构,它的作用就像我们现实生活中颁发证件的公司,如护照办理机构。国内的CA认证中心主要分为区域性CA认证中心和行业性CA认证中心。
网页加密
应用“数字证书”的实例:https协议。这个协议主要用于网页加密。
首先,客户端向服务器发出加密请求。
服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。
客户端(浏览器)的“证书管理器”,有“受信任的根证书颁发机构”列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内。
如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告。
如果这张数字证书不是由受信任的机构颁发的,浏览器会发出另一种警告。
如果数字证书是可靠的,客户端就可以使用证书中的服务器公钥,对信息进行加密,然后与服务器交换加密信息。
代码实现
/**
* 生成签名
*
* @param input : 原文
* @param algorithm : 算法
* @param privateKey : 私钥
* @return : 签名
* @throws Exception
*/
private static String getSignature(String input, String algorithm, PrivateKey privateKey) throws Exception {
// 获取签名对象
Signature signature = Signature.getInstance(algorithm);
// 初始化签名
signature.initSign(privateKey);
// 传入原文
signature.update(input.getBytes());
// 开始签名
byte[] sign = signature.sign();
// 对签名数据进行Base64编码
return Base64.encode(sign);
}
/**
* 校验签名
*
* @param input : 原文
* @param algorithm : 算法
* @param publicKey : 公钥
* @param signaturedData : 签名
* @return : 数据是否被篡改
* @throws Exception
*/
private static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signaturedData) throws Exception {
// 获取签名对象
Signature signature = Signature.getInstance(algorithm);
// 初始化签名
signature.initVerify(publicKey);
// 传入原文
signature.update(input.getBytes());
// 校验数据
return signature.verify(Base64.decode(signaturedData));
}
@Test
public void test4() throws Exception {
String a = "123";
PublicKey publicKey = getPublicKey("a.pub","RSA");
PrivateKey privateKey = getPrivateKey("a.pri","RSA");
String signaturedData = getSignature(a, "sha256withrsa", privateKey);
boolean b = verifySignature(a, "sha256withrsa", publicKey, signaturedData);
System.out.println(b);
}
keytool工具的使用
Keytool是一个Java提供的证书管理工具,需要JDK环境。
常用命令
# 生成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
生成私钥公钥
①生成密钥证书,下边命令生成密钥证书,采用RSA 算法,每个证书包含公钥和私钥。
keytool -genkeypair -alias coydone -keyalg RSA -keypass coydone
-keystore coydone.jks -storepass coydone
-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名,xc.keystore保存了生成的证书
-storepass:密钥库的访问密码
②查询证书信息
keytool -list -keystore coydone.jks
③删除别名
keytool -delete -alias guigu -keystore coydone.jsk
导出公钥
openssl是一个加解密工具包,这里使用openssl来导出公钥信息。
安装openssl:http://slproweb.com/products/Win32OpenSSL.html
配置openssl的path环境变量,如下图:
本教程配置在C:\OpenSSL-Win64\bin
cmd进入coydone.jks
文件所在目录执行如下命令(如下命令在windows下执行,会把-
变成中文方式,请将它改成英文的-
):
keytool -list -rfc --keystore coydone.jks | openssl x509 -inform pem -pubkey
将生成的公钥拷贝到文本public.key文件中,合并为一行,可以将它放到需要实现授权认证的工程中。
评论区