[11] 密码学
1. 基本概念#
密码在我们的生活中有着重要的作用,那么密码究竟来自何方,为何会产生呢?
密码学是网络安全、信息安全、区块链等产品的基础,常见的非对称加密、对称加密、散列函数等,都属于密码学范畴。
密码学有数千年的历史,从最开始的替换法到如今的非对称加密算法,经历了古典密码学、近代密码学和现代密码学三个阶段。密码学不仅仅是数学家们的智慧,更是如今网络空间安全的重要基础。
1.1 古典密码学#
在古代的战争中,多见使用隐藏信息的方式保护重要的通信资料。比如先把需要保护的信息用化学药水写到纸上,药水干后,纸上看不出任何的信息,需要使用另外的化学药水涂抹后才可以阅读纸上的信息。
这些方法都是在保护重要的信息不被他人获取,但藏信息的方式比较容易被他人识破,例如增加哨兵的排查力度,就会发现其中的猫腻,因而随后发展出了较难破解的古典密码学。
a. 替换法#
替换法很好理解,就是用固定的信息将原文替换成无法直接阅读的密文信息。例如将 b
替换成 w
,e
替换成 p
,这样 bee
单词就变换成了 wpp
,不知道替换规则的人就无法阅读出原文的含义。
替换法有〈单表替换〉和〈多表替换〉两种形式。单表替换即只有一张原文密文对照表单,发送者和接收者用这张表单来加密解密。在上述例子中,表单即为:a b c d e - s w t r p
。
多表替换即有多张原文密文对照表单,不同字母可以用不同表单的内容替换。例如约定好表单为:表单①#abcde-swtrp 、表单②#abcde-chfhk 、表单③#abcde-jftou。
规定第一个字母用第三张表单,第二个字母用第一张表单,第三个字母用第二张表单,这时 bee
单词就变成了 (312)fpk
,破解难度更高,其中 312 又叫做「密钥」,密钥可以事先约定好,也可以在传输过程中标记出来。
b. 移位法#
移位法就是将原文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后得出密文,典型的移位法应用有 “恺撒密码”。例如约定好向后移动 2 位(abcde - cdefg),这样 bee
单词就变换成了 dgg
。
同理替换法,移位法也可以采用多表移位的方式,典型的多表案例是“维尼吉亚密码”(又译维热纳尔密码),属于多表密码的一种形式。
【补充】在密码学中,恺撒密码是一种最简单且最广为人知的加密技术。
凯撒密码最早由古罗马军事统帅盖乌斯·尤利乌斯·凯撒在军队中用来传递加密信息,故称凯撒密码。这是一种位移加密方式,只对 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
计数。
/**
* @author 6x7
* @Description 凯撒加密和解密
* @createTime 2022年05月18日
*/
public class KaiserDemo {
public static void main(String[] args) {
String content = "HelloWorld";
String encode = encode(content, "3");
String decode = decode(encode, "3");
System.out.println(encode);
System.out.println(decode);
encode = process(content, "3", true);
decode = process(encode, "3", false);
System.out.println(encode);
System.out.println(decode);
}
public static String process(String origin, String key, boolean encode) {
int offset = Integer.parseInt(key);
char[] src = origin.toCharArray();
char[] dst = new char[origin.length()];
for (int i = 0; i < src.length; i++) {
dst[i] = (char) (src[i] + (encode ? offset : -1 * offset));
}
return new String(dst);
}
public static String encode(String origin, String key) {
int offset = Integer.parseInt(key);
char[] src = origin.toCharArray();
char[] dst = new char[origin.length()];
for (int i = 0; i < src.length; i++) {
dst[i] = (char) (src[i] + offset);
}
return new String(dst);
}
public static String decode(String origin, String key) {
int offset = Integer.parseInt(key);
char[] src = origin.toCharArray();
char[] dst = new char[origin.length()];
for (int i = 0; i < src.length; i++) {
dst[i] = (char) (src[i] - offset);
}
return new String(dst);
}
}
加密者选择将组成信息的字母替代成别的字母,比如说将 a
写成 1
,这样就不能被解密者直接拿到信息了。
c. 破解方式#
古典密码虽然很简单,但是在密码史上是使用的最久的加密方式,直到“概率论”的数学方法被发现,古典密码就被破解了。
英文单词中字母出现的频率是不同的,e
以 12.702% 的百分比占比最高,z
只占到 0.074%,感兴趣的可以去百科查字母频率详细统计数据。如果密文数量足够大,仅仅采用频度分析法就可以破解单表的替换法或移位法。
以英文字母为例,为了确定每个英文字母的出现频率,分析一篇或者数篇普通的英文文章,英文字母出现频率最高的是 e
,接下来是 t
,然后是 a
……,然后检查要破解的密文,也将每个字母出现的频率整理出来,假设密文中出现频率最高的字母是 j
,那么就可能是 e
的替身,如果密码文中出现频率次高的是 P
,那么可能是 t
的替身,以此类推便就能解开加密信息的内容。这就是「频率分析法」。
- 将明文字母的出现频率与密文字母的频率相比较的过程;
- 通过分析每个符号出现的频率而轻易地破译代换式密码;
- 在每种语言中,冗长的文章中的字母表现出一种可对之进行分辨的频率;
e
是英语中最常用的字母,其出现频率为 1/8。
多表的替换法或移位法虽然难度高一些,但如果数据量足够大的话,也是可以破解的。以维尼吉亚密码算法为例,破解方法就是先找出密文中完全相同的字母串,猜测密钥长度,得到密钥长度后再把同组的密文放在一起,使用频率分析法破解。
1.2 近代密码学#
古典密码的安全性受到了威胁,外加使用便利性较低,到了工业化时代,近现代密码被广泛应用。
恩尼格玛机是二战时期纳粹德国使用的加密机器,后被英国破译,参与破译的人员有被称为计算机科学之父、人工智能之父的图灵。
恩尼格玛机使用的加密方式本质上还是移位和替代,只不过因为密码表种类极多,破解难度高,同时加密解密机器化,使用便捷,因而在二战时期得以使用。
1.3 现代密码学#
a. 散列函数#
散列函数,也叫“摘要函数”或“哈希函数”,可将任意长度的消息经过运算,变成固定长度数值,常见的有 MD5、SHA-1、SHA256,多应用在文件校验,数字签名中。
- MD5 可以将任意长度的原文生成一个 128 位(16 字节)的哈希值;
- SHA-1 可以将任意长度的原文生成一个 160 位(20 字节)的哈希值;
b. 对称加密#
对称密码应用了相同的加密密钥和解密密钥。对称密码分为:序列密码(流密码),分组密码(块密码)两种。
〈流加密〉是对信息流中的每一个元素(1 个字母或 1 个比特)作为基本的处理单元进行加密,〈块加密〉是先对信息流分块,再对每一块分别加密。
例如原文为 1234567890
,〈流加密〉即先对 1
进行加密,再对 2
进行加密,再对 3
进行加密…… 最后拼接成密文;〈块加密〉先分成不同的块,如 1234
成块,5678
成块,90XX
(XX
为补位数字)成块,再分别对不同块进行加密,最后拼接成密文。前文提到的古典密码学加密方法,都属于〈流加密〉。
c. 非对称加密#
对称密码的密钥安全极其重要,加密者和解密者需要提前协商密钥,并各自确保密钥的安全性,一但密钥泄露,即使算法是安全的也无法保障原文信息的私密性。
在实际的使用中,远程的提前协商密钥不容易实现,即使协商好,在远程传输过程中也容易被他人获取,因此非对称密钥此时就凸显出了优势。
非对称密码有两支密钥,公钥(publickey)和私钥(privatekey),加密和解密运算使用的密钥不同。
用公钥对原文进行加密后,需要由私钥进行解密;用私钥对原文进行加密后(此时一般称为签名),需要由公钥进行解密(此时一般称为验签)。公钥可以公开的,大家使用公钥对信息进行加密,再发送给私钥的持有者,私钥持有者使用私钥对信息进行解密,获得信息原文。因为私钥只有单一人持有,因此不用担心被他人解密获取信息原文。
2. 对称加密#
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为“对称加密”,也称为“单密钥加密”。
【示例】我们现在有一个原文 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 使用(或者转 16 进制串);
2.1 可读性编码#
a. Base64#
Base64 是网络上最常见的用于传输 8Bit 字节码的可读性编码算法之一。
可读性编码算法不是为了保护数据的安全性,而是为了可读性!可读性编码不改变信息内容,只改变信息内容的表现形式。
所谓 Base64,即是说在编码过程中使用了 64 种字符:'A'-'Z'、'a'-'z'、'0'-'9'、'+'、'/'。
Base58 是 Bitcoin(比特币)中使用的一种编码方式,主要用于产生 Bitcoin 的钱包地址。相比 Base64,Base58 不使用数字 '0'、字母大写 'O'、字母大写 'I'、字母小写 'i' 以及 '+' 和 '/' 符号。
【编码规则】base64 是 3 个字节为一组,一组 24 位,然后把 3 个字节按每组 6 位(缺少的 2 位,会在高位进行补 0)切分,转出来 4 组。这样做的好处在于取的是后面 6 位,去掉高 2 位,取值就可以控制在 0~63(00111111) 了,所以就叫 base64!
这里还有一个问题,上节测试代码的 base64 有个 =
,但是在映射表里面没有发现 =
,这个地方需要注意,=
非常特殊,因为 base64 是 3 个字节一组做处理,而当我们的位数不够的时候,它就会使用 =
来补齐。
- 还剩 2 字节:将这两个字节的一共 16 个二进制位,还是按照上面的规则,6 个一组,转成 3 组,最后一组除了前面加两个
0
以外,后面也要加两个0
,这样得到一个三位的 base64 编码,再在末尾补上一个=
; - 还剩 1 字节:将这一个字节的 8 个二进制位,按照上面的规则转成两组,最后一组除了前面加 2 个 0 以外,后面再加 4 个 0。这样得到一个两位的 base64 编码,再在末尾补上两个
=
。
public class Base64Demo {
public static void main(String[] args) {
// [ASCII] '1' - 49 - 00110001
// [6个/组] [00]001100 [00]01[0000]
// [base64] M Q ==
System.out.println(Base64.encode("1".getBytes())); // MQ==
// [ASCII] '1' - 49 - 00110001, '2' - 50 - 00110010
// [6个/组] [00]001100 [00]010011 [00]0010[00]
// [base64] M T I =
System.out.println(Base64.encode("12".getBytes())); // MTI=
// ...
System.out.println(Base64.encode("123".getBytes())); // MTIz
}
}
b. HexStr#
转成 16 进制数也是一种增强加密结果可读性的方式。
private static final char[] HEX_CHARS =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
public static String byteArray2HexStr(byte[] byteArray) {
if (byteArray == null) {
return null;
}
char[] hexChars = new char[byteArray.length * 2];
for (int j = 0; j < byteArray.length; j++) {
int v = byteArray[j] & 0xFF;
hexChars[j * 2] = HEX_CHARS[v >>> 4];
hexChars[j * 2 + 1] = HEX_CHARS[v & 0x0F];
}
return new String(hexChars);
}
public static byte[] hexStr2ByteArray(String hexString) {
if (hexString == null) {
return null;
}
if (hexString.length() == 0) {
return new byte[0];
}
byte[] byteArray = new byte[hexString.length() / 2];
for (int i = 0; i < byteArray.length; i++) {
String subStr = hexString.substring(2 * i, 2 * i + 2);
byteArray[i] = ((byte) Integer.parseInt(subStr, 16));
}
return byteArray;
}
2.2 DES 加密#
public class DesDemo {
/**
* “算法/模式/填充” 或 “算法”
*/
private static final String TRANSFORMATION = "DES";
/**
* 算法
*/
private static final String ALGORITHM = "DES";
/**
* 使用 DES 进行加密,密钥必须为 8 字节
*/
private static final String KEY = "12345678";
public static void main(String[] args) throws Exception {
String input = "888888";
String encrypt = encrypt(input);
System.out.println("decrypt: " + decrypt(encrypt));
}
public static String decrypt(String input) throws Exception {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decodeBase64 = Base64.decode(input);
byte[] result = cipher.doFinal(decodeBase64);
return new String(result);
}
private static String encrypt(String input) throws Exception {
// 1. 创建加密对象
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
// 2. 创建加密规则
// - param1: 密钥的字节数组
// - param2: 算法
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
// 3. 加密初始化
// - param1: 模式(加密/解密)
// - param2: 加密规则
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
// 4. 调用加密方法
byte[] result = cipher.doFinal(input.getBytes());
// [-122, 72, 89, -124, -111, -106, -85, -59]
System.out.println("result: " + Arrays.toString(result));
// 5. 直接输出会出现乱码,因为 ↑ 可能会有负数,但 Ascii 码表里没负数对应字符,所以乱码。
// 可配合使用 base64 进行转码以增强可读性
String encode = Base64.encode(result);
// hkhZhJGWq8U=
System.out.println("base64: " + encode);
return encode;
}
}
base64 导包的时候,需要注意别导错了,需要导入 apache 包。
【补充】toString()
与 new String()
用法区别
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
/**
* @author 6x7
* @createTime 2022年05月18日
* @Description new String() 和 toString() 的区别
* - toString() 该方法调用的实际上是 Object#toString(),返回的是 hash 值
* - new String() 该方法根据参数(字节数组),使用JVM默认编码格式把这个字节数组进行decode,找到对应的字符。
*/
public class StringDemo {
public static void main(String[] args) {
String str = "TU0jV0xBTiNVYys5bEdiUjZlNU45aHJ0bTdDQStBPT0jNjQ2NDY1Njk4IzM5OTkwMDAwMzAwMA==";
String rlt1 = new String(Base64.decode(str));
// [√] MM#WLAN#Uc+9lGbR6e5N9hrtm7CA+A==#646465698#399900003000
System.out.println(rlt1);
String rlt2 = Base64.decode(str).toString();
// [×] [B@74a14482
System.out.println(rlt2);
}
}
2.3 AES 加密#
AES 加密解密代码跟 DES 一样。需要注意的是,密钥 KEY 和加密向量 IV 都需要 16 个字节。
a. 加密方式#
ECB(Electronic codebook):电子密码本,需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密。
- 优点:可以并行处理数据;
- 缺点:同样的原文生成同样的密文,不能很好的保护数据;
CBC(Cipher-block chaining):密码块链接,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。
- 优点:同样的原文生成的密文不一样;
- 缺点:串行处理数据
b. 填充模式#
需要按块处理的数据,数据长度不符合块处理需求时,按照一定的方法填充满块长的规则。
- PKCS5Padding 数据块的大小为 8 位,不够就补足;
- NoPadding 不填充
- 在 DES 加密算法下,要求原文长度必须是 8byte 的整数倍;
- 在 AES 加密算法下,要求原文长度必须是 16byte 的整数倍;
默认情况下,加密模式和填充模式为:ECB/PKCS5Padding
。如果使用 CBC 模式,在初始化 Cipher 对象时需要增加参数,初始化向量 IV:IvParameterSpec iv = new IvParameterSpec(IV.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)
c. 示例代码#
public class AesDemo {
private static final String ALGORITHM = "AES";
/**
* “算法/模式/填充” 或 “算法”
* AES/ECB/PKCS5Padding(默认)
*/
private static final String ECB_ALGORITHM = "AES/ECB/PKCS5Padding";
private static final String CBC_ALGORITHM = "AES/CBC/PKCS5Padding";
/**
* AES 加密算法,密钥的大小必须是 16 个字节
*/
private static final String KEY = "cBssbHB3ZA==HKXT";
/**
* 初始化向量,长度必须是 16 字节
*/
private static final String IV = "1234567890ABCDEF";
public static void main(String[] args) throws Exception {
String input = "888888";
String encryptECB = encryptECB(input);
System.out.println("[ECB] decrypt: " + decryptECB(encryptECB));
String encryptCBC = encryptCBC(input);
System.out.println("[CBC] decrypt: " + decryptCBC(encryptCBC));
}
public static String encryptCBC(String input) throws Exception {
// 1. 创建加密对象
Cipher cipher = Cipher.getInstance(CBC_ALGORITHM);
// 2. 创建加密规则
// - param1: 密钥的字节数组
// - param2: 算法
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
// 3. 加密初始化
// - param1: 模式(加密/解密)
// - param2: 加密规则
// - param3: 初始向量
IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
// 4. 调用加密方法
byte[] result = cipher.doFinal(input.getBytes());
// [-122, 72, 89, -124, -111, -106, -85, -59]
System.out.println("result: " + Arrays.toString(result));
// 5.1 使用 base64 进行转码以增强可读性 - hkhZhJGWq8U=
System.out.println("encode->Base64: " + Base64.encode(result));
// 5.2 使用 Bin->Hex 进行转码以增强可读性 - 832AA27302AD26A46525E43047254047
System.out.println("encode->HexStr: " + byteArray2HexStr(result));
return byteArray2HexStr(result);
}
public static String decryptCBC(String input) throws Exception {
Cipher cipher = Cipher.getInstance(CBC_ALGORITHM);
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
byte[] result = cipher.doFinal(hexStr2ByteArray(input));
return new String(result);
}
public static String decryptECB(String input) throws Exception {
Cipher cipher = Cipher.getInstance(ECB_ALGORITHM);
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
// byte[] result = cipher.doFinal(Base64.decode(input));
byte[] result = cipher.doFinal(hexStr2ByteArray(input));
return new String(result);
}
public static String encryptECB(String input) throws Exception {
// 1. 创建加密对象
Cipher cipher = Cipher.getInstance(ECB_ALGORITHM);
// 2. 创建加密规则
// - param1: 密钥的字节数组
// - param2: 加密类型
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
// 3. 加密初始化
// - param1: 模式(加密/解密)
// - param2: 加密规则
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
// 4. 调用加密方法
byte[] result = cipher.doFinal(input.getBytes());
// [-122, 72, 89, -124, -111, -106, -85, -59]
System.out.println("result: " + Arrays.toString(result));
// 5.1 使用 base64 进行转码以增强可读性 - hkhZhJGWq8U=
System.out.println("encode->Base64: " + Base64.encode(result));
// 5.2 使用 Bin->Hex 进行转码以增强可读性 - 832AA27302AD26A46525E43047254047
System.out.println("encode->HexStr: " + byteArray2HexStr(result));
return byteArray2HexStr(result);
}
// ...
}
控制台打印:
result: [-125, 42, -94, 115, 2, -83, 38, -92, 101, 37, -28, 48, 71, 37, 64, 71]
encode->Base64: gyqicwKtJqRlJeQwRyVARw==
encode->HexStr: 832AA27302AD26A46525E43047254047
[ECB] decrypt: 888888
result: [-2, 125, 110, -75, -64, 84, 22, -117, -61, -25, 28, -114, -110, 115, -123, -90]
encode->Base64: /n1utcBUFovD5xyOknOFpg==
encode->HexStr: FE7D6EB5C054168BC3E71C8E927385A6
[CBC] decrypt: 888888
3. 非对称加密#
非对称加密算法又称现代加密算法,是计算机通信安全的基石,保证了加密数据不会被破解。
与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)、私有密钥(privatekey)。
公开密钥和私有密钥是一对:如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,只有用对应的公开密钥才能解密。
因为加密和解密使用的是两个不同的密钥,所以这种算法叫作“非对称加密算法”。
3.1 RSA/ECC#
RSA 算法和 ECC 算法是目前流行的主要两种 SSL 证书公钥加密算法。
- RSA 加密算法是现在广泛使用的现代加密的第一代加密算法,它是基于大素数分解难的数学问题来保证加密算法的安全性。设计一个加密算法需要满足一个条件,就是能够找到一个满足 trapdoor 的函数,也就是在计算上保证在一个方向是计算容易而在另一个方向上计算困难的特性,RSA 刚好满足这一特性。随着硬件的发展,为了继续保证 RSA 的安全性,RSA 现在的密钥长度要求越来越大了,密钥长度的变大虽然能够保证安全性,但是对于之前容易的乘法运算因为数据的变大而变的计算不容易和需要消耗更多的能量,而现在发展的潮流是移动端,移动端本身是一个低能耗的设备因此使用越来越大 RSA 密钥来保证安全性就可能不会是一个好的办法了;
- Elliptic Curve Cryptography 是 ECC 的全称,表示基于椭圆曲线的加密算法,既然是加密算法肯定需要有一个 trapdoor 函数,ECC 是基于椭圆曲线的,可以由椭圆曲线的特性得到,一个点 A 做 dot 操作 n 次后得到一个点 Z,我们不能由点 A 和点 Z 的已知来得到 n 次操作经过的所有点,这就满足了在一个方向上 dot 操作容易而找到参与操作的点是难的特性。ECC 的私钥是 priv,在 g 做 priv 次的乘法运算后得到公钥 pub。我们知道了 pub 而不能容易的知道私钥 priv 这是基于椭圆曲线上计算离散对数是困难的数学问题。ECC 的 key=256 位的密钥就能跟 RSA 的 key=2048 位密钥保证相同的安全性,ECC 相对于 RSA 既能保证安全性又能节约计算资源。
static final String ALGORITHM = "RSA";
public static void main(String[] args) throws Exception {
test();
String pubPath = "u:/a.pub";
String priPath = "u:/a.pri";
generateKeyPair2File(ALGORITHM, pubPath, priPath);
}
/**
* 测试加解密 (公钥加密,私钥解密;私钥加密,公钥解密)
*
* @throws Exception
*/
private static void test() throws Exception {
String input = "HelloWorld";
// 密钥对生成器使用 getInstance 工厂方法(返回给定类的实例的静态方法)构造
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
// 生成公钥和私钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 提供加密和解密的功能,是构成 Java加密扩展(JCE)框架的核心。
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
// 加密
byte[] encrypt = cipher.doFinal(input.getBytes());
System.out.println("[私钥加密] " + Base64.encode(encrypt));
// 解密
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] decrypt = cipher.doFinal(encrypt);
System.out.println("[公钥解密] " + new String(decrypt));
}
/**
* 保存公钥和私钥到文件
*
* @param algorithm 加密算法
* @param pubPath 公钥保存路径
* @param priPath 私钥保存路径
* @throws Exception
*/
private static void generateKeyPair2File(String algorithm, String pubPath, String priPath) throws Exception {
// 密钥对生成器使用 getInstance 工厂方法(返回给定类的实例的静态方法)构造
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
// 生成公钥和私钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 获取公钥和私钥的字节数组
byte[] privateKeyEncoded = privateKey.getEncoded();
byte[] publicKeyEncoded = publicKey.getEncoded();
// base64 转码
String encodePub = Base64.encode(publicKeyEncoded);
String encodePri = Base64.encode(privateKeyEncoded);
System.out.println("[公钥] " + encodePub);
System.out.println("[私钥] " + encodePri);
FileUtils.write(new File(pubPath), encodePub, Charset.defaultCharset());
FileUtils.write(new File(priPath), encodePri, Charset.defaultCharset());
}
public static PublicKey getPubKeyFromFile(String algorithm, String keyPath) throws Exception {
String publicKeyStr = FileUtils.readFileToString(new File(keyPath), Charset.defaultCharset());
// 将密钥(类型为 Key 不透明加密密钥) 转换为关键规范 (基础密钥材料的透明表示),反之亦然。
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 创建公钥 Key 的规则
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(publicKeyStr));
// 从提供的密钥规范(密钥材料)生成私钥对象
return keyFactory.generatePublic(keySpec);
}
public static PrivateKey getPriKeyFromFile(String algorithm, String keyPath) throws Exception {
String privateKeyStr = FileUtils.readFileToString(new File(keyPath), Charset.defaultCharset());
// 将密钥(类型为 Key 不透明加密密钥) 转换为关键规范 (基础密钥材料的透明表示),反之亦然。
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 创建私钥 Key 的规则
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyStr));
// 从提供的密钥规范(密钥材料)生成私钥对象
return keyFactory.generatePrivate(keySpec);
}
3.2 SSL 协议#
// todo
3.3 rel:摘要+签名#
a. 数字摘要#
消息摘要(Message Digest)又称为数字摘要(Digital Digest),它是一个唯一对应一个消息或文本的固定长度的值,由一个单向 hash 加密函数对消息进行作用而产生。
使用数字摘要生成的值是不可以篡改的,为了保证文件或者值的安全。
无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用 MD5 算法摘要的消息有 128 个比特位,用 SHA-1 算法摘要的消息最终有 160 比特位的输出。
只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。
消息摘要是单向、不可逆的。
- MD5
- SHA1
- SHA256
- SHA512
public static void main(String[] args) throws NoSuchAlgorithmException {
digest("123", "md5");
digest("123", "sha-1");
digest("123", "sha-256");
digest("123", "sha-512");
}
private static void digest(String input, String algorithm) throws NoSuchAlgorithmException {
// 为应用程序提供消息摘要算法的功能,如 SHA-1 或 SHA-256。
// 消息摘要是采用任意大小的数据并输出固定长度散列值的安全单向散列函数。
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
// 摘要计算
byte[] digest = messageDigest.digest(input.getBytes());
// - 使用 base64 对结果转码进行显示
System.out.println(String.format("[%9s#digest] %s", algorithm, Base64.encode(digest)));
// - 使用 byte2Hex 对结果转码进行展示
System.out.println(String.format("[%9s#digest] %s", algorithm, AesDemo.byteArray2HexStr(digest)));
}
b. 数字签名#
https://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html
数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。数字签名是非对称密钥加密技术与数字摘要技术的应用。
static final String ALGORITHM = "RSA";
public static void main(String[] args) throws Exception {
PublicKey publicKey = RsaDemo.getPubKeyFromFile(ALGORITHM, "u:/a.pub");
PrivateKey privateKey = RsaDemo.getPriKeyFromFile(ALGORITHM, "u:/a.pri");
String signature = sign("HelloWorld", "SHA256withRSA", privateKey);
System.out.println("[生成签名] " + signature);
System.out.println("[签名校验] "+verify("HelloWorld", "SHA256withRSA", publicKey, signature));
}
/**
*
* @param input
* @param algorithm SHA1withDSA | SHA1withRSA | SHA256withRSA
* @param privateKey
* @return
* @throws Exception
*/
private static String sign(String input, String algorithm, PrivateKey privateKey) throws Exception {
// Signature 用于向应用程序提供数字签名算法的功能
Signature signature = Signature.getInstance(algorithm);
// 初始化签名
signature.initSign(privateKey);
// 传入原文
signature.update(input.getBytes());
// 生成签名
byte[] sign = signature.sign();
// base64 转码
return Base64.encode(sign);
}
private static boolean verify(String input, String algorithm, PublicKey publicKey, String signatureData) throws Exception {
// Signature 用于向应用程序提供数字签名算法的功能
Signature signature = Signature.getInstance(algorithm);
// 初始化签名
signature.initVerify(publicKey);
// 传入原文
signature.update(input.getBytes());
// 校验签名
return signature.verify(Base64.decode(signatureData));
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-05-20 11-Java 与线程
2021-05-20 10-JMM