密码学算法主要包含三种
- 信息摘要算法
- 数字签名算法
- 数据加密算法
信息摘要算法
所谓信息摘要,就是对任意数据进行处理,能得到一段固定长度的文本输出
信息摘要算法也被称为哈希算法/散列算法
信息摘要算法的特点有:
- 对于任意长度的数据输入,其输出长度固定
- 不可逆
- 理论上不能由输出结果推导出输入数据
- 也存在暴力破解的情况,可以通过加盐方式提升安全性
- 对于输入数据变化敏感
- 即使输入数据发生极小变化,其输出结果也会有明显变化
- 数据压缩
- 通常来说,信息摘要算法得到的结果比源数据要小
- 防哈希冲突
- 不同的源数据输入,得到相同结果的几率很小
- 但是哈希冲突的情况也不能避免,由于输出长度固定,所以数据组合数量是有限的
信息摘要算法常用于以下场景:
- 数据一致性校验
- 数据完整性校验
普通摘要算法
常见MD/SHA
算法都是基于RFC
也就是数字签名标准Digital Signature Standard,DSS
标准实现
RFC
文档:https://www.sip.org.cn/wp-content/uploads/2023/02/SIP-rfc3261-cn-james-zhu-v1.pdf
实现原理
其中算法的大致计算过程类似,只是其计算细节不同
逻辑简述
摘要算法的核心是摘要算法实例,其主要因素有
- 包含了一个状态存储数据数组
state[]
- 提供了一个状态更新方法,基于输入数据对
state[]
进行修改- 其中包括了
- 输入数据补位
- 补位结果分块
- 数据分块压缩
- 其中包括了
- 提供了一个签名方法,基于
state[]
输出结果
那么对输入信息进行签名,实际就是修改摘要算法实例中的state[]
,其过程如下:
- 首先对输入信息进行补位操作
- 对输入数据补位的结果进行分块
- 遍历使用每个数据分块调用内部的压缩方法,对
state[]
进行修改 - 最终调用签名方法,基于
state[]
输出结果
实际上,信息输入可以多次进行
细节分析
- 初始信息定义
-
摘要实例相关
-
digestLength
- 当前摘要算法的摘要结果的长度,单位为
byte
- 当前摘要算法的摘要结果的长度,单位为
-
状态存储数据数组
state[]
- 摘要算法为何可以保证不论任何长度的输入,都可以得到定长的输出,其实际是因为其结果就是由
state[]
进行存储- 输入信息只是通过计算取修改此状态存储
- 通常来说,数组的元素类型取决于初始化魔数
magic
的位长(magicLen
,bit
位数量)32bit:int
64bit:long
- 状态数组长度,取决于
digestLength
digestLength / magicLen
,计算单位为bit
- 摘要算法为何可以保证不论任何长度的输入,都可以得到定长的输出,其实际是因为其结果就是由
-
初始化魔数
magic
- 实际就是固定的初始化状态,在计算初始时,存储数据数组
state[]
需要使用magic
进行初始化
- 实际就是固定的初始化状态,在计算初始时,存储数据数组
-
-
更新操作相关
-
blockSize
- 对输入数据补位结果进行分块,每个数据分块大小,单位为
byte
- 对输入数据补位结果进行分块,每个数据分块大小,单位为
-
x[]
-
数组子块存储数组
-
类型与
state[]
保持一致 -
数组长度取决于魔数
magic
的位长blockSize / magicLen
,计算单位为bit
-
-
- 状态数组初始化
- 实际就是摘要实例对新的数据进行摘要计算前,需要基于
magic
对存储数据数组state[]
进行初始化 magic
的初始化值,取决于RFC3174
标准定义
- 输入数据补位
-
为何需要对输入数据进行补位,原因是便于后续的数据分块处理
-
补位过程如下
-
补位长度计算
- 在处理过程中,需要对数据进行分块处理,所以数据位长需要满足
% (blockSize * 8) == 0
- 补位计算时以
bit
为单位
- 补位计算时以
- 假设
- 源数据位长为
inputBitLen
- 补位数据位长为
fillBitLen
- 源数据位长为
- 同时在数据补位后,我们需要记录源数据位长,所以需要额外的
bit
空间来存储inputBitLen
大小,此bit
空间长度记为inputBitLenStoreSize
- 实际上对于不同算法,
inputBytesNum
大小是有限制的 - 例如
MD5
算法限制输入< 2^64 bit
,则需要64bit
的空间来存储其位长,其对应inputBitLenStoreSize = 64
- 实际上对于不同算法,
- 最终要满足
(inputBitLen + fillBitLen + inputBitLenStoreSize) % (blockSize * 8) == 0
- 以此得到
fillBitLen
的具体长度
- 以此得到
- 在处理过程中,需要对数据进行分块处理,所以数据位长需要满足
-
数据补位
- 补位数据为
1000...
,即第一位补1
,后面补0
- 补位数据为
-
- 补位结果数据计算处理(调用压缩方法,更新状态)
- 将数据按
blockSize
进行分块,按序对其进行处理 - 每个分块数据,拆分多个子块
- 将分块数据按照魔数
magicLen
的位长拆分子块,存储到x[]
中
- 将分块数据按照魔数
- 对每个分块数据,进行具体计算处理
- 计算处理实际入参为拆分后的
x[]
- 按照
RFC3174
标准定义具体逻辑进行计算
- 计算处理实际入参为拆分后的
- 将计算结果累加到
state[]
中进行存储
- 统计输出结果
- 当所有数据分块都完成计算,则它们的计算结果都累计到了
state[]
中 - 按索引序将
state[]
中存储的数据转换为16
进制表示,然后按序拼接,就得到最后的结果
注意点:
- 上述步骤
3/4
整体完成了输入数据处理和状态更新的动作,在步骤5
输出最终结果之前,步骤3/4
可以多次执行- 其含义就是
RFC3174
中摘要算法可以支持多次输入,组合输出
- 其含义就是
具体实现可以参考下面MD5
的伪代码实现
常见算法类型
可以通过Security
查看
// [SHA-384, SHA-224, SHA-256, MD2, SHA, SHA-512, MD5]
Set<String> algorithms = Security.getAlgorithms("MessageDigest");
常见算法简述
MD5
MD5(Message Digest Algorithm 5)
,也称为信息摘要算法第五版
其特性如下:
- 运算速度快
- 均使用
32
位与、或、非、位移
等位运算
- 均使用
MD5
算法是不安全的- 可以被暴力破解
- 通常需要通过加盐处理来提高安全等级
伪代码实现
public class ForgeMD5 {
// 循环位移位数矩阵
private static final int S11 = 7, S12 = 12, S13 = 17, S14 = 22;
private static final int S21 = 5, S22 = 9, S23 = 14, S24 = 20;
private static final int S31 = 4, S32 = 11, S33 = 16, S34 = 23;
private static final int S41 = 6, S42 = 10, S43 = 15, S44 = 21;
/**
* 基础信息定义
*/
// 结果输出为 16byte = 128bit
private int digestLength = 16;
// 分块大小 64byte = 512bit
private int blockSize = 64;
// 状态存储
// 输出结果为 16byte,魔数 magic 长度为 4byte = 16bit
// 所以状态数组长度为 4
private int[] state = new int[4];
// 子分块存储
// 分块大小 64byte = 512bit,,魔数 magic 长度为 4byte = 16bit
// 所以数组长度为 16
private int[] x = new int[16];
/**
* 构造
*/
public ForgeMD5() {
resetState();
}
/**
* 状态 state[] 重置
*/
private void resetState() {
// magic 在 RFC3174 中定义
// 取值为实际 magic 的小端序,便于计算
state[0] = 0x67452301;
state[1] = 0xefcdab89;
state[2] = 0x98badcfe;
state[3] = 0x10325476;
}
/**
* 状态更新,即根据源数据计算后更新状态 state[]
*
* @param origin
*/
public void update(String origin) {
// 1. 数据填充
int[] fillResult = fill(origin);
// 2. 分块数据单独处理
for (int i = 0; i < fillResult.length / 16; i++) {
int[] temp = new int[16];
for (int j = 0; j < 16; j++) {
temp[j] = fillResult[i * 16 + j];
}
// 3. 计算进行, 状态更新
compress(temp);
}
}
/**
* 签名
*
* @return
*/
public String digest() {
// 转 16 进制结果
String result = convertToHex(state[0]) + convertToHex(state[1]) + convertToHex(state[2]) + convertToHex(state[3]);
// 重置状态
resetState();
return result;
}
private String digest(String origin) {
update(origin);
return digest();
}
/**
* 源数据填充后输出
* 实际是小端序排列,便于输出
* 1. 在末尾添加 10000... 使其 bit 满足 512 * N + 448
* 2. 在末尾添加 64bit 记录源数据长度
*/
private int[] fill(String origin) {
// 1. 分块数量确定
// 一个字符占用 1byte = 8bit
// MD5 输入限制为 2^64 bit, 所以需要使用 64bit = 8byte 来存储长度信息
// 分块大小为 512bit = 64byte
int length = origin.length();
// int 进行除法运算会向下取整
boolean fillFlag = false;
int num = (length + 8) / 64;
if ((length + 8) % 64 != 0) {
num += 1;
// 表示进行补位
fillFlag = true;
}
// 2. 定义输出容器
// 分块大小为 512bit = 64byte
int[] result = new int[num * 16];
// 3. 源数据处理:将 4 * char -> int 进行存储,且以小端序存储
// 一个 char 字符占用 8bit = 1byte
// int 类型占用 4byte,所以每 4 个 char 作为一组进行处理,并转换为小端序存储
// i >> 2 :使每 4 个 char(8bit) 碎银对应一个 int(32bit), (0 ~ 3) >> 2 = 0 ;(1 ~ 7) >> 2 = 1
// 或运算 |= char << ((i % 4) * 8):使 4 个 char(8bit) 字符按小端序存储到 int(32bit) 的对应位中
int i;
for (i = 0; i < length; i++) {
result[i >> 2] |= origin.charAt(i) << ((i % 4) * 8);
}
// 4. 数据填充 1000...
if (fillFlag) {
// 在尾部补上 1,其他位默认为 0 ,则不用补充
// 0x80 的二进制表示为 1000 0000 ,使用或运算实现补 0
// 因为源数据是字符串,其中每个字符 占 8bit, 所以需要补位时, 一定是以 byte 为单位进行补全
result[i >> 2] |= 0x80 << ((i % 4) * 8);
}
// 5. 末尾补充源数据长度
// MD5 使用 64bit = 8byte = 2int 来存储
// 1char = 8bit, 所以 length * 8 表示其 bit 位长
// 而实际上 Java 中 String 类型字符长度限制为 2^16-1 = 65535
// 则位长 = length * 8 远远小于 2^32,则使用 1int = 32 bit 能存储
result[num * 16 - 2] = length * 8;
return result;
}
/**
* 分块压缩实现
*/
private void compress(int[] block) {
// 分组数据填充到 x[] 数组中, 重置 x[] 数组状态
for (int i = 0; i < block.length; i++) {
x[i] = block[i];
}
// 状态临时存储
int a = state[0];
int b = state[1];
int c = state[2];
int d = state[3];
/* Round 1 */
a = FF ( a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
d = FF ( d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
c = FF ( c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
b = FF ( b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
a = FF ( a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
d = FF ( d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
c = FF ( c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
b = FF ( b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
a = FF ( a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
d = FF ( d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
c = FF ( c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
b = FF ( b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
a = FF ( a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
d = FF ( d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
c = FF ( c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
b = FF ( b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
/* Round 2 */
a = GG ( a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
d = GG ( d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
c = GG ( c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
b = GG ( b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
a = GG ( a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
d = GG ( d, a, b, c, x[10], S22, 0x2441453); /* 22 */
c = GG ( c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
b = GG ( b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
a = GG ( a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
d = GG ( d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
c = GG ( c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
b = GG ( b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
a = GG ( a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
d = GG ( d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
c = GG ( c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
b = GG ( b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
/* Round 3 */
a = HH ( a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
d = HH ( d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
c = HH ( c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
b = HH ( b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
a = HH ( a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
d = HH ( d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
c = HH ( c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
b = HH ( b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
a = HH ( a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
d = HH ( d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
c = HH ( c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
b = HH ( b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
a = HH ( a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
d = HH ( d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
c = HH ( c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
b = HH ( b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
/* Round 4 */
a = II ( a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
d = II ( d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
c = II ( c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
b = II ( b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
a = II ( a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
d = II ( d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
c = II ( c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
b = II ( b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
a = II ( a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
d = II ( d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
c = II ( c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
b = II ( b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
a = II ( a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
d = II ( d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
c = II ( c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
b = II ( b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}
/**
* 分步计算函数 FF GG HH II
*/
private static int FF(int a, int b, int c, int d, int x, int s, int ac) {
a += ((b & c) | ((~b) & d)) + x + ac;
return ((a << s) | (a >>> (32 - s))) + b;
}
private static int GG(int a, int b, int c, int d, int x, int s, int ac) {
a += ((b & d) | (c & (~d))) + x + ac;
return ((a << s) | (a >>> (32 - s))) + b;
}
private static int HH(int a, int b, int c, int d, int x, int s, int ac) {
a += ((b ^ c) ^ d) + x + ac;
return ((a << s) | (a >>> (32 - s))) + b;
}
private static int II(int a, int b, int c, int d, int x, int s, int ac) {
a += (c ^ (b | (~d))) + x + ac;
return ((a << s) | (a >>> (32 - s))) + b;
}
/**
* 整数变成 16进制字符串
*/
private String convertToHex(int a) {
String str = "";
for (int i = 0; i < 4; i++) {
str += String.format("%2s", Integer.toHexString(((a >> i * 8) % (1 << 8)) & 0xff)).replace(' ', '0');
}
return str;
}
}
SHA
SHA(Secure Hash Algorithm)
算法也称为安全散列算法,它是一系列算法的统称
其实现原理代码与MD5
实现类似,主要区别:
- 就是其对应的基础信息取值
- 状态计算函数不同
类别 | 源数据长度(bit) | block分块位长(bit) | magic位长(bit) | magic数量 | 计算步骤数量 | 摘要长度(bit) |
---|---|---|---|---|---|---|
SHA-1 | < 2^64 | 512 | 32 | 5 | 80 | 160 |
SHA-224 | < 2^64 | 512 | 32 | 7 | 64 | 224 |
SHA-256 | < 2^64 | 512 | 32 | 8 | 64 | 256 |
SHA-384 | < 2^128 | 1024 | 64 | 6 | 80 | 384 |
SHA-512 | < 2^128 | 1024 | 64 | 8 | 80 | 512 |
API使用示例
public class MessageDigestTest {
/**
* JDK 自带
* java.security.MessageDigest
*/
private String digest(String origin, String algorithm) throws Exception {
// 1. 获取编码算法对应 MessageDigest 实例,算法类型由 algorithm 参数决定
MessageDigest digest = MessageDigest.getInstance(algorithm);
// 2. 对源数据进行编码,得到字节数组结果
byte[] bytes = digest.digest(origin.getBytes());
// 3. 将字节数组结果转为 16 进制表示
return toHexString(bytes);
}
/**
* byte 数组转为 16 进制表示
*/
private String toHexString(byte[] origin) {
StringBuilder sb = new StringBuilder();
for (byte b : origin) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
MAC算法
MAC
算法,就是消息验证码Message Authentication Code
,是一种带秘钥的信息摘要算法
MAC
算法是一种特殊的信息摘要算法
- 主要作用不是生成摘要消息,而是保证数据完整性
- 安全系数高
- 普通信息摘要算法由于摘要信息长度有限,所以可能被暴力破解源信息
MAC
引入一个秘钥,提高了安全性
HMAC
HMAC(Hash-based Message Authentication Code)
是一种消息认证码MAC
技术实现
- 其基于摘要算法为基础
- 其主要作用
- 使用秘钥来保证认证信息源身份
- 使用哈希算法来保证内容不被篡改
对应不同基础摘要算法类型,常见MAC
算法类型也有多种
MD
系列:HmacMD2、HmacMD4、HmacMD5
SHA
系列:HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384、HmacSHA512
实现原理
对于一个MAC
算法而言,其主要由三个主要部分组成:
- 一个基础的摘要/哈希算法
H
- 一个秘钥
key
- 一个特殊处理的函数
其主要步骤如下:
-
选择一个摘要算法
H
,创建其摘要实例- 其签名结果输出长度为
L
- 其签名结果输出长度为
-
确定一个固定的秘钥
key
,并对其进行加工,并使用filled
字节数组存储- 如果
key
长度小于L
,则在后面补0
以达到L
的长度,得到结果 - 如果
key
长度大于L
,则以摘要实例对key
计算签名,得到结果 - 可以借助
ByteBuffer#allocate
实现
- 如果
-
将加工结果
filled
中每个字节数据与ipad(0x36)
进行异或处理,并使用k_ipad
字节数组存储结果 -
将加工结果
filled
中每个字节数据与opad(0x5C)
进行异或处理,并使用k_opad
字节数组存储结果 -
使用摘要实例,顺序输入
k_ipad
与源数据data
,签名后得到签名结果temp
- 签名后会重置摘要实例状态
-
使用摘要实例,顺序输入
k_opad
与temp
,签名后得到最终结果
通常来说
-
ipad(0x36)
与opad(0x5C)
是固定存储 -
推荐
key
的长度等于L
HmacMD5
HmacMD5
就是HMAC
中选择MD5
算法作为哈希算法的情况
伪代码实现
public class HMACMD5Test {
private static final Charset CHARSET = StandardCharsets.UTF_8;
// MD5签名算法的结果长度为 16byte = 64bit
private static final int GROUP_BYTE_CAPACITY = 64;
byte[] k_ipad;
byte[] k_opad;
private MessageDigest digest;
public HMACMD5Test() {
try {
this.digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException exception) {}
}
@Test
public void test() throws Exception {
String origin = "234234是框架东方丽景开始考试考试看看";
// 获取秘钥
KeyGenerator keyGenerator = KeyGenerator.getInstance("HMACMD5");
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyBytes = secretKey.getEncoded();
// HMAC-MD5 加密
String encode = encode(origin, keyBytes);
System.out.println(encode);
}
/**
* 加密
*
* @param origin 源数据
* @param keyBytes 秘钥字节表示
*/
public String encode(String origin, byte[] keyBytes) throws Exception {
byte[] originBytes = origin.getBytes(CHARSET);
// 1. key 加工
byte[] filled = fill(keyBytes);
// 2. 位运算
k_ipad = bitwise(filled, (byte) 0x36);
k_opad = bitwise(filled, (byte) 0x5C);
// 3. 组合编码运算
// 3.1 依次由 k_ipad / originBytes 更新 MessageDigest 状态后签名
digest.update(k_ipad);
digest.update(originBytes);
// 签名结果由 temp 保存
byte[] temp = digest.digest();
// 3.2 依次由 k_opad / temp 更新 MessageDigest 状态后签名
digest.update(k_opad);
digest.update(temp);
// 签名结果由 temp 保存
temp = digest.digest();
// 4. temp 结果转 16 进制输出
return toHexString(temp);
}
/**
* 每个字节进行位运算
*/
private byte[] bitwise(byte[] origin, byte base) {
byte[] result = new byte[origin.length];
for (int i = 0; i < origin.length; i++) {
result[i] = (byte) (origin[i] ^ base);
}
return result;
}
/**
* 长度补 0 到 64bit
* 借助 ByteBuffer 实现
*/
private byte[] fill(byte[] originBytes) throws Exception {
ByteBuffer buffer = ByteBuffer.allocate(GROUP_BYTE_CAPACITY);
if (originBytes.length > GROUP_BYTE_CAPACITY) {
buffer.put(digest.digest(originBytes));
} else {
buffer.put(originBytes);
}
return buffer.array();
}
/**
* byte 数组转为 16 进制表示
*/
private String toHexString(byte[] origin) {
StringBuilder sb = new StringBuilder();
for (byte b : origin) {
// 转 16 进制
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
API使用示例
public void test() throws Exception {
String origin = "234234是框架东方丽景开始考试考试看看";
String algorithm = "HmacMD5";
// 获取秘钥
KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
SecretKey secretKey = keyGenerator.generateKey();
// 获取 MAC 处理实例
Mac mac = Mac.getInstance(algorithm);
// 初始化秘钥
mac.init(secretKey);
// 处理源数据,得到字节结果
byte[] bytes = mac.doFinal(origin.getBytes("UTF-8"));
// 转 16 进制输出
String result = toHexString(bytes);
System.out.println(result);
}
数据加密算法
我们首先来理解一个概念:
- 明文
- 正常的、可识别具体含义的信息
- 密文
- 加密后无法识别具体含义的信息
对应于加解密:
- 加密
- 将可识别的明文转换为不可识别的密文
- 解密
- 基于不可识别的密文,解析出它对应的将可识别的明文
加密对称性:
- 指的是加密/解密使用的秘钥是否相同
- 对称加密
- 加密与解密都是使用相同的秘钥
- 非对称加密
- 加密与解密分别使用公/私秘钥
对称加密算法
对称加密算法特点:
- 计算量小、加密速度快、加密效率高
- 秘钥泄露后存储安全泄露问题
常见对称加密算法有:
-
DES
-
AES
-
RC4
DES
DES
是一个分组加密算法,且加解密使用同一个秘钥
- 秘钥长度必须为
64bit
,但在计算时,实际使用的秘钥长度为56bit
- 每个
8bit
的最后1bit
为奇偶校验位
- 每个
实现原理
加密步骤:
1、IP置换
2、密钥置换
3、E扩展置换
4、S盒代替
5、P盒置换
6、IP-1末置换
DES
算法的过程比较复杂,参考:https://www.cnblogs.com/idreamo/p/9333753.html
API使用示例
public class DesDemo {
private static final String DES = "DES";
private static final Charset CHARSET = StandardCharsets.UTF_8;
/**
* DES加密
* @param origin 明文
* @param key 秘钥
*/
public String encrypt(String origin, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(DES);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, DES);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] bytes = cipher.doFinal(origin.getBytes(CHARSET));
// 将加密后字节数据以Base64进行编码
return Base64.getEncoder().encodeToString(bytes);
}
/**
* DES解密
* @param ciphertext
* @param key
*/
public String decrypt(String ciphertext, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(DES);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, DES);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
return new String(bytes, CHARSET);
}
/**
* 获取DES秘钥
*/
public byte[] generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(DES);
// 默认秘钥长度为56
keyGenerator.init(56);
SecretKey secretKey = keyGenerator.generateKey();
return secretKey.getEncoded();
}
@Test
public void desTest() throws Exception {
String origin = "你是个大傻子吗??你是个大傻子吗??你是个大傻子吗??";
System.out.println(StringFormat.format("加密前:{}", origin));
byte[] key = generateKey();
System.out.println(StringFormat.format("生成的秘钥Base64为:{}", Base64.getEncoder().encodeToString(key)));
String encrypted = encrypt(origin, key);
System.out.println(StringFormat.format("加密并Base64转换后:{}", encrypted));
String decrypted = decrypt(encrypted, key);
System.out.println(StringFormat.format("解密后:{}", decrypted));
}
}
DESede
DESede
算法也称为三重DES
算法,是DES
算法的升级版,实际就是对明文进行三重DES
算法
- 秘钥长度
- 其需要进行三次
DES
算法,每次都需要一个的DES
秘钥,可以称为key[1/2/3]
- 我们所知
DES
秘钥长度为64bit
,实际使用长度为56bit
- 三倍长度秘钥
168bit
- 使用三个独立的
DES
秘钥 - 长度为
3 * 56 = 168
- 加密强度最高
- 使用三个独立的
- 双倍长度秘钥
112bit
- 其中
key1
与key3
是相同的 - 长度为
2 * 56 = 112
- 加密强度弱于三倍长度秘钥,不推荐使用
- 其中
- 其需要进行三次
加密过程:
- 使用
key1
加密明文 - 使用
key2
解密上一步结果 - 使用
key3
加密上一步结果得到最终密文
解密过程:
- 使用
key3
解密密文 - 使用
key2
加密上一步结果 - 使用
key1
解密上一步结果得到明文
API使用示例
API
使用与DES
类似,不同点在于
- 其对应
algorithm = 'DESede'
- 秘钥长度可选为
112/168
AES
数字签名算法
数字签名算法可以理解为结合了信息摘要算法与数据加密算法
- 对信息进行哈希获取摘要签名
- 再对摘要签名进行加密
API详解
秘钥相关
Key
Key
是所有秘钥的顶级接口
// java.security.Key
public interface Key extends java.io.Serializable {
// 获取算法类型
public String getAlgorithm();
// 秘钥的主编码格式
public String getFormat();
// 以主编码格式返回编码后的秘钥
public byte[] getEncoded();
}
Key
通过不同的实现对应不同类型的秘钥
SecretKey
:对称秘钥接口PublicKey/PrivateKey
:非对称公私秘钥接口
KeySpec
KeySpec
表示秘钥的规格
// java.security.spec.KeySpec
public interface KeySpec {}
对称秘钥
SecretKey
SecretKey
是Key
对应对称秘钥的
// javax.crypto.SecretKey
public interface SecretKey extends java.security.Key {}
分析可知:
SecretKey
是一个空接口,其主要是用于秘钥分类- 对称秘钥主要通过
SecretKey
的实现类实现
KeyGenerator
KeyGenerator
是一个秘钥生成器,它是一个帮助我们生成算法所需秘钥的工具类
// javax.crypto.KeyGenerator
public class KeyGenerator {
// 对应算法类型
private final String algorithm;
// 算法能力的具体提供者
private Provider provider;
// 秘钥生成具体实现
private volatile KeyGeneratorSpi spi;
// 获取 KeyGenerator 实例
public static final KeyGenerator getInstance(String algorithm){...}
// 设置秘钥长度,对于某些可以支持多个长度秘钥的算法
public final void init(int keysize) {...}
// 秘钥生成,返回的是 SecretKey 实例
public final SecretKey generateKey() {...}
}
KeyGeneratorSpi
KeyGeneratorSpi
是秘钥生成的具体实现抽象父类
// javax.crypto.KeyGeneratorSpi
public abstract class KeyGeneratorSpi {
// 初始化引擎,支持多种参数
protected abstract void engineInit(SecureRandom random);
protected abstract void engineInit(AlgorithmParameterSpec params, SecureRandom random)
protected abstract void engineInit(int keysize, SecureRandom random);
// 引擎生成秘钥
protected abstract SecretKey engineGenerateKey();
}
实现
AESKeyGenerator
AESKeyGenerator
是KeyGeneratorSpi
对应AES
加密算法的实现类
// com.sun.crypto.provider.AESKeyGenerator
public final class AESKeyGenerator extends KeyGeneratorSpi {
// 随机数源
private SecureRandom random = null;
// 秘钥长度,单位为 bit
private int keySize = 16;
// 生成 SecretKey 实例
protected SecretKey engineGenerateKey() {
SecretKeySpec aesKey = null;
if (this.random == null) {
this.random = SunJCE.getRandom();
}
// 定义秘钥存储数组
byte[] keyBytes = new byte[keySize];
// 使用随机数填充
this.random.nextBytes(keyBytes);
// 创建 SecretKeySpec 实例
aesKey = new SecretKeySpec(keyBytes, "AES");
return aesKey;
}
}
分析
KeyGeneratorSpi
实现类的主要要素有
SecureRandom
- 秘钥生成过程中,获取随机源数的工具
keySize
- 秘钥长度
- 通常来说会有初始值,且在
engineInit
方法中进行校验
参考engineGenerateKey
方法可知
- 主要通过
SecureRandom
实现随机数填充 - 返回的是
SecretKeySpec
实例
SecretKeySpec
// javax.crypto.spec.SecretKeySpec
public class SecretKeySpec implements KeySpec, SecretKey {
// 秘钥存储
private byte[] key;
// 算法类型
private String algorithm;
// 获取编码后秘钥
public byte[] getEncoded() {
// 使用 clone 复制,保证管理的秘钥内容不变
return this.key.clone();
}
}
非对称秘钥
信息摘要相关
普通摘要算法
基类
MessageDigest
MessageDigest
是一个用于进行信息摘要计算的工具
- 其主要对应基于
RFC3174
规范实现的算法,主要包括MD<n>/SHA-<n>
系列的算法
// java.security.MessageDigest
public abstract class MessageDigest extends MessageDigestSpi {
// 对应算法类型
private String algorithm;
// 根据指定算法类型 algorithm 获取对应签名实例
public static MessageDigest getInstance(String algorithm){...}
// 根据输入更新内部状态
public void update(byte input) {...}
// 将内部状态结果输出,完成签名
public byte[] digest() {...}
}
分析可知:
MessageDigest
是一个抽象类,其对应不同算法类型有不同实现
注意点:
MessageDigest
所支持的算法类型,可以通过Security
查看
// [SHA-384, SHA-224, SHA-256, MD2, SHA, SHA-512, MD5]
Set<String> algorithms = Security.getAlgorithms("MessageDigest");
MessageDigest
的具体签名工作是由DigestBase
对应的实现类完成
DigestBase
DigestBase
是MessageDigest
完成签名工作的具体实现基类
// sun.security.provider.DigestBase
abstract class DigestBase extends MessageDigestSpi implements Cloneable {
// 结果输出 byte 数
private int digestLength = 16;
// 分块大小 byte 数
private int blockSize = 64;
// 分组数据压缩实现(分组数据计算实现)
abstract void implCompress(byte[] b, int ofs);
// 签名实现,获取前面结果
abstract void implDigest(byte[] out, int ofs);
}
对应不同算法,DigestBase
通过不同实现类完成
举个
标签:12,String,int,秘钥,算法,byte,public From: https://www.cnblogs.com/u1314/p/18317788