Trong câu trả lời này, tôi chọn cách tiếp cận chủ đề chính "Ví dụ mã hóa / giải mã Java AES đơn giản" chứ không phải câu hỏi gỡ lỗi cụ thể vì tôi nghĩ điều này sẽ mang lại lợi nhuận cho hầu hết người đọc.
Đây là một bản tóm tắt đơn giản về bài đăng trên blog của tôi về mã hóa AES trong Java, vì vậy tôi khuyên bạn nên đọc qua nó trước khi triển khai bất kỳ thứ gì. Tuy nhiên, tôi vẫn sẽ cung cấp một ví dụ đơn giản để sử dụng và đưa ra một số gợi ý về những điều cần chú ý.
Trong ví dụ này, tôi sẽ chọn sử dụng mã hóa được xác thực với Chế độ Galois / Bộ đếm hoặc chế độ GCM . Lý do là trong hầu hết các trường hợp, bạn muốn tính toàn vẹn và tính xác thực kết hợp với tính bảo mật (đọc thêm trong blog ).
Hướng dẫn mã hóa / giải mã AES-GCM
Dưới đây là các bước cần thiết để mã hóa / giải mã bằng AES-GCM với Kiến trúc mã hóa Java (JCA) . Không trộn lẫn với các ví dụ khác , vì sự khác biệt nhỏ có thể làm cho mã của bạn hoàn toàn không an toàn.
1. Tạo khóa
Vì nó phụ thuộc vào trường hợp sử dụng của bạn, tôi sẽ giả sử trường hợp đơn giản nhất: một khóa bí mật ngẫu nhiên.
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");
Quan trọng:
2. Tạo vectơ khởi tạo
Một vectơ khởi tạo (IV) được sử dụng để cùng một khóa bí mật sẽ tạo ra các văn bản mật mã khác nhau .
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
Quan trọng:
3. Mã hóa bằng IV và Key
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);
Quan trọng:
- sử dụng thẻ xác thực 16 byte / 128 bit (được sử dụng để xác minh tính toàn vẹn / tính xác thực)
- thẻ xác thực sẽ được tự động thêm vào văn bản mật mã (trong triển khai JCA)
- vì GCM hoạt động giống như một mật mã luồng, không cần đệm
- sử dụng
CipherInputStream
khi mã hóa khối lượng lớn dữ liệu
- muốn dữ liệu bổ sung (không bí mật) được kiểm tra nếu nó đã được thay đổi? Bạn có thể muốn sử dụng dữ liệu được liên kết với
cipher.updateAAD(associatedData);
Thêm tại đây.
3. Nối tiếp thành một tin nhắn
Chỉ cần nối IV và bản mã. Như đã nêu ở trên, IV không cần phải bí mật.
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Tùy chọn mã hóa bằng Base64 nếu bạn cần biểu diễn chuỗi. Sử dụng triển khai tích hợp của Android hoặc Java 8 (không sử dụng Apache Commons Codec - đó là một triển khai tồi tệ). Mã hóa được sử dụng để "chuyển đổi" các mảng byte thành biểu diễn chuỗi để làm cho nó ASCII an toàn, ví dụ:
String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
4. Chuẩn bị giải mã: Deserialize
Nếu bạn đã mã hóa tin nhắn, trước tiên hãy giải mã nó thành mảng byte:
byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
Quan trọng:
5. Giải mã
Khởi tạo mật mã và đặt các thông số tương tự như khi mã hóa:
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);
Quan trọng:
- đừng quên để thêm dữ liệu liên quan với
cipher.updateAAD(associatedData);
nếu bạn thêm nó trong mã hóa.
Đoạn mã hoạt động có thể được tìm thấy trong ý chính này.
Lưu ý rằng các triển khai Android (SDK 21+) và Java (7+) gần đây nhất phải có AES-GCM. Các phiên bản cũ hơn có thể thiếu nó. Tôi vẫn chọn chế độ này, vì nó dễ thực hiện hơn ngoài việc hiệu quả hơn so với chế độ tương tự của Encrypt-then-Mac (ví dụ: AES-CBC + HMAC ). Xem bài viết này về cách triển khai AES-CBC với HMAC .