Elgamal

一、概述

ElGamal 加密算法是一种公钥加密算法,由Taher Elgamal 在1985年提出,基于离散对数问题的计算困难性(即:在某些群中,计算离散对数非常困难)。它是 非对称加密 的一种典型实现,与 RSA 加密算法类似,具有加密和解密需要不同密钥的特性。ElGamal 加密算法广泛用于数字签名(例如 DSA),也可以作为其他加密方案(如 PGP)的一部分。

二、算法框架

三、主要部分分析

密钥生成

ElGamal 使用一对密钥:公钥和私钥。

公钥 (p, g, y):p:一个大素数(用于构建群体,通常是 128 位或更大)。g:一个原根(生成素数群的生成元)。y:y = g^x mod p,其中 x 是私钥,y 是公钥的组成部分。

私钥 x:x 是一个随机生成的秘密数(1 < x < p-1),用于解密。

代码实现:

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
63
64
65
# Miller-Rabin 素性测试
def is_prime(n, k=5):
if n <= 1 or n % 2 == 0:
return False
if n == 2 or n == 3:
return True
r, d = 0, n - 1
while d % 2 == 0:
r += 1
d //= 2
for _ in range(k):
a = random.randrange(2, n - 1)
x = pow(a, d, n)
if x == 1 or x == n - 1:
continue
for _ in range(r - 1):
x = pow(x, 2, n)
if x == n - 1:
break
else:
return False
return True
# 自动生成大素数
def generate_large_prime(bits=128):
while True:
p = random.getrandbits(bits)
p |= (1 << bits - 1) | 1 # 确保最高位和最低位为1
if is_prime(p):
return p
# 快速幂取模
def mod_exp(base, exponent, mod):
result = 1
base %= mod
while exponent > 0:
if exponent % 2:
result = (result * base) % mod
exponent //= 2
base = (base * base) % mod
return result
# 快速找原根(适合大素数)
def find_primitive_root(p):
if p == 2:
return 1
phi = p - 1
factors = set()
n = phi
i = 2
while i * i <= n:
if n % i == 0:
factors.add(i)
while n % i == 0:
n //= i
i += 1
if n > 1:
factors.add(n)
for g in range(2, p):
if all(pow(g, phi // f, p) != 1 for f in factors):
return g
return None
# 密钥生成
def generate_keys(p):
g = find_primitive_root(p)
x = random.randint(2, p - 2)
y = mod_exp(g, x, p)
return (p, g, y), x

加密

对消息 m(需满足 0≤m<p ,十进制数)的加密步骤如下:

  1. 选择随机数 k
    • k 需满足 1<k<p−1,且 与 p−1 互质
    • 注意:若 g 的阶为 q(如 p=2q+1 且 q 为素数),则 k应满足 1≤k≤q−1,无需强制互质。
  2. 计算中间值 a和密文 b
    • a=g^k mod p,b=m⋅y^k mod p
    • 输出密文:(a,b)。

解密过程

使用私钥 x 解密密文 (a,b):

  1. 计算共享密钥 s

s=a^x mod p=(g^k)^x mod p=g^(xk) mod p

  1. 计算 s **的模逆元 s_inv
    • s_inv≡s^(−1) mod p
    • 通过扩展欧几里得算法或费马小定理(若 p 为素数)计算。
  2. 还原消息 m

image

完整代码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import random

# Miller-Rabin 素性测试
def is_prime(n, k=5):
if n <= 1 or n % 2 == 0:
return False
if n == 2 or n == 3:
return True
r, d = 0, n - 1
while d % 2 == 0:
r += 1
d //= 2
for _ in range(k):
a = random.randrange(2, n - 1)
x = pow(a, d, n)
if x == 1 or x == n - 1:
continue
for _ in range(r - 1):
x = pow(x, 2, n)
if x == n - 1:
break
else:
return False
return True

# 自动生成大素数
def generate_large_prime(bits=128):
while True:
p = random.getrandbits(bits)
p |= (1 << bits - 1) | 1 # 确保最高位和最低位为1
if is_prime(p):
return p

# 快速幂取模
def mod_exp(base, exponent, mod):
result = 1
base %= mod
while exponent > 0:
if exponent % 2:
result = (result * base) % mod
exponent //= 2
base = (base * base) % mod
return result

# 快速找原根(适合大素数)
def find_primitive_root(p):
if p == 2:
return 1
phi = p - 1
factors = set()
n = phi
i = 2
while i * i <= n:
if n % i == 0:
factors.add(i)
while n % i == 0:
n //= i
i += 1
if n > 1:
factors.add(n)
for g in range(2, p):
if all(pow(g, phi // f, p) != 1 for f in factors):
return g
return None

# 密钥生成
def generate_keys(p):
g = find_primitive_root(p)
x = random.randint(2, p - 2)
y = mod_exp(g, x, p)
return (p, g, y), x

# 加密
def encrypt(p, g, y, m):
k = random.randint(2, p - 2)
a = mod_exp(g, k, p)
b = (m * mod_exp(y, k, p)) % p
return a, b, k

# 解密
def decrypt(p, x, a, b):
s = mod_exp(a, x, p)
s_inv = pow(s, -1, p)
m = (b * s_inv) % p
return m, s, s_inv

# 主程序(只显示核心加解密过程)
if __name__ == "__main__":
# 获取用户输入的 p 位数
try:
bits = int(input("请输入要生成素数的位数:"))
if bits < 8:
raise ValueError("位数应当大于等于 8 位")
except ValueError as e:
print(f"输入无效: {e}")
exit(1)

print(f"正在生成 {bits} 位素数用于 ElGamal 密钥,请稍等...")
p = generate_large_prime(bits)
public_key, private_key = generate_keys(p)
g, y = public_key[1], public_key[2]
x = private_key

print("\n密钥生成完毕:")
print(f"私钥 x: {x}")
print(f"公钥 (g={g}, y={y})")

message = int(input("\n请输入要加密的整数消息(必须小于 p): "))
if message >= p:
raise ValueError("消息必须小于 p")

a, b, k = encrypt(p, g, y, message)
print("\n 加密过程:")
print(f"随机 k: {k}")
print(f"计算 a = g^k mod p = {a}")
print(f"计算 b = m * y^k mod p = {b}")

m_decrypted, s, s_inv = decrypt(p, x, a, b)
print("\n解密过程:")
print(f"计算共享密钥 s = a^x mod p = {s}")
print(f"计算 s 的逆 s^(-1) mod p = {s_inv}")
print(f"解密后消息 m = b * s^(-1) mod p = {m_decrypted}")

print("\n解密是否成功?", "是" if m_decrypted == message else "否")

elgamal.py

     

RSA

一、RSA介绍

RSA加密算法是一种非对称加密算法,所谓非对称,就是指该算法加密和解密使用不同的密钥,即使用加密密钥进行加密、解密密钥进行解密。 在RSA算法中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)KR是需要保密的。 加密算法E和解密算法D也都是公开的。

对极大整数做因数分解的难度决定了RSA算法的可靠性。 换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用RSA加密的信息的可靠性就肯定会极度下降。 但找到这样的算法的可能性是非常小的。今天只有短的RSA钥匙才可能被强力方式解破。到目前为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被解破的。

二、RSA算法详解

RSA的加密过程可以通过一个公式来表示:

加密:

解密:

rsa加密的python实现过程

首先要生成两个大素数 p, q (保密)

计算 n=pq,image= (p-1)*(q-1)。【n公开;image即欧拉函数值,需要保密 】

随机选取正整数 image,满足。image【e是公开的加密密钥,即E 】

计算d,满足image。【d是保密的解密密钥】

由此得到公钥(n,e),私钥 (n,d) 或 (n,p,q)。

加密变换:对于明文 m ,密文为 n。

解密变换:对于密文 c ,明文为 n。

大素数生成

Miller-Rabin 素性检测(Miller-Rabin Primality Test)是一种基于概率的素数检测算法,常用于判断一个大整数是否为素数。它比最基本的试除法效率高得多,是目前实际应用中(比如RSA密钥生成)经常使用的一种方法。

假设我们想检测一个奇数 n>2 是否为素数:

  1. 分解 n−1 的形式
    将 n−1 分解为image,其中 d 是奇数(非“奇素数”,只需为奇数)。
    示例
    • 若 n=25 ,则 n−1=24=image,此时 s=3 ,d=3。
    • 若 n=9 ,则 n−1=8=image,此时 d=1。
  2. 选择基数 a
    随机选取 a 满足 1<a<n−1 。通常选择多个不同的 a 以提高准确性。
  3. 模幂计算与判定
    • 计算image
    • 若 x≡1 mod n 或 x≡n−1 mod n:通过当前测试,继续下一轮。
    • 否则:循环平方 x 最多 s−1 次,每次计算 x=x^2 mod n。
      • 若某次得到 x ≡ n−1 mod n,则通过当前测试。
      • 若所有循环后仍未通过,则 n 为合数。
  4. 重复测试
    用不同的 a 重复上述步骤。选择 k 个不同的 a ,可将误判概率降至image

下面我给出python的代码实现:(部分做了微调) 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 针对随机取得p,q两个数的素性检测     
def miller_rabin_test(n): # n为要检验得数
p = n - 1
r = 0
# 寻找满足n-1 = 2^s * m 的s,m两个数
# n -1 = 2^r * p
while p % 2 == 0: # 最后得到为奇数的p(即m)
r += 1
p /= 2
b = random.randint(2, n - 2) # 随机取b=(0.n)
# 如果情况1 b得p次方 与1 同余 mod n
if fastExpMod(b, p, n) == 1:
return True # 通过测试,可能为素数
# 情况2 b得(2^r *p)次方 与-1 (n-1) 同余 mod n
for i in range(7): # 检验六次
if fastExpMod(b, (2 ** i) * p, n) == n - 1:
return True # 如果该数可能为素数,
return False # 不可能是素数

这里我们看到在测试的时候,使用到了大数的幂次取模,所以这里我们要先实现大数模的算法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 模N大数的幂乘的快速算法
def fastExpMod(b, e, m): # 底数,幂,大数N
result = 1
e = int(e) #这里不转化的话,在python3下会出现type error
while e != 0:
if e % 2 != 0: # 按位与
e -= 1
result = (result * b) % m
else:
e >>= 1
b = (b * b) % m
print(f" 快速幂结果: {result}")
return result

 下面给出大素数生成的代码吧:(上面的重复内容不再写出)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 生成大素数:
def create_prime_num(bits):
print(f"\n开始生成 {bits} 位大素数:")
while True:
# 如果经过10次素性检测,那么很大概率上,这个数就是素数
n = random.randint(2 ** (int(bits) - 1), 2 ** int(bits) - 1)
if n % 2 != 0:
print(f" 尝试候选数: {n}")
if all(miller_rabin_test(n) for _ in range(5)):
print(f" >>> 确认素数: {n}\n")
return n
else:
print(f" 非素数,重新尝试...\n")

我们得到生成的大素数之后,就比较简单了。

生成密钥

这里直接给出代码实现:

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
# 生成密钥(包括公钥和私钥)
def create_keys(keylength):
p = create_prime_num(keylength // 2)
q = create_prime_num(keylength // 2)
n = p * q
fn = (p - 1) * (q - 1)
e = selectE(fn)
d = match_d(e, fn)
return (n, e, d)

# 选择加密指数 e
def selectE(fn):
while True:
e = random.randint(2, fn - 1)
if math.gcd(e, fn) == 1:
return e

# 根据选择的e,匹配出唯一的d
def match_d(e, fn):
def extended_gcd(a, b):
if b == 0:
return (1, 0)
else:
x1, y1 = extended_gcd(b, a % b)
x, y = y1, x1 - (a // b) * y1
return (x, y)
x, _ = extended_gcd(e, fn)
return x % fn

至此我们得到了公钥和私钥。

加解密实现

1
2
3
4
5
6
#加密
def encrypt(M, e, n):
return fastExpMod(M, e, n)
#解密
def decrypt( C, d, m):
return fastExpMod(C, d, m)

测试结果

可以完成读取文本的加密,注意标点符号如果是中文的标点,会出现乱码,但不会影响信息的读取。

完整代码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import random
import math

# 快速模幂运算
def fastExpMod(b, e, m):
result = 1
e = int(e)
while e != 0:
if e % 2 != 0:
e -= 1
result = (result * b) % m
continue
e >>= 1
b = (b * b) % m
return result

# Miller-Rabin 素性检测
def miller_rabin_test(n):
p = n - 1
r = 0
while p % 2 == 0:
r += 1
p //= 2 # 确保是整数
b = random.randint(2, n - 2)
if fastExpMod(b, p, n) == 1:
return True
for i in range(7):
if fastExpMod(b, (2 ** i) * p, n) == n - 1:
return True
return False

# 生成大素数
def create_prime_num(bits):
while True:
n = random.randint(2 ** (int(bits) - 1), 2 ** int(bits) - 1)
if n % 2 != 0:
if all(miller_rabin_test(n) for _ in range(10)):
return n

# 生成密钥对
def create_keys(keylength):
p = create_prime_num(keylength // 2)
q = create_prime_num(keylength // 2)
n = p * q
fn = (p - 1) * (q - 1)
e = selectE(fn)
d = match_d(e, fn)
return (n, e, d)

# 选择加密指数 e
def selectE(fn):
while True:
e = random.randint(2, fn - 1)
if math.gcd(e, fn) == 1:
return e

# 计算解密指数 d
def match_d(e, fn):
def extended_gcd(a, b):
if b == 0:
return (1, 0)
else:
x1, y1 = extended_gcd(b, a % b)
x, y = y1, x1 - (a // b) * y1
return (x, y)
x, _ = extended_gcd(e, fn)
return x % fn

# 加解密函数
def encrypt(M, e, n):
return fastExpMod(M, e, n)

def decrypt(C, d, n):
return fastExpMod(C, d, n)

# 交互菜单
def display():
print("_________________RSA_________________")
print("1. 加密 Encrypt")
print("2. 解密 Decrypt")
print("q. 退出 Quit")
print("_____________________________________")

# 主函数
def main():
while True:
display()
choice = input("请选择操作: ").strip().lower()

if choice == '1':
plaintext = input("请输入要加密的明文(支持中文): ")
n, e, d = create_keys(16) # 密钥长度可改为更大
print(f"\n生成的公钥: (n={n}, e={e})")
print(f"请妥善保存私钥以解密: (n={n}, d={d})")

encrypted_list = [encrypt(ord(ch), e, n) for ch in plaintext]
print(f"\n加密结果(密文整数列表): {encrypted_list}\n")

elif choice == '2':
try:
n, d = map(int, input("请输入私钥(格式: n d): ").split())
encrypted_input = input("请输入密文(整数列表,例如:[123, 456]): ")
encrypted_list = eval(encrypted_input)

decrypted = ''.join([chr(decrypt(c, d, n)) for c in encrypted_list])
print(f"\n解密结果: {decrypted}\n")
except Exception as e:
print(f"解密出错: {e}\n")

elif choice == 'q':
print("退出程序")
break
else:
print("无效选项,请重新输入。\n")

if __name__ == '__main__':
main()

RSA.py

RSA_1.py

MD5(Rivest设计)

一、概述

MD5(Message Digest Algorithm 5)是一种广泛使用的散列函数,用于提供消息的完整性保护。它可以将长度小于image比特的信息输入,输出固定长度的128比特(16字节)散列值。

输入消息以512比特的分组为单位处理。

二、算法框架

L:消息的长度

N:消息扩充后分组个数

image:代表一个分组

三、主要步骤

填充消息(Padding)

在MD5算法中,首先需要对输入信息进行填充,使其位长对512求余的结果等于448,并且填充必须进行,即使其位长对512求余的结果等于448。因此,信息的位长(Bits Length)将被扩展至N*512+448,N为一个非负整数,N可以是零。填充的方法如下:

  1. 在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。

  2. 在这个结果后面附加一个以64位二进制表示的填充前信息长度,如果二进制表示的填充前信息长度超过64位,则取低64位。经过这两步的处理,信息的位长=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。

初始化 MD5 缓冲区

MD5 使用 四个 32-bit 寄存器作为中间结果和最终结果的缓冲区,初始化值为:

A=0x01234567, B=0x89ABCDEF, C=0xFEDCBA98, D=0x76543210。

在运算过程中涉及大端和小端的转换问题。

A, B, C, D = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476。(小端)

处理 512bit 分组

将填充后的消息分成512位(64字节)的块,对每个块进行以下处理:

将块分成16个32位字

将512位的块分成16个32位的小端序字,记为M[0]到M[15]。

初始化哈希值

将当前哈希值(A,B,C,D)保存到临时变量(a,b,c,d)中。

主循环

MD5有4轮,每轮16次操作,共64次操作。每轮使用不同的非线性函数:

  • 第一轮(F函数): F(X,Y,Z) = (X ∧ Y) ∨ (¬X ∧ Z)
  • 第二轮(G函数): G(X,Y,Z) = (X ∧ Z) ∨ (Y ∧ ¬Z)
  • 第三轮(H函数): H(X,Y,Z) = X ⊕ Y ⊕ Z
  • 第四轮(I函数): I(X,Y,Z) = Y ⊕ (X ∨ ¬Z)

每轮操作形式为:<font style="color:#464646;">a = b + ((a + F(b,c,d) + M[k] + T[i]) <<< s)</font>

  • <<< 表示循环左移
  • T[i]是预先定义的常数表中的一个值。image

  • s是循环左移的位数
  • k是消息字索引

4轮操作之前,先将前一分组的链接变量复制到另外四个备用记录单元AA、BB、CC、DD。


所有这些完成之后,将a、b、c、d分别在原来基础上再加上AA、BB、CC、DD。
image

image
然后用数据继续运行以上算法。

最终输出 MD5 哈希值

所有分组处理完成后,将最终的 A, B, C, D 连接成 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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#得到十六进制、128比特的散列值
import struct
import math

# 循环左移
def left_rotate(x, n):
return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF

# 计算 T 常数表(64 个值)
T = [int(2**32 * abs(math.sin(i + 1))) & 0xFFFFFFFF for i in range(64)]

# 4 轮使用的函数
F = lambda x, y, z: (x & y) | (~x & z)
G = lambda x, y, z: (x & z) | (y & ~z)
H = lambda x, y, z: x ^ y ^ z
I = lambda x, y, z: y ^ (x | ~z)

# 每轮的左移位数
S = [
(7, 12, 17, 22) * 4, # 1st round
(5, 9, 14, 20) * 4, # 2nd round
(4, 11, 16, 23) * 4, # 3rd round
(6, 10, 15, 21) * 4 # 4th round
]

# 处理的 M[i] 顺序
M_index = [
list(range(16)), # 1st round
[1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12], # 2nd round
[5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2], # 3rd round
[0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9] # 4th round
]

# 消息填充
def md5_padding(message):
# 计算消息长度(比特)
original_length = len(message) * 8
# 追加 10000000
message += b'\x80'
# 填充 0 直到消息长度模 512 余 448
while (len(message) * 8) % 512 != 448:
message += b'\x00'
# 追加长度(小端存储)
message += struct.pack('<Q', original_length)
return message

# 将 32 位整数从当前字节序转换为大端字节序
def to_big_endian_32(x):
return struct.unpack('>I', struct.pack('<I', x))[0]

# MD5 计算
def md5(message):
# 初始化 MD5 缓冲区,按照小端字节序的初始值
A, B, C, D = 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476

# 进行消息填充
message = md5_padding(message)
# 将消息分割成 512-bit 分块
chunks = [message[i: i + 64] for i in range(0, len(message), 64)]

# 处理每个 512-bit 分块
for chunk in chunks:
# 小端解析 16 个 32-bit 字
M = list(struct.unpack('<16I', chunk))
# 复制原始值
AA, BB, CC, DD = A, B, C, D

# 64 轮循环
for round_num in range(4):
# 选择 F/G/H/I 函数
func = [F, G, H, I][round_num]
for i in range(16):
# 选择 M[k] 的索引
k = M_index[round_num][i]
# 选择循环左移位数
shift = S[round_num][i]

temp = (AA + func(BB, CC, DD) + M[k] + T[i + round_num * 16]) & 0xFFFFFFFF
# 循环左移
temp = left_rotate(temp, shift)
# 加上 B 值
temp = (BB + temp) & 0xFFFFFFFF

# 轮换 A, B, C, D
AA, BB, CC, DD = DD, temp, BB, CC

# 打印当前轮次结果
print(f"Round {round_num * 16 + i + 1:02}: A={AA:08x}, B={BB:08x}, C={CC:08x}, D={DD:08x}")

# 累加结果
A = (A + AA) & 0xFFFFFFFF
B = (B + BB) & 0xFFFFFFFF
C = (C + CC) & 0xFFFFFFFF
D = (D + DD) & 0xFFFFFFFF

# 将每个 32 位整数转换为大端字节序
A = to_big_endian_32(A)
B = to_big_endian_32(B)
C = to_big_endian_32(C)
D = to_big_endian_32(D)

# 组合最终哈希值
return f'{A:08x}{B:08x}{C:08x}{D:08x}'

# 运行示例
message = input("请输入要加密的字符串: ").encode('utf-8')
md5_hash = md5(message)
print(f"\nMD5哈希值: {md5_hash}")

MD5.py

MD5_1.py

五、实例

MD5(“iscbupt”)=”16838a414adaec12d8d86f735fd183b7”

SHA-1

一、概述

SHA-1(Secure Hash Algorithm 1)是一种广泛应用的哈希算法,由美国国家安全局(NSA)设计,首次发布于 1993 年。作为 SHA 系列算法的一员,SHA-1 在数字签名数据完整性验证密码学应用中有重要意义。然而,随着计算能力的提升,SHA-1 的安全性逐渐受到威胁,已不再推荐用于强安全需求的应用。

SHA-1 是第一代 SHA 算法标准,后来的 SHA-224、SHA-256、SHA-384 和 SHA-512 被统称为 SHA-2。

它可以将长度小于image比特的信息输入,输入消息以512比特的分组为单位处理,输出固定长度的160比特(16字节)散列值。主循环4轮,每轮20次操作。

二、主要步骤

数据预处理

  • 填充消息:首先,在原始消息的末尾添加一个 1 比特,然后填充 0 比特,直到消息长度模 512 等于 448。
  • 附加长度:在填充后的消息末尾附加一个 64 比特的无符号整数,表示原始消息的长度(以比特为单位)。

消息分组

将预处理后的消息分割成多个 512 比特的分组。

初始化常量

初始化 5 个 32 位的常量,这些常量将作为初始的哈希值:

1
2
3
4
5
h0 = 0x67452301
h1 = 0xEFCDAB89
h2 = 0x98BADCFE
h3 = 0x10325476
h4 = 0xC3D2E1F0

压缩函数处理

总体

对于每个 512 比特的分组,执行以下操作:

  • 扩展消息:将 512 比特的分组扩展为 80 个 32 位的字。
  • 初始化工作变量:将当前的哈希值赋值给 5 个工作变量(a、b、c、d、e)。
  • 80 轮迭代:在每一轮中,根据当前轮数选择不同的逻辑函数和常量,更新工作变量。
  • 更新哈希值:将工作变量的值累加到当前的哈希值上。

步函数

对于消息按照如下的方式进行处理:

  • 前16个字节([0, 15]),转换成32位无符号整数。
  • 对于后面的字节([16, 79])按照下面的公式进行处理
1
W(t) = ROTL^1 (W(t-3) XOR W(t-8) XOR W(t-14) XOR W(t-16)) //ROTL^n (x):表示对32比特的变量x循环左移n比特。
  • A = H0, B = H1, C = H2, D = H3, E = H4
  • 做如下80轮的散列操作
1
2
3
4
5
6
TEMP = ROTL^5 (A) + f(t;B,C,D) + E + W(t) + K(t);
E = D;
D = C;
C = ROTL^30 (B);
B = A;
A = TEMP;
  • H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E

解释一下,其中f函数如下:

1
2
3
4
f(t;B,C,D) = (B AND C) OR ((NOT B) AND D)         ( 0 <= t <= 19)
f(t;B,C,D) = B XOR C XOR D (20 <= t <= 39)
f(t;B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59)
f(t;B,C,D) = B XOR C XOR D (60 <= t <= 79)

k函数如下:

1
2
3
4
K(t) = 0x5A827999         ( 0 <= t <= 19)
K(t) = 0x6ED9EBA1 (20 <= t <= 39)
K(t) = 0x8F1BBCDC (40 <= t <= 59)
K(t) = 0xCA62C1D6 (60 <= t <= 79)

输出结果

将最终的 5 个 32 位哈希值(h0、h1、h2、h3、h4)拼接成一个 160 位的哈希结果。

三、完整代码

SHA-1.py

SHA-1_1.py

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
import math


# 异或算法
def xor_func(a, b):
a = bin(a)[2:] #将整数作为参数传递给bin函数,它就会返回一个字符串形式的二进制表示。
b = bin(b)[2:] #[2:]去掉二进制表示的'0b'前缀
while len(a) < 32:
a = '0' + a
while len(b) < 32:
b = '0' + b
c = '' #令c初始为空,异或后的值作为字符串拼接
for i in range(0, 32):
if a[i] == b[i]: #对应位数上的值相等异或结果为0
c = c + '0'
else: #反之位数为1
c = c + '1'
return c


# 与运算
def and_func(a, b):
a = bin(a)[2:]
b = bin(b)[2:]
while len(a) < 32:
a = '0' + a
while len(b) < 32:
b = '0' + b
c = ''
for i in range(0, 32):
#两位同时为"1",结果才为"1",否则为0
if a[i] == '1' and b[i] == '1':
c = c + '1'
else:
c = c + '0'
return c


# ROTL运算 即轮函数运算,压缩函数由 轮函数运算组成
def ROTL_func(a, n):
a = a * 2 ** n #幂运算符 ** 来表示次方,也可以用内置函数pow()
#a=a*pow(2,n)
t = int(a / 2 ** 32)
a = a % (2 ** 32) + t
return a


# 非门:输入端和输出端的电平状态总是反相的,所以非门运算就是1取0,0则取1
def NOT_func(a):
a = bin(a)[2:]
while len(a) < 32:
a = '0' + a
c = ''
for ch in a:
if ch == '0':
c = c + '1'
else:
c = c + '0'
return c


# 步函数算法编写
def step_func(W, list):
A = list[0]
B = list[1]
C = list[2]
D = list[3]
E = list[4]
t = 0
# t=0-19的计算
while t <= 19:
k = 0x5A827999 # K1
if t <= 15:
w = W[t]
else:
#int(x,base) 可选参数 base,用于指定输入字符串的进制
#下面即是将x转化为对应进制再进行异或运算
w = xor_func(int(W[t - 3], 16), int(W[t - 8], 16)) # t>15后进行新的赋值
w = xor_func(int(w, 2), int(W[t - 14], 16))
w = xor_func(int(w, 2), int(W[t - 16], 16))
temp = w[0]
w = w[1:] + temp
w = hex(int(w, 2))
W.append(w)
t += 1
x = and_func(int(B), int(C)) #与
y = NOT_func(B) #非
z = and_func(int(y, 2), int(D)) #与
a = int(xor_func(int(x, 2), int(z, 2)), 2) # Ch(x,y,z)
e = E
E = D
D = C
C = ROTL_func(int(B), 30) % (2 ** 32)
B = A
A = int(ROTL_func(int(A), 5) + a + int(e) + int(w, 16) + int(k)) % (2 ** 32)

# t=20-39的计算
while t <= 39 and t >= 20:
k = 0x6ED9EBA1 # K2
w = xor_func(int(W[t - 3], 16), int(W[t - 8], 16))
w = xor_func(int(w, 2), int(W[t - 14], 16))
w = xor_func(int(w, 2), int(W[t - 16], 16))
temp = w[0]
w = w[1:] + temp
w = hex(int(w, 2))
W.append(w)

x = int(xor_func(B, C), 2) #
y = int(xor_func(x, D), 2) # Parity(x,y,z)
e = E
E = D
D = C
C = ROTL_func(int(B), 30) % (2 ** 32)
B = A
A = int(ROTL_func(int(A), 5) + y + int(e) + int(w, 16) + int(k)) % (2 ** 32)
t += 1

# t=40-59的计算
while t <= 59 and t >= 40:
k = 0x8f1bbcdc # K3
w = xor_func(int(W[t - 3], 16), int(W[t - 8], 16))
w = xor_func(int(w, 2), int(W[t - 14], 16))
w = xor_func(int(w, 2), int(W[t - 16], 16))
temp = w[0]
w = w[1:] + temp
w = hex(int(w, 2))
W.append(w)

x = int(and_func(B, C), 2) #
y = int(and_func(B, D), 2) #
z = int(and_func(C, D), 2) #
xx = int(xor_func(x, y), 2) #
yy = int(xor_func(xx, z), 2) # Maj(x,y,z)
e = E
E = D
D = C
C = ROTL_func(int(B), 30) % (2 ** 32)
B = A
A = int(ROTL_func(int(A), 5) + yy + int(e) + int(w, 16) + int(k)) % (2 ** 32)
t += 1

# t=60-79的计算
while t <= 79 and t >= 60:
k = 0xca62c1d6 # K4
w = xor_func(int(W[t - 3], 16), int(W[t - 8], 16))
w = xor_func(int(w, 2), int(W[t - 14], 16))
w = xor_func(int(w, 2), int(W[t - 16], 16))
temp = w[0]
w = w[1:] + temp
w = hex(int(w, 2))
W.append(w)

x = int(xor_func(B, C), 2) #
y = int(xor_func(x, D), 2) # Parity(x,y,z)
e = E
E = D
D = C
C = ROTL_func(int(B), 30) % (2 ** 32)
B = A
A = int(ROTL_func(int(A), 5) + y + int(e) + int(w, 16) + int(k)) % (2 ** 32)
t += 1

AA = 0x67452301
BB = 0xEFCDAB89
CC = 0x98BADCFE
DD = 0x10325476
EE = 0xC3D2E1F0

A = (AA + A) % (2 ** 32)
B = (BB + B) % (2 ** 32)
C = (CC + C) % (2 ** 32)
D = (DD + D) % (2 ** 32)
E = (EE + E) % (2 ** 32)

# 给列表传值
list[0] = A
list[1] = B
list[2] = C
list[3] = D
list[4] = E


# 读取需要加密的明文
def main():
A = 0x67452301
B = 0xEFCDAB89
C = 0x98BADCFE
D = 0x10325476
E = 0xC3D2E1F0
list = [A, B, C, D, E]

print('请输入明文:')
m = input()

W = []
l = len(m)
x = hex(len(m) * 8)
n = 0 # 字符串的读取位置
while 1:
# 长度大于64,即所占字节大于512,需要分段处理
if l > 64:
m = m[n:]
nn = 0
p = ''
for i in range(1, 64):
if int(nn / 4) * 4 + 4 <= len(m):
p = p + hex(ord(m[nn]))[2:4]
if (nn + 1) % 4 == 0:
W.append(p)
p = ''
nn = nn + 1
step_func(W, list)
n += 64
l -= 64
W = []
# 长度在56与64之间,若要字节在448到512之间,若要其字节满足与512模448同余,还需额外添加使其达到1024字节
elif l >= 56 and l <= 64:
mm = m[n:]
nn = 0
p = ''
j = 0
for i in range(1, 64):
if int(nn / 4) * 4 + 4 <= len(mm):
p = p + hex(ord(mm[nn]))[2:4]
if (nn + 1) % 4 == 0:
W.append(p)
j += 1
p = ''
nn = nn + 1
else:
break
judge = 0
for i in range(0, 4):
if nn < len(mm):
p = p + hex(ord(mm[nn]))[2:4]
nn = nn + 1
else:
if judge == 0:
p = p + '80'
judge = 1
elif judge == 1:
p = p + '00'
W.append(p)
j += 1
if j == 15:
p = '00000000'
W.append(p)
step_func(W, list)
W = []
p = '00000000'
for i in range(0, 14):
W.append(p)
length = len(x) - 2
p = ''
for i in range(0, 16 - length):
p = p + '0'
p = p + x[2:]
W.append(p[0:8])
W.append(p[8:16])
step_func(W, list)
break
# 长度小于56,字节数在0到448之间,需要补全字节使其长度为512
elif l < 56:
mm = m[n:]
nn = 0
p = ''
for i in range(1, 56):
if int(nn / 4) * 4 + 4 <= len(mm):
p = p + hex(ord(mm[nn]))[2:4]
if (nn + 1) % 4 == 0:
W.append(p)
p = ''
nn = nn + 1
else:
break
judge = 0
for i in range(0, 4):
if nn < len(mm):
p = p + hex(ord(mm[nn]))[2:4]
nn = nn + 1
else:
if judge == 0:
p = p + '80'
judge = 1
elif judge == 1:
p = p + '00'
W.append(p)
for i in range(int(len(mm) / 4) + 1, 14):
p = '00000000'
W.append(p)
length = len(x) - 2
p = ''
for i in range(0, 16 - length):
p = p + '0'
p = p + x[2:]
W.append(p[0:8])
W.append(p[8:16])
step_func(W, list)
break

c = hex(list[0])[2:] + hex(list[1])[2:] + hex(list[2])[2:] + hex(list[3])[2:] + hex(list[4])[2:]
print('\n最终密文为:', c)

main()

四、实例

SHA1(“iscbupt”)=”664dc9f017dc1aee4a4366bcfb8511afc89f9430”

SM3

一、概述

SM3密码杂凑算法是中国国家密码管理局颁布的一种密码Hash函数,它与SM4分组密码、SM2椭圆曲线公钥密钥一起都是中国商用密码的重要组成部分。

SM3是中华人民共和国政府采用的一种密码散列函数标准,由国家密码管理局于2010年12月17日发布。相关标准为“GM/T 0004-2012 《SM3密码杂凑算法》”。
在商用密码体系中,SM3主要用于数字签名及验证、消息认证码生成及验证、随机数生成等,其算法公开。据国家密码管理局表示,其安全性及效率与SHA-256相当。

对长度为 l (1 < l < image) 比特的消息m,SM3杂凑算法经过填充和迭代压缩,生成杂凑值,杂凑值长度为256比特。

注:字:长度为32的比特串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
IV = [0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e]

T = [0x79cc4519] * 16 + [0x7a879d8a] * 48

def rotate_left(x, n):
return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF

def FF(j, X, Y, Z):
if 0 <= j <= 15:
return X ^ Y ^ Z
else:
return (X & Y) | (X & Z) | (Y & Z)

def GG(j, X, Y, Z):
if 0 <= j <= 15:
return X ^ Y ^ Z
else:
return (X & Y) | (~X & Z)

def P0(X):
return X ^ rotate_left(X, 9) ^ rotate_left(X, 17)

def P1(X):
return X ^ rotate_left(X, 15) ^ rotate_left(X, 23)

二、填充

解释:

假设消息m 的长度为 l 比特。首先将比特“1”添加到消息的末尾,再添加k 个“0”,k是满足l + 1 + k ≡ 448 mod 512的最小的非负整数。然后再添加一个64位比特串,该比特串是长度 l 的二进制表示。填充后的消息m′ 的比特长度为512的倍数。

例如:对消息01100001 01100010 01100011,其长度 l =24,经填充得到比特串:

代码:
1
2
3
4
5
6
7
8
def sm3_padding(message):
message_length = len(message) * 8
message = bytearray(message)
message.append(0x80)
while (len(message) * 8) % 512 != 448:
message.append(0x00)
message.extend(message_length.to_bytes(8, byteorder='big'))
return message

三、迭代压缩

迭代过程

解释:

将填充后的消息m′按512比特进行分组:image

其中n=(l+k+65)/512

对m′按下列方式迭代:

image

消息扩散

解释:

将消息分组image按以下方法扩展生成132个字image, 用于压缩函数CF:

压缩函数

解释:

由SM3的压缩函数的算法可以看出,压缩函数进行了64次循环迭代。SM3的压缩函数CF把每一个512位的消息分组 image 压缩成256位。经过个数据分组之间的迭代处理后把 l 位的信息压缩成256位的hash值。其中布尔函数image(X,Y,Z)和image(X,Y,Z)是非线性函数,经过循环迭代后提供混淆作用。置换函数imageimage是线性函数,经过循环迭代后提供扩散作用,确保算法的安全性。

单块处理函数代码实现

  • 功能:对单个 512 位(64 字节)的数据块进行压缩处理。具体步骤如下:
    1. 消息扩展:将输入的 64 字节数据块转换为 68 个 32 位整数 W,并进一步生成 64 个 32 位整数 W1
    2. 初始化中间变量:将输入的中间哈希值 V 拆分为 8 个 32 位整数 A, B, C, D, E, F, G, H
    3. 64 轮迭代:根据常量 T[j]W[j]W1[j],以及 FFGGP0 等函数,更新中间变量的值。
    4. 输出结果:将迭代后的中间变量与输入的中间哈希值进行异或操作,得到新的中间哈希值。
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
def sm3_process_block(block, V):
W = []
for i in range(16):
W.append(int.from_bytes(block[i * 4:(i + 1) * 4], byteorder='big'))
for i in range(16, 68):
W.append(P1(W[i - 16] ^ W[i - 9] ^ rotate_left(W[i - 3], 15)) ^ rotate_left(W[i - 13], 7) ^ W[i - 6])
W1 = []
for i in range(64):
W1.append(W[i] ^ W[i + 4])
A, B, C, D, E, F, G, H = V
for j in range(64):
SS1 = rotate_left((rotate_left(A, 12) + E + rotate_left(T[j], j % 32)) & 0xFFFFFFFF, 7)
SS2 = SS1 ^ rotate_left(A, 12)
TT1 = (FF(j, A, B, C) + D + SS2 + W1[j]) & 0xFFFFFFFF
TT2 = (GG(j, E, F, G) + H + SS1 + W[j]) & 0xFFFFFFFF
D = C
C = rotate_left(B, 9)
B = A
A = TT1
H = G
G = rotate_left(F, 19)
F = E
E = P0(TT2)
return [(x ^ y) & 0xFFFFFFFF for x, y in zip([A, B, C, D, E, F, G, H], V)]

四、杂凑值

image

输出256比特的杂凑值y = ABCDEFGH。处理过程如下:

五、完整代码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
IV = [0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e]
T = [0x79cc4519] * 16 + [0x7a879d8a] * 48

def rotate_left(x, n):
return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF

def FF(j, X, Y, Z):
if 0 <= j <= 15:
return X ^ Y ^ Z
else:
return (X & Y) | (X & Z) | (Y & Z)

def GG(j, X, Y, Z):
if 0 <= j <= 15:
return X ^ Y ^ Z
else:
return (X & Y) | (~X & Z)

#置换函数
def P0(X):
return X ^ rotate_left(X, 9) ^ rotate_left(X, 17)

def P1(X):
return X ^ rotate_left(X, 15) ^ rotate_left(X, 23)

#消息填充函数
def sm3_padding(message):
message_length = len(message) * 8
message = bytearray(message)
message.append(0x80)
while (len(message) * 8) % 512 != 448:
message.append(0x00)
message.extend(message_length.to_bytes(8, byteorder='big'))
return message

#单块处理函数
def sm3_process_block(block, V):
W = []
for i in range(16):
W.append(int.from_bytes(block[i * 4:(i + 1) * 4], byteorder='big'))
for i in range(16, 68):
W.append(P1(W[i - 16] ^ W[i - 9] ^ rotate_left(W[i - 3], 15)) ^ rotate_left(W[i - 13], 7) ^ W[i - 6])
W1 = []
for i in range(64):
W1.append(W[i] ^ W[i + 4])
A, B, C, D, E, F, G, H = V
for j in range(64):
SS1 = rotate_left((rotate_left(A, 12) + E + rotate_left(T[j], j % 32)) & 0xFFFFFFFF, 7)
SS2 = SS1 ^ rotate_left(A, 12)
TT1 = (FF(j, A, B, C) + D + SS2 + W1[j]) & 0xFFFFFFFF
TT2 = (GG(j, E, F, G) + H + SS1 + W[j]) & 0xFFFFFFFF
D = C
C = rotate_left(B, 9)
B = A
A = TT1
H = G
G = rotate_left(F, 19)
F = E
E = P0(TT2)
return [(x ^ y) & 0xFFFFFFFF for x, y in zip([A, B, C, D, E, F, G, H], V)]

#哈希计算函数
def sm3_hash(message):
message = sm3_padding(message)
V = IV
for i in range(0, len(message), 64):
block = message[i:i + 64]
V = sm3_process_block(block, V)
result = b''
for v in V:
result += v.to_bytes(4, byteorder='big')
return result.hex()


if __name__ == "__main__":
message = input("请输入要计算 SM3 哈希值的消息: ").encode()
hash_value = sm3_hash(message)
print("SM3 Hash:", hash_value)

六、示例

  1. 输入消息为“abc”

杂凑值:66c7f0f4 62eeedd9 d1f2d46b dc10e4e2 4167c487 5cf2f7a2 297da02b 8f4ba8e0

sm3.py

sm3_1.py

SM3在线加密工具

AES(Advanced Encryption Standard)

一、AES概述

AES加密算法,即Rijndael算法,是一种对称分组密码,它可以使用长度为128、192和256位的密钥处理128位的数据块。这些不同的“风格”可以称为“AES-128”、“AES-192” 和 “AES-256”。

本文使用python实现AES-128,使用CBC模式。

二、AES算法框架

  • 字节代替(SubBytes):用一个S盒完成分组的字节到字节的代替。
  • 行移位(ShiftRows):一个简单的置换。
  • 列混淆(MixColumns):利用域GF(2^8)上的算术特性的一个代替。
  • 轮密钥加(AddRoundKey):当前分组和扩展密钥的一部分进行按位异或XOR。

三、密钥扩展

解释

AES首先将初始密钥输入到一个4*4的状态矩阵中,如下图所示:

这个4*4矩阵的每一列的4个字节组成一个字,矩阵4列的4个字依次命名为W[0]、W[1]、W[2]和W[3],它们构成一个以字为单位的数组W。例如,设密钥K为”abcdefghijklmnop”,则K0 = ‘a’,K1 = ‘b’, K2 = ‘c’,K3 = ‘d’,W[0] = “abcd”。

接着,对W数组扩充40个新列,构成总共44列的扩展密钥数组。新列以如下的递归方式产生:

1.如果i不是4的倍数,那么第i列由如下等式确定:

W[i]=W[i-4]⨁W[i-1]

2.如果i是4的倍数,那么第i列由如下等式确定:

W[i]=W[i-4]⨁T(W[i-1])

其中,T是一个有点复杂的函数。

函数T由3部分组成:字循环、字节代换和轮常量异或,这3部分的作用分别如下。

a.字循环:将1个字中的4个字节循环左移1个字节。即将输入字[b0, b1, b2, b3]变换成[b1,b2,b3,b0]。

b.字节代换:对字循环的结果使用S盒进行字节代换。

c.轮常量异或:将前两步的结果同轮常量Rcon[j]进行异或,其中j表示轮数。

轮常量Rcon[j]是一个字,其值见下表。

j 1 2 3 4 5
Rcon[j] 01 00 00 00 02 00 00 00 04 00 00 00 08 00 00 00 10 00 00 00
j 6 7 8 9 10
Rcon[j] 20 00 00 00 40 00 00 00 80 00 00 00 1B 00 00 00 36 00 00 00

下面举个例子:

设初始的128位密钥为:

3C A1 0B 21 57 F0 19 16 90 2E 13 80 AC C1 07 BD

那么4个初始值为:

W[0] = 3C A1 0B 21

W[1] = 57 F0 19 16

W[2] = 90 2E 13 80

W[3] = AC C1 07 BD

下面求扩展的第1轮的子密钥(W[4],W[5],W[6],W[7])。

由于4是4的倍数,所以:

W[4] = W[0] ⨁ T(W[3])

T(W[3])的计算步骤如下:

循环地将W[3]的元素移位:AC C1 07 BD变成C1 07 BD AC;

将 C1 07 BD AC 作为S盒的输入,输出为78 C5 7A 91;

将78 C5 7A 91与第一轮轮常量Rcon[1]进行异或运算,将得到79 C5 7A 91,因此,T(W[3])=79 C5 7A 91,故

W[4] = 3C A1 0B 21 ⨁ 79 C5 7A 91 = 45 64 71 B0

其余的3个子密钥段的计算如下:

W[5] = W[1] ⨁ W[4] = 57 F0 19 16 ⨁ 45 64 71 B0 = 12 94 68 A6

W[6] = W[2] ⨁ W[5] =90 2E 13 80 ⨁ 12 94 68 A6 = 82 BA 7B 26

W[7] = W[3] ⨁ W[6] = AC C1 07 BD ⨁ 82 BA 7B 26 = 2E 7B 7C 9B

所以,第一轮的密钥为 45 64 71 B0 12 94 68 A6 82 BA 7B 26 2E 7B 7C 9B。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def gen_key(key):
key_bytes = key.encode('utf-8')
if len(key_bytes) != 16:
raise ValueError("Key must be 16 bytes after UTF-8 encoding.")
key_hex = [hex(b) for b in key_bytes]
key_rotate = []
w = [[] for i in range(0, 44)]
for i in range(0, 16):
w[i // 4].append(key_hex[i])
for i in range(4, 44):
gw = copy.deepcopy(w[i - 1])
if i % 4 == 0:
gw[0], gw[1], gw[2], gw[3] = gw[1], gw[2], gw[3], gw[0]
gw = substitute(gw) #g(w(i-1))
gw[0] = hex(int(gw[0], 16) ^ rcon[i // 4 - 1])
for j in range(0, 4):
w[i].append(hex(int(gw[j], 16) ^ int(w[i-4][j], 16)))
key_rotate = [w[i * 4] + w[i * 4 + 1] + w[i * 4 + 2] + w[ i* 4 + 3] for i in range(0, 11)] # 轮密钥列表,每个元素都是有16个字节的列表
return key_rotate

四、加解密过程

字节代换(SubBytes)

解释

AES的字节代换其实就是一个简单的查表操作。AES定义了一个S盒和一个逆S盒。
AES的S盒和逆S盒:

状态矩阵中的元素按照下面的方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,取出S盒或者逆S盒中对应的行的元素作为输出。例如,加密时,输出的字节S1为0x12,则查S盒的第0x01行和0x02列,得到值0xc9,然后替换S1原有的0x12为0xc9。状态矩阵经字节代换后的图如下:

S盒是按照这个公式计算出来的:GF(2^8) = GF(2) [x]/(x^8 + x^4 + x^3 + x + 1)

AES128加密-S盒和逆S盒构造推导及代码实现Rijndael S-box初识白盒密码有限域算术

代码
1
2
3
4
5
6
7
8
def substitute(m_hex, inverse=False):
m_s = []
box = s_box if not inverse else i_s_box
for i in m_hex:
x, y = int(i, 16) // 16, int(i, 16) % 16
temp = hex(box[x*16+y])
m_s.append(temp)
return m_s

行移位(ShiftRows)

解释

行移位是一个4x4的矩阵内部字节之间的置换,用于提供算法的扩散性。

行移位变换完成基于行的循环移位操作,变换方法为:第0行不变,第1行循环左移1个字节,第2行循环左移两个字节,第3行循环左移3个字节。如下图所示:(从上往下读)

逆向行移位即是相反的操作,即:第一行保持不变,第二行循环右移1个字节,第三行循环右移两个字节,第四行循环左移3个字节。

代码
1
2
3
4
5
6
7
8
9
10
11
12
def shiftrows(a, inverse=False): #inverse为True时表示为逆操作,默认为False
if not inverse:
return [ a[0], a[5], a[10], a[15],
a[4], a[9], a[14], a[3],
a[8], a[13], a[2], a[7],
a[12], a[1], a[6], a[11] ]
else :
return[ a[0], a[13], a[10], a[7],
a[4], a[1], a[14], a[11],
a[8], a[5], a[2], a[15],
a[12], a[9], a[6], a[3] ]

列混淆(MixColumns)

解释

列混淆是将状态数组的每一列乘以一个矩阵,其中乘法是在有限域GF(2^8)上进行的,分为正向列混淆和列混淆逆变换两种操作。正向列混淆用于加密操作,列混淆逆变换用于解密操作。
正向列混淆变换过程:

逆向列混淆变换可以再乘以矩阵的逆得到。

代码
1
2
3
4
5
6
7
8
9
10
11
12
def mixcolumn(m_row, inverse=False):
matrix = mix_column_matrix if not inverse else i_mix_column_matrix
m_col = []
for i in range(0, 16):
x, y = i % 4, i // 4
result = 0
for j in range(0, 4):
result ^= (mul(matrix[x * 4 + j], int(m_row[y * 4 + j], 16)))
result = mod(result)
m_col.append(hex(result))
return m_col

轮密钥加(AddRoundKey)

解释

轮密钥加是将轮密钥简单地与状态进行逐比特异或。这个操作相对简单,其依据的原理是“任何数和自身的异或结果为0”。加密过程中,每轮的输入与轮子密钥异或一次;因此,解密时再异或上该轮的轮子密钥即可恢复。

轮密钥加过程可以看成是字逐位异或的结果,也可以看成字节级别或者位级别的操作。也就是说,可以看成S0 S1 S2 S3 组成的32位字与W[4i]的异或运算。

五、完整代码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#分组长度为16个字节,加密10轮
#AES-128
import copy
import os

# 密钥扩展
def gen_key(key):
key_bytes = key.encode('utf-8')
if len(key_bytes) != 16:
raise ValueError("Key must be 16 bytes after UTF-8 encoding.")
key_hex = [hex(b) for b in key_bytes]
key_rotate = []
w = [[] for i in range(0, 44)]
for i in range(0, 16):
w[i // 4].append(key_hex[i])
for i in range(4, 44):
gw = copy.deepcopy(w[i - 1])
if i % 4 == 0:
gw[0], gw[1], gw[2], gw[3] = gw[1], gw[2], gw[3], gw[0]
gw = substitute(gw) #g(w(i-1))
gw[0] = hex(int(gw[0], 16) ^ rcon[i // 4 - 1])
for j in range(0, 4):
w[i].append(hex(int(gw[j], 16) ^ int(w[i-4][j], 16)))
key_rotate = [w[i * 4] + w[i * 4 + 1] + w[i * 4 + 2] + w[ i* 4 + 3] for i in range(0, 11)] # 轮密钥列表,每个元素都是有16个字节的列表
return key_rotate

# 两个多项式相乘
def mul(poly1, poly2):
result = 0
for index in range(poly2.bit_length()):
if poly2 & (1 << index):
result ^= (poly1 << index)
return result

# 多项式poly模多项式100011011
def mod(poly, mod = 0b100011011):
while poly.bit_length() > 8:
poly ^= (mod << (poly.bit_length() - 9))
return poly

#对输入的十六进制列表 m_hex 进行字节代换操作。
def substitute(m_hex, inverse=False):
m_s = []
box = s_box if not inverse else i_s_box
for i in m_hex:
x, y = int(i, 16) // 16, int(i, 16) % 16
temp = hex(box[x*16+y])
m_s.append(temp)
return m_s

s_box = [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16]
i_s_box = [0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D]

rcon = [0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]

def xor(a, key):
return [hex(int(ai, 16) ^ int(ki, 16)) for ai, ki in zip(a, key)]

# 行移位
def shiftrows(a, inverse=False): #inverse为True时表示为逆操作,默认为False
if not inverse:
return [ a[0], a[5], a[10], a[15],
a[4], a[9], a[14], a[3],
a[8], a[13], a[2], a[7],
a[12], a[1], a[6], a[11] ]
else :
return[ a[0], a[13], a[10], a[7],
a[4], a[1], a[14], a[11],
a[8], a[5], a[2], a[15],
a[12], a[9], a[6], a[3] ]

# 列混淆
def mixcolumn(m_row, inverse=False):
matrix = mix_column_matrix if not inverse else i_mix_column_matrix
m_col = []
for i in range(0, 16):
x, y = i % 4, i // 4
result = 0
for j in range(0, 4):
result ^= (mul(matrix[x * 4 + j], int(m_row[y * 4 + j], 16)))
result = mod(result)
m_col.append(hex(result))
return m_col

# 列混合乘的矩阵
mix_column_matrix = [0x2, 0x3, 0x1, 0x1,
0x1, 0x2, 0x3, 0x1,
0x1, 0x1, 0x2, 0x3,
0x3, 0x1, 0x1, 0x2]
# 列混合乘的逆矩阵
i_mix_column_matrix = [0xe, 0xb, 0xd, 0x9,
0x9, 0xe, 0xb, 0xd,
0xd, 0x9, 0xe, 0xb,
0xb, 0xd, 0x9, 0xe]


def aes_encrypt_block(block, key_rotate):
state = block
state = xor(state, key_rotate[0])
for rnd in range(1, 10):
state = substitute(state)
state = shiftrows(state)
state = mixcolumn(state)
state = xor(state, key_rotate[rnd])
state = substitute(state)
state = shiftrows(state)
state = xor(state, key_rotate[10])
return [int(b, 16) for b in state]

def aes_decrypt_block(block, key_rotate):
state = block
state = xor(state, key_rotate[10])
state = shiftrows(state, inverse=True)
state = substitute(state, inverse=True)
for rnd in range(9, 0, -1):
state = xor(state, key_rotate[rnd])
state = mixcolumn(state, inverse=True)
state = shiftrows(state, inverse=True)
state = substitute(state, inverse=True)
state = xor(state, key_rotate[0])
return [int(b, 16) for b in state]

def pad(data):
pad_len = 16 - (len(data) % 16)
return data + bytes([pad_len] * pad_len)

def unpad(data):
pad_len = data[-1]
return data[:-pad_len]


def aes_cbc_encrypt(plaintext, key):
key_rotate = gen_key(key)
iv = os.urandom(16)
print(f"\n[生成随机IV] (16字节): {iv.hex()}")
plaintext = pad(plaintext.encode())
blocks = [plaintext[i:i+16] for i in range(0, len(plaintext), 16)]
ciphertext = iv
prev = iv
for block in blocks:
block = bytes([b ^ p for b, p in zip(block, prev)])
encrypted = aes_encrypt_block([hex(b)[2:].zfill(2) for b in block], key_rotate)
encrypted_bytes = bytes(encrypted)
ciphertext += encrypted_bytes
prev = encrypted_bytes
return ciphertext.hex()

def aes_cbc_decrypt(ciphertext, key):
key_rotate = gen_key(key)
ciphertext = bytes.fromhex(ciphertext)
iv = ciphertext[:16]
blocks = [ciphertext[i:i+16] for i in range(16, len(ciphertext), 16)]
plaintext = b''
prev = iv
for block in blocks:
decrypted = aes_decrypt_block([hex(b)[2:].zfill(2) for b in block], key_rotate)
decrypted = bytes([d ^ p for d, p in zip(decrypted, prev)])
plaintext += decrypted
prev = block
return unpad(plaintext).decode()

if __name__ == '__main__':
mode = input("请输入模式 (encrypt/decrypt): ").strip().lower()
key = input("请输入16字节密钥: ").strip()
if len(key.encode('utf-8')) != 16:
print("错误:密钥长度必须为16字节!")
else:
if mode == "encrypt":
plaintext = input("请输入明文: ").strip()
print("加密后密文:", aes_cbc_encrypt(plaintext, key))
elif mode == "decrypt":
ciphertext = input("请输入密文 (16进制字符串): ").strip()
try:
print("解密后明文:", aes_cbc_decrypt(ciphertext, key))
except Exception as e:
print(f"解密失败: {e}")
else:
print("无效模式,请输入 encrypt 或 decrypt")

aes.py

aes1.py –有每轮结果的版本

DES(Data Encryption_Standard)

DES是一种对称密钥的块加密算法。谓之 “对称密钥”,是因为加密、解密用的密钥是一样的(这不同于 RSA 等非对称密钥体系)。谓之 “块加密”,是因为这种算法把明文划分为很多个等长的块(block),对每个块进行加密,最后以某种手段拼在一起。“块加密”亦称“分组加密”。

DES概述

DES 的功能是:给定一个 64 位的明文和一个 64 位的密钥,输出一个 64 位的密文。这个密文可以用相同的密钥解密。所谓“64位的密钥”,其实里面只有56位在起作用。剩余的位可以直接丢弃,或者当作奇偶校验位。

虽然 DES 一次只能加密 8 个字节,但我们只需要把明文划分成每 8 个字节一组的块,就可以实现任意长度明文的加密。如果明文长度不是 8 个字节的倍数,还得进行填充。现在流行的填充方式是 PKCS7 / PKCS5

算法特点:分组比较短、密钥太短、密码生命周期短、运算速度较慢。

Key(密钥):为7个字节共56位,是DES算法的工作密钥(若说密钥为64位,其指的也是56位的秘钥加上8位奇偶校验位,奇偶校验位为第8,16,24,32,40,48,56,64位)
Data(数据):为8个字节64位,是要被加密或被解密的数据
Mode(模式): 为DES的工作方式,有两种:加密或解密。

DES算法框架

DES 算法是在 Feistel network (费斯妥网络)的基础上执行的。以下是 DES 算法的流程图:

可以看到整个算法分为两大部分——迭代加密(左边的 16 轮迭代操作),以及密钥调度(右边生成子密钥的算法)。

所谓密钥调度,就是从一把 64-bit 的主钥匙,得到 16 把 48-bit 的子钥匙。

每一轮迭代,都是接收一组 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L, R</font>,返回 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L', R'</font> ,作为下一轮迭代的 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L, R</font> . 迭代过程如下:

L’=R

R’=L^F(R,subkey)

其中F函数(称为轮函数)是整个算法的核心,功能是:以一个子密钥,加密 32-bit 的信息。

密钥调度算法


首先,采用“选择置换1 (PC-1)”,从 64 位 key 里面选出 56 位。这一步属于encode,对信息安全没有帮助。PC-1 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 选择置换1,输入 key 为长度为 64 的 0/1 数组
# 从64位输入密钥中选择56位,分为左右两个28位半密钥
def PC1(key):
pc1_l = [57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36]
pc1_r = [63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4]

return [key[x-1] for x in pc1_l], [key[x-1] for x in pc1_r]

经过 PC-1 之后,我们有了左、右两个半密钥,长度都是 28 位。接下来,我们每一轮把左、右半密钥左旋几位,再调用 PC-2 方法来造子密钥。框架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 子密钥生成算法,由一个64位主密钥导出16个48位子密钥
def keyGen(key):
assert len(key) == 64

l, r = PC1(key)
off = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
res = []

for x in range(16):
l = leftRotate(l, off[x])
r = leftRotate(r, off[x])

res.append(PC2(l + r))

return res

其中, <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">leftRotate</font> 是循环左移,实现如下:

1
2
3
4
5
# 循环左移off位
def leftRotate(a, off):
return a[off:] + a[:off]

assert leftRotate([0, 1, 0, 1, 1], 2) == [0, 1, 1, 0, 1]

PC-2 又是一个简单置换,用于从左右半密钥拼起来的 56 位密钥中,选取 48 位作为一个子密钥。实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 选择置换2
# 从56位的密钥中选取48位子密钥
def PC2(key):
assert len(key) == 56

pc2 = [14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32]
return [key[x-1] for x in pc2]

这样,我们就实现了密钥调度算法。执行 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">keyGen</font> ,即可获得 16 把子密钥,长度均为 48-bit. 不难看出,整个密钥调度的过程都是对主密钥的 encode. 生成这么多子密钥的目的,是使得加密迭代变得更加复杂、难以分析。

加密迭代

加密迭代的过程是先把信息进行一次初始置换(IP置换);再进行 16 轮迭代;最后再给 (R+L) 这个数组来一次最终置换(FP置换),即可输出作为密文。框架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def DES(plain, key, method):
subkeys = keyGen(int2bin(key, 64))

if method == 'decrypt':
subkeys = subkeys[::-1]

m = IP(int2bin(plain, 64))

l, r = np.array(m, dtype=int).reshape(2, -1).tolist()

for i in range(16):
l, r = goRound(l, r, subkeys[i])

return bin2int(FP(r + l))

在这里, <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">goRound</font> 是提供 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L, R</font> 和本轮加密使用的子密钥,返回 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L', R'</font> 以供下一轮迭代。 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">IP</font><font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">FP</font> 都是简单置换,对于密码安全没有任何意义,只是一个历史遗留原因。规定如下:

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
# 初始置换
def IP(a):
ip = [58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7]
return [a[x-1] for x in ip]
testM = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1]
assert IP(testM) == [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0]

# 最终置换
def FP(a):
fp = [40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25]
return [a[x-1] for x in fp]

注意到 FP 就是 IP 的逆函数。这是为了保证“加密、解密算法几乎完全相同”。而至于 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">goRound</font> ,我们知道它干的事情是

L’=R

R’=L^F(R,subkey)

代码实现如下:

1
2
def goRound(l, r, subKey):
return r, binXor(l, Feistel(r, subKey))

因此,DES 的安全性在很大程度上取决于 F 函数,也就是轮函数。那么 Feistel 函数是干了什么事呢?来看下面一张流程图:

一个 32-bit 的块,经过一个扩张(Expand函数),变成 48 位,然后与子密钥异或。得到的 48-bit 的结果分为 8 组,每一组是 6-bit 的数据,丢进对应的 S 盒,输出 4-bit 的信息。把这些输出收集起来,一共是 4*8 = 32 位,做一次置换 (P 置换),得到 32-bit 的结果。这与输进来的 32-bit 信息是等长度的。

1
2
3
4
5
6
7
8
9
10
# F函数,用于处理一个半块
def Feistel(a, subKey):
assert len(a) == 32
assert len(subKey) == 48

t = binXor(Expand(a), subKey)
t = S(t)
t = P(t)

return t

Expand 算法是指定的,P 置换是一个简单置换,因此都是 encode 过程。而这个 32-bit 的半块与 subkey 的混合过程,以及 S 盒提供的强有力的混淆能力,提供了 DES 体系的核心安全性。

Expand 算法如下:

1
2
3
4
5
6
7
8
9
10
11
12
# 扩张置换,将32位的数据扩展到48位
def Expand(a):
assert len(a) == 32
e = [32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1]
return [a[x-1] for x in e]

P 置换如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# P置换
def P(a):
assert len(a) == 32

p = [16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25]
return [a[x-1] for x in p]

需要注意一点:这个 P 置换是精心设计的,使得这一轮同一个 S 盒 输出的四个 bit,在下一回合的扩张之后,交由四个不同的 S 盒去处理。

接下来唯一要处理的就是 S 盒了。它一共有 8 张表,表长成下面这个样子:

扩张之后的半块与子密钥异或之后,得到了 48 位结果;这些结果分成 8 个组,然后第一组使用 S1 这张表进行变换,第二组使用 S2 进行变换……依次类推。现在我们假设第二组是 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">101100</font> ,来看它在 S盒变换之后会得到什么结果:

由于这是第二组,故查询 S2 表。
它的首位、末尾是 10 ,故查询第三行。
它的中间 4 位是 0110 ,查表知结果是 13.
把 13 转为二进制,得到 1101 ,于是这就是输出。

代码实现:
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
# S盒变换,输入48位,输出32位
def S(a):
assert len(a) == 48

S_box = [[14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7,
0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8,
4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0,
15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13],
[15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10,
3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5,
0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15,
13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9],
[10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8,
13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1,
13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7,
1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12],
[7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15,
13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9,
10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4,
3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14],
[2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9,
14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6,
4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14,
11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3],
[12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11,
10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8,
9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6,
4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13],
[4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1,
13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6,
1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2,
6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12],
[13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7,
1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2,
7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8,
2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11]]

a = np.array(a, dtype=int).reshape(8, 6)
res = []

for i in range(8):
# 用 S_box[i] 处理6位a[i],得到4位输出
p = a[i]
r = S_box[i][bin2int([p[0], p[5], p[1], p[2], p[3], p[4]])]
res.append(int2bin(r, 4))

res = np.array(res).flatten().tolist()
assert len(res) == 32

return res

完整代码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
from functools import reduce
import numpy as np

def pad_data(data):
""" 对输入数据进行 64-bit 块填充(PKCS7 填充) """
byte_data = data.encode('utf-8') # 转换为字节
pad_len = 8 - (len(byte_data) % 8) # 计算填充长度
byte_data += bytes([pad_len] * pad_len) # 添加填充字节
return [int.from_bytes(byte_data[i:i+8], 'big') for i in range(0, len(byte_data), 8)]

def unpad_data(data_blocks):
""" 去除填充并转换回字符串 """
byte_data = b''.join(block.to_bytes(8, 'big') for block in data_blocks)
pad_len = byte_data[-1] # 获取填充的字节数
return byte_data[:-pad_len].decode('utf-8')


# 整数转二进制数组,指定位长 n,大端序
def int2bin(a, n):
assert 0<=n and a < 2**n # 断言,确保 n 为非负数,且 a 在 n 位二进制能表示的范围内
res = np.zeros(n, dtype = int)

for x in range(n):
res[n-x-1] = a % 2
a = a // 2
return res.tolist()

assert int2bin(0x1a, 10) == [0, 0, 0, 0, 0, 1, 1, 0, 1, 0] #验证

# 二进制数组转整数,大端序 左移方式 (x * 2 + y) 处理二进制位。
def bin2int(a):
return reduce(lambda x,y: x*2+y, a)

assert bin2int([0, 0, 0, 0, 0, 1, 1, 0, 1, 0]) == 0x1a

# 循环左移off位
def leftRotate(a, off):
return a[off:] + a[:off]

assert leftRotate([0, 1, 0, 1, 1], 2) == [0, 1, 1, 0, 1]

# 异或
def binXor(a, b):
assert len(a) == len(b)
return [x^y for x, y in zip(a, b)]

assert binXor([1, 1, 0, 1], [0, 1, 1, 0]) == [1, 0, 1, 1]

# 初始置换
def IP(a):
ip = [58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7]
return [a[x-1] for x in ip]

testM = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1]
assert IP(testM) == [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0]

# 最终置换
def FP(a):
fp = [40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25]
return [a[x-1] for x in fp]

# 选择置换1
# 从64位输入密钥中选择56位,分为左右两个28位半密钥
def PC1(key):
pc1_l = [57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36]
pc1_r = [63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4]

return [key[x-1] for x in pc1_l], [key[x-1] for x in pc1_r]

testKey = [0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1]
testL, testR = PC1(testKey)
assert testL + testR == [1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1]

# 选择置换2
# 从56位的密钥中选取48位子密钥
def PC2(key):
assert len(key) == 56

pc2 = [14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32]
return [key[x-1] for x in pc2]

# 子密钥生成算法,由一个64位主密钥导出16个48位子密钥
def keyGen(key):
assert len(key) == 64

l, r = PC1(key)
off = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
res = []

for x in range(16):
l = leftRotate(l, off[x])
r = leftRotate(r, off[x])

res.append(PC2(l + r))

return res

assert keyGen(testKey)[-1] == [1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1]

# S盒变换,输入48位,输出32位
def S(a):
assert len(a) == 48

S_box = [[14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7,
0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8,
4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0,
15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13],
[15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10,
3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5,
0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15,
13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9],
[10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8,
13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1,
13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7,
1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12],
[7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15,
13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9,
10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4,
3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14],
[2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9,
14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6,
4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14,
11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3],
[12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11,
10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8,
9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6,
4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13],
[4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1,
13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6,
1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2,
6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12],
[13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7,
1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2,
7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8,
2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11]]

a = np.array(a, dtype=int).reshape(8, 6)
res = []

for i in range(8):
# 用 S_box[i] 处理6位a[i],得到4位输出
p = a[i]
r = S_box[i][bin2int([p[0], p[5], p[1], p[2], p[3], p[4]])]
res.append(int2bin(r, 4))

res = np.array(res).flatten().tolist()
assert len(res) == 32

return res

# 扩张置换,将32位的半块扩展到48位
def Expand(a):
assert len(a) == 32
e = [32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1]
return [a[x-1] for x in e]

# P置换
def P(a):
assert len(a) == 32

p = [16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25]
return [a[x-1] for x in p]

# F函数,用于处理一个半块
def Feistel(a, subKey):
assert len(a) == 32
assert len(subKey) == 48

t = binXor(Expand(a), subKey)
t = S(t)
t = P(t)

return t

def goRound(l, r, subKey):
return r, binXor(l, Feistel(r, subKey))

def DES(plain, key, method):
subkeys = keyGen(int2bin(key, 64))

if method == 'decrypt':
subkeys = subkeys[::-1]

m = IP(int2bin(plain, 64))

l, r = np.array(m, dtype=int).reshape(2, -1).tolist()

for i in range(16):
l, r = goRound(l, r, subkeys[i])

return bin2int(FP(r + l))


# print(hex(DES(0x11aabbccddeeff01, 0xcafababedeadbeaf, 'encrypt')))
# # 0x2973a7e54ec730a3
# print(hex(DES(0x2973a7e54ec730a3, 0xcafababedeadbeaf, 'decrypt')))
# # 0x11aabbccddeeff01


# ECB模式加密
def des_ecb_encrypt(plain_blocks, key):
"""
使用 DES 电子密码本(ECB)模式对明文块列表进行加密。

:param plain_blocks: list[int],64-bit 明文块列表
:param key: int,64-bit 密钥
:return: list[int],加密后的 64-bit 密文块列表
"""
return [DES(block, key, 'encrypt') for block in plain_blocks]

# ECB模式解密
def des_ecb_decrypt(cipher_blocks, key):
return [DES(block, key, 'decrypt') for block in cipher_blocks]

def get_input_blocks():
""" 让用户输入数据,并转换为 64-bit 块 """
data = input("请输入数据(可以是字符串、整数或十六进制):").strip()
if data.startswith("0x"): # 处理十六进制输入
blocks = [int(data, 16)]
elif data.isdigit(): # 处理整数输入
blocks = [int(data)]
else: # 处理字符串输入
blocks = pad_data(data)
return blocks

def main():
key = int(input("请输入 64-bit 密钥(16进制):"), 16) # 获取密钥
mode = input("请选择模式(encrypt/decrypt):").strip().lower()

if mode == 'encrypt':
plaintext_blocks = get_input_blocks()
cipher_blocks = des_ecb_encrypt(plaintext_blocks, key)
print("加密后:", [hex(c) for c in cipher_blocks])
elif mode == 'decrypt':
cipher_blocks = get_input_blocks()
decrypted_blocks = des_ecb_decrypt(cipher_blocks, key)
try:
print("解密后:", unpad_data(decrypted_blocks)) # 试图转换回字符串
except:
print("解密后:", [hex(c) for c in decrypted_blocks]) # 不是字符串时输出十六进制
else:
print("无效模式,请输入 'encrypt' 或 'decrypt'")

if __name__ == "__main__":
main()


工作模式:

  • ECB模式:每个明文块独立加密,无依赖关系。
  • CBC模式:每个块加密前与前一个密文异或,需初始向量IV。

des1.py –有每轮结果的版本

des.py