แนวทางปฏิบัติที่ดีที่สุดในการใช้การเข้ารหัส AES ใน Android คืออะไร


90

ทำไมฉันถึงถามคำถามนี้:

ฉันรู้ว่ามีคำถามมากมายเกี่ยวกับการเข้ารหัส AES แม้แต่สำหรับ Android และมีข้อมูลโค้ดมากมายหากคุณค้นหาในเว็บ แต่ในทุกๆหน้าในทุกๆคำถามของ Stack Overflow ฉันพบการใช้งานอื่นที่มีความแตกต่างที่สำคัญ

ผมจึงสร้างคำถามนี้เพื่อค้นหา "แนวทางปฏิบัติที่ดีที่สุด" ฉันหวังว่าเราจะรวบรวมรายการข้อกำหนดที่สำคัญที่สุดและตั้งค่าการใช้งานที่ปลอดภัยได้จริง!

ฉันอ่านเกี่ยวกับเวกเตอร์การเริ่มต้นและเกลือ ไม่ใช่การใช้งานทั้งหมดที่ฉันพบว่ามีคุณสมบัติเหล่านี้ คุณต้องการหรือไม่? มันเพิ่มความปลอดภัยมากไหม? คุณใช้มันอย่างไร? อัลกอริทึมควรเพิ่มข้อยกเว้นหากไม่สามารถถอดรหัสข้อมูลที่เข้ารหัสได้หรือไม่ หรือว่าไม่ปลอดภัยและควรส่งคืนสตริงที่อ่านไม่ได้? อัลกอริทึมสามารถใช้ Bcrypt แทน SHA ได้หรือไม่?

ฉันพบการใช้งานทั้งสองนี้อย่างไร พวกเขาโอเคไหม? สมบูรณ์แบบหรือบางสิ่งที่สำคัญขาดหายไป? สิ่งเหล่านี้ปลอดภัยหรือไม่?

อัลกอริทึมควรใช้สตริงและ "รหัสผ่าน" สำหรับการเข้ารหัสจากนั้นเข้ารหัสสตริงด้วยรหัสผ่านนั้น เอาต์พุตควรเป็นสตริง (ฐานสิบหกหรือฐาน 64?) อีกครั้ง การถอดรหัสควรเป็นไปได้เช่นกัน

การใช้งาน AES ที่สมบูรณ์แบบสำหรับ Android คืออะไร?

การนำไปใช้ # 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;
        }

}

ที่มา: http://pocket-for-android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html

การนำไปใช้ # 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));
    }

}

ที่มา: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml


ฉันกำลังพยายามใช้โซลูชัน 1 แต่ต้องใช้บางคลาส คุณมีซอร์สโค้ดแบบเต็มหรือไม่
albanx

1
ไม่ฉันไม่ได้ขอโทษ แต่ฉันทำให้มันใช้งานได้โดยเพียงแค่ลบimplements ICryptoและเปลี่ยนthrows CryptoExceptionไปthrows Exceptionเรื่อย ๆ ดังนั้นคุณจะไม่ต้องการชั้นเรียนเหล่านั้นอีกต่อไป
caw

แต่ยังมีคลาส HexEncoder หายไป? ฉันสามารถหาได้ที่ไหน?
albanx

HexEncoder เป็นส่วนหนึ่งของไลบรารี BouncyCastle ฉันคิดว่า คุณสามารถดาวน์โหลดได้ หรือคุณสามารถ google สำหรับ "byte [] ถึง hex" และอีกทางหนึ่งใน Java
caw

ขอบคุณมาร์โก แต่ผมสังเกตเห็นว่ามี 3 วิธีgetSecretKey, getHash, generateSaltในการดำเนินการครั้งแรกที่ไม่ได้ใช้ บางทีฉันอาจจะผิด แต่คลาสนี้จะใช้เข้ารหัสสตริงในทางปฏิบัติได้อย่างไร
albanx

คำตอบ:


37

การใช้งานที่คุณระบุในคำถามของคุณไม่ถูกต้องทั้งหมดและไม่ควรใช้การนำไปใช้ตามที่เป็น ในสิ่งต่อไปนี้ฉันจะพูดถึงแง่มุมของการเข้ารหัสตามรหัสผ่านใน Android

คีย์และแฮช

ฉันจะเริ่มพูดคุยเกี่ยวกับระบบที่ใช้รหัสผ่านด้วยยาดม เกลือเป็นตัวเลขที่สร้างขึ้นแบบสุ่ม ไม่ใช่ "อนุมาน" การนำไปใช้ 1 รวมถึงgenerateSalt()วิธีการที่สร้างตัวเลขสุ่มที่มีการเข้ารหัสที่แข็งแกร่ง เนื่องจากเกลือมีความสำคัญต่อความปลอดภัยจึงควรเก็บเป็นความลับเมื่อสร้างขึ้นแม้ว่าจะต้องสร้างเพียงครั้งเดียว หากนี่เป็นเว็บไซต์การเก็บความลับของเกลือนั้นค่อนข้างง่าย แต่สำหรับแอปพลิเคชันที่ติดตั้ง (สำหรับเดสก์ท็อปและอุปกรณ์พกพา) สิ่งนี้จะยากกว่ามาก

วิธีนี้getHash()จะส่งคืนแฮชของรหัสผ่านที่กำหนดและเกลือที่เชื่อมต่อกันเป็นสตริงเดียว อัลกอริทึมที่ใช้คือ SHA-512 ซึ่งส่งคืนแฮช 512 บิต วิธีนี้จะส่งคืนแฮชที่มีประโยชน์สำหรับการตรวจสอบความสมบูรณ์ของสตริงดังนั้นจึงอาจใช้การโทรgetHash()ด้วยรหัสผ่านหรือเพียงแค่เกลือเนื่องจากเพียงแค่เชื่อมพารามิเตอร์ทั้งสองเข้าด้วยกัน เนื่องจากวิธีนี้จะไม่ใช้ในระบบการเข้ารหัสที่ใช้รหัสผ่านฉันจะไม่พูดถึงมันเพิ่มเติม

วิธีการgetSecretKey()สาเหตุสำคัญจากการเป็นcharอาร์เรย์ของรหัสผ่านและเกลือ hex generateSalt()เข้ารหัสเป็นกลับมาจาก อัลกอริทึมที่ใช้คือ PBKDF1 (ฉันคิดว่า) จาก PKCS5 โดยมี SHA-256 เป็นฟังก์ชันแฮชและส่งคืนคีย์ 256 บิต getSecretKey()สร้างคีย์โดยการสร้างแฮชของรหัสผ่านเกลือและตัวนับซ้ำ ๆ (ขึ้นอยู่กับจำนวนการวนซ้ำที่ระบุไว้ในPBE_ITERATION_COUNTที่นี่ 100) เพื่อเพิ่มเวลาที่ต้องใช้ในการโจมตีด้วยกำลังเดรัจฉาน ความยาวของเกลือควรมีความยาวอย่างน้อยที่สุดเท่าที่คีย์ถูกสร้างขึ้นในกรณีนี้อย่างน้อย 256 บิต ควรตั้งค่าการนับการวนซ้ำให้นานที่สุดโดยไม่ทำให้ล่าช้าเกินสมควร สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเกลือและซ้ำนับในรากศัพท์ที่สำคัญดูส่วนที่ 4 ในRFC2898

อย่างไรก็ตามการนำไปใช้งานใน PBE ของ Java จะมีข้อบกพร่องหากรหัสผ่านมีอักขระ Unicode นั่นคือรหัสที่ต้องการมากกว่า 8 บิตในการแสดง ตามที่ระบุไว้ในPBEKeySpec"กลไก PBE ที่กำหนดไว้ใน PKCS # 5 จะดูเฉพาะลำดับต่ำ 8 บิตของแต่ละอักขระ" เมื่อต้องการแก้ไขปัญหานี้คุณสามารถลองสร้างสตริงฐานสิบหก (ซึ่งจะมีเพียงตัวอักษร 8 บิต) ทุกตัวอักษร 16 PBEKeySpecบิตในรหัสผ่านก่อนที่จะผ่านไปยัง ตัวอย่างเช่น "ABC" จะกลายเป็น "004100420043" โปรดทราบด้วยว่า PBEKeySpec "ร้องขอรหัสผ่านเป็นอาร์เรย์ถ่านดังนั้นจึงสามารถเขียนทับ [ด้วยclearPassword()] เมื่อเสร็จสิ้น" (เกี่ยวกับ "การปกป้องสตริงในหน่วยความจำ" ดูคำถามนี้) ฉันไม่เห็นปัญหาใด ๆ

การเข้ารหัส

เมื่อสร้างคีย์แล้วเราสามารถใช้เพื่อเข้ารหัสและถอดรหัสข้อความได้

ในการนำไปใช้งาน 1 อัลกอริทึมการเข้ารหัสที่ใช้คือAES/CBC/PKCS5PaddingAES ในโหมดการเข้ารหัส Cipher Block Chaining (CBC) โดยมีช่องว่างภายในกำหนดไว้ใน PKCS # 5 (โหมดการเข้ารหัส AES อื่น ๆ ได้แก่ โหมดตัวนับ (CTR), โหมดสมุดโค้ดอิเล็กทรอนิกส์ (ECB) และโหมดตัวนับ Galois (GCM) อีกคำถามหนึ่งเกี่ยวกับ Stack Overflowมีคำตอบที่อธิบายรายละเอียดเกี่ยวกับโหมดการเข้ารหัส AES ต่างๆและโหมดที่แนะนำให้ใช้ โปรดทราบด้วยว่ามีการโจมตีหลายครั้งในการเข้ารหัสโหมด CBC ซึ่งบางส่วนกล่าวถึงใน RFC 7457)

โปรดทราบว่าคุณควรใช้โหมดการเข้ารหัสที่ตรวจสอบข้อมูลที่เข้ารหัสเพื่อความสมบูรณ์ (เช่นการเข้ารหัสที่พิสูจน์ตัวตนด้วยข้อมูลที่เกี่ยวข้อง AEAD ตามที่อธิบายไว้ใน RFC 5116) อย่างไรก็ตามAES/CBC/PKCS5Paddingไม่ได้ให้ตรวจสอบความสมบูรณ์ดังนั้นจึงอยู่คนเดียวไม่แนะนำ สำหรับวัตถุประสงค์ของ AEAD ขอแนะนำให้ใช้ความลับที่มีความยาวอย่างน้อยสองเท่าของคีย์เข้ารหัสปกติเพื่อหลีกเลี่ยงการโจมตีคีย์ที่เกี่ยวข้องครึ่งแรกทำหน้าที่เป็นคีย์เข้ารหัสและครึ่งหลังทำหน้าที่เป็นคีย์สำหรับการตรวจสอบความสมบูรณ์ (นั่นคือในกรณีนี้ให้สร้างความลับเดียวจากรหัสผ่านและเกลือและแยกความลับนั้นออกเป็นสองส่วน)

การใช้งาน Java

ฟังก์ชันต่างๆในการนำไปใช้งาน 1 ใช้ผู้ให้บริการเฉพาะคือ "BC" สำหรับอัลกอริทึม โดยทั่วไปแล้วไม่แนะนำให้ร้องขอผู้ให้บริการที่เฉพาะเจาะจงเนื่องจากผู้ให้บริการบางรายไม่สามารถใช้งานได้กับการใช้งาน Java ทั้งหมดไม่ว่าจะขาดการสนับสนุนเพื่อหลีกเลี่ยงการทำซ้ำโค้ดหรือด้วยเหตุผลอื่น ๆ คำแนะนำนี้มีความสำคัญอย่างยิ่งนับตั้งแต่มีการเผยแพร่ตัวอย่าง Android P ในช่วงต้นปี 2018 เนื่องจากฟังก์ชันบางอย่างจากผู้ให้บริการ "BC" ได้เลิกใช้งานแล้วโปรดดูบทความ "การเปลี่ยนแปลงการเข้ารหัสใน Android P" ในบล็อกนักพัฒนาซอฟต์แวร์ Android ดูข้อมูลเบื้องต้นเกี่ยวกับผู้ให้บริการ Oracleรู้เบื้องต้นเกี่ยวกับผู้ให้บริการของออราเคิล

ดังนั้นจึงPROVIDERไม่ควรมีอยู่และ-BCควรลบสตริงออกPBE_ALGORITHMควรจะถูกลบออกจากการดำเนินการ 2 ถูกต้องในแง่นี้

ไม่เหมาะสมสำหรับวิธีการที่จะจับข้อยกเว้นทั้งหมด แต่ให้จัดการเฉพาะข้อยกเว้นที่ทำได้ การใช้งานที่ระบุในคำถามของคุณอาจทำให้เกิดข้อยกเว้นที่ตรวจสอบได้หลายประการ เมธอดสามารถเลือกที่จะรวมเฉพาะข้อยกเว้นที่ตรวจสอบแล้วด้วย CryptoException หรือระบุข้อยกเว้นที่ตรวจสอบเหล่านั้นในthrowsข้อ เพื่อความสะดวกการตัดข้อยกเว้นเดิมด้วย CryptoException อาจเหมาะสมที่นี่เนื่องจากมีข้อยกเว้นที่ตรวจสอบแล้วจำนวนมากที่คลาสสามารถโยนได้

SecureRandom ใน Android

ตามรายละเอียดในบทความ "ความคิดที่ปลอดภัยบางอย่าง" ในบล็อกนักพัฒนา Android การใช้งานjava.security.SecureRandomใน Android รุ่นก่อนปี 2013 มีข้อบกพร่องที่ลดความแรงของตัวเลขสุ่มที่ส่งมอบ ข้อบกพร่องนี้สามารถบรรเทาได้ตามที่อธิบายไว้ในบทความนั้น


การสร้างความลับสองครั้งนั้นค่อนข้างสิ้นเปลืองในความคิดของฉันคุณสามารถแยกความลับที่สร้างขึ้นเป็นสองส่วนได้อย่างง่ายดายหรือหากมีบิตไม่เพียงพอให้เพิ่มตัวนับ (1 สำหรับคีย์แรก 2 สำหรับคีย์ที่สอง) ใน ลับและดำเนินการแฮชเดียว ไม่จำเป็นต้องทำซ้ำทั้งหมดสองครั้ง
Maarten Bodewes

ขอขอบคุณข้อมูลเกี่ยวกับ HMAC และเกลือ ฉันจะไม่ใช้ HMAC ในครั้งนี้ แต่ในภายหลังอาจมีประโยชน์มาก และโดยทั่วไปนี่เป็นสิ่งที่ดีอย่างไม่ต้องสงสัย
caw

ขอบคุณมากสำหรับการแก้ไขทั้งหมดและการแนะนำที่ยอดเยี่ยมสำหรับการเข้ารหัส AES ใน Java!
caw

1
มันควรจะ. getInstanceมีโอเวอร์โหลดที่ใช้เฉพาะชื่อของอัลกอริทึม ตัวอย่าง: Cipher.getInstance ()ผู้ให้บริการหลายรายรวมถึง Bouncy Castle อาจได้รับการลงทะเบียนในการใช้งาน Java และการโอเวอร์โหลดประเภทนี้จะค้นหารายชื่อผู้ให้บริการสำหรับหนึ่งในผู้ให้บริการที่ใช้อัลกอริทึมที่กำหนด คุณควรลองและดู
Peter O.

1
ใช่มันจะค้นหาผู้ให้บริการตามลำดับที่กำหนดโดย Security.getProviders () - แม้ว่าตอนนี้จะตรวจสอบด้วยว่าผู้ให้บริการนั้นยอมรับคีย์หรือไม่ในระหว่างการเรียก init () ที่อนุญาตให้เข้ารหัสด้วยฮาร์ดแวร์ รายละเอียดเพิ่มเติมที่นี่: docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/...
Maarten Bodewes

18

ไม่ควรใช้ # 2 เนื่องจากใช้เฉพาะ "AES" (ซึ่งหมายถึงการเข้ารหัสโหมด ECB บนข้อความซึ่งเป็นรหัสที่ไม่สำคัญ) สำหรับการเข้ารหัส ผมจะพูดถึง # 1

การใช้งานครั้งแรกดูเหมือนจะเป็นไปตามแนวทางปฏิบัติที่ดีที่สุดสำหรับการเข้ารหัส โดยทั่วไปค่าคงที่จะตกลงแม้ว่าทั้งขนาดเกลือและจำนวนการวนซ้ำสำหรับการแสดง PBE จะอยู่ในระดับสั้น ยิ่งไปกว่านั้นดูเหมือนว่าจะเป็นสำหรับ AES-256 เนื่องจากการสร้างคีย์ PBE ใช้ 256 เป็นค่าฮาร์ดโค้ด (น่าเสียดายหลังจากค่าคงที่ทั้งหมด) ใช้ CBC และ PKCS5Padding ซึ่งอย่างน้อยก็เป็นสิ่งที่คุณคาดหวัง

สิ่งที่ขาดหายไปอย่างสิ้นเชิงคือการป้องกันการพิสูจน์ตัวตน / ความสมบูรณ์ดังนั้นผู้โจมตีสามารถเปลี่ยนข้อความรหัสได้ ซึ่งหมายความว่าการโจมตี padding oracle เป็นไปได้ในรูปแบบไคลเอนต์ / เซิร์ฟเวอร์ นอกจากนี้ยังหมายความว่าผู้โจมตีสามารถลองและเปลี่ยนแปลงข้อมูลที่เข้ารหัสได้ สิ่งนี้อาจส่งผลให้เกิดข้อผิดพลาดบางอย่างเนื่องจากการขยายช่องว่างหรือเนื้อหาไม่ได้รับการยอมรับจากแอปพลิเคชัน แต่นั่นไม่ใช่สถานการณ์ที่คุณต้องการ

การจัดการข้อยกเว้นและการตรวจสอบความถูกต้องของข้อมูลสามารถปรับปรุงได้การจับ Exception มักจะผิดในหนังสือ Furhtermore คลาสใช้ ICrypt ซึ่งฉันไม่รู้ ฉันรู้ว่าการมีวิธีเดียวที่ไม่มีผลข้างเคียงในชั้นเรียนนั้นค่อนข้างแปลก โดยปกติคุณจะทำให้คงที่ ไม่มีการบัฟเฟอร์ของอินสแตนซ์ Cipher เป็นต้นดังนั้นทุกออบเจ็กต์ที่ต้องการจึงได้รับการสร้าง ad-nauseum อย่างไรก็ตามคุณสามารถลบ ICrypto ออกจากคำจำกัดความได้อย่างปลอดภัยในกรณีนี้คุณยังสามารถ refactor รหัสเป็นวิธีการแบบคงที่ (หรือเขียนใหม่เพื่อให้เป็นเชิงวัตถุมากขึ้นตามที่คุณเลือก)

ปัญหาคือเสื้อคลุมใด ๆ มักจะตั้งสมมติฐานเกี่ยวกับกรณีการใช้งาน การบอกว่าเสื้อคลุมถูกหรือผิดจึงเป็นสองชั้น นี่คือเหตุผลที่ฉันพยายามหลีกเลี่ยงการสร้างคลาส Wrapper อยู่เสมอ แต่อย่างน้อยก็ดูเหมือนจะไม่ผิดอย่างชัดเจน


ขอบคุณมากสำหรับคำตอบโดยละเอียดนี้! ฉันรู้ว่ามันน่าเสียดาย แต่ฉันยังไม่รู้ส่วนการตรวจสอบโค้ด: D ขอบคุณสำหรับคำแนะนำนี้ฉันจะตรวจสอบให้ แต่คำถามนี้ก็ตรงกับความคิดของฉันเช่นกันเพราะฉันไม่เพียงต้องการตรวจสอบข้อมูลโค้ดเหล่านี้ แต่ฉันต้องการถามคุณทุกประเด็นว่ามีความสำคัญอย่างไรเมื่อใช้การเข้ารหัส AES ใน Android และคุณก็พูดถูกอีกครั้งข้อมูลโค้ดนี้มีไว้สำหรับ AES-256 คุณจะบอกว่านี่เป็นการใช้งาน AES-256 อย่างปลอดภัยหรือไม่? กรณีการใช้งานคือฉันต้องการจัดเก็บข้อมูลข้อความในฐานข้อมูลอย่างปลอดภัย
วัว

1
มันดูดี แต่ความคิดที่จะไม่มีการตรวจสอบความสมบูรณ์และการรับรองความถูกต้องจะรบกวนฉัน หากคุณมีพื้นที่เพียงพอฉันจะพิจารณาเพิ่ม HMAC บนข้อความเข้ารหัสอย่างจริงจัง ที่กล่าวว่าในขณะที่คุณกำลังพยายามเพิ่มการรักษาความลับฉันคิดว่ามันเป็นข้อดี แต่ไม่ใช่ข้อกำหนดโดยตรง
Maarten Bodewes

แต่ถ้าเจตนาเพียงว่าคนอื่นไม่ควรเข้าถึงข้อมูลที่เข้ารหัสฉันก็ไม่ต้องการ HMAC ใช่ไหม? หากพวกเขาเปลี่ยนไซเฟอร์เท็กซ์และบังคับให้ผลลัพธ์ของการถอดรหัส "ผิด" ไม่มีปัญหาจริงหรือ?
วัว

หากสิ่งนั้นไม่ได้อยู่ในสถานการณ์ความเสี่ยงของคุณก็ไม่เป็นไร หากพวกเขาสามารถทริกเกอร์การถอดรหัสซ้ำ ๆ โดยระบบหลังจากแก้ไขข้อความเข้ารหัส (การโจมตีแบบออราเคิลแบบขยายช่องว่าง) พวกเขาสามารถถอดรหัสข้อมูลโดยไม่ต้องรู้คีย์ พวกเขาไม่สามารถทำเช่นนี้ได้หากพวกเขาเพียงแค่เก็บข้อมูลไว้ในระบบที่ไม่มีคีย์ แต่นั่นเป็นเหตุผลว่าทำไมจึงควรเพิ่ม HMAC โดยส่วนตัวแล้วฉันคิดว่าระบบที่มี AES-128 และ HMAC ปลอดภัยกว่า AES-256 ที่ไม่มี - แต่อย่างที่กล่าวไว้อาจไม่จำเป็น
Maarten Bodewes

1
ทำไมไม่ใช้ AES ใน Galois / Counter-mode (AES-GCM) ถ้าคุณต้องการความสมบูรณ์?
Kimvais

1

คุณได้ถามคำถามที่น่าสนใจ เช่นเดียวกับอัลกอริทึมทั้งหมดคีย์การเข้ารหัสคือ "ซอสลับ" เนื่องจากเมื่อเป็นที่รู้จักของสาธารณชนแล้วทุกสิ่งทุกอย่างก็เช่นกัน ดังนั้นคุณจึงมองหาวิธีต่างๆในเอกสารนี้โดย Google

ความปลอดภัย

นอกจากการเรียกเก็บเงินในแอปของ Google แล้วยังให้แง่คิดเกี่ยวกับความปลอดภัยซึ่งเป็นข้อมูลเชิงลึกอีกด้วย

billing_best_practices


ขอบคุณสำหรับลิงก์เหล่านี้! คุณหมายถึงอะไร "เมื่อคีย์การเข้ารหัสหมดทุกอย่างจะหมดไปด้วย"
caw

สิ่งที่ฉันหมายถึงคือคีย์การเข้ารหัสต้องปลอดภัยหากใครสามารถจับได้ข้อมูลที่เข้ารหัสของคุณจะดีพอ ๆ กับข้อความธรรมดา โปรดโหวตหากคุณพบว่าคำตอบของฉันมีประโยชน์ต่อระดับ :-)
the100rabh

0

ใช้ BouncyCastle Lightweight API มี 256 AES พร้อม PBE และ Salt
นี่คือโค้ดตัวอย่างซึ่งสามารถเข้ารหัส / ถอดรหัสไฟล์

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();
    }
}

ขอขอบคุณ! นี่อาจเป็นโซลูชันที่ดีและปลอดภัย แต่ฉันไม่ต้องการใช้ซอฟต์แวร์ของบุคคลที่สาม ฉันแน่ใจว่าต้องเป็นไปได้ที่จะใช้ AES อย่างปลอดภัยด้วยตัวเอง
caw

2
ขึ้นอยู่กับว่าคุณต้องการรวมการป้องกันการโจมตีช่องด้านข้างหรือไม่ โดยทั่วไปคุณควรคิดว่ามันค่อนข้างไม่ปลอดภัยที่จะใช้อัลกอริทึมการเข้ารหัสด้วยตัวคุณเอง เนื่องจาก AES CBC มีอยู่ใน Java runtime libs ของ Oracle จึงควรใช้สิ่งเหล่านี้และใช้ไลบรารี Bouncy Castle หากอัลกอริทึมไม่พร้อมใช้งาน
Maarten Bodewes

มันหายไปความหมายของbuf(ฉันจริงๆหวังว่ามันจะไม่ได้เป็นstaticเขต) นอกจากนี้ยังดูเหมือนว่าทั้งสองอย่างencrypt()และdecrypt()จะไม่สามารถประมวลผลบล็อกสุดท้ายได้อย่างถูกต้องหากอินพุตเป็นจำนวน 1024 ไบต์
tc.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.