Modular math in Cryptography - Module trong mật mã học
I. Một số kiến thức cơ bản về module
1. Định nghĩa
Trong toán học, số học mô đun là một hệ thống số học dành cho số nguyên. Modular math đóng vai trò quan trọng trong mật mã học, giữa chúng tồn tại sự liên hệ mật thiết. Có thể coi phần lớn lý thuyết mật mã được xây dựng dựa trên cơ sở của số học module trong toán học, tiêu biểu có thể kể đến hệ mã hóa RSA làm việc trên các phép toán module.
Khái niệm module còn được gọi là đồng dư thức, xét tới hai số nguyên và có cùng số dư khi chia cho số nguyên . Khi đó ta nói " đồng dư với theo mô đun ", ký hiệu:
Một số ví dụ:
2. Tính chất và các phép toán trên module
Số học module cũng có các tính chất tương tự như phép toán thông thường:
Tính phản xạ:
Tính đối xứng:
Tính bắc cầu:
Phép cộng, trừ:
Phép nhân:
Lũy thừa:
Đa thức hệ số nguyên :
3. Cài đặt trong Python
Để tìm số dư của phép chia cho , chúng ta có thể sử dụng toán tử %
:
Để tìm số dư của lũy thừa cho , Python hỗ trợ hàm pow(base, exponent, modulus)
(tham số modulus
nếu không sử dụng thì hàm này chỉ đơn giản là tính phép tính lũy thừa). Ví dụ cần tìm trong phép tính
II. Bài toán thặng dư bình phương (Quadratic Residues)
1. Lý thuyết
Bài toán thặng dư bình phương xoay quanh việc tìm nghiệm trong phương trình module:
Ví dụ, là một nghiệm của phương trình vì .
Dễ thấy một tính chất cơ bản: Nếu là nghiệm của phương trình thăng dư bình phương thì cũng là một nghiệm thỏa mãn, vì
Một ý tưởng đơn giản để tìm ra một nghiệm thỏa mãn phương trình là thực hiện vét cạn giá trị từ tới , cài đặt như sau:
def brute_quadratic_residues(a, n):
for x in range(n):
if pow(x, 2, n) == a:
return x
Trong trường hợp phương trình có nghiệm, ta nói là số chính phương module .
Chúng ta có thể sử dụng hàm gmpy2.jacobi(a, n)
trong Python để kiểm tra số có phải là số chính phương module hay không, hàm trả về nếu đúng, ngược lại trả về . Ví dụ chương trình sau in ra các số chính phương module từ tới :
def list_modular_square_num(n):
res = []
for x in range(1, n):
if gmpy2.jacobi(x, n) == 1:
res.append(x)
return res
2. Bài toán thặng dư bình phương trên module số nguyên tố
Chúng ta thường xem xét bài toán thặng dư bình phương với module là số nguyên tố . Một định lý thường được sử dụng: Nếu là một số nguyên tố lẻ thì trong các số có đúng số chính phương module . Có thể chứng minh đơn giản:
Xét là một thặng dư bình phương module , phương trình có đúng hai nghiệm (khác module) trong . Như vậy mỗi số sẽ tương ứng với hai số (là nghiệm phương trình thặng dư) trong số . Suy ra số số như vậy là , hay có đúng số chính phương module .
Ngoài ra chúng ta có tiêu chuẩn Euler: Nếu là một số nguyên tố lẻ và , là số chính phương module khi và chỉ khi:
Chương trình kiểm tra đơn giản:
def check(a, p):
if pow(a, (p-1)//2, p) == 1:
return 1
else:
return -1
III. Legendre
1. Lý thuyết
Legendre thực chất là một ký hiệu bổ trợ cho thặng dư bình phương. Với là số nguyên tố lẻ và là số nguyên không chia hết cho , ký hiệu Legendre được định nghĩa như sau:
Ví dụ:
2. Một số tính chất
Cho là số nguyên tố lẻ và , là các số nguyên không chia hết cho , ta có:
IV. Challenge CTF
1. Challenge 1 - Adrien's Signs
File source.py
có hàm encrypt_flag()
thực hiện mã hóa flag:
def encrypt_flag(flag):
ciphertext = []
plaintext = ''.join([bin(i)[2:].zfill(8) for i in flag])
for b in plaintext:
e = randint(1, p)
n = pow(a, e, p)
if b == '1':
ciphertext.append(n)
else:
n = -n % p
ciphertext.append(n)
return ciphertext
Hàm không quá phức tạp, trước hết, flag được chuyển sang dạng nhị phân, sau đó duyệt qua từng phần tử của chuỗi nhị phân này, nếu gặp bit thì in ra , nếu gặp bit thì in ra , trong đó n = pow(a, e, p)
với là một số được sinh ngẫu nhiên trong đoạn trong mỗi lượt duyệt. Output được hiển thị trong file output.txt
như sau:
Chúng ta cần xem xét điểm đặc biệt của các số được in ra. Chú ý cách tính n = pow(a, e, p)
hoặc p - pow(a, e, p)
(với được cho trước), chúng ta cố gắng suy nghĩ tới các tính chất liên quan tới kết quả của trong module (là số nguyên tố). Từ đó không khó để phát hiện ra rằng là một số chính phương module theo tiêu chuẩn Euler:
Lúc này, dễ dàng nhận ra pow(a, e, p)
cũng là một số chính phương module , theo tiêu chuẩn Euler lại có pow(pow(a, e, p), (p - 1) // 2, p) = 1
. Như vậy, khi thì là một số chính phương module . Trường hợp ngược lại thì không là số chính phương module , tức .
Bởi vậy, chúng ta chỉ cần duyệt qua từng phần tử của mảng trong file output, kiểm tra phần tử đó có phải số chính phương module hay không, từ đó tìm được từng ký tự nhị phân của flag. Lời giải tham khảo:
from Crypto.Util.number import long_to_bytes
p = 1007621497415251
ct = [...]
flag = ''
for n in ct:
if pow(n, (p - 1) // 2, p) == 1:
flag = flag + '1'
else:
flag = flag + '0'
flag = int(flag, 2)
flag = long_to_bytes(flag)
print(flag)
2. Challenge 2
2.1. Đề bài
Bạn đọc có thể tải xuống source code tại link. Challenge bao gồm hai file chall.py
và output.txt
. Trước khi đi tới phần phân tích đề bài, bạn đọc có thể thử dành thời gian đọc hiểu source code và tìm ra flag nhé!
File chall.py
bao gồm một số hàm và sinh bản mã tại file output.txt
chứa dãy chữ số lớn:
2.2. Phân tích source code
Trước hết chúng ta sẽ phân tích từng hàm được định nghĩa.
def gmc(a, p):
if pow(a, (p-1)//2, p) == 1:
return 1
else:
return -1
Hàm gmc()
thực hiện kiểm tra số có phải là số chính phương module hay không (từ cách sử dụng hàm pow()
có thể thấy đây chính là tiêu chuẩn Euler).
def gen_key():
[gp,gq] = [getPrime(512) for i in range(2)]
gN = gp * gq
return gN, gq, gp
Hàm gen_key()
sinh hai số nguyên tố gp
, gq
có độ dài bit và trả về cả số gN
là tích của chúng (giống với số trong thuật toán RSA).
def gen_x(gq,gp):
while True:
x = getRandomNBitInteger(512)
if gmc(x,gp) ^ gmc(x,gq) == -2:
return x
Hàm gen_x()
sinh ra số nguyên x
ngẫu nhiên dài bit, điều kiện gmc(x,gp) ^ gmc(x,gq) == -2
cho thấy x
chỉ có thể là số chính phương module gp
hoặc gq
(không xảy ra đồng thời).
def gen_y(gN):
gy_list = []
while len(gy_list) != F_LEN:
ty = getRandomNBitInteger(768)
if gcd(ty,gN) == 1:
gy_list.append(ty)
return gy_list
Hàm gen_y
trả về một list gồm F_LEN
số nguyên ngẫu nhiên ty
thỏa mãn đồng thời:
ty
có độ dài bit.ty
nguyên tố cùng nhay vớigN
.
if __name__ == '__main__':
flag = bin(bytes_to_long(flag))[2:]
F_LEN = len(flag)
N, q, p = gen_key()
x = gen_x(q, p)
y_list = gen_y(N)
ciphertext = []
for i in range(F_LEN):
tc = pow(y_list[i],2) * pow(x,int(flag[i])) % N
ciphertext.append(tc)
with open('./output.txt','w') as f:
f.write(str(N) + '\n')
for i in range(F_LEN):
f.write(str(ciphertext[i]) + '\n')
Cuối cùng là hàm main
, flag ban đầu ở dạng byte được chuyển sang dạng nhị phân, F_LEN
là độ dài của flag ở hệ nhị phân. List ciphertext
gồm F_LEN
số tc
sinh bởi công thức pow(y_list[i],2) * pow(x,int(flag[i])) % N
. Cuối cùng in ra file output.txt
số N
, và list ciphertext
theo từng dòng.
2.3. Ý tưởng
Từ file output.txt
chúng ta thu được số N
, biết được giá trị của F_LEN
và toàn bộ list ciphertext
theo công thức pow(y_list[i],2) * pow(x,int(flag[i])) % N
, đây là điểm then chốt của challenge.
Do flag ở hệ nhị phân, nên int(flag[i])
chỉ nhận giá trị hoặc , từ đó pow(x,int(flag[i]))
chỉ nhận giá trị hoặc . Sử dụng ý tưởng số chính phương module , ta suy luận:
- Khi vị trí của flag là , thì
ciphertext[i] = pow(y_list[i],2) % N
là phép tính lấy số dư khi chia một số chính phương choN
, chính là một số chính phương moduleN
(có nghiệm lày_list[i]
theo moduleN
). - Khi vị trí của flag là , do
x
chỉ có thể là số chính phương module hoặc module , nênciphertext[i] = pow(y_list[i],2) * x % N
không là số chính phương moduleN
. (*)
Chứng minh nhận xét (*): không mất tính tổng quát có thể coi là số chính phương module , khi đó không là số chính phương module , biểu diễn dưới dạng ký hiệu Legendre có:
Đặt pow(y_list[i],2)=y^2
, giả sử ngược lại, là số chính phương module , suy ra là số chính phương module , hay .
Thực hiện biến đổi:
Mâu thuẫn, nên điều giả sử là sai. Do đó ciphertext[i] = pow(y_list[i],2) * x % N
không là số chính phương module N
.
Như vậy, chúng ta chỉ cần kiểm tra từng giá trị ciphertext[i]
có phải số chính phương module N
hay không bằng hàm gmpy2.jacobi()
. Xin dành bạn đọc phần viết chương trình giải mã cho challenge.
Tài liệu tham khảo
All rights reserved