Sau khi đã hiểu rõ nguyên lý hoạt động và quy trình ký/xác minh, ở bài học này chúng ta sẽ cùng nhau viết một ứng dụng Python hoàn chỉnh theo dạng Module chuyên nghiệp.
Ứng dụng này sẽ mô phỏng một Hệ thống Chữ ký số thực tế (Digital Signature System) có khả năng:
Sinh cặp khóa RSA và xuất/nhập khóa dưới dạng chuỗi Base64 (dễ dàng lưu vào file .pem hoặc gửi qua mạng).
Ký số một file tài liệu bất kỳ (PDF, Word, TXT, Image...) hoặc một chuỗi văn bản.
Xác minh chữ ký số từ file và chữ ký nhận được.
Toàn bộ chương trình vẫn chỉ sử dụng thư viện chuẩn của Python (hashlib, math, base64, os).
Chúng ta sẽ tổ chức chương trình thành các Lớp (Class) đại diện cho các thành phần trong thực tế:
┌────────────────────────────────────────────────────────────────────────┐ │ HỆ THỐNG CHỮ KÝ SỐ (RSA + SHA-256) │ └───────────────────────────────────┬────────────────────────────────────┘ │ ┌──────────────────────────────┼──────────────────────────────┐ ▼ ▼ ▼ ┌──────────────────────────┐ ┌──────────────────────────┐ ┌──────────────────────────┐ │ Class RSAKeyManager │ │ Class FileHashEngine │ │ Class DigitalSignature │ ├──────────────────────────┤ ├──────────────────────────┤ ├──────────────────────────┤ │ • Sinh cặp khóa RSA │ │ • Băm văn bản (String) │ │ • Ký dữ liệu/file │ │ • Export khóa sang Base64│ │ • Băm File tài liệu lớn │ │ • Xác minh chữ ký số │ │ • Import khóa từ Base64 │ │ (Đọc từng block 4KB) │ │ • Kiểm tra tính toàn vẹn │ └──────────────────────────┘ └──────────────────────────┘ └──────────────────────────┘
digital_signature_system.py)Các bạn sinh viên có thể copy đoạn code dưới đây và chạy trực tiếp bằng lệnh python digital_signature_system.py trên bất kỳ máy tính:
import hashlib
import math
import base64
import os
# =====================================================================
# MODULE 1: QUẢN LÝ CẶP KHÓA RSA (RSA KEY MANAGER)
# =====================================================================
class RSAKeyManager:
"""Quản lý sinh cặp khóa RSA và chuyển đổi định dạng lưu trữ"""
def __init__(self, p=2729, q=2777, e=65537):
# Chọn số nguyên tố vừa đủ lớn để minh họa
self.p = p
self.q = q
self.n = p * q
self.phi_n = (p - 1) * (q - 1)
self.e = e
# Tính d = e^(-1) mod phi_n (Nghịch đảo mô-đun)
self.d = pow(self.e, -1, self.phi_n)
def get_public_key(self) -> tuple:
"""Trả về Khóa công khai (e, n)"""
return (self.e, self.n)
def get_private_key(self) -> tuple:
"""Trả về Khóa bí mật (d, n)"""
return (self.d, self.n)
@staticmethod
def export_key_to_base64(key_tuple: tuple) -> str:
"""Đóng gói cặp số khóa thành chuỗi Base64 chuẩn để lưu vào file hoặc truyền qua API"""
exp, mod = key_tuple
raw_str = f"{exp}:{mod}"
return base64.b64encode(raw_str.encode('utf-8')).decode('utf-8')
@staticmethod
def import_key_from_base64(b64_str: str) -> tuple:
"""Giải mã chuỗi Base64 trở lại cặp số khóa (exponent, modulus)"""
raw_str = base64.b64decode(b64_str.encode('utf-8')).decode('utf-8')
exp_str, mod_str = raw_str.split(':')
return (int(exp_str), int(mod_str))
# =====================================================================
# MODULE 2: HÀM BĂM FILE VÀ VĂN BẢN (FILE HASH ENGINE)
# =====================================================================
class FileHashEngine:
"""Xử lý tính mã băm SHA-256 cho văn bản hoặc file dung lượng lớn"""
@staticmethod
def hash_text(text: str) -> bytes:
"""Băm chuỗi văn bản thông thường"""
return hashlib.sha256(text.encode('utf-8')).digest()
@staticmethod
def hash_file(file_path: str) -> bytes:
"""
Băm file tài liệu (PDF, Word, Ảnh...).
Đọc theo từng khối (block 4096 bytes) để không bị ngốn RAM khi file lớn.
"""
sha256 = hashlib.sha256()
with open(file_path, 'rb') as f:
while chunk := f.read(4096):
sha256.update(chunk)
return sha256.digest()
# =====================================================================
# MODULE 3: ENGINE KÝ SỐ VÀ XÁC MINH (DIGITAL SIGNATURE ENGINE)
# =====================================================================
class DigitalSignatureEngine:
"""Thực hiện các thao tác Ký và Xác minh chữ ký số"""
@staticmethod
def sign_hash(hash_bytes: bytes, private_key: tuple) -> str:
"""
Ký số lên mã băm bằng Private Key.
S = (Hash_int ^ d) mod n
"""
d, n = private_key
# 1. Chuyển bytes mã băm thành số nguyên
hash_int = int.from_bytes(hash_bytes, byteorder='big') % n
# 2. Thực hiện phép toán RSA để ký
signature_int = pow(hash_int, d, n)
# 3. Đóng gói kết quả dạng Base64
sig_bytes = signature_int.to_bytes((signature_int.bit_length() + 7) // 8, byteorder='big')
return base64.b64encode(sig_bytes).decode('utf-8')
@staticmethod
def verify_hash(hash_bytes: bytes, signature_b64: str, public_key: tuple) -> bool:
"""
Xác minh chữ ký số bằng Public Key.
Hash_int = (Signature_int ^ e) mod n
"""
e, n = public_key
try:
# 1. Giải mã Base64 chữ ký số về số nguyên
sig_bytes = base64.b64decode(signature_b64.encode('utf-8'))
signature_int = int.from_bytes(sig_bytes, byteorder='big')
# 2. Giải mã bằng Public Key RSA
decrypted_hash_int = pow(signature_int, e, n)
# 3. Tính mã băm kỳ vọng từ dữ liệu thực tế
expected_hash_int = int.from_bytes(hash_bytes, byteorder='big') % n
# 4. So sánh
return decrypted_hash_int == expected_hash_int
except Exception:
return False
# =====================================================================
# BƯỚC 3: KỊCH BẢN CHẠY THỬ NGHIỆM CHƯƠNG TRÌNH (DEMO)
# =====================================================================
if __name__ == "__main__":
print("================================================================")
print(" DEMO HỆ THỐNG CHỮ KÝ SỐ HOÀN CHỈNH (PURE PYTHON)")
print("================================================================\n")
# --- THỬ NGHIỆM 1: TẠO KHÓA VÀ XUẤT ĐỊNH DẠNG BASE64 ---
key_mgr = RSAKeyManager()
pub_key = key_mgr.get_public_key()
priv_key = key_mgr.get_private_key()
pub_b64 = RSAKeyManager.export_key_to_base64(pub_key)
priv_b64 = RSAKeyManager.export_key_to_base64(priv_key)
print("🔑 [STEP 1] KHỞI TẠO VÀ XUẤT KHÓA BASE64:")
print(f" • Public Key (Base64) : {pub_b64}")
print(f" • Private Key (Base64): {priv_b64}\n")
# --- THỬ NGHIỆM 2: KÝ VÀ XÁC MINH VĂN BẢN THƯỜNG ---
print("📄 [STEP 2] KÝ VÀ XÁC MINH CHUỖI VĂN BẢN:")
contract_text = "Dự thảo hợp đồng kinh tế số 102/2026/HD-KTE."
print(f" • Nội dung: '{contract_text}'")
# 1. Băm văn bản
text_hash = FileHashEngine.hash_text(contract_text)
# 2. Ký văn bản
signature_b64 = DigitalSignatureEngine.sign_hash(text_hash, priv_key)
print(f" ✍️ Chữ ký số nhận được (Base64): {signature_b64}")
# 3. Xác minh với Public Key đã import từ Base64
imported_pub_key = RSAKeyManager.import_key_from_base64(pub_b64)
is_valid = DigitalSignatureEngine.verify_hash(text_hash, signature_b64, imported_pub_key)
print(f" 🔍 Kết quả xác minh ban đầu: {'✅ HỢP LỆ' if is_valid else '❌ KHÔNG HỢP LỆ'}\n")
# --- THỬ NGHIỆM 3: TẠO FILE TẠM VÀ KÝ SỐ TRÊN FILE THỰC TẾ ---
print("📁 [STEP 3] KÝ VÀ XÁC MINH FILE TÀI LIỆU THỰC TẾ (.TXT/.PDF):")
demo_filename = "hop_dong_demo.txt"
# Tạo một file văn bản giả lập
with open(demo_filename, "w", encoding="utf-8") as f:
f.write("CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM\nBản cam kết bảo mật thông tin.")
# 1. Băm file
file_hash = FileHashEngine.hash_file(demo_filename)
# 2. Ký file
file_sig_b64 = DigitalSignatureEngine.sign_hash(file_hash, priv_key)
print(f" • Đã tạo file: '{demo_filename}'")
print(f" ✍️ Chữ ký số cho File (Base64): {file_sig_b64}")
# 3. Xác minh file hợp lệ
is_file_valid = DigitalSignatureEngine.verify_hash(file_hash, file_sig_b64, imported_pub_key)
print(f" 🔍 Xác minh file gốc: {'✅ HỢP LỆ' if is_file_valid else '❌ KHÔNG HỢP LỆ'}")
# 4. Giả lập Hacker chỉnh sửa nội dung file
with open(demo_filename, "a", encoding="utf-8") as f:
f.write("\n[Hacker chèn thêm dòng này]") # Thêm nội dung vào cuối file
# 5. Băm và xác minh lại file đã bị sửa
tampered_file_hash = FileHashEngine.hash_file(demo_filename)
is_tampered_valid = DigitalSignatureEngine.verify_hash(tampered_file_hash, file_sig_b64, imported_pub_key)
print(f" 🚨 Xác minh sau khi file bị Hacker sửa: {'✅ HỢP LỆ' if is_tampered_valid else '❌ BÁO ĐỘNG: File đã bị sửa đổi!'}\n")
# Dọn dẹp file tạm
if os.path.exists(demo_filename):
os.remove(demo_filename)
Kỹ thuật đọc file theo Chunk (FileHashEngine.hash_file):
Đoạn code while chunk := f.read(4096): đọc từng khối 4KB của file vào bộ nhớ, tính toán mã băm cuốn chiếu. Cách này giúp máy tính không bị hết RAM dù bạn băm một file phim hay file PDF nặng vài GB.
Đóng gói Base64 (export_key_to_base64 & signature_b64):
Kết quả RSA sau khi ký là một số nguyên rất lớn hoặc dãy byte nhị phân thô (Raw Binary Bytes). Việc chuyển sang Base64 giúp chuỗi khóa và chuỗi chữ ký chỉ chứa các ký tự chữ và số an toàn (A-Z, a-z, 0-9, +, /), dễ dàng chèn vào file XML, JSON hay truyền qua Web API.
Phân tách trách nhiệm (Separation of Concerns):
Phía người gửi chỉ cần giữ Private Key.
Phía người nhận chỉ cần nhận Public Key (Base64) + Chữ ký số (Base64) + File tài liệu là có thể tự tiến hành xác minh độc lập mà không cần tương tác lại với người gửi.
Câu hỏi: Trong mã nguồn trên, tại sao ở hàm hash_file, chúng ta chỉ truyền file_hash (dãy bytes mã băm) vào hàm sign_hash mà không truyền trực tiếp toàn bộ nội dung file vào?
Đáp án:
Vì thuật toán mã hóa RSA có giới hạn kích thước dữ liệu đầu vào (dữ liệu phải nhỏ hơn mô-đun $n$). Nếu đưa toàn bộ file vài Megabyte vào RSA, phép tính sẽ thất bại hoặc cực kỳ chậm. Vì vậy, ta băm file thành mã băm 32-bytes (SHA-256) trước, rồi mới dùng RSA để ký lên 32-bytes đó.