Các byte ban đầu không chính xác sau khi giải mã Java AES / CBC


116

Ví dụ sau có gì sai?

Vấn đề là phần đầu tiên của chuỗi được giải mã là vô nghĩa. Tuy nhiên, phần còn lại vẫn ổn, tôi nhận được ...

Result: eB6OgeS��i are you? Have a nice day.
@Test
public void testEncrypt() {
  try {
    String s = "Hello there. How are you? Have a nice day.";

    // Generate key
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
    SecretKey aesKey = kgen.generateKey();

    // Encrypt cipher
    Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Encrypt
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
    cipherOutputStream.write(s.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();
    byte[] encryptedBytes = outputStream.toByteArray();

    // Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    outputStream = new ByteArrayOutputStream();
    ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
    CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
    byte[] buf = new byte[1024];
    int bytesRead;
    while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        outputStream.write(buf, 0, bytesRead);
    }

    System.out.println("Result: " + new String(outputStream.toByteArray()));

  } 
  catch (Exception ex) {
    ex.printStackTrace();
  }
}

48
KHÔNG SỬ DỤNG BẤT KỲ CÂU TRẢ LỜI NÀO CỦA CÂU HỎI NÀY TRONG DỰ ÁN NGHIÊM TÚC! Tất cả các ví dụ được cung cấp trong câu hỏi này đều dễ bị lừa bởi tiên tri đệm và nói chung là việc sử dụng mật mã rất tệ. Bạn sẽ giới thiệu lỗ hổng mật mã nghiêm trọng trong dự án của mình bằng cách sử dụng bất kỳ đoạn mã nào dưới đây.
HoLyVieR

16
@HoLyVieR, Về các trích dẫn sau: "Bạn không nên phát triển thư viện mật mã của riêng mình""sử dụng API cấp cao mà khung của bạn cung cấp". Không ai ở đây đang phát triển thư viện mật mã của riêng họ. Chúng tôi chỉ đơn giản là đang sử dụng API cấp cao đã tồn tại mà khung công tác java cung cấp. Thưa ông là hoàn toàn không chính xác.
k170

10
@MaartenBodewes, Chỉ vì cả hai đồng ý không có nghĩa là cả hai đều đúng. Các nhà phát triển giỏi biết sự khác biệt giữa gói một API cấp cao và viết lại một API cấp thấp. Những người đọc tốt sẽ nhận thấy rằng OP đã yêu cầu một "ví dụ mã hóa / giải mã java AES đơn giản" và đó chính xác là những gì anh ta nhận được . Tôi cũng không đồng ý với các câu trả lời khác, đó là lý do tại sao tôi đăng một câu trả lời của riêng mình. Có lẽ các bạn cũng nên thử như vậy và khai sáng cho chúng tôi bằng kiến ​​thức chuyên môn của bạn.
k170

6
@HoLyVieR Đó thực sự là điều ngớ ngẩn nhất mà tôi từng đọc trên SO! Bạn là ai để nói với mọi người những gì họ có thể và không thể phát triển?
TedTrippin

14
Tôi vẫn không thấy ví dụ nào @HoLyVieR. Hãy xem một số, hoặc con trỏ đến các thư viện? Không mang tính xây dựng gì cả.
danieljimenez

Câu trả lời:


245

Rất nhiều người bao gồm cả tôi gặp phải rất nhiều vấn đề khi thực hiện công việc này do thiếu một số thông tin như quên chuyển đổi sang Base64, vectơ khởi tạo, bộ ký tự, v.v. Vì vậy, tôi nghĩ đến việc tạo một mã đầy đủ chức năng.

Hy vọng điều này sẽ hữu ích cho tất cả các bạn: Để biên dịch, bạn cần thêm jar Apache Commons Codec, có sẵn tại đây: http://commons.apache.org/proper/commons-codec/download_codec.cgi

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string: "
                    + Base64.encodeBase64String(encrypted));

            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}

47
Nếu bạn không muốn phụ thuộc vào thư viện Apache Commons Codec của bên thứ 3, bạn có thể sử dụng javax.xml.bind.DatatypeConverter của JDK để thực hiện mã hóa / giải mã Base64: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0

8
Bạn có đang sử dụng IV không đổi ?!
vianna77

36
Java 8 đã có các công cụ Base64: java.util.Base64.getDecoder () và java.util.Base64.getEncoder ()
Hristo Stoyanov

11
IV không cần phải bí mật, nhưng nó phải khó đoán đối với chế độ CBC (và duy nhất đối với CTR). Nó có thể được gửi cùng với bản mã. Một cách phổ biến để làm điều này là thêm tiền tố IV vào bản mã và cắt nó ra trước khi giải mã. Nó nên được tạo ra thông quaSecureRandom
Artjom B.

6
Mật khẩu không phải là chìa khóa. IV phải là ngẫu nhiên.
Maarten Bodewes

40

Dưới đây là một giải pháp mà không Apache Commons CodecBase64:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

    public AdvancedEncryptionStandard(byte[] key)
    {
        this.key = key;
    }

    /**
     * Encrypts the given plain text
     *
     * @param plainText The plain text to encrypt
     */
    public byte[] encrypt(byte[] plainText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(plainText);
    }

    /**
     * Decrypts the given byte array
     *
     * @param cipherText The data to decrypt
     */
    public byte[] decrypt(byte[] cipherText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(cipherText);
    }
}

Ví dụ sử dụng:

byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
        encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);

System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));

Bản in:

Hello world!
դ;��LA+�ߙb*
Hello world!

5
Đây là một ví dụ hoàn hảo về chức năng, giống như của @ chandpriyankara. Nhưng tại sao phải xác định chữ ký của encrypt(String)và không encrypt(byte[] )?. Mã hóa (giải mã quá) là một quá trình dựa trên byte (dù sao cũng là AES). Mã hóa lấy byte làm đầu vào và xuất ra byte, giải mã cũng vậy (trường hợp cụ thể: Cipherđối tượng thì có). Bây giờ, một trường hợp sử dụng cụ thể có thể có các byte được mã hóa đến từ một Chuỗi hoặc được gửi dưới dạng một Chuỗi (tệp đính kèm MIME base64 cho Thư ...), nhưng đó là vấn đề về mã hóa byte, trong đó tồn tại hàng trăm các giải pháp, hoàn toàn không liên quan đến AES / mã hóa.
GPI

3
@GPI: Có, nhưng tôi thấy nó hữu ích hơn Stringsvì về cơ bản đó là những gì tôi làm việc với 95% thời gian và cuối cùng bạn vẫn chuyển đổi.
BullyWiiPlaza

9
Không, điều này không tương đương với mã của chandpriyankara! Mã của bạn sử dụng ECB thường không an toàn và không được mong muốn. Nên chỉ định rõ ràng CBC. Khi CBC được chỉ định, mã của bạn sẽ bị hỏng.
Dan

Hoạt động hoàn hảo, hoàn toàn không an toàn và sử dụng các phương pháp lập trình rất tệ. lớp được đặt tên xấu. Kích thước khóa không được kiểm tra trước. Nhưng quan trọng nhất, mã sử dụng chế độ ECB không an toàn, ẩn vấn đề trong câu hỏi ban đầu . Cuối cùng, nó không chỉ định mã hóa ký tự, có nghĩa là việc giải mã thành văn bản có thể không thành công trên các nền tảng khác.
Maarten Bodewes

24

Có vẻ như bạn đang xử lý không đúng với Véc tơ khởi tạo (IV) của mình. Đã lâu rồi kể từ lần cuối tôi đọc về AES, IV và chuỗi khối, nhưng dòng của bạn

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());

dường như không ổn. Trong trường hợp AES, bạn có thể coi vectơ khởi tạo là "trạng thái ban đầu" của một phiên bản mật mã và trạng thái này là một chút thông tin mà bạn không thể lấy từ khóa của mình mà từ tính toán thực tế của mật mã đang mã hóa. (Người ta có thể tranh luận rằng nếu IV có thể được trích xuất từ ​​khóa, thì nó sẽ không có ích gì, vì khóa đã được cấp cho cá thể mật mã trong giai đoạn init của nó).

Do đó, bạn nên lấy IV dưới dạng byte [] từ phiên bản mật mã ở cuối mã hóa của bạn

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();

và bạn nên khởi tạo của bạn Ciphertrong DECRYPT_MODEvới byte này []:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Sau đó, việc giải mã của bạn sẽ ổn. Hi vọng điêu nay co ich.


Cảm ơn vì đã giúp một người mới. Tôi lấy ví dụ này từ các bài viết khác. Tôi không cho là bạn biết làm thế nào để tránh phải tiêm IV? Tôi đã thấy, nhưng chưa thử, các ví dụ AES khác không sử dụng nó.
TedTrippin

Bỏ qua điều đó, tôi đã tìm ra câu trả lời! Tôi cần sử dụng AES / ECB / PKCS5Padding.
TedTrippin

20
Hầu hết các trường hợp bạn không muốn sử dụng ECB. Chỉ cần google tại sao.
João Fernandes

2
@Mushy: đã đồng ý rằng việc chọn và thiết lập rõ ràng IV, từ một nguồn ngẫu nhiên đáng tin cậy, tốt hơn là chỉ để cá thể Cihper chọn một. Mặt khác, câu trả lời này giải quyết vấn đề ban đầu là gây nhầm lẫn vectơ khởi tạo cho khóa. Đó là lý do tại sao nó đã được ủng hộ lúc đầu. Giờ đây, bài đăng này đã trở thành một đoạn mã mẫu nhiều hơn, và mọi người ở đây đã thực hiện một số ví dụ tuyệt vời - chỉ bên cạnh câu hỏi ban đầu là về cái gì.
GPI

3
@GPI Bình chọn. Các "ví dụ tuyệt vời" khác không tuyệt vời như vậy, và chúng không thực sự giải quyết được câu hỏi. Thay vào đó, đây dường như là nơi cho những người mới sao chép một cách mù quáng các mẫu mật mã mà không hiểu rằng có thể có các vấn đề bảo mật - và như mọi khi, vẫn có.
Maarten Bodewes

17

IV mà bạn sử dụng để giải mã không chính xác. Thay thế mã này

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

Với mã này

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

Và điều đó sẽ giải quyết vấn đề của bạn.


Dưới đây bao gồm một ví dụ về một lớp AES đơn giản trong Java. Tôi không khuyên bạn nên sử dụng lớp này trong môi trường sản xuất, vì nó có thể không tính đến tất cả các nhu cầu cụ thể của ứng dụng của bạn.

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AES 
{
    public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        byte[] transformedBytes = null;

        try
        {
            final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

            cipher.init(mode, keySpec, ivSpec);

            transformedBytes = cipher.doFinal(messageBytes);
        }        
        catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) 
        {
            e.printStackTrace();
        }
        return transformedBytes;
    }

    public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        //Retrieved from a protected local file.
        //Do not hard-code and do not version control.
        final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";

        //Retrieved from a protected database.
        //Do not hard-code and do not version control.
        final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";

        //Extract the iv and the ciphertext from the shadow entry.
        final String[] shadowData = shadowEntry.split(":");        
        final String base64Iv = shadowData[0];
        final String base64Ciphertext = shadowData[1];

        //Convert to raw bytes.
        final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
        final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);

        //Decrypt data and do something with it.
        final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);

        //Use non-blocking SecureRandom implementation for the new IV.
        final SecureRandom secureRandom = new SecureRandom();

        //Generate a new IV.
        secureRandom.nextBytes(ivBytes);

        //At this point instead of printing to the screen, 
        //one should replace the old shadow entry with the new one.
        System.out.println("Old Shadow Entry      = " + shadowEntry);
        System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
        System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
    }
}

Lưu ý rằng AES không liên quan gì đến mã hóa, đó là lý do tại sao tôi chọn xử lý nó một cách riêng biệt và không cần bất kỳ thư viện bên thứ ba nào.


Trước hết, bạn đã không trả lời câu hỏi ban đầu. Thứ hai, tại sao bạn trả lời một câu hỏi đã được trả lời và được chấp nhận? Tôi nghĩ rằng bảo vệ phải ngăn chặn thư rác này.
TedTrippin

14
Giống như câu trả lời được chấp nhận, tôi đã chọn trả lời câu hỏi của bạn qua ví dụ. Tôi đã cung cấp một đoạn mã đầy đủ chức năng, cho bạn thấy cách xử lý đúng cách với vectơ khởi tạo, trong số những thứ khác. Đối với câu hỏi thứ hai của bạn, tôi cảm thấy rằng cần có câu trả lời cập nhật vì codec Apache không còn cần thiết nữa. Vì vậy, không, đây không phải là thư rác. Ngừng trippin.
k170

7
Một IV có một mục đích cụ thể mà là ngẫu nhiên các bản mã và cung cấp bảo mật ngữ nghĩa. Nếu bạn sử dụng cùng một cặp khóa + IV thì những kẻ tấn công có thể xác định xem bạn đã gửi tin nhắn có cùng tiền tố như trước đó hay chưa. IV không cần phải bí mật, nhưng nó phải không thể đoán trước. Một cách phổ biến là chỉ cần thêm tiền tố IV vào bản mã và cắt nó ra trước khi giải mã.
Artjom B.

4
downvote: hardcoded IV, xem Artjom B. bình luận ở trên tại sao nó xấu
Murmel

1
Chế độ CTR nên được ghép nối với NoPadding. Chế độ CTR chắc chắn là không cần thiết thay vì CBC (trừ khi thầy mo đệm phù hợp), nhưng nếu CTR được sử dụng, sau đó sử dụng "/NoPadding". CTR là một chế độ biến AES thành mật mã luồng và mật mã luồng hoạt động trên byte thay vì khối.
Maarten Bodewes

16

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 CipherInputStreamkhi 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 .


Vấn đề là yêu cầu ví dụ rõ ràng là lạc đề trên SO. Và vấn đề lớn hơn là đây là những đoạn mã chưa được đánh giá, rất khó xác thực. Tôi đánh giá cao nỗ lực, nhưng tôi không nghĩ rằng SO nên là nơi cho việc này.
Maarten Bodewes

1
Mặc dù vậy, tôi rất ngưỡng mộ nỗ lực, vì vậy tôi sẽ chỉ chỉ ra một sai lầm duy nhất: "iv phải không thể đoán trước kết hợp với độc nhất (tức là sử dụng iv ngẫu nhiên)" - điều này đúng với chế độ CBC nhưng không đúng với GCM.
Maarten Bodewes

this is true for CBC mode but not for GCMý bạn là toàn bộ, hay chỉ là nó không thực sự cần phải đoán trước?
Patrick Favre

1
"Nếu bạn không nhận được chủ đề thì có lẽ bạn không nên sử dụng các nguyên mẫu cấp thấp ngay từ đầu" chắc chắn, điều đó NÊN là vậy, nhiều nhà phát triển vẫn làm điều đó. Tôi không chắc việc hạn chế đưa nội dung chất lượng cao liên quan đến bảo mật / mật mã ở những nơi thường không có nhiều giải pháp phù hợp cho việc này. - thx vì đã chỉ ra lỗi lầm của tôi btw
Patrick Favre

1
OK, chỉ vì tôi thích nội dung answer wrt (hơn là mục đích): việc xử lý IV ​​có thể được đơn giản hóa đặc biệt là trong quá trình giải mã: Java giúp dễ dàng tạo IV trực tiếp từ một mảng byte hiện có. Tương tự đối với việc giải mã, không phải bắt đầu ở độ lệch 0. Tất cả việc sao chép này đơn giản là không cần thiết. Ngoài ra, nếu bạn phải gửi độ dài cho IV (bạn phải không?) Thì tại sao không sử dụng một byte duy nhất (không dấu) - bạn sẽ không vượt qua 255 byte cho IV, phải không?
Maarten Bodewes

2

Phiên bản Runnable của Trình chỉnh sửa Trực tuyến: -

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());

            //System.out.println("encrypted string: "
              //      + Base64.encodeBase64String(encrypted));

            //return Base64.encodeBase64String(encrypted);
            String s = new String(Base64.getEncoder().encode(encrypted));
            return s;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}

Tuyệt, rất vui vì nó đã giúp!
Bhupesh Pant

Mật khẩu không phải là khóa, IV không được tĩnh. Vẫn là mã được nhập theo chuỗi, điều này khiến bạn không thể hủy khóa. Không có dấu hiệu nào cho thấy phải làm gì với IV, cũng không có ý kiến ​​cho rằng nó không thể đoán trước được.
Maarten Bodewes

1

Thông thường, bạn nên dựa vào giải pháp được cung cấp thư viện tiêu chuẩn:

private static void stackOverflow15554296()
    throws
        NoSuchAlgorithmException, NoSuchPaddingException,        
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException
{

    // prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();
    String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
            aesKey.getEncoded()
    );

    // cipher engine
    Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

    // cipher input
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
    byte[] clearTextBuff = "Text to encode".getBytes();
    byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);

    // recreate key
    byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
    SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");

    // decipher input
    aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
    byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
    System.out.println(new String(decipheredBuff));
}

Điều này in ra "Văn bản để mã hóa".

Giải pháp dựa trên Hướng dẫn Tham khảo Kiến trúc Mật mã Java và câu trả lời https://stackoverflow.com/a/20591539/146745 .


5
Không bao giờ sử dụng chế độ ECB. Giai đoạn = Stage.
Konstantino Sparakis

1
Không nên sử dụng ECB nếu mã hóa nhiều hơn một khối dữ liệu bằng cùng một khóa, vì vậy đối với "Văn bản để mã hóa" là đủ tốt. stackoverflow.com/a/1220869/146745
andrej

Chìa khóa @AndroidDev được tạo ra trong chuẩn bị phần chính: aesKey = keygen.generateKey ()
Andrej

1

Đây là một cải tiến so với câu trả lời được chấp nhận.

Các thay đổi:

(1) Sử dụng IV ngẫu nhiên và thêm nó vào văn bản được mã hóa

(2) Sử dụng SHA-256 để tạo khóa từ cụm mật khẩu

(3) Không phụ thuộc vào Apache Commons

public static void main(String[] args) throws GeneralSecurityException {
    String plaintext = "Hello world";
    String passphrase = "My passphrase";
    String encrypted = encrypt(passphrase, plaintext);
    String decrypted = decrypt(passphrase, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
}

private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}

private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
    return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}

public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
    byte[] initVector = new byte[16];
    SecureRandom.getInstanceStrong().nextBytes(initVector);
    Cipher cipher = getCipher();
    cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] encrypted = cipher.doFinal(value.getBytes());
    return DatatypeConverter.printBase64Binary(initVector) +
            DatatypeConverter.printBase64Binary(encrypted);
}

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}

Hàm băm vẫn không phải là hàm tạo khóa dựa trên mật khẩu / PBKDF. Bạn sử dụng khóa ngẫu nhiên hoặc bạn sử dụng PBKDF chẳng hạn như PBKDF2 / Mã hóa dựa trên mật khẩu.
Maarten Bodewes

@MaartenBodewes Bạn có thể đề xuất một cải tiến không?
wvdz

PBKDF2 hiện có trong Java, vì vậy tôi nghĩ tôi chỉ đề xuất một cái. OK, tôi không viết mã một cái nào, nhưng theo tôi thì điều đó đòi hỏi hơi quá nhiều. Có rất nhiều ví dụ về Mã hóa dựa trên mật khẩu.
Maarten Bodewes

@MaartenBodewes Tôi nghĩ đó có thể là một bản sửa lỗi đơn giản. Vì tò mò, những lỗ hổng cụ thể sẽ là gì khi sử dụng mã này?
wvdz

0

Một giải pháp khác sử dụng java.util.Base64 với Spring Boot

Lớp mã hóa

package com.jmendoza.springboot.crypto.cipher;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class Encryptor {

    @Value("${security.encryptor.key}")
    private byte[] key;
    @Value("${security.encryptor.algorithm}")
    private String algorithm;

    public String encrypt(String plainText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
    }

    public String decrypt(String cipherText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

Lớp EncryptorController

package com.jmendoza.springboot.crypto.controller;

import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cipher")
public class EncryptorController {

    @Autowired
    Encryptor encryptor;

    @GetMapping(value = "encrypt/{value}")
    public String encrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.encrypt(value);
    }

    @GetMapping(value = "decrypt/{value}")
    public String decrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.decrypt(value);
    }
}

application.properties

server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0

Thí dụ

http: // localhost: 8082 / cipher / encode / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http: // localhost: 8082 / cipher / decrypt / 2h41HH8Shzc4BRU3hVDOXA ==

jmendoza


-1

Phiên bản tối ưu hóa của câu trả lời được chấp nhận.

  • không có lib của bên thứ 3

  • bao gồm IV vào tin nhắn được mã hóa (có thể công khai)

  • mật khẩu có thể có độ dài bất kỳ

Mã:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Encryptor {
    public static byte[] getRandomInitialVector() {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
            byte[] initVector = new byte[cipher.getBlockSize()];
            randomSecureRandom.nextBytes(initVector);
            return initVector;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static byte[] passwordTo16BitKey(String password) {
        try {
            byte[] srcBytes = password.getBytes("UTF-8");
            byte[] dstBytes = new byte[16];

            if (srcBytes.length == 16) {
                return srcBytes;
            }

            if (srcBytes.length < 16) {
                for (int i = 0; i < dstBytes.length; i++) {
                    dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                }
            } else if (srcBytes.length > 16) {
                for (int i = 0; i < srcBytes.length; i++) {
                    dstBytes[i % dstBytes.length] += srcBytes[i];
                }
            }

            return dstBytes;
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String encrypt(String key, String value) {
        return encrypt(passwordTo16BitKey(key), value);
    }

    public static String encrypt(byte[] key, String value) {
        try {
            byte[] initVector = Encryptor.getRandomInitialVector();
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String encrypted) {
        return decrypt(passwordTo16BitKey(key), encrypted);
    }

    public static String decrypt(byte[] key, String encrypted) {
        try {
            String[] encryptedParts = encrypted.split(" ");
            byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
            if (initVector.length != 16) {
                return null;
            }

            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }
}

Sử dụng:

String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);

Ví dụ đầu ra:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World

Chức năng lấy lại mật khẩu của bạn không an toàn. Tôi không mong đợi e.printStackTrace()trong cái gọi là mã tối ưu hóa.
Maarten Bodewes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.