Mã hóa đối xứng trong Java: Triển khai AES và DES với Cipher

Trong hệ thống bảo mật ứng dụng, mã hóa đối xứng vẫn giữ vai trò then chốt nhờ hiệu suất cao và độ tin cậy khi xử lý khối lượng lớn dữ liệu. Hai thuật toán phổ biến nhất là AES (Advanced Encryption Standard) và DES (Data Encryption Standard), cả hai đều được hỗ trợ đầy đủ qua lớp Cipher trong thư viện Java Cryptography Extension (JCE).

Cấu trúc chuỗi chuyển đổi (transformation)

Mỗi phiên bản Cipher được khởi tạo bằng một chuỗi mô tả gồm ba thành phần bắt buộc hoặc tùy chọn:

  • Thuật toán: ví dụ AES, DES
  • Chế độ hoạt động: như CBC (Cipher Block Chaining), ECB, CFB, OFB
  • Phương thức điền (padding): ví dụ PKCS5Padding, NoPadding, ISO10126Padding

Ví dụ hợp lệ: "AES/CBC/PKCS5Padding" — thiếu bất kỳ thành phần nào sẽ dẫn đến việc JCE sử dụng giá trị mặc định từ provider (thường là ECB cho DES và PKCS5Padding).

Khai báo tham số bắt buộc

Để đảm bảo tính tương thích và an toàn, ba yếu tố sau phải đồng bộ giữa mã hóa và giải mã:

  1. Khóa bí mật (SecretKeySpec): Độ dài phải đúng chuẩn — AES yêu cầu 16, 24 hoặc 32 byte; DES yêu cầu chính xác 8 byte.
  2. Vector khởi tạo (IV) (IvParameterSpec): Phải có độ dài khớp với kích thước khối (16 byte cho AES, 8 byte cho DES) và không được tái sử dụng với cùng khóa.
  3. Chuỗi transformation: Phải giống nhau hoàn toàn ở cả hai phía.

Mã hóa AES-128 với CBC và Base64

Dưới đây là hàm mã hóa minh họa cách xây dựng pipeline rõ ràng, loại bỏ các comment thừa và tối ưu tên biến:

public String secureEncrypt(String rawInput) throws Exception {
    byte[] inputBytes = rawInput.getBytes(StandardCharsets.UTF_8);
    byte[] secretKeyBytes = "mySecureKey128bit".getBytes(StandardCharsets.UTF_8);
    byte[] ivBytes = "initVector16Byte!".getBytes(StandardCharsets.UTF_8);

    SecretKeySpec encryptionKey = new SecretKeySpec(secretKeyBytes, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, ivSpec);

    byte[] encryptedBytes = cipher.doFinal(inputBytes);
    return Base64.getEncoder().encodeToString(encryptedBytes);
}

Giải mã tương ứng

Hàm giải mã chỉ khác ở chế độ khởi tạo và thứ tự xử lý (giải mã Base64 trước):

public String secureDecrypt(String encodedCipherText) throws Exception {
    byte[] decodedBytes = Base64.getDecoder().decode(encodedCipherText);
    byte[] secretKeyBytes = "mySecureKey128bit".getBytes(StandardCharsets.UTF_8);
    byte[] ivBytes = "initVector16Byte!".getBytes(StandardCharsets.UTF_8);

    SecretKeySpec decryptionKey = new SecretKeySpec(secretKeyBytes, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, decryptionKey, ivSpec);

    byte[] decryptedBytes = cipher.doFinal(decodedBytes);
    return new String(decryptedBytes, StandardCharsets.UTF_8);
}

Lưu ý quan trọng về bảo mật

  • Không dùng ECB cho dữ liệu có cấu trúc lặp — nó tiết lộ mẫu rõ ràng.
  • IV phải được sinh ngẫu nhiên mỗi lần mã hóa (ví dụ bằng SecureRandom) và truyền kèm cùng ciphertext.
  • DES đã lỗi thời — nên tránh sử dụng trong hệ thống mới; ưu tiên AES-128 trở lên.
  • Luôn chỉ định rõ charset (ví dụ UTF_8) khi chuyển đổi chuỗi ↔ mảng byte để tránh lỗi mã hóa trên nền tảng khác nhau.

Khởi tạo Cipher và quản lý trạng thái

Lớp Cipher không phải thread-safe. Mỗi instance chỉ nên dùng cho một luồng duy nhất hoặc được khởi tạo lại bằng init() trước mỗi lần sử dụng. Gọi doFinal() sẽ tự động reset trạng thái nội bộ — cho phép tái sử dụng cùng instance nếu cần xử lý nhiều khối dữ liệu tuần tự.

Thẻ: aes DES Java-Cryptography Cipher PKCS5Padding

Đăng vào ngày 6 tháng 8 lúc 15:09