1.European ePassport

MTC3 AES key — encoded in the machine readable zone of a European ePassport (link)
题目需要破解一个欧洲护照,图片中给出了护照的一部分内容,和一个未知字符。已知初始化矢量即IV为零,填充为01-00。所以首先我们需要找到缺失的字符是什么。根据规则定义了unknown_number()函数。

1
2
3
4
5
6
7
def unknown_number():
number = "111116"
weight = "731"
total = 0
for i in range(len(number)):
total += int(number[i]) * int(weight[i % 3])
return total % 10 #7

经过调用函数我们找到缺失的字符是7所以我们能够补齐护照得到12345678<8<<<1110182<1111167<<<<<<<<<<<<<<<4。然后我们利用这个护照序列
计算出k_seed。

1
2
3
4
def calculate_kseed():
MRZ_information = "12345678<8<<<1110182<1111167<<<<<<<<<<<<<<<4"
H_information = hashlib.sha1((MRZ_information[:10] + MRZ_information[13:20] + MRZ_information[21:28]).encode()).hexdigest()
return H_information[:32]

从 K_seed 计算出 Ka 和 Kb

1
2
3
4
def calculate_ka_kb(K_seed):
d = K_seed + "00000001"
H_d = hashlib.sha1(binascii.unhexlify(d)).hexdigest()
return H_d[:16], H_d[16:32]

分别对Ka和Kb进行奇偶校验

1
2
3
4
def parity_check(hex_str):
binary_str = bin(int(hex_str, 16))[2:].zfill(64)
k_list = [(byte := binary_str[i:i + 7]) + ('1' if byte.count('1') % 2 == 0 else '0') for i in range(0, len(binary_str), 8)]
return hex(int(''.join(k_list), 2))[2:].zfill(16)

得到key之后解密密文删掉填充得到最终答案

1
2
Key: ea8645d97ff725a898942aa280c43179
Decrypted message: Herzlichen Glueckwunsch. Sie haben die Nuss geknackt. Das Codewort lautet: Kryptographie!

完整代码

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
import hashlib
import base64
from Crypto.Cipher import AES
import binascii

def pad(text):
padding_len = AES.block_size - len(text) % AES.block_size
padding = b'\x01' + b'\x00' * (padding_len - 1)
return text + padding

def unpad(text):
return text.rstrip(b'\x00').rstrip(b'\x01')

def unknown_number():
number = "111116"
weight = "731"
total = sum(int(number[i]) * int(weight[i % 3]) for i in range(len(number)))
return total % 10

def calculate_kseed():
MRZ_information = "12345678<8<<<1110182<1111167<<<<<<<<<<<<<<<4"
H_information = hashlib.sha1((MRZ_information[:10] + MRZ_information[13:20] + MRZ_information[21:28]).encode()).hexdigest()
return H_information[:32]

def calculate_ka_kb(K_seed):
d = K_seed + "00000001"
H_d = hashlib.sha1(binascii.unhexlify(d)).hexdigest()
return H_d[:16], H_d[16:32]

def parity_check(hex_str):
binary_str = bin(int(hex_str, 16))[2:].zfill(64)
k_list = [(byte := binary_str[i:i + 7]) + ('1' if byte.count('1') % 2 == 0 else '0') for i in range(0, len(binary_str), 8)]
return hex(int(''.join(k_list), 2))[2:].zfill(16)

def decrypt_message(encrypted_text):
K_seed = calculate_kseed()
ka, kb = calculate_ka_kb(K_seed)
key = parity_check(ka) + parity_check(kb)
print(f"Key: {key}")

ciphertext = base64.b64decode(encrypted_text)
IV = '0' * 32

cipher = AES.new(binascii.unhexlify(key), AES.MODE_CBC, binascii.unhexlify(IV))
decrypted_padded = cipher.decrypt(ciphertext)
decrypted_message = unpad(decrypted_padded).decode('utf-8', errors='ignore')

print(f"Decrypted message: {decrypted_message}")

if __name__ == "__main__":
print(unknown_number())
encrypted_text = '9MgYwmuPrjiecPMx61O6zIuy3MtIXQQ0E59T3xB6u0Gyf1gYs2i3K9Jxaa0zj4gTMazJuApwd6+jdyeI5iGHvhQyDHGVlAuYTgJrbFDrfB22Fpil2NfNnWFBTXyf7SDI'
decrypt_message(encrypted_text)

2. Crypto Challenge Set 2

(1) Implement PKCS#7 padding

如题目所述就是应用PKCS#7这种填充方法

1
2
3
4
def pkcs7_pad(text, block_size):
padding_len = block_size - (len(text) % block_size)
padding = bytes([padding_len] * padding_len)
return text + padding

(2) Implement CBC mode

首先需要按照题目所述写一个ECB函数如下

1
2
3
def ecb_decrypt(cipher_text, key):
cipher = AES.new(key, AES.MODE_ECB)
return cipher.decrypt(cipher_text)

因为这个是基于CBC的密码所以说要写一个解密CBC的函数并且输出去除PKCS#7填充的明文

1
2
3
4
5
6
7
8
9
10
11
12
def cbc_decrypt(cipher_text, key, iv):
block_size = len(key)
plain_text = b''
previous_block = iv

for i in range(0, len(cipher_text), block_size):
block = cipher_text[i:i + block_size]
decrypted_block = ecb_decrypt(block, key)
decrypted_block = xor_bytes(decrypted_block, previous_block)
plain_text += decrypted_block
previous_block = block
return pkcs7_unpad(plain_text)

最后写出我们的 main函数运算,输入已知的key'YELLOW SUBMARINE'和16位空IV
以及读取密文文件。

1
2
3
4
5
6
7
if __name__ == "__main__":
key = b'YELLOW SUBMARINE'
iv = b'\x00' * 16
with open("2_2.txt",'r') as f:
ciphertext = base64.b64decode(f.read())
plain_text = cbc_decrypt(ciphertext, key, iv)
print(f"plaintext: {plain_text.decode()}")

通过调用函数能够得到最终答案

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
I'm back and I'm ringin' the bell 
A rockin' on the mike while the fly girls yell
In ecstasy in the back of me
Well that's my DJ Deshay cuttin' all them Z's
Hittin' hard and the girlies goin' crazy
Vanilla's on the mike, man I'm not lazy.

I'm lettin' my drug kick in
It controls my mouth and I begin
To just let it flow, let my concepts go
My posse's to the side yellin', Go Vanilla Go!

Smooth 'cause that's the way I will be
And if you don't give a damn, then
Why you starin' at me
So get off 'cause I control the stage
There's no dissin' allowed
I'm in my own phase
The girlies sa y they love me and that is ok
And I can dance better than any kid n' play

Stage 2 -- Yea the one ya' wanna listen to
It's off my head so let the beat play through
So I can funk it up and make it sound good
1-2-3 Yo -- Knock on some wood
For good luck, I like my rhymes atrocious
Supercalafragilisticexpialidocious
I'm an effect and that you can bet
I can take a fly girl and make her wet.

I'm like Samson -- Samson to Delilah
There's no denyin', You can try to hang
But you'll keep tryin' to get my style
Over and over, practice makes perfect
But not if you're a loafer.

You'll get nowhere, no place, no time, no girls
Soon -- Oh my God, homebody, you probably eat
Spaghetti with a spoon! Come on and say it!

VIP. Vanilla Ice yep, yep, I'm comin' hard like a rhino
Intoxicating so you stagger like a wino
So punks stop trying and girl stop cryin'
Vanilla Ice is sellin' and you people are buyin'
'Cause why the freaks are jockin' like Crazy Glue
Movin' and groovin' trying to sing along
All through the ghetto groovin' this here song
Now you're amazed by the VIP posse.

Steppin' so hard like a German Nazi
Startled by the bases hittin' ground
There's no trippin' on mine, I'm just gettin' down
Sparkamatic, I'm hangin' tight like a fanatic
You trapped me once and I thought that
You might have it
So step down and lend me your ear
'89 in my time! You, '90 is my year.

You're weakenin' fast, YO! and I can tell it
Your body's gettin' hot, so, so I can smell it
So don't be mad and don't be sad
'Cause the lyrics belong to ICE, You can call me Dad
You're pitchin' a fit, so step back and endure
Let the witch doctor, Ice, do the dance to cure
So come up close and don't be square
You wanna battle me -- Anytime, anywhere

You thought that I was weak, Boy, you're dead wrong
So come on, everybody and sing this song

Say -- Play that funky music Say, go white boy, go white boy go
play that funky music Go white boy, go white boy, go
Lay down and boogie and play that funky music till you die.

Play that funky music Come on, Come on, let me hear
Play that funky music white boy you say it, say it
Play that funky music A little louder now
Play that funky music, white boy Come on, Come on, Come on
Play that funky music

(((貌似是一段歌词

(3) An ECB/CBC detection oracle

按照题目意思首先生成随机AES密钥。

1
2
def generate_random_aes_key():
return os.urandom(16)

之后创建一个加密oracle,在明文之前和之后附加随机字节,然后使用ECB或CBC模式随机加密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def encryption_oracle(input_data):
key = generate_random_aes_key()
prepend = os.urandom(random.randint(5, 10))
append = os.urandom(random.randint(5, 10))
plain_text = prepend + input_data + append

if random.randint(0, 1) == 0:# 使用ECB模式
cipher = AES.new(key, AES.MODE_ECB)
padded_text = pkcs7_pad(plain_text, AES.block_size)
encrypted = cipher.encrypt(padded_text)
mode = "ECB"
else:# 使用CBC模式
iv = os.urandom(AES.block_size)
encrypted = cbc_encrypt(plain_text, key, iv)
mode = "CBC"
return encrypted, mode

最后编写一个函数来检测使用的加密模式是ECB还是CBC,这里用在ECB模式下,相同的明文块会生成相同的密文块而CBC模式下,每个明文块在加密前会与前一个密文块进行异或操作,使得相同的明文块生成不同的密文块。所以运用这个不同来判断是什么模式。

1
2
3
4
5
6
7
def detect_encryption_mode(encrypted_data):
block_size = AES.block_size
blocks = [encrypted_data[i:i + block_size] for i in range(0, len(encrypted_data), block_size)]
if len(set(blocks)) != len(blocks):
return "ECB"
else:
return "CBC"

最后调用main函数,这里的明文是三块一样的就可以用上吗的方法判断了。

1
2
3
4
5
6
7
8
if __name__ == "__main__":
key = b'YELLOW SUBMARINE'
iv = b'\x00' * 16 * 3
plain_text = b"\xFF" * 16 * 3
encrypted, mode = encryption_oracle(plain_text)
detected_mode = detect_encryption_mode(encrypted)
print(f"Actual mode: {mode}")
print(f"Detected mode: {detected_mode}")

(4) Byte-at-a-time ECB decryption (Simple)

就是攻击函数就是写一堆A然后把不知道的放在最后,像这样“AAAT”A已知T未知的数据块进行加密,并将得到的密文存储。然后,你向oracle请求加密“AAAi”的数据块识别第一个目标字节,当返回的密文与之前存储密文相同时,知道字节i就是目标字节T。

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
import base64
from Crypto import Random
from Crypto.Cipher import AES

UNKNOWN_STRING = base64.b64decode(
b"Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg"
b"aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq"
b"dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg"
b"YnkK"
)
KEY = Random.new().read(16)

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

def encryption_oracle(your_string):
plaintext = pad(your_string + UNKNOWN_STRING)
cipher = AES.new(KEY, AES.MODE_ECB)
return cipher.encrypt(plaintext)

def detect_block_size():
initial_len = len(encryption_oracle(b""))
for i in range(1, 256):
data = b"A" * i
new_len = len(encryption_oracle(data))
if new_len != initial_len:
return new_len - initial_len

def detect_mode(cipher):
block_size = 16
blocks = [cipher[i:i + block_size] for i in range(0, len(cipher), block_size)]
return "ECB" if len(blocks) > len(set(blocks)) else "not ECB"

def ecb_decrypt(block_size):
known_bytes = b""
while True:
block_index = len(known_bytes) // block_size
block_offset = block_size - 1 - (len(known_bytes) % block_size)
prefix = b"A" * block_offset
target_block = encryption_oracle(prefix)[: (block_index + 1) * block_size]

found = False
for i in range(256):
guess = prefix + known_bytes + bytes([i])
if encryption_oracle(guess)[: (block_index + 1) * block_size] == target_block:
known_bytes += bytes([i])
found = True
break
if not found:
print(f"Decrypted text: {known_bytes.decode('ascii', errors='ignore')}")
return

def main():
block_size = detect_block_size()
print(f"Detected block size: {block_size}")
cipher = encryption_oracle(b"A" * 50)
mode = detect_mode(cipher)
print(f"Detected mode: {mode}")
ecb_decrypt(block_size)

if __name__ == "__main__":
main()

运行结果

1
2
3
4
Rollin' in my 5.0
With my rag-top down so my hair can blow
The girlies on standby waving just to say hi
Did you stop? No, I just drove by

(5) ECB cut-and-paste

这个题就是读取这个信息然后能够把有用的信息提出来比如email、uid等信息
我在这里设置了自动生成的密钥,在给定信息后加密、解密、然后打印识别的信息。

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
import re
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes

def parse_kv(kv_string):
return dict(pair.split('=') for pair in kv_string.split('&'))

def profile_for(email):
email = re.sub(r'[&=]', '', email)
profile = {
'email': email,
'uid': 10,
'role': 'user'
}
return f"email={profile['email']}&uid={profile['uid']}&role={profile['role']}"

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

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

def generate_key():
return get_random_bytes(16)

def encrypt_profile(profile, key):
cipher = AES.new(key, AES.MODE_ECB)
padded_profile = pad(profile.encode())
return cipher.encrypt(padded_profile)

def decrypt_profile(ciphertext, key):
cipher = AES.new(key, AES.MODE_ECB)
decrypted = unpad(cipher.decrypt(ciphertext))
return decrypted.decode()

def create_admin_profile():
key = generate_key()
email1 = "foo@bar.com"
email2 = "foo@bar.comadmin" + "\x0b" * 11

encrypted1 = encrypt_profile(profile_for(email1), key)
encrypted2 = encrypt_profile(profile_for(email2), key)
crafted_ciphertext = encrypted1[:32] + encrypted2[16:32]

decrypted_profile = decrypt_profile(crafted_ciphertext, key)
return parse_kv(decrypted_profile)

if __name__ == "__main__":
print(parse_kv("foo=bar&baz=qux&zap=zazzle"))
print(profile_for("foo@bar.com"))
key = generate_key()
encrypted = encrypt_profile(profile_for("foo@bar.com"), key)
print('---------------------------------')
print(decrypt_profile(encrypted, key))
print(create_admin_profile())

输出

1
2
3
4
5
{'foo': 'bar', 'baz': 'qux', 'zap': 'zazzle'}
email=foo@bar.com&uid=10&role=user
---------------------------------
email=foo@bar.com&uid=10&role=user
{'email': 'foo@bar.com', 'uid': '10', 'role': 'usmadmi'}

(6) Byte-at-a-time ECB decryption (Harder)

随机前缀的长度是不确定的,所以说我们首先需要确定前缀的长度。
由于有一个随机前缀,在逐渐增加输入的过程中,某一段的密文会变得稳定,这表示该块已完全由前缀和我们构造的输入组成。然后其他的和前面那个simple是一样的。

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
import os
import random
from random import randint
import base64
from Crypto.Cipher import AES
from Crypto.Util import Padding

key = os.urandom(16)
prefix = os.urandom(randint(1, 15))
target = base64.b64decode(
"Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg"
"aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq"
"dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg"
"YnkK"
)
def encrypt(message):
plaintext = Padding.pad(prefix + message + target, 16)
cipher = AES.new(key, AES.MODE_ECB)
return cipher.encrypt(plaintext)


# Finding the block size
previous_length = len(encrypt(b''))
for i in range(20):
length = len(encrypt(b'X' * i))
if length != previous_length:
block_size = length - previous_length
size_prefix_plus_target_aligned = previous_length
min_known_ptxt_size_to_align = i
break
else:
raise Exception('did not detect any change in ciphertext length')

assert block_size == 16

# Finding the prefix size
def split_bytes_in_blocks(data, block_size):
return [data[i:i + block_size] for i in range(0, len(data), block_size)]

previous_blocks = None
for i in range(1, block_size + 1):
blocks = split_bytes_in_blocks(encrypt(b'X' * i), block_size)
if previous_blocks is not None and blocks[0] == previous_blocks[0]:
prefix_size = block_size - i + 1
break
previous_blocks = blocks
else:
raise Exception('did not detect constant ciphertext block')

assert prefix_size == len(prefix)

# Compute the size of the target
target_size = size_prefix_plus_target_aligned - min_known_ptxt_size_to_align - prefix_size
assert target_size == len(target)

# Decrypt the target bytes
known_target_bytes = b""
for _ in range(target_size):
r = prefix_size
k = len(known_target_bytes)
padding_length = (-k - 1 - r) % block_size
padding = b"X" * padding_length

target_block_number = (k + r) // block_size
target_slice = slice(target_block_number * block_size, (target_block_number + 1) * block_size)
target_block = encrypt(padding)[target_slice]

for i in range(256):
message = padding + known_target_bytes + bytes([i])
block = encrypt(message)[target_slice]
if block == target_block:
known_target_bytes += bytes([i])
break

print(known_target_bytes.decode())

答案是

1
2
3
4
Rollin' in my 5.0
With my rag-top down so my hair can blow
The girlies on standby waving just to say hi
Did you stop? No, I just drove by

(7) PKCS#7 padding validation

这个就写几个PKCS#7的测试就行了

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
def validate_and_strip_pkcs7_padding(plaintext):
if not plaintext:
raise ValueError("The plaintext is empty")

padding_value = plaintext[-1]
if padding_value < 1 or padding_value > 16:
raise ValueError("Invalid padding value")

if plaintext[-padding_value:] != bytes([padding_value]) * padding_value:
raise ValueError("Invalid PKCS#7 padding")
return plaintext[:-padding_value]

try:
result = validate_and_strip_pkcs7_padding(b"ICE ICE BABY\x04\x04\x04\x04")
print(result.decode()) # output: ICE ICE BABY
except ValueError as e:
print(e)

try:
result = validate_and_strip_pkcs7_padding(b"ICE ICE BABY\x05\x05\x05\x05")
print(result.decode())
except ValueError as e:
print(e) # output: Invalid PKCS#7 padding

try:
result = validate_and_strip_pkcs7_padding(b"ICE ICE BABY\x01\x02\x03\x04")
print(result.decode())
except ValueError as e:
print(e) # output: Invalid PKCS#7 padding

(8) CBC bit flipping attacks

我们要让;admin=true;出现在消息中但是不可以用";“或”=",所以说设置sanitize_input函数将输入中的 ‘;’ 和 ‘=’ 字符转义成对应的16进制表示,防止用户直接输入;admin=true;

1
2
def sanitize_input(userdata):
return re.sub(r'[;=]', lambda x: f"%{ord(x.group(0)):02x}", userdata)#将字符转换为16进制

然后接受用户的输入数据,首先将其转义,然后将它嵌入到一个固定的前缀和后缀之间,组成完整的明文。最后,该明文经过AES CBC模式加密后返回密文。

1
2
3
4
5
6
7
8
9
10
def encrypt(userdata):
prefix = b"comment1=cooking%20MCs;userdata="
suffix = b";comment2=%20like%20a%20pound%20of%20bacon"
sanitized_userdata = sanitize_input(userdata.decode()).encode()
plaintext = prefix + sanitized_userdata + suffix
padded_plaintext = pad(plaintext)
cipher = AES.new(KEY, AES.MODE_CBC, IV)
ciphertext = cipher.encrypt(padded_plaintext)
print(f"Plaintext: {plaintext}")
return ciphertext

生成一个初始的密文16个字母A。定位我们希望进行位翻转的区域,通过亦或运算操作修改密文的第二个块这将影响解密出的第三个块的明文。而我们希望将明文中的16个字母A变成 ;admin=true;所以编写翻转攻击函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def bitflipping_attack():
# 生成初始的密文
userdata = b"A" * 16
ciphertext = encrypt(userdata)
print(f"Ciphertext: {ciphertext}")

# 修改密文以注入 ";admin=true;"
block_size = 16
modified_ciphertext = bytearray(ciphertext)
target = b";admin=true;"

for i in range(len(target)):
modified_ciphertext[block_size + i] ^= ord('A') ^ target[i]

# 检查修改后的密文是否包含 ";admin=true;"
return decrypt(bytes(modified_ciphertext))

最后运行出来的答案

1
2
3
4
Plaintext: b'comment1=cooking%20MCs;userdata=AAAAAAAAAAAAAAAA;comment2=%20like%20a%20pound%20of%20bacon'
Ciphertext: b"\x1e^H\xec\xfen\xa8\x90\xf0\xae&j\x9a<\x1a\x03\xc7!4\x7f[\xce\xf5\xcc~GC)\xaeA\x15\xd2\xd0\xefz\xdf##\x11\xaf\xdaOP\x1albKi\xc4\x80#\x00\xa1\x18s\xf2\xec%\x11\x0f\xba'\xf6\xca\x16e\xd6\xddxJU\xf1\x1c\xda\x8b\xado\xe5\n\xfa\x83'1\x01\xe8;\x0e\n\x84/\xbf\x14\xf9\x93\x1f\xac"
Decrypted: b'comment1=cooking\x19yD\xf4*p\xd9B\xd1\xde\x08#\x8cb\x9b\xaf;admin=true;AAAA;comment2=%20like%20a%20pound%20of%20bacon'
Bitflipping attack successful: True

实验总结

在这次密码实验中,我实现了多个重要的加密功能和攻击,包括 PKCS#7 填充ECB/CBC 检测逐字节 ECB 解密(简单与复杂版)、以及 CBC 位翻转攻击等多个实验。通过这些实验,我对加密算法AES运用更加熟练,也了解到CBC模式下的位翻转攻击以及ECB模式的可预测性也就是漏洞。我知道了,加密不仅要保证数据的机密性,还必须结合完整性验证等手段,才能有效防御攻击者的篡改和攻击。

源代码链接

https://github.com/cool-chicken/cryptography-exp/tree/main/密码学实验二