Chuyển tới nội dung chính

Mã hoá nâng cao & Chữ ký số

Mục tiêu bài học

Sau bài này, bạn sẽ:

  • Phân biệt được mã hoá đối xứng (Symmetric) và bất đối xứng (Asymmetric)
  • Sử dụng Cipher API để mã hoá/giải mã với AES
  • Tạo cặp khoá RSA với KeyPairGenerator
  • Hiểu và implement chữ ký số (Digital Signature) với Signature API
  • Biết khi nào dùng Symmetric vs Asymmetric encryption

Symmetric vs Asymmetric Encryption

Đặc điểmSymmetric (AES)Asymmetric (RSA)
Khoá1 khoá (secret key)2 khoá (public + private)
Tốc độNhanh (10-1000x)Chậm
Kích thước khoá128/192/256 bit2048/4096 bit
Giới hạn dataKhông giới hạnGiới hạn bởi kích thước khoá
Vấn đềChia sẻ khoá an toànChậm, không mã hoá data lớn trực tiếp
Use caseMã hoá data, file, databaseTrao đổi khoá, chữ ký số, TLS handshake
Trong thực tế: Hybrid Encryption

TLS, PGP, và hầu hết các hệ thống dùng kết hợp cả hai: RSA để trao đổi khoá AES, sau đó dùng AES để mã hoá data.

Lựa chọn thuật toán mã hoá

Khi nào dùng Symmetric? Khi nào dùng Asymmetric? Quyết định dựa trên use case:

Quy tắc ngắn gọn:

  • Data lớn? → AES (hoặc Hybrid nếu cần trao đổi key)
  • Nhiều người nhận? → Hybrid (RSA cho mỗi người, AES cho data)
  • Chữ ký số? → RSA/ECDSA Signature
  • Chia sẻ key khó? → Asymmetric RSA hoặc Hybrid

Hybrid Encryption — Chi tiết

Symmetric Encryption với AES

AES là gì?

AES (Advanced Encryption Standard) là thuật toán mã hoá đối xứng được dùng rộng rãi nhất. Nó hỗ trợ 3 kích thước khoá: 128, 192, 256 bit.

AES Mode of Operation

ModeTên đầy đủĐặc điểmKhuyến nghị
ECBElectronic CodebookBlock giống nhau → ciphertext giống nhauKhông dùng
CBCCipher Block ChainingCần IV, block phụ thuộc block trướcOK nhưng có padding oracle
GCMGalois/Counter ModeAuthenticated encryption, nhanhKhuyến nghị
OCP Trap — Cipher.getInstance("AES") defaults to ECB

Khi gọi Cipher.getInstance("AES")không chỉ định mode, Java mặc định dùng ECB mode (với SunJCE provider). ECB là mode KHÔNG AN TOÀN vì cùng plaintext block luôn cho cùng ciphertext block.

💡 Analogy — "ECB Penguin": Tưởng tượng bạn mã hoá ảnh con chim cánh cụt bằng ECB. Vì mỗi block 16 bytes được mã hoá độc lập, những block giống nhau (ví dụ vùng trắng) sẽ cho cùng ciphertext. Kết quả: bạn vẫn nhìn thấy hình dạng con cánh cụt trong ảnh đã mã hoá!

// ❌ INSECURE — "AES" defaults to "AES/ECB/PKCS5Padding"
Cipher cipher = Cipher.getInstance("AES");

// ✅ SECURE — luôn chỉ định mode + padding
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

AES-GCM — Mã hoá an toàn

AESEncryption.java
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.SecureRandom;
import java.util.Base64;

public class AESEncryption {

private static final int GCM_IV_LENGTH = 12; // 96 bits
private static final int GCM_TAG_LENGTH = 128; // 128 bits

/**
* Mã hoá dữ liệu với AES-GCM.
* Trả về: IV + Ciphertext (Base64)
*/
public static String encrypt(String plaintext, SecretKey key) throws Exception {
// Tạo IV (Initialization Vector) ngẫu nhiên
byte[] iv = new byte[GCM_IV_LENGTH];
new SecureRandom().nextBytes(iv);

// Cấu hình Cipher
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);

// Mã hoá
byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8"));

// Ghép IV + Ciphertext để lưu trữ
byte[] result = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(ciphertext, 0, result, iv.length, ciphertext.length);

return Base64.getEncoder().encodeToString(result);
}

/**
* Giải mã dữ liệu.
*/
public static String decrypt(String encryptedBase64, SecretKey key)
throws Exception {
byte[] decoded = Base64.getDecoder().decode(encryptedBase64);

// Tách IV và Ciphertext
byte[] iv = new byte[GCM_IV_LENGTH];
byte[] ciphertext = new byte[decoded.length - GCM_IV_LENGTH];
System.arraycopy(decoded, 0, iv, 0, iv.length);
System.arraycopy(decoded, iv.length, ciphertext, 0, ciphertext.length);

// Cấu hình Cipher cho decryption
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);

// Giải mã
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, "UTF-8");
}

public static void main(String[] args) throws Exception {
// Tạo khoá AES 256-bit
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey key = keyGen.generateKey();

// Mã hoá
String original = "Thông tin tài khoản ngân hàng: 1234-5678-9012";
String encrypted = encrypt(original, key);
System.out.println("Encrypted: " + encrypted);

// Giải mã
String decrypted = decrypt(encrypted, key);
System.out.println("Decrypted: " + decrypted);

// Xác minh
System.out.println("Match: " + original.equals(decrypted));
}
}
Output
Encrypted: dG9rZW5JVi4uLi4uLkVuY3J5cHRlZERhdGEuLi4=
Decrypted: Thông tin tài khoản ngân hàng: 1234-5678-9012
Match: true

Quy trình mã hoá/giải mã với Cipher API

Java Cipher API hoạt động theo state machine với các bước rõ ràng:

Lưu ý quan trọng:

  • IV phải unique cho mỗi lần mã hoá (dùng SecureRandom)
  • IV không cần bảo mật, có thể gửi kèm ciphertext
  • Key phải bảo mật tuyệt đối, không hardcode trong code
  • GCM mode tự động xác thực (authenticated encryption) - không cần HMAC riêng

Lưu trữ và tải khoá AES

KeyStorage.java
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class KeyStorage {
// Xuất khoá ra Base64 string (để lưu vào config/env)
public static String keyToString(SecretKey key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}

// Khôi phục khoá từ Base64 string
public static SecretKey stringToKey(String keyStr) {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
return new SecretKeySpec(keyBytes, "AES");
}

public static void main(String[] args) throws Exception {
// Tạo khoá mới
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey originalKey = keyGen.generateKey();

// Lưu khoá
String keyString = keyToString(originalKey);
System.out.println("Key (Base64): " + keyString);

// Khôi phục khoá
SecretKey restoredKey = stringToKey(keyString);
System.out.println("Keys match: " +
java.util.Arrays.equals(originalKey.getEncoded(), restoredKey.getEncoded()));
}
}
Không hardcode khoá trong source code
// ❌ KHÔNG BAO GIỜ làm thế này
SecretKey key = new SecretKeySpec("my-secret-key!!!".getBytes(), "AES");

// ✅ Đúng — đọc từ environment variable hoặc KeyStore
String keyStr = System.getenv("ENCRYPTION_KEY");
SecretKey key = new SecretKeySpec(Base64.getDecoder().decode(keyStr), "AES");

Asymmetric Encryption với RSA

Tạo cặp khoá RSA

RSAEncryption.java
import java.security.*;
import javax.crypto.Cipher;
import java.util.Base64;

public class RSAEncryption {

/**
* Tạo cặp khoá RSA 2048-bit.
*/
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048, new SecureRandom());
return kpg.generateKeyPair();
}

/**
* Mã hoá bằng Public Key.
*/
public static String encrypt(String plaintext, PublicKey publicKey)
throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(plaintext.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(encrypted);
}

/**
* Giải mã bằng Private Key.
*/
public static String decrypt(String ciphertext, PrivateKey privateKey)
throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
return new String(decrypted, "UTF-8");
}

public static void main(String[] args) throws Exception {
// Tạo cặp khoá
KeyPair keyPair = generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();

System.out.println("Public Key Algorithm: " + publicKey.getAlgorithm());
System.out.println("Key Size: " + publicKey.getEncoded().length * 8 + " bits");

// Mã hoá bằng public key
String original = "Mã PIN: 123456";
String encrypted = encrypt(original, publicKey);
System.out.println("Encrypted: " + encrypted.substring(0, 40) + "...");

// Giải mã bằng private key
String decrypted = decrypt(encrypted, privateKey);
System.out.println("Decrypted: " + decrypted);
}
}
Giới hạn kích thước RSA

RSA với 2048-bit key chỉ mã hoá được tối đa ~190 bytes (với OAEP padding). Để mã hoá data lớn, dùng Hybrid Encryption: RSA mã hoá khoá AES, AES mã hoá data.

Hybrid Encryption — Kết hợp RSA + AES

HybridEncryption.java
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import java.security.*;
import java.util.Base64;

public class HybridEncryption {

/**
* Mã hoá data lớn: RSA(AES key) + AES(data)
*/
public static String[] encrypt(String data, PublicKey rsaPublicKey)
throws Exception {
// 1. Tạo khoá AES ngẫu nhiên
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey aesKey = keyGen.generateKey();

// 2. Mã hoá AES key bằng RSA
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded());

// 3. Mã hoá data bằng AES-GCM
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(128, iv));
byte[] encryptedData = aesCipher.doFinal(data.getBytes("UTF-8"));

// Ghép IV + encrypted data
byte[] combined = new byte[iv.length + encryptedData.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length);

return new String[]{
Base64.getEncoder().encodeToString(encryptedKey),
Base64.getEncoder().encodeToString(combined)
};
}

public static void main(String[] args) throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.generateKeyPair();

String largeData = "Dữ liệu lớn cần mã hoá...".repeat(100);
System.out.println("Original size: " + largeData.length() + " chars");

String[] result = encrypt(largeData, keyPair.getPublic());
System.out.println("Encrypted key length: " + result[0].length());
System.out.println("Encrypted data length: " + result[1].length());
}
}

GCM Nonce — Tuyệt đối không tái sử dụng

Nonce (Number used ONCE) trong AES-GCM phải unique cho mỗi lần mã hoá với cùng key. Tái sử dụng nonce = phá vỡ hoàn toàn tính bảo mật.

// ❌ INSECURE — nonce cố định
byte[] nonce = new byte[]{1,2,3,4,5,6,7,8,9,10,11,12};

// ❌ INSECURE — counter nonce có thể trùng khi restart
static int counter = 0;
byte[] nonce = ByteBuffer.allocate(12).putInt(counter++).array();

// ✅ SECURE — random nonce
byte[] nonce = new byte[12];
new SecureRandom().nextBytes(nonce);

Tại sao nguy hiểm? Với AES-GCM, nếu dùng lại nonce + key:

  • Attacker có thể XOR hai ciphertext → lấy XOR của hai plaintext
  • Attacker có thể giả mạo authentication tag → forgery attack

RSA Padding Modes

PaddingAn toàn?Lý do
RSA/ECB/NoPaddingTextbook RSA, dễ tấn công
RSA/ECB/PKCS1Padding⚠️Vulnerable to Bleichenbacher's attack
RSA/ECB/OAEPWithSHA-256AndMGF1PaddingKhuyến nghị — provably secure

So sánh AES Encryption Modes

Các mode mã hoá AES khác nhau về cách xử lý block và mức độ bảo mật:

Lý do ECB không an toàn: Cùng plaintext block luôn tạo ra cùng ciphertext block, làm lộ patterns trong data (xem "ECB Penguin" ở trên).

Lý do GCM tốt nhất: Vừa mã hoá vừa xác thực (authenticated encryption), ngăn chặn cả tampering lẫn forgery attacks.

ECDSA — Chữ ký số hiệu quả hơn RSA

ECDSA (Elliptic Curve Digital Signature Algorithm) cho bảo mật tương đương RSA nhưng với key nhỏ hơn nhiều:

EC Key SizeRSA Tương đươngTốc độ ký
256-bit3072-bit RSA10x nhanh hơn
384-bit7680-bit RSANhanh hơn
521-bit15360-bit RSANhanh hơn
ECDSAExample.java
import java.security.*;

public class ECDSAExample {
public static void main(String[] args) throws Exception {
// Tạo cặp khoá EC 256-bit
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair keyPair = kpg.generateKeyPair();

// Ký bằng ECDSA
Signature sig = Signature.getInstance("SHA256withECDSA");
sig.initSign(keyPair.getPrivate());
sig.update("Dữ liệu cần ký".getBytes());
byte[] signature = sig.sign();

// Xác minh
sig.initVerify(keyPair.getPublic());
sig.update("Dữ liệu cần ký".getBytes());
boolean valid = sig.verify(signature);
System.out.println("ECDSA valid: " + valid);
System.out.println("Signature size: " + signature.length + " bytes");
}
}

Password-Based Encryption (PBE)

Khi người dùng cung cấp mật khẩu thay vì khoá, dùng PBE để derive key từ password:

PBEExample.java
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;

public class PBEExample {
public static SecretKey deriveKey(char[] password, byte[] salt)
throws Exception {
PBEKeySpec spec = new PBEKeySpec(password, salt,
600_000, // iterations
256 // key length
);
SecretKeyFactory factory =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] keyBytes = factory.generateSecret(spec).getEncoded();
spec.clearPassword(); // Xoá password khỏi memory
return new SecretKeySpec(keyBytes, "AES");
}
}

Digital Signature — Chữ ký số

Chữ ký số hoạt động như thế nào?

Chữ ký số dùng private key để kýpublic key để xác minh, đảm bảo:

  1. Authentication — xác minh người gửi
  2. Integrity — dữ liệu không bị sửa đổi
  3. Non-repudiation — người gửi không thể phủ nhận
DigitalSignatureExample.java
import java.security.*;
import java.util.Base64;

public class DigitalSignatureExample {

/**
* Ký dữ liệu bằng Private Key.
*/
public static String sign(String data, PrivateKey privateKey)
throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signatureBytes = signature.sign();
return Base64.getEncoder().encodeToString(signatureBytes);
}

/**
* Xác minh chữ ký bằng Public Key.
*/
public static boolean verify(String data, String signatureBase64,
PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(data.getBytes("UTF-8"));
byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
return signature.verify(signatureBytes);
}

public static void main(String[] args) throws Exception {
// Tạo cặp khoá
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.generateKeyPair();

// Dữ liệu cần ký
String document = "Hợp đồng: Thanh toán 100,000,000 VND cho dự án XYZ";

// Ký
String digitalSignature = sign(document, keyPair.getPrivate());
System.out.println("Signature: " + digitalSignature.substring(0, 40) + "...");

// Xác minh — document gốc
boolean isValid = verify(document, digitalSignature, keyPair.getPublic());
System.out.println("Valid signature: " + isValid);

// Xác minh — document bị sửa đổi
String tampered = "Hợp đồng: Thanh toán 999,000,000 VND cho dự án XYZ";
boolean isTampered = verify(tampered, digitalSignature, keyPair.getPublic());
System.out.println("Tampered valid: " + isTampered);
}
}
Output
Signature: MEUCIQDh7pq9y5L/...
Valid signature: true
Tampered valid: false

So sánh thuật toán Signature

Thuật toánKích thước khoáTốc độ kýTốc độ verifyKhuyến nghị
SHA256withRSA2048+ bitChậmNhanhPhổ biến, tương thích rộng
SHA256withECDSA256 bitNhanhNhanhHiện đại, khoá nhỏ hơn
SHA512withRSA4096 bitRất chậmTrung bìnhBảo mật cao nhất

Quy trình ký và xác minh

Ví dụ thực tế: Document Signing Service

DocumentSigner.java
import java.security.*;
import java.time.Instant;
import java.util.Base64;

public class DocumentSigner {
private final PrivateKey privateKey;
private final PublicKey publicKey;

public DocumentSigner() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.generateKeyPair();
this.privateKey = keyPair.getPrivate();
this.publicKey = keyPair.getPublic();
}

/**
* Ký document kèm timestamp.
*/
public SignedDocument signDocument(String content, String signer)
throws Exception {
String timestamp = Instant.now().toString();
String dataToSign = content + "|" + signer + "|" + timestamp;

Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(dataToSign.getBytes("UTF-8"));
String signature = Base64.getEncoder().encodeToString(sig.sign());

return new SignedDocument(content, signer, timestamp, signature);
}

/**
* Xác minh document đã ký.
*/
public boolean verifyDocument(SignedDocument doc) throws Exception {
String dataToVerify = doc.content() + "|" + doc.signer() + "|" + doc.timestamp();

Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(publicKey);
sig.update(dataToVerify.getBytes("UTF-8"));
return sig.verify(Base64.getDecoder().decode(doc.signature()));
}

// Record cho signed document
record SignedDocument(String content, String signer,
String timestamp, String signature) {}

public static void main(String[] args) throws Exception {
DocumentSigner service = new DocumentSigner();

// Ký document
SignedDocument signed = service.signDocument(
"Biên bản họp ngày 15/01/2025", "Nguyễn Văn A");

System.out.println("Content: " + signed.content());
System.out.println("Signer: " + signed.signer());
System.out.println("Timestamp: " + signed.timestamp());
System.out.println("Valid: " + service.verifyDocument(signed));
}
}

Lỗi thường gặp

Lỗi thường gặp

1. Dùng RSA/ECB/PKCS1Padding (không an toàn):

// ❌ Sai — vulnerable to padding oracle attack
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

// ✅ Đúng — dùng OAEP padding
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");

2. Dùng AES/ECB (không an toàn):

// ❌ Sai — cùng plaintext block → cùng ciphertext block
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

// ✅ Đúng — dùng GCM mode
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

3. Tái sử dụng IV/Nonce:

// ❌ Sai — dùng IV cố định
byte[] iv = new byte[12]; // Toàn 0!

// ✅ Đúng — IV ngẫu nhiên cho mỗi lần mã hoá
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);

4. RSA key quá nhỏ:

// ❌ Sai — 1024-bit RSA đã bị phá
kpg.initialize(1024);

// ✅ Đúng — tối thiểu 2048-bit
kpg.initialize(2048);
OCP Trap — Cipher state machine

Cipherstate machine: phải gọi init() trước doFinal(). Sau doFinal(), Cipher tự reset về trạng thái initialized — có thể gọi doFinal() lại mà không cần init() lại (cho cùng key/mode).

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// State: UNINITIALIZED

cipher.init(Cipher.ENCRYPT_MODE, key, spec);
// State: INITIALIZED

byte[] result = cipher.doFinal(data);
// State: quay lại INITIALIZED (cùng key/spec)

// ❌ Nhưng với GCM, doFinal() lần 2 PHẢI dùng nonce MỚI!
// cipher.doFinal(data2); // IllegalStateException với GCM!
📖 Theo Oracle Docs — Cipher

Cipher hỗ trợ hai chế độ: single-part (doFinal(input)) và multi-part (update(part1) + update(part2) + doFinal()). Multi-part dùng cho streaming data lớn không thể load hết vào memory.

Tham khảo: javax.crypto.Cipher — Java 21 API

Bài tập

Bài 1: AES File Encryption [Cơ bản]

Viết chương trình mã hoá/giải mã file sử dụng AES-GCM. Khoá nhập từ command line (Base64 string). Hỗ trợ 2 mode: encryptdecrypt.

Gợi ý

Đọc file bằng Files.readAllBytes(), mã hoá bằng AES-GCM, ghi kết quả ra file mới (thêm extension .enc).

Bài 2: RSA Key Pair Manager [Trung bình]

Xây dựng class quản lý cặp khoá RSA:

  • generateAndSave(String publicFile, String privateFile) — tạo và lưu khoá ra file (PEM format Base64)
  • loadPublicKey(String file) — đọc public key từ file
  • loadPrivateKey(String file) — đọc private key từ file
  • Test: tạo khoá, lưu, đọc lại, dùng để mã hoá/giải mã
Gợi ý

Dùng KeyFactory với X509EncodedKeySpec (public) và PKCS8EncodedKeySpec (private) để chuyển đổi.

Bài 3: Secure Message Exchange [Thách thức]

Implement hệ thống trao đổi tin nhắn bảo mật giữa Alice và Bob:

  • Mỗi người có cặp khoá RSA riêng
  • Alice gửi tin nhắn: mã hoá bằng public key của Bob + ký bằng private key của Alice
  • Bob nhận: xác minh chữ ký bằng public key của Alice + giải mã bằng private key của Bob
  • Dùng Hybrid Encryption cho data lớn
Gợi ý

Tạo class SecureMessage chứa: encrypted AES key, encrypted data, và digital signature.

Tóm tắt

Khái niệmMô tả
Symmetric (AES)1 khoá, nhanh, dùng cho data lớn
Asymmetric (RSA)2 khoá (public/private), chậm, dùng cho key exchange và signature
AES-GCMMode mã hoá khuyến nghị, có authenticated encryption
Hybrid EncryptionRSA mã hoá khoá AES, AES mã hoá data
Digital SignaturePrivate key ký, public key xác minh — đảm bảo integrity và authentication

Tiếp theo

Ở bài tiếp theo, chúng ta sẽ tìm hiểu KeyStore, SSL/TLS & Secure Networking — cách quản lý khoá an toàn và thiết lập kết nối mạng bảo mật.

Bài 4: KeyStore, SSL/TLS & Secure Networking