Các phương pháp hay nhất để sử dụng mã hóa AES trong Android là gì?


90

Tại sao tôi hỏi câu hỏi này:

Tôi biết đã có rất nhiều câu hỏi về mã hóa AES, ngay cả đối với Android. Và có rất nhiều đoạn mã nếu bạn tìm kiếm trên Web. Nhưng trên mỗi trang, trong mỗi câu hỏi về Stack Overflow, tôi tìm thấy một cách triển khai khác với những điểm khác biệt lớn.

Vì vậy, tôi tạo câu hỏi này để tìm một "phương pháp hay nhất". Tôi hy vọng chúng tôi có thể thu thập danh sách các yêu cầu quan trọng nhất và thiết lập một triển khai thực sự an toàn!

Tôi đọc về vectơ khởi tạo và muối. Không phải tất cả các triển khai tôi tìm thấy đều có các tính năng này. Vậy bạn có cần nó không? Nó có tăng tính bảo mật rất nhiều không? Làm thế nào để bạn thực hiện nó? Thuật toán có nên nêu ra các ngoại lệ nếu dữ liệu được mã hóa không thể giải mã được không? Hay điều đó không an toàn và nó sẽ chỉ trả về một chuỗi không thể đọc được? Thuật toán có thể sử dụng Bcrypt thay vì SHA không?

Điều gì về hai triển khai tôi tìm thấy? Họ có ổn không? Hoàn hảo hay thiếu một số thứ quan trọng? Cái gì trong số này là an toàn?

Thuật toán nên lấy một chuỗi và một "mật khẩu" để mã hóa và sau đó mã hóa chuỗi bằng mật khẩu đó. Đầu ra phải là một chuỗi (hex hoặc base64?) Một lần nữa. Tất nhiên cũng nên giải mã.

Triển khai AES hoàn hảo cho Android là gì?

Thực hiện # 1:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class AdvancedCrypto implements ICrypto {

        public static final String PROVIDER = "BC";
        public static final int SALT_LENGTH = 20;
        public static final int IV_LENGTH = 16;
        public static final int PBE_ITERATION_COUNT = 100;

        private static final String RANDOM_ALGORITHM = "SHA1PRNG";
        private static final String HASH_ALGORITHM = "SHA-512";
        private static final String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
        private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
        private static final String SECRET_KEY_ALGORITHM = "AES";

        public String encrypt(SecretKey secret, String cleartext) throws CryptoException {
                try {

                        byte[] iv = generateIv();
                        String ivHex = HexEncoder.toHex(iv);
                        IvParameterSpec ivspec = new IvParameterSpec(iv);

                        Cipher encryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
                        encryptionCipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
                        byte[] encryptedText = encryptionCipher.doFinal(cleartext.getBytes("UTF-8"));
                        String encryptedHex = HexEncoder.toHex(encryptedText);

                        return ivHex + encryptedHex;

                } catch (Exception e) {
                        throw new CryptoException("Unable to encrypt", e);
                }
        }

        public String decrypt(SecretKey secret, String encrypted) throws CryptoException {
                try {
                        Cipher decryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
                        String ivHex = encrypted.substring(0, IV_LENGTH * 2);
                        String encryptedHex = encrypted.substring(IV_LENGTH * 2);
                        IvParameterSpec ivspec = new IvParameterSpec(HexEncoder.toByte(ivHex));
                        decryptionCipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
                        byte[] decryptedText = decryptionCipher.doFinal(HexEncoder.toByte(encryptedHex));
                        String decrypted = new String(decryptedText, "UTF-8");
                        return decrypted;
                } catch (Exception e) {
                        throw new CryptoException("Unable to decrypt", e);
                }
        }

        public SecretKey getSecretKey(String password, String salt) throws CryptoException {
                try {
                        PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), HexEncoder.toByte(salt), PBE_ITERATION_COUNT, 256);
                        SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM, PROVIDER);
                        SecretKey tmp = factory.generateSecret(pbeKeySpec);
                        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), SECRET_KEY_ALGORITHM);
                        return secret;
                } catch (Exception e) {
                        throw new CryptoException("Unable to get secret key", e);
                }
        }

        public String getHash(String password, String salt) throws CryptoException {
                try {
                        String input = password + salt;
                        MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM, PROVIDER);
                        byte[] out = md.digest(input.getBytes("UTF-8"));
                        return HexEncoder.toHex(out);
                } catch (Exception e) {
                        throw new CryptoException("Unable to get hash", e);
                }
        }

        public String generateSalt() throws CryptoException {
                try {
                        SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
                        byte[] salt = new byte[SALT_LENGTH];
                        random.nextBytes(salt);
                        String saltHex = HexEncoder.toHex(salt);
                        return saltHex;
                } catch (Exception e) {
                        throw new CryptoException("Unable to generate salt", e);
                }
        }

        private byte[] generateIv() throws NoSuchAlgorithmException, NoSuchProviderException {
                SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
                byte[] iv = new byte[IV_LENGTH];
                random.nextBytes(iv);
                return iv;
        }

}

Nguồn: http://pocket-for-android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html

Thực hiện # 2:

import java.security.SecureRandom;

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

/**
 * Usage:
 * <pre>
 * String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
 * ...
 * String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
 * </pre>
 * @author ferenc.hechler
 */
public class SimpleCrypto {

    public static String encrypt(String seed, String cleartext) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = encrypt(rawKey, cleartext.getBytes());
        return toHex(result);
    }

    public static String decrypt(String seed, String encrypted) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] enc = toByte(encrypted);
        byte[] result = decrypt(rawKey, enc);
        return new String(result);
    }

    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(seed);
        kgen.init(128, sr); // 192 and 256 bits may not be available
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }


    private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }

    private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    public static String toHex(String txt) {
        return toHex(txt.getBytes());
    }
    public static String fromHex(String hex) {
        return new String(toByte(hex));
    }

    public static byte[] toByte(String hexString) {
        int len = hexString.length()/2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
        return result;
    }

    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2*buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }
    private final static String HEX = "0123456789ABCDEF";
    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
    }

}

Nguồn: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml


Tôi đang cố gắng triển khai giải pháp 1 nhưng nó cần một số lớp. bạn có mã nguồn đầy đủ không?
albanx

1
Không, tôi chưa, xin lỗi. Nhưng tôi đã làm cho nó hoạt động bằng cách đơn giản là xóa implements ICryptovà thay đổi throws CryptoExceptionthành throws Exception, v.v. Vì vậy, bạn sẽ không cần những lớp học đó nữa.
caw

Nhưng cũng thiếu lớp HexEncoder? Tôi có thể tìm thấy nó ở đâu?
albanx

Tôi nghĩ HexEncoder là một phần của thư viện BouncyCastle. Bạn chỉ cần tải xuống. Hoặc bạn có thể google để tìm "byte [] thành hex" và ngược lại trong Java.
caw

Cảm ơn Marco. Nhưng tôi nhận thấy rằng có 3 phương pháp getSecretKey, getHash, generateSalttrong việc thực hiện đầu tiên mà không sử dụng. Có thể tôi sai nhưng làm thế nào mà lớp này có thể được sử dụng để mã hóa một chuỗi trong thực tế?
albanx

Câu trả lời:


37

Cả cách triển khai bạn đưa ra trong câu hỏi của mình đều không hoàn toàn chính xác và cả cách triển khai bạn đưa ra cũng không được sử dụng như hiện tại. Trong phần tiếp theo, tôi sẽ thảo luận về các khía cạnh của mã hóa dựa trên mật khẩu trong Android.

Phím và băm

Tôi sẽ bắt đầu thảo luận về hệ thống dựa trên mật khẩu với các muối. Muối là một số được tạo ngẫu nhiên. Nó không được "suy ra". Triển khai 1 bao gồm một generateSalt()phương pháp tạo ra một số ngẫu nhiên mạnh về mặt mật mã. Vì muối rất quan trọng đối với vấn đề bảo mật, nên nó cần được giữ bí mật khi nó được tạo ra, mặc dù nó chỉ cần được tạo ra một lần. Nếu đây là một trang Web, việc giữ bí mật về muối tương đối dễ dàng, nhưng đối với các ứng dụng đã cài đặt (dành cho máy tính để bàn và thiết bị di động), điều này sẽ khó hơn nhiều.

Phương thức getHash()trả về một hàm băm của mật khẩu và muối đã cho, được nối thành một chuỗi duy nhất. Thuật toán được sử dụng là SHA-512, trả về một băm 512 bit. Phương thức này trả về một hàm băm hữu ích để kiểm tra tính toàn vẹn của một chuỗi, vì vậy nó cũng có thể được sử dụng bằng cách gọi getHash()chỉ với một mật khẩu hoặc chỉ một muối, vì nó chỉ đơn giản là nối cả hai tham số. Vì phương pháp này sẽ không được sử dụng trong hệ thống mã hóa dựa trên mật khẩu, tôi sẽ không thảo luận thêm về nó.

Phương thức này getSecretKey()lấy khóa từ một charmảng mật khẩu và một muối được mã hóa hex, được trả về từ generateSalt(). Thuật toán được sử dụng là PBKDF1 (tôi nghĩ) từ PKCS5 với SHA-256 là hàm băm và trả về khóa 256 bit. getSecretKey()tạo khóa bằng cách tạo liên tục các hàm băm của mật khẩu, muối và bộ đếm (lên đến số lần lặp được cung cấp PBE_ITERATION_COUNT, ở đây là 100) để tăng thời gian cần thiết để thực hiện một cuộc tấn công bạo lực. Độ dài của muối ít nhất phải dài bằng với khóa được tạo, trong trường hợp này, ít nhất là 256 bit. Số lần lặp nên được đặt càng lâu càng tốt mà không gây ra độ trễ bất hợp lý. Để biết thêm thông tin về muối và số lần lặp lại trong dẫn xuất khóa, hãy xem phần 4 trong RFC2898 .

Tuy nhiên, việc triển khai trong PBE của Java là thiếu sót nếu mật khẩu chứa các ký tự Unicode, tức là những ký tự yêu cầu nhiều hơn 8 bit để được biểu diễn. Như đã nêu trong PBEKeySpec, "cơ chế PBE được định nghĩa trong PKCS # 5 chỉ xem xét 8 bit bậc thấp của mỗi ký tự". Để khắc phục sự cố này, bạn có thể thử tạo một chuỗi hex (sẽ chỉ chứa các ký tự 8 bit) của tất cả các ký tự 16 bit trong mật khẩu trước khi chuyển nó đến PBEKeySpec. Ví dụ: "ABC" trở thành "004100420043". Cũng lưu ý rằng PBEKeySpec "yêu cầu mật khẩu dưới dạng mảng char, vì vậy nó có thể được ghi đè [bằng clearPassword()] khi hoàn tất". (Liên quan đến "bảo vệ chuỗi trong bộ nhớ", hãy xem câu hỏi này .) Tuy nhiên, tôi không thấy bất kỳ vấn đề nào,

Mã hóa

Khi một khóa được tạo, chúng ta có thể sử dụng nó để mã hóa và giải mã văn bản.

Trong cách triển khai 1, thuật toán mật mã được sử dụng là AES/CBC/PKCS5PaddingAES trong chế độ mã hóa chuỗi khối Cipher (CBC), với phần đệm được xác định trong PKCS # 5. (Các chế độ mật mã AES khác bao gồm chế độ bộ đếm (CTR), chế độ sổ mã điện tử (ECB) và chế độ bộ đếm Galois (GCM). Một câu hỏi khác trên Stack Overflow chứa các câu trả lời thảo luận chi tiết về các chế độ mật mã AES khác nhau và các chế độ được khuyến nghị sử dụng. Cũng cần biết rằng có một số cuộc tấn công vào mã hóa chế độ CBC, một số trong số đó được đề cập trong RFC 7457.)

Lưu ý rằng bạn nên sử dụng chế độ mã hóa cũng kiểm tra tính toàn vẹn của dữ liệu được mã hóa (ví dụ: mã hóa được xác thực với dữ liệu liên quan , AEAD, được mô tả trong RFC 5116). Tuy nhiên, AES/CBC/PKCS5Paddingnó không cung cấp tính năng kiểm tra tính toàn vẹn, vì vậy nó không được khuyến khích . Đối với mục đích AEAD, khuyến nghị sử dụng bí mật dài ít nhất gấp đôi so với khóa mã hóa thông thường, để tránh các cuộc tấn công khóa liên quan: nửa đầu dùng làm khóa mã hóa và nửa sau dùng làm khóa để kiểm tra tính toàn vẹn. (Có nghĩa là, trong trường hợp này, tạo một bí mật duy nhất từ ​​mật khẩu và muối, và chia bí mật đó thành hai.)

Triển khai Java

Các chức năng khác nhau trong triển khai 1 sử dụng một trình cung cấp cụ thể, cụ thể là "BC", cho các thuật toán của nó. Tuy nhiên, nói chung, không nên yêu cầu các nhà cung cấp cụ thể vì không phải tất cả các nhà cung cấp đều có sẵn trên tất cả các triển khai Java, cho dù thiếu hỗ trợ, để tránh trùng lặp mã hoặc vì các lý do khác. Lời khuyên này đặc biệt trở nên quan trọng kể từ khi phát hành bản xem trước Android P vào đầu năm 2018, vì một số chức năng từ nhà cung cấp "BC" đã không còn được dùng ở đó - hãy xem bài viết "Những thay đổi về mật mã trong Android P" trong Blog nhà phát triển Android. Xem thêm Giới thiệu về Nhà cung cấp Oracle .

Do đó, PROVIDERkhông nên tồn tại và chuỗi -BCphải được xóa khỏi PBE_ALGORITHM. Thực hiện 2 là đúng về mặt này.

Không thích hợp cho một phương pháp để bắt tất cả các ngoại lệ, mà là chỉ xử lý các ngoại lệ mà nó có thể. Các triển khai được đưa ra trong câu hỏi của bạn có thể tạo ra nhiều trường hợp ngoại lệ đã được kiểm tra. Một phương thức có thể chọn chỉ bọc các ngoại lệ đã kiểm tra đó với CryptoException hoặc chỉ định các ngoại lệ đã kiểm tra đó trong throwsmệnh đề. Để thuận tiện, gói ngoại lệ ban đầu với CryptoException có thể thích hợp ở đây, vì có nhiều khả năng ngoại lệ đã được kiểm tra mà các lớp có thể ném ra.

SecureRandom trong Android

Như đã nêu chi tiết trong bài viết "Một số suy nghĩ về bảo mật", trong Blog nhà phát triển Android, việc triển khai các java.security.SecureRandombản phát hành Android trước năm 2013 có một lỗ hổng làm giảm độ mạnh của các số ngẫu nhiên mà nó cung cấp. Lỗ hổng này có thể được giảm thiểu như được mô tả trong bài báo đó.


Việc tạo bí mật kép đó hơi lãng phí theo ý kiến ​​của tôi, bạn có thể dễ dàng chia bí mật đã tạo thành hai hoặc - nếu không có đủ bit - hãy thêm bộ đếm (1 cho khóa đầu tiên, 2 cho khóa thứ hai) vào bí mật và thực hiện một phép băm duy nhất. Không cần thực hiện tất cả các lần lặp lại hai lần.
Maarten Bodewes

Cảm ơn thông tin về HMAC và muối. Tôi sẽ không sử dụng HMAC lần này nhưng sau này nó có thể rất hữu ích. Và nói chung, đây là một điều tốt, không nghi ngờ gì nữa.
caw

Cảm ơn bạn rất nhiều về tất cả các chỉnh sửa và bài giới thiệu tuyệt vời này (bây giờ) về mã hóa AES trong Java!
caw

1
Nó nên. getInstancecó một quá tải chỉ lấy tên của thuật toán. Ví dụ: Cipher.getInstance () Một số nhà cung cấp, bao gồm Bouncy Castle, có thể được đăng ký trong việc triển khai Java và loại quá tải này tìm kiếm danh sách các nhà cung cấp cho một trong số họ triển khai thuật toán đã cho. Bạn nên thử nó và xem.
Peter O.

1
Đúng vậy, nó sẽ tìm kiếm các nhà cung cấp theo thứ tự được đưa ra bởi Security.getProviders () - mặc dù bây giờ nó cũng sẽ kiểm tra xem khóa có được nhà cung cấp đó chấp nhận trong cuộc gọi init () cho phép mã hóa phần cứng hỗ trợ hay không. Thêm chi tiết tại đây: docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/… .
Maarten Bodewes

18

# 2 không bao giờ được sử dụng vì nó chỉ sử dụng "AES" (có nghĩa là mã hóa chế độ ECB trên văn bản, một điều tuyệt vời không) cho mật mã. Tôi sẽ chỉ nói về số 1.

Việc triển khai đầu tiên dường như tuân thủ các phương pháp mã hóa tốt nhất. Các hằng số nói chung là OK, mặc dù cả kích thước muối và số lần lặp lại để thực hiện PBE đều ở khía cạnh ngắn. Hơn nữa, nó dường như dành cho AES-256 vì thế hệ khóa PBE sử dụng 256 như một giá trị được mã hóa cứng (thật đáng tiếc sau tất cả những hằng số đó). Nó sử dụng CBC và PKCS5Padding ít nhất là những gì bạn mong đợi.

Hoàn toàn thiếu là bất kỳ xác thực / bảo vệ toàn vẹn nào, vì vậy kẻ tấn công có thể thay đổi văn bản mật mã. Điều này có nghĩa là các cuộc tấn công padding oracle có thể xảy ra trong mô hình máy khách / máy chủ. Điều đó cũng có nghĩa là kẻ tấn công có thể thử và thay đổi dữ liệu được mã hóa. Điều này có thể sẽ dẫn đến một số lỗi ở đâu đó vì phần đệm hoặc nội dung không được ứng dụng chấp nhận, nhưng đó không phải là tình huống bạn muốn gặp phải.

Việc xử lý ngoại lệ và xác thực đầu vào có thể được nâng cao, việc bắt Exception luôn sai trong sách của tôi. Furhtermore, lớp học thực hiện ICrypt, mà tôi không biết. Tôi biết rằng chỉ có các phương thức mà không có tác dụng phụ trong một lớp thì hơi kỳ lạ. Thông thường, bạn sẽ làm cho những động tĩnh đó. Không có bộ đệm của các cá thể Mật mã, v.v., vì vậy mọi đối tượng được yêu cầu đều được tạo ad-nauseum. Tuy nhiên, có vẻ như bạn có thể xóa ICrypto khỏi định nghĩa một cách an toàn, trong trường hợp đó, bạn cũng có thể cấu trúc lại mã thành các phương thức tĩnh (hoặc viết lại nó để hướng đối tượng hơn, tùy chọn của bạn).

Vấn đề là bất kỳ trình bao bọc nào cũng luôn đưa ra các giả định về trường hợp sử dụng. Do đó, để nói rằng một trình bao bọc là đúng hay sai là không đúng. Đây là lý do tại sao tôi luôn cố gắng tránh tạo ra các lớp trình bao bọc. Nhưng ít nhất nó không có vẻ sai rõ ràng.


Cảm ơn bạn rất nhiều vì câu trả lời chi tiết này! Tôi biết là rất tiếc nhưng tôi chưa biết phần xem lại mã: D Cảm ơn vì gợi ý này, tôi sẽ kiểm tra điều đó. Nhưng câu hỏi này cũng phù hợp ở đây theo ý kiến ​​của tôi vì tôi không chỉ muốn xem xét các đoạn mã này. Thay vào đó, tôi muốn hỏi bạn tất cả những khía cạnh nào là quan trọng khi triển khai mã hóa AES trong Android. Và bạn lại đúng, đoạn mã này dành cho AES-256. Vì vậy, bạn sẽ nói rằng đây là cách triển khai AES-256 an toàn? Trường hợp sử dụng là tôi chỉ muốn lưu trữ thông tin văn bản một cách an toàn trong cơ sở dữ liệu.
caw

1
Nó có vẻ tốt, nhưng ý tưởng về việc không có kiểm tra tính toàn vẹn và xác thực sẽ làm phiền tôi. Nếu bạn có đủ dung lượng, tôi sẽ nghiêm túc xem xét việc thêm HMAC trên bản mã. Điều đó nói rằng, vì bạn có thể đang cố gắng đơn giản thêm tính bảo mật, tôi sẽ coi đó là một điểm cộng lớn, nhưng không trực tiếp là một yêu cầu.
Maarten Bodewes

Nhưng nếu mục đích chỉ là những người khác không nên có quyền truy cập vào thông tin được mã hóa, thì tôi không cần HMAC, phải không? Nếu họ thay đổi bản mã và buộc kết quả giải mã "sai", thì không có vấn đề gì thực sự xảy ra, phải không?
caw

Nếu điều đó không nằm trong kịch bản rủi ro của bạn, thì điều đó cũng tốt. Nếu bằng cách nào đó họ có thể kích hoạt giải mã lặp đi lặp lại của hệ thống sau khi thay đổi văn bản mật mã (một cuộc tấn công bằng lời nói thần kỳ) thì họ có thể giải mã dữ liệu mà không cần biết khóa. Họ không thể làm điều này nếu họ chỉ nắm giữ dữ liệu trên một hệ thống không có khóa. Nhưng đó là lý do tại sao cách tốt nhất là thêm HMAC. Cá nhân tôi sẽ xem xét một hệ thống có AES-128 và HMAC an toàn hơn AES-256 không có - nhưng như đã nói, có lẽ không bắt buộc.
Maarten Bodewes

1
Tại sao không sử dụng AES ở chế độ Galois / Counter-mode (AES-GCM) nếu bạn muốn toàn vẹn?
Kimvais

1

Bạn đã hỏi một câu hỏi khá thú vị. Như với tất cả các thuật toán, khóa mật mã là "nước sốt bí mật", vì một khi nó được công chúng biết đến, mọi thứ khác cũng vậy. Vì vậy, bạn xem xét các cách tới tài liệu này của Google

Bảo vệ

Bên cạnh đó, Thanh toán trong ứng dụng của Google cũng đưa ra những suy nghĩ về bảo mật cũng rất sâu sắc

billing_best_practices


Cảm ơn vì những liên kết này! Chính xác thì ý bạn là gì khi "khi khóa mật mã bị mất, mọi thứ khác cũng bị mất"?
caw

Ý tôi là khóa mã hóa cần phải được bảo mật, nếu ai đó có thể nắm được điều đó, thì dữ liệu được mã hóa của bạn cũng tốt như bản rõ. Hãy upvote nếu bạn tìm thấy câu trả lời của tôi hữu ích cho một mức độ :-)
the100rabh

0

Sử dụng API nhẹ BouncyCastle. Nó cung cấp 256 AES Với PBE và Salt.
Đây là mã mẫu, có thể mã hóa / giải mã tệp.

public void encrypt(InputStream fin, OutputStream fout, String password) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
        aesCBC.init(true, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(true, aesCBCParams);

        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public void decrypt(InputStream fin, OutputStream fout, String password) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
        aesCBC.init(false, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(false, aesCBCParams);

        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                // int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Cảm ơn bạn! Đây có lẽ là một giải pháp tốt và an toàn nhưng tôi không muốn sử dụng phần mềm của bên thứ ba. Tôi chắc chắn rằng phải có thể tự mình triển khai AES theo cách an toàn.
caw

2
Tùy thuộc nếu bạn muốn bao gồm bảo vệ chống lại các cuộc tấn công kênh bên. Nói chung, bạn nên cho rằng việc tự triển khai các thuật toán mật mã là khá không an toàn . Vì AES CBC có sẵn trong Java runtime libs của Oracle, có lẽ tốt nhất bạn nên sử dụng chúng và sử dụng thư viện Bouncy Castle nếu không có thuật toán.
Maarten Bodewes

Nó thiếu định nghĩa buf(tôi thực sự hy vọng nó không phải là một staticlĩnh vực). Nó cũng giống như cả hai encrypt()decrypt()sẽ không xử lý khối cuối cùng một cách chính xác nếu đầu vào là bội số của 1024 byte.
tc.

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.