加密算法
前言
在数字化飞速发展的今天,信息的安全与隐私保护显得尤为重要。加密算法成为了我们守护数据安全的重要武器。它们如同数字世界的锁与钥匙,确保信息在传输和存储过程中不被非法获取或篡改。本文将以Java语言为基础去讲解各类常用的加密方式(加密算法是通用的,并不局限于语言)。
加密算法进化史
古典密码学阶段(1949年以前)
古典密码学是加密算法发展的初级阶段。其核心思想主要基于替换和置换两种策略。替换即将明文中的每个字符替换成另一种字符以产生密文,而置换则是将明文的字符顺序按照某种规则打乱。恺撒密码就是替换加密的一个简单示例,它通过将字母表中的每个字母按照固定偏移量进行替换来实现加密。以下列举两种比较广为人知的加密方式。
摩斯密码-替换策略
摩斯密码是一种用于传输信息的编码系统,通过使用短脉冲(点)和长脉冲(划)的组合来表示字母、数字和标点符号。摩斯密码最初被用于电报通信,但至今仍在某些情况下使用,如无线电通信、求救信号等。摩斯密码使用了两个基本的信号单位:点(·)和划(—)。通过这两个单位的组合,可以表达不同的字母、数字和标点符号。
通过字母码表可以发现,国际通用的求救信号SOS对应的摩斯密码为 “…—…”
恺撒密码-置换策略
恺撒密码是一种最简单且最广为人知的加密技术。它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。
1
2
3例如将abcde按照向后移3位的方式进行加密
原文:abcde
密文:defgh
近代密码学阶段(1949-1976年)
近代密码学是在古典密码学的基础上发展而来的,其特点在于引入了更为复杂的数学原理和方法。随着计算机技术的不断发展,传统的密码算法逐渐被破解,因此近代密码学采用了更加复杂的算法,如RSA加密算法、椭圆曲线加密算法等,以保证密码的安全性。此阶段主要在于承前启后,为信息安全领域奠定了坚实的基础,为后续的现代密码学阶段的发展提供了重要的支撑和启示。
现代密码学阶段(1976年至今)
现代密码学是在近代密码学的基础上进一步发展和完善的。现代密码学中的算法可以看作是一个复杂的函数变换,它使用密钥将明文转换成密文。现代密码学强调“一切秘密寓于密钥之中”的原则,即算法可以公开,但密钥必须保密。后续所介绍各种加密方式都处于现代密码学阶段。
编码算法
Base64编码
简介
Base64编码是一种用64个字符来表示任意二进制数据的方法。它是网络上最常见的用于传输8Bit字节码的编码方式之一。Base64编码可用于在HTTP环境下传递较长的标识信息。采用Base64编码具有不可读性,需要解码后才能阅读。
Base64编码的思想是,通过查表,找到每个字节对应的可打印字符,从而直接使用字符串表示二进制数据。它通常用于将二进制数据编码为ASCII字符串格式,这样数据就可以在文本格式中传输和存储,而不会丢失信息。
编码规则
大写字母(A-Z)、小写字母(a-z)、数字(0-9)和两个附加字(通常为”+”和”/“)组成了 Base64 编码中使用的 63 个 ASCII 字符集。当二进制数据的长度不是三个字节的倍数时,最后一个字符”=”字符用于填充。从而使用64个可打印字符来表示二进制数据,如下所列为Base64编码索引表:
ASCII编码索引表:
原理解析
二进制和ACSII码
二进制是计算机数据的底层表现形式,每一个二进制位(bit)有
0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。ASCII编码基于二进制是计算机存储和传输文本数据的基础,它使得不同计算机之间可以正确地解释和显示相同的字符。
总的来说,二进制是计算机内部信息表示和处理的基础,而ASCII码则是将文本信息转换为二进制以便计算机处理的一种最初方式。关于二进制和ACSII码本文不过多赘述,感兴趣👉🏻可自行百度。
进制计算原理
以字符串 “Man” 为例,解析Base64的编码过程。Man 由 M、a 、n 3 个字符组成,它们对应的 ASCII码为 77、97、110。
下述为二进制位计算ASCII方式:
Man 使用ASCII表对应二进制表示为:01001101 01100001 01101110
将二进制数值以每6个字节进行切分:010011 010110 000101 101110
将6位二进制转化为十进制根据对照表找到Base64编码字符
进制计算方式:遵循“权值相加”的原则。每一位上的数字乘以该位对应的权值(即2的幂次方,从右往左依次是2^0, 2^1, 2^2, …),然后将所有乘积相加值对应Base64编码表字符索引
由图可知,Man (3字节)编码的结果为 TWFu(4字节),很明显经过 base64 编码后体积会增加 1/3。Man 这个字符串的长度刚好是 3,我们可以用 4 个 base64 单元来表示
使用实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/***
* Base64编码
* @author deng
* @date 2024/3/31
*/
public class Base64Test {
/***
* UTF8字符集
*/
private static final String UTF8 = StandardCharsets.UTF_8.name();
/***
* 使用JDK相关工具包进行Base64编解码
* @param args
* @throws UnsupportedEncodingException
*/
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "进击的小小程序员";
System.out.printf("原字符串:%s\n", str);
// 编码
String encoderStr = Base64.getEncoder().encodeToString(str.getBytes(UTF8));
System.out.printf("编码后字符串:%s\n", encoderStr);
// 解码
byte[] decodeByte = Base64.getDecoder().decode(encoderStr.getBytes(UTF8));
String decodeStr = new String(decodeByte, UTF8);
System.out.printf("解码后字符串:%s\n", decodeStr);
}
}
URL编码
简介
URL编码(也称为百分比编码)是一种机制,用于在URL中插入那些不能直接在URL中使用的字符。这种编码方式通过用一系列百分号(%)后跟两个十六进制数字来替换每个不能打印的字符。这两个十六进制数字代表原始字符的ASCII码。
编码规则
RFC3986 文档规定了 URL 中非保留字符,即无需转义的没有任何特殊含义的字符,其定义如下:
1
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
ALPHA 表示 52 大小写英文字母,DIGIT 表示 10 个数字,后面依次时连字符、点号、下划线和波浪号。
除了上面非保留字符,其他任何字符出现在 URL 的不同部分时,如果与该部分的保留字符发生冲突或不可打印或超出 ASCII 表示范围,均需要对其编码。
原理解析
将需要转码的字符,按指定编码方式(默认使用UTF-8编码)转化为字节流,每个字节按16进制表示,使用
%加上两位字符0123456789ABCDEF代表一个字节的十六进制形式。例如:汉字
"你好"UTF-8字节流打印为:
-28 -67 -96 -27 -91 -67对应的16进制表示为:
E4 BD A0 E5 A5 BDURLEncode编译后为:
%E4%BD%A0%E5%A5%BD使用实例
大部分使用场景在于通过浏览器进行Http网络请求时,若请求query中包含中文,中文会被编码为
%16进制形式例如:
(编码前)
https://www.baidu.com/s?wd=你好
(编码后)https://www.baidu.com/s?wd=%E4%BD%A0%E5%A5%BD下述我们用Java方式来模拟URL编解码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/***
* URL编码
* @author deng
* @date 2024/4/2
*/
public class UrlTest {
/***
* UTF8字符集
*/
private static final String UTF8 = StandardCharsets.UTF_8.name();
/***
* 使用JDK相关工具包进行URL编解码
* @param args
* @throws UnsupportedEncodingException
*/
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "进击的小小程序员";
System.out.printf("原字符串:%s\n", str);
// 编码
String encoderStr = URLEncoder.encode(str, UTF8);
System.out.printf("编码后字符串:%s\n", encoderStr);
// 解码
String decodeStr = URLDecoder.decode(encoderStr, UTF8);
System.out.printf("解码后字符串:%s\n", decodeStr);
}
}
摘要算法
简介
摘要算法也称为哈希算法或散列算法,是一种将任意长度的数据(如一段文本或文件)转换为固定长度的数据摘要的技术。这种转换是通过一系列复杂的数学运算和规则实现的,使得原始数据和生成的摘要之间存在一种确定的映射关系。摘要算法在多个领域有广泛应用,包括数据完整性校验、密码存储和数字签名等。
特征
- 固定输出长度:不论输入数据的长度如何,摘要算法总是输出固定长度的数据摘要。这种固定长度的输出使得摘要算法在处理大量数据时非常高效,同时也方便了数据的存储和传输。
- 单向性:摘要算法是单向的,意味着从摘要无法恢复出原始的输入数据。这种单向性保证了数据的安全性,使得即使摘要被泄露,原始数据仍然保持机密。
- 雪崩效应:摘要算法对输入数据的微小变化非常敏感。即使输入数据只有微小的差异,生成的摘要也会有显著的不同。这种特性使得摘要算法在检测数据篡改方面非常有效。
- 碰撞避免:理想情况下,摘要算法应该能够避免碰撞,即不同的输入数据生成相同的摘要。然而,在实际应用中,由于摘要长度的限制和算法设计的复杂性,完全避免碰撞是不可能的。但现代摘要算法(如SHA-256和SHA-3)已经大大减少了碰撞的可能性,使得在实际应用中几乎不可能找到具有相同摘要的不同输入。
- 计算效率:摘要算法的计算效率通常较高,可以快速处理大量数据。这使得摘要算法在需要实时处理大量数据的场景中非常适用,如网络通信、文件传输等。
常见摘要算法
- MD(Message Digest 消息摘要算法):主要包括MD2、MD4 和 MD5 共 3 种算法;
- SHA(Secure Hash Algorithm 安全散列算法:主要包括其代表算法 SHA-1 和 SHA-1 算法的变种 SHA-2 系列算法(包含 SHA-224、SHA-256、SHA-384 和 SHA-512);
- MAC(Message Authentication Code 消息认证码算法):综合了上述两种算法,主要包括 HmacMD5、HmacSHA1、HmacSHA256、HmacSHA384 和 HmacSHA512 算法。
MD(消息摘要算法)
简介
MD是一系列密码散列函数,用于将任意长度的数据(如文本或二进制文件)映射为较短的固定长度的值,即哈希值或消息摘要。这些哈希值通常用于确保数据的完整性,验证数据的来源,或在密码学中用于其他目的。
MD发展史
MD2:MD2是MD算法家族的早期成员,于1989年由罗纳德·李维斯特(Ronald Linn Rivest)设计并发布。MD2算法是第一个被广泛使用的消息摘要算法之一,它接受任意长度的输入,并生成一个固定长度的哈希值。然而,随着计算机技术的发展,研究人员发现MD2算法存在安全漏洞,尤其是在处理某些特定输入时。
MD4:为了改进MD2算法的安全性,李维斯特在1990年发布了MD4算法。MD4算法在MD2的基础上进行了改进,增加了数据的预处理步骤,提高了哈希函数的复杂性和安全性。MD4算法在当时被广泛认为是一个相对安全的哈希函数,被用于许多安全应用中。
但是随着密码学研究的深入和计算机算力的提升,研究人员在1996年发现了MD4算法存在碰撞问题,即不同的输入数据可能产生相同的MD4哈希值。这意味着MD4算法无法确保数据的唯一性和完整性,因此不再适用于需要高度安全性的场合。
MD5:为了解决MD4算法的安全问题,李维斯特在1992年发布了MD5算法。MD5算法在MD4的基础上进行了进一步改进,增加了”安全带”的概念,提高了哈希函数的复杂性和安全性。MD5算法在发布后迅速成为最广泛使用的哈希函数之一,被广泛应用于各种安全应用中,如文件完整性校验、数字签名等。
然而,随着计算机技术的进一步发展和密码学研究的深入,研究人员在2004年发现了MD5算法也存在碰撞问题。虽然MD5算法在大多数应用中仍然是安全的,但对于需要高度安全性的场合(如密码存储和数字签名等),专家一般建议改用其他更安全的哈希函数,如SHA-2系列算法。
MD5工作原理
- 填充:在进行MD5加密之前,首先需要对待加密的数据进行填充。填充的目的是让数据的长度能够满足512位的整数倍,以便后续的处理。填充的方式是在数据的末尾添加一个bit为1,然后再添加若干个bit为0的位,直到数据的长度满足要求。
- 初始化:对MD5算法中的四个32位寄存器进行初始化,这四个寄存器分别为A、B、C、D。初始时,这四个寄存器的值通过一个预定义的方式设置,而且对于每一次加密过程,这些寄存器的初值都是相同的。
- 计算:计算是MD5算法的核心部分,它包括四轮循环处理。在每一轮中,通过对每一组512位的数据进行操作,更新A、B、C、D四个寄存器的值,最终得到一个128位的哈希值。这个过程中包括了多次按位运算、位移运算、逻辑函数和模2^32相加等操作。
- 输出:MD5算法将循环运算得到的结果进行一系列的位操作,得到最终的128位哈希值。这个哈希值是由32个16进制字符组成的,通常以32位字符串的形式呈现。
想详细了解原理过程参考 MD5加密算法详解
使用实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62/***
* 加密工具类
* @author deng
* @date 2024/5/7
*/
public class HexUtil {
/***
* 字节数组转为16进制字符串,如位数不足则前面补0
* @param digestBytes
* @return
*/
public static String convertBytes2HexStr(byte[] digestBytes) {
StringBuilder sb = new StringBuilder();
for (byte digestByte : digestBytes) {
// 转化为16进制字符串
String hex = Integer.toHexString(((int) digestByte) & 0xff);
// 字符串长度为1时补0
if (hex.length() == 1) {
sb.append("0");
}
sb.append(hex);
}
return sb.toString();
}
}
import com.blog.web.common.utils.HexUtil;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
/***
* MD5加密
* @author deng
* @date 2024/5/7
*/
public class Md5Test {
/***
* UTF8字符集
*/
private static final String UTF8 = StandardCharsets.UTF_8.name();
/***
* 使用JDK原生实现MD5
* @param args
*/
public static void main(String[] args) throws Exception {
String str = "进击的小小程序员";
String algorithm = "MD5";
System.out.printf("原字符串:%s\n", str);
// 获取消息摘要算法对象
MessageDigest md = MessageDigest.getInstance(algorithm);
// 获取原始内容的字节数组
byte[] originalBytes = str.getBytes(UTF8);
// 获取到摘要结果
byte[] digestBytes = md.digest(originalBytes);
// 把每一个字节转为16进制字符,最终再拼接所有字符
String hexStr = HexUtil.convertBytes2HexStr(digestBytes);
System.out.printf("加密后字符串:%s\n", hexStr);
}
}当前例子介绍使用jdk原生库实现md5加密,实际开发中可使用相关工具库实现,如
Hutool1
DigestUtil.md5Hex("进击的小小程序员");
SHA(安全散列算法)
简介
SHA是由美国专门制定密码算法的标准机构 —— 美国国家标准技术研究院(NIST)制定的,SHA 系列算法的摘要长度分别为:SHA 为 20 字节(160位)、SHA256为 32 字节(256位)、 SHA384为 48 字节(384位)、SHA512为 64 字节(512位),由于它产生的数据摘要的长度更长,因此更难以发生碰撞,因此也更为安全,它是未来数据摘要算法的发展方向。由于 SHA 系列算法的数据摘要长度较长,因此其运算速度与 MD5 相比,也相对较慢。
SHA发展史
SHA-0:1993 年,NIST 公布了 SHA 算法家族的第一个版本,FIPS PUB 180。为避免混淆,现在我们称之为 SHA-0 算法。但 SHA-0 算法在公布不久后就被 NSA 撤回,原因是 NSA 发现 SHA-0 算法中含有会降低密码安全性的错误。由此,SHA-0 算法还未正式推广就已夭折。
SHA-1:由于SHA-0的安全问题,NIST在1995年发布了修订版本FIPS PUB 180-1,通常称为SHA-1。SHA-1在许多安全协议中得到了广泛应用,包括TLS、GnuPG、SSH、S/MIME和IPsec等。SHA-1被视为MD5的后继者,由于SHA-1生成的哈希值是160位,相比MD5的128位哈希值,它提供了更高的安全性。
然而,随着密码学研究的深入和计算能力的提升,SHA-1的安全性也逐渐受到质疑。2017年,荷兰密码学研究小组CWI和Google正式宣布攻破了SHA-1,这标志着SHA-1已不再适用于需要高度安全性的场景。
SHA-2:为了应对SHA-1的安全挑战,NIST在2001年开始设计SHA-2系列算法,并在2002年发布了包括SHA-224、SHA-256、SHA-384和SHA-512在内的多个变体。SHA-2系列算法提供了更高的安全性和更强的抗碰撞性。
至今为止,尚未出现对SHA-2系列算法的有效攻击。SHA-2系列算法已成为当前最广泛使用的哈希函数之一,被广泛应用于各种需要保证数据完整性和真实性的场景。
SHA-3:在SHA-2发布后不久,NIST启动了一个名为SHA-3的竞赛,以寻找一个可以替代SHA-2的新的哈希函数。经过多轮评选和测试,NIST在2015年宣布Keccak算法赢得了SHA-3竞赛,并将其正式命名为SHA-3。SHA-3与SHA-2在设计和实现上有所不同,但它也提供了类似的安全性和性能。
工作原理
参考sha256加密过程可视化网站 sha256加密解析
使用实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import com.blog.web.common.utils.HexUtil;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
/***
* Sha256加密
* @author deng
* @date 2024/5/7
*/
public class Sha256Test {
/***
* UTF8字符集
*/
private static final String UTF8 = StandardCharsets.UTF_8.name();
/***
* 使用JDK原生实现Sha256
* @param args
*/
public static void main(String[] args) throws Exception {
String str = "进击的小小程序员";
String algorithm = "SHA-256";
System.out.printf("原字符串:%s\n", str);
// 获取消息摘要算法对象
MessageDigest md = MessageDigest.getInstance(algorithm);
// 获取原始内容的字节数组
byte[] originalBytes = str.getBytes(UTF8);
// 获取到摘要结果
byte[] digestBytes = md.digest(originalBytes);
// 把每一个字节转为16进制字符,最终再拼接所有字符(封装进制转换方法相同)
String hexStr = HexUtil.convertBytes2HexStr(digestBytes);
System.out.printf("加密后字符串:%s\n", hexStr);
}
}当前例子介绍使用jdk原生库实现sha256加密,实际开发中可使用相关工具库实现,如
Hutool1
DigestUtil.sha256Hex("进击的小小程序员");
MAC(消息认证码算法)
简介
MAC是含有密钥散列函数算法,兼容了 MD 和 SHA 算法的特性,并在此基础上加入了密钥。因为 MAC 算法融合了密钥散列函数(keyed-Hash),通常我们也把 MAC 称为HMAC。
MAC 算法主要集合了 MD 和 SHA 两大系列消息摘要算法。
- MD 系列算法有 HmacMD2、HmacMD4 和 HmacMD5 三种算法。
- SHA 系列算法有 HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384 和 HmacSHA512 五种算法。
特征
- 完整性:MAC能确保信息的完整性,即信息在传输或存储过程中没有被篡改。这是通过对比接收到的MAC值和重新计算的MAC值来实现的。如果两者相同,那么信息就被认为是完整的。
- 认证:MAC能提供对信息来源的身份验证。因为只有知道密钥的实体才能生成有效的MAC值,所以接收者可以通过验证MAC值来确认信息的来源。
- 密钥依赖性:MAC值的计算依赖于密钥,不同的密钥会产生不同的MAC值。这增加了攻击者伪造有效MAC值的难度。
使用 MAC 验证消息完整性的具体过程:假设通信双方 A 和 B 共享密钥 K,A用消息认证码算法将 K 和消息 M 计算出消息验证码
Mac,然后将Mac和 M 一起发送给 B。B 接收到Mac和 M 后,利用 M 和 K 计算出新的验证码Mac*,若Mac*和Mac相等则验证成功,证明消息未被篡改。由于攻击者没有密钥 K,攻击者修改了消息内容后无法计算出相应的消息验证码,因此 B 就能够发现消息完整性遭到破坏。使用实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42import com.blog.web.common.utils.HexUtil;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
/***
* MAC加密
* @author deng
* @date 2024/5/8
*/
public class MacTest {
/***
* UTF8字符集
*/
private static final String UTF8 = StandardCharsets.UTF_8.name();
/***
* 使用JDK原生实现Mac
* @param args
*/
public static void main(String[] args) throws Exception {
String str = "进击的小小程序员";
String algorithm = "HmacMD5";
// 指定密钥
String key = "123";
System.out.printf("原字符串:%s\n", str);
// 获取消息摘要算法对象
Mac mac = Mac.getInstance(algorithm);
// 获取key对象并初始化mac
SecretKey secretKey = new SecretKeySpec(key.getBytes(UTF8), algorithm);
mac.init(secretKey);
// 获取原始内容的字节数组
byte[] originalBytes = str.getBytes(UTF8);
// 获取到摘要结果
byte[] digestBytes = mac.doFinal(originalBytes);
// 把每一个字节转为16进制字符,最终再拼接所有字符
String hexStr = HexUtil.convertBytes2HexStr(digestBytes);
System.out.printf("加密后字符串:%s\n", hexStr);
}
}当前例子介绍使用jdk原生库实现HmacMD5加密,实际开发中可使用相关工具库实现,如
Hutool1
DigestUtil.hmac(HmacAlgorithm.HmacMD5, key.getBytes(UTF8)).digestHex("进击的小小程序员");
加盐加密
简介
密码加盐是一种增加密码安全性的技术,其主要思想是在密码加密的过程中,为密码添加一些额外的随机字符串或固定字符串,以增加密码的随机性和复杂性,从而提高密码的安全性。密码加盐往往要生成一个随机数,然后将盐值和密码进行组合,再进行哈希运算。这样生成的密码哈希值就不仅取决于密码本身,还受到盐值的影响,攻击者无法通过简单的彩虹表等方式来破解密码。
作用
为什么密码进行加密后还需要加盐?
- 防止彩虹表攻击:彩虹表是一种预先计算好的哈希值与明文密码之间的对应表。当黑客获取了数据库的哈希密码后,他们可以使用彩虹表进行快速查找,以尝试找到对应的明文密码。而加盐加密通过在密码哈希过程中加入一个随机生成的盐值,使得即使相同的密码,因为盐值的不同,其哈希值也会不同,从而大大增加了彩虹表攻击的难度。
- 提高密码破解成本:当密码设置过于简单时,仅仅使用加密算法可能不足以保护密码的安全。在这种情况下,黑客可能使用暴力破解的方法,尝试所有可能的密码组合,直到找到正确的密码。而加盐加密使得即使黑客获取了数据库的哈希密码和盐值,他们也需要对每个可能的密码和盐值的组合进行哈希计算,从而大大提高了密码破解的成本。
- 防止密码重用:在某些情况下,用户可能会在不同的网站或服务上使用相同的密码。如果黑客在某个网站上获取了用户的密码哈希值,他们可能会尝试在其他网站上使用相同的密码进行登录。而加盐加密使得即使两个网站使用相同的加密算法和相同的密码,由于盐值的不同,其哈希值也会不同,从而防止了密码的重用。
加盐加密是一种提高密码安全性的有效方法,它可以增加密码破解的难度和成本,保护用户的数据安全。实例
假设用户的密码是”123456”,盐值为”username”,则密码加盐的过程如下:
1
2
31、将盐值和密码组合,username + 123456。
2、对"username123456"进行哈希运算,得到密码的哈希值。
3、将密码哈希值存储在数据库中,盐值保存于数据库盐值字段中。将密码加盐后,即使两个用户的密码相同,由于盐的不同,其哈希值也会不同,从而增加了破解难度,提高了密码的安全性。(盐值插入密码中的规则以及插入、加密先后顺序都可自行约定)
对称加密
简介
对称加密(Symmetric Encryption),也被称为私钥加密或秘密密钥加密,是一种使用单个密钥对数据进行加密和解密的加密方法。在对称加密中,加密和解密使用的是同一个密钥。
特征
- 密钥单一性:对称加密使用单个密钥(也称为私钥或秘密密钥)来进行加密和解密操作。这意味着加密和解密过程中使用的是相同的密钥。
- 加密与解密速度:对称加密算法通常设计得较为简单且高效,因此加密和解密速度较快。这使得对称加密在处理大量数据或实时通信时非常有用。
- 密钥管理复杂性:由于加密和解密使用相同的密钥,密钥的安全分发和管理成为了一个挑战。密钥必须在安全的通道上传输,并且必须确保只有授权的接收者才能访问该密钥。
- 数据保密性:对称加密的主要目标是保护数据的保密性。通过使用强加密算法和足够长的密钥,可以确保加密后的数据在未经授权的情况下无法被解密。
- 缺乏身份验证:虽然对称加密可以确保数据的保密性,但它并不提供身份验证或数据完整性保护。这意味着攻击者可以冒充合法的发送者发送伪造的消息,或者篡改消息的内容而不被检测出来。
常用算法
DES(Data Encryption Standard):这是第一个被广泛使用的对称加密算法,但由于其密钥长度较短(56位),已经被认为不够安全。
从规格上来说,密钥长度是64比特,但由于每隔7比特会设置一个用于错误检查的比特,因此实际长度为56比特3DES:基于DES的加强版本,对一块数据用三个不同的密钥进行三次加密来提高安全性,但速度相对较慢。
3DES的加密过程,并不是进行三次DES加密(加密→加密→加密),而是以密钥1、密钥2、密钥3的顺序,进行(加密→解密→加密)的过程。AES(Advanced Encryption Standard):是目前最广泛使用的对称加密算法之一。AES支持不同长度的密钥(128位、192位、256位),并被认为是足够安全的。
模式
默认情况下, 加密模式和填充模式为 : ECB/PKCS5Padding
加密模式(Encryption Modes)
加密模式定义了如何将加密算法应用于数据块(或称为明文块)来生成密文。它关注的是如何将明文数据有效地转换为密文数据,并考虑到数据的完整性和安全性。
电子密码本模式(ECB):将加密的数据分成若干组,每组的大小与加密密钥长度相同,然后每组都用相同的密钥进行加密。
密码块链接模式(CBC):将明文分成固定长度的块,然后将前一个加密块输出的密文与下一个要加密的明文块进行异或(XOR)操作,再将计算结果用密钥进行加密得到密文。只有第一个明文分组特殊,需要提前为其生成一个与分组长度相同的比特序列,进行XOR运算,这个比特序列称为初始化向量(Initialization Vector),简称IV。
计数器模式(CTR):该模式也会产生一个密钥流,它通过递增一个计数器来产生连续的密钥流。对该计数器进行加密,再与明文分组进行XOR运算,计算得出密文分组。
输出反馈模式(OFB):产生一个密钥流,即将密码算法的前一个输出值,做为当前密码算法的输入值。该输入值再与明文分组进行XOR运行,计算得出密文分组。该模式需要一个IV,进行加密后做为第一个分组的输入。
密码反馈模式(CFB):将前一个密文分组进行加密,再与当前明文分组进行XOR运算,来生成密文分组。同样CFB模式也需要一个IV。
填充模式(Padding Modes)
填充模式用于处理在加密过程中遇到的非完整数据块(即数据长度不是块加密算法所要求的长度的倍数)。它确保了在加密过程中数据块的大小符合加密算法的要求,从而保证了加密过程的正确性和安全性。
No Padding:不填充,要求明文的长度,必须是加密算法分组长度的整数倍。
DES加密算法时,要求原文数据长度必须是8个字节的整数倍AES加密算法时,要求原文数据长度必须是16个字节的整数倍1
2
3
4# 明文数据
...... | DD DD DD DD DD DD DD DD | DD DD DD DD DD DD DD DD |
# 填充后的数据
...... | DD DD DD DD DD DD DD DD | DD DD DD DD DD DD DD DD |Zero Padding:零填充,使用零字节填充数据块以达到所需的长度。
1
2
3
4# 明文数据
...... | DD DD DD DD DD DD DD DD | DD DD DD DD
# 填充后的数据
...... | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 |PKCS#5/PKCS#7:填充字节的值等于需要填充的字节数。
1
2
3
4# 明文数据
...... | DD DD DD DD DD DD DD DD | DD DD DD DD
# 填充后的数据
...... | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |ANSI X.923:在数据块的最后添加一个字节,其值等于需要填充的字节数(不包括这个字节本身),然后使用零字节填充剩余的空间。
1
2
3
4# 明文数据
...... | DD DD DD DD DD DD DD DD | DD DD DD DD
# 填充后的数据
...... | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 04 |
使用实例
以常用的AES对称加密为例,使用
Hutool工具库进行加解密1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40/***
* AES加解密
* @param args
*/
public static void main(String[] args) {
// 原字符串
String str = "进击的小小程序员";
// 密钥
String key = "12345678abcdefgh";
byte[] keyBytes = key.getBytes(CharsetUtil.CHARSET_UTF_8);
System.out.printf("原字符串:%s\n", str);
// 构建默认加密 ECB/PKCS5Padding
AES aes = new AES(keyBytes);
// 加密为16进制表示
String encryptHexStr = aes.encryptHex(str);
// 加密为base64表示
String encryptBase64Str = aes.encryptBase64(str);
// 解密为原字符串
String decryptStr = aes.decryptStr(encryptHexStr, CharsetUtil.CHARSET_UTF_8);
System.out.printf("AES默认加密后字符串【16进制】:%s\n", encryptHexStr);
System.out.printf("AES默认加密后字符串【base64】:%s\n", encryptBase64Str);
System.out.printf("AES默认解密后字符串:%s\n", decryptStr);
System.out.println("=======切换加密模式为CBC========");
// 偏移量
String iv = "1234567812345678";
byte[] ivBytes = iv.getBytes(CharsetUtil.CHARSET_UTF_8);
// 构建其它加密 CBC/PKCS5Padding
aes = new AES(Mode.CBC, Padding.PKCS5Padding, keyBytes, ivBytes);
// 加密为16进制表示
encryptHexStr = aes.encryptHex(str);
// 加密为base64表示
encryptBase64Str = aes.encryptBase64(str);
// 解密为原字符串
decryptStr = aes.decryptStr(encryptHexStr, CharsetUtil.CHARSET_UTF_8);
System.out.printf("AES中CBC模式加密后字符串【16进制】:%s\n", encryptHexStr);
System.out.printf("AES中CBC模式加密后字符串【base64】:%s\n", encryptBase64Str);
System.out.printf("AES中CBC模式解密后字符串:%s\n", decryptStr);
}
非对称加密
简介
非对称加密(Asymmetric Encryption),也被称为公钥加密,是一种密码学方法,它使用一对密钥来进行加密和解密操作。这对密钥包括一个公钥和一个私钥:公钥负责加密数据,私钥负责解密数据。
特征
- 安全性:虽然公钥是公开的,但是无法从公钥推导出私钥(在合理的时间内),因此非对称加密提供了很高的安全性。即使公钥被截获,攻击者也无法解密使用该公钥加密的数据,除非他们拥有对应的私钥。
- 数字签名:私钥还可以用于创建数字签名,这是一种验证数据完整性和来源真实性的方法。数字签名允许数据的接收者验证数据在传输过程中是否被篡改,并且确认数据确实来自声称的发送者。
- 计算复杂性:非对称加密算法通常比对称加密算法更复杂,需要更多的计算资源来执行加密和解密操作。
- 密钥长度:为了达到相同的安全级别,非对称加密的密钥长度通常比对称加密的密钥长度更长。
- 灵活性:不同的实体可以拥有不同的公钥和私钥对,从而允许在复杂的网络环境中进行安全的通信。
- 前向安全性:一些非对称加密算法(如ECC)提供了前向安全性,这意味着即使私钥在将来被泄露,过去使用该私钥加密的数据仍然保持安全。
常见算法
- RSA(Rivest-Shamir-Adleman):RSA算法是最早和最广泛使用的非对称加密算法之一。它基于大数分解的困难性,使用两个大素数来生成公钥和私钥。发送方使用接收方的公钥进行数据加密,只有接收方拥有私钥才能解密数据。RSA算法还可以用于数字签名和密钥交换。
- Diffie-Hellman:Diffie-Hellman算法用于密钥交换,允许双方在不共享密钥的情况下安全地进行通信。该算法基于离散对数问题,通过在公开通信信道上交换信息来生成共享密钥。Diffie-Hellman算法在互联网协议中广泛应用,例如在安全套接层(SSL/TLS)中用于密钥交换。
- ECC(Elliptic Curve Cryptography):ECC是一种基于椭圆曲线数学的非对称加密算法。它使用椭圆曲线上的点来生成公钥和私钥,并使用基于离散对数问题的计算难题来提供安全性。ECC算法具有相同的安全强度,但使用更短的密钥长度相比于RSA和Diffie-Hellman,因此可以提供更高的性能和更小的存储需求。
- ElGamal:ElGamal算法是一种基于离散对数问题的非对称加密算法。它可用于加密和数字签名,并提供了前向保密性,即使攻击者获得了公钥和一些密文,也无法推导出明文或私钥。ElGamal算法在一些安全通信协议和电子投票系统中得到了广泛应用。
原理解析
以常用的RSA算法为例,RSA公开密钥密码体制原理是:根据数论,寻求两个大素数比较简单,而将它们的乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
解密过程:
选择两个大素数P、Q
设P = 7,Q = 17
计算N = P x Q
N = 7 x 17 = 119
选择一个公钥E,使其不是(P - 1)与(Q - 1)的因子
(P - 1) = 6 = 2 x 3
(Q - 1) = 16 = 2 x 2 x 2 x 2
因此我们选的公钥E不能有因子2和3。我们取E = 5
选择私钥D,满足:(D x E) mod (P - 1) x (Q - 1) = 1
(D x 5) mod 6 x 16 = 1
(D x 5) mod 96 = 1
经计算,取D = 77
加密时,从明文PT计算密文CT:CT = PT^E mod N
假设明文为10
CT = 10^5 mod 119 = 40
将密文CT发送给接收方
将40发送给接收方
解密时,从密文CT得到明文PT:PT = CT^D mod N
PT = 40^77 mod 119 = 10
假设B要接收A的加密消息,首先生成公钥E和私钥D,私钥D自己保留,公钥E和数字N发布出去,攻击者拿到公钥E和数字N,似乎可以通过试错法计算出私钥D。从解密过程可以看出,攻击者只要从N中分解出P和Q,就可以破解私钥。但在实际应用中,选取的因数是非常大,比如2048位的因数,即使借助性能强大的超级计算机,也需要很长的时间。
使用实例
以常用的RSA非对称加密为例,使用
Hutool工具库进行加解密1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39/***
* RSA加解密
* @param args
*/
public static void main(String[] args) {
// 原字符串
String str = "进击的小小程序员";
System.out.printf("原字符串:%s\n", str);
// 生成公私钥对
KeyPair pair = SecureUtil.generateKeyPair("RSA");
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
// 获得公钥
String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
System.out.printf("公钥:%s\n", publicKeyStr);
// 获得私钥
String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
System.out.printf("私钥:%s\n", privateKeyStr);
// 构建RSA加密对象
RSA rsa = new RSA(privateKeyStr, publicKeyStr);
// 公钥加密
byte[] encrypt = rsa.encrypt(str, KeyType.PublicKey);
System.out.printf("公钥加密:%s\n", Base64.getEncoder().encodeToString(encrypt));
// 私钥解密
byte[] decrypt = rsa.decrypt(encrypt, KeyType.PrivateKey);
System.out.printf("私钥解密:%s\n", new String(decrypt, StandardCharsets.UTF_8));
// 生成签名
Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, privateKeyStr, publicKeyStr);
byte[] data = str.getBytes(StandardCharsets.UTF_8);
byte[] signed = sign.sign(data);
String signedStr = Base64.getEncoder().encodeToString(signed);
System.out.printf("签名:%s\n", signedStr);
// 验证签名
boolean verify = sign.verify(data, Base64.getDecoder().decode(signedStr));
System.out.printf("验签:%s\n", verify);
}
加密总结
各类加密区别
- 编码算法
- 定义:编码算法是将信息从一种形式或格式转换为另一种形式或格式的过程。它通常用于将模拟信息(如语音、视频)转换为数字信息,或将数据转换为特定系统可以理解的格式。
- 用途:在数据通信、文件存储、多媒体处理等领域中广泛使用,如文本编码(ASCII、UTF-8)、图像编码(JPEG、PNG)、音频编码(MP3、AAC)等。
- 特点:编码过程通常是可逆的,即可以通过解码算法将编码后的数据还原为原始数据。
- 摘要算法(或哈希算法)
- 定义:摘要算法是一种将任意长度的数据映射为固定长度数据的算法。它通常用于验证数据的完整性,通过比较原始数据和其摘要(哈希值)来确认数据是否被篡改。
- 用途:在数字签名、密码存储、数据完整性验证等场景中广泛应用。
- 特点:摘要算法是单向的,即无法从摘要中恢复原始数据;同时,相同的输入数据总是产生相同的摘要,不同的输入数据产生不同的摘要(或具有极低的碰撞率)。
- 对称加密
- 定义:对称加密是一种使用单个密钥对数据进行加密和解密的加密算法。
- 用途:在需要高效加密大量数据的场合中使用,如HTTPS协议中的会话密钥加密。
- 特点:加密和解密使用相同的密钥,因此密钥的分发和管理是安全性的关键;算法公开,但密钥必须保密;加密速度快,适合大数据量加密。
- 非对称加密
- 定义:非对称加密使用一对密钥(公钥和私钥)对数据进行加密和解密。公钥用于加密数据,私钥用于解密数据。
- 用途:在需要验证数据发送者身份或确保数据在传输过程中不被篡改的场合中使用,如SSL/TLS协议、数字签名等。
- 特点:公钥可以公开分发,而私钥必须保密;加密速度相对较慢,但提供了更高的安全性;常用于密钥交换、数字签名等场景。

























