Android에서 AES 암호화를 사용하는 모범 사례는 무엇입니까?


90

내가이 질문을하는 이유 :

Android에서도 AES 암호화에 대해 많은 질문이있었습니다. 그리고 웹을 검색하면 많은 코드 조각이 있습니다. 그러나 모든 단일 페이지에서 모든 스택 오버플로 질문에서 큰 차이점이있는 또 다른 구현을 찾습니다.

그래서 "모범 사례"를 찾기 위해이 질문을 만들었습니다. 가장 중요한 요구 사항 목록을 수집하고 정말 안전한 구현을 설정할 수 있기를 바랍니다.

초기화 벡터와 솔트에 대해 읽었습니다. 내가 찾은 모든 구현에 이러한 기능이있는 것은 아닙니다. 그래서 필요합니까? 보안을 많이 강화합니까? 어떻게 구현합니까? 암호화 된 데이터를 해독 할 수없는 경우 알고리즘이 예외를 발생시켜야합니까? 아니면 안전하지 않고 읽을 수없는 문자열을 반환해야합니까? 알고리즘이 SHA 대신 Bcrypt를 사용할 수 있습니까?

내가 찾은이 두 가지 구현은 어떻습니까? 괜찮아? 완벽하거나 중요한 것이 누락 되었습니까? 이 중 어떤 것이 안전합니까?

알고리즘은 암호화를 위해 문자열과 "암호"를 가져온 다음 해당 암호로 문자열을 암호화해야합니다. 출력은 다시 문자열 (hex 또는 base64?)이어야합니다. 물론 복호화도 가능해야합니다.

Android를위한 완벽한 AES 구현은 무엇입니까?

구현 # 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 2012-06-25

1
아뇨, 미안 해요. 그러나 나는 단순히 삭제 implements ICrypto하고 변경 throws CryptoException하는 throws Exception등으로 작동했습니다. 따라서 더 이상 이러한 수업이 필요하지 않습니다.
caw

하지만 HexEncoder 클래스도 없습니까? 어디서 찾을 수 있습니까?
albanx

HexEncoder는 BouncyCastle 라이브러리의 일부라고 생각합니다. 다운로드 만하면됩니다. 또는 "byte [] to hex"를 검색하고 Java에서 반대 방향으로 검색 할 수 있습니다.
caw

마르코 감사합니다. 그러나 나는 3 가지 방법이 있다는 것을 발견 getSecretKey, getHash, generateSalt사용되지 않는 첫 번째 구현은. 내가 틀렸을 수도 있지만 실제로이 클래스를 사용하여 문자열을 암호화 할 수 있습니까?
albanx 2012-06-27

답변:


37

질문에 제공 한 구현은 완전히 정확하지 않으며 제공하는 구현도 그대로 사용해서는 안됩니다. 다음에서는 Android의 암호 기반 암호화 측면에 대해 설명합니다.

키 및 해시

솔트를 사용하여 암호 기반 시스템에 대해 논의하기 시작합니다. 소금은 무작위로 생성 된 숫자입니다. 그것은 "추론"되지 않습니다. 구현 1에는 generateSalt()강력한 암호화 난수를 생성 하는 방법이 포함됩니다 . 소금은 보안에 중요하므로 한 번만 생성하면되지만 생성 된 후에는 비밀로 유지해야합니다. 이것이 웹 사이트 인 경우 솔트 비밀을 유지하는 것이 상대적으로 쉽지만 설치된 응용 프로그램 (데스크톱 및 모바일 장치 용)의 경우 훨씬 더 어려울 것입니다.

이 메서드 getHash()는 단일 문자열로 연결된 주어진 암호와 솔트의 해시를 반환합니다. 사용 된 알고리즘은 512 비트 해시를 반환하는 SHA-512입니다. 이 메서드는 문자열의 무결성을 확인하는 데 유용한 해시를 반환하므로 getHash()단순히 두 매개 변수를 연결하기 때문에 암호 만 사용 하거나 솔트 만 사용하여 호출 하여 사용할 수도 있습니다 . 이 방법은 암호 기반 암호화 시스템에서 사용되지 않으므로 더 이상 논의하지 않겠습니다.

메서드 getSecretKey()는에서 char반환 된대로 암호 및 16 진수 인코딩 솔트 배열 에서 키를 파생합니다 generateSalt(). 사용 된 알고리즘은 SHA-256을 해시 함수로 사용하는 PKCS5의 PBKDF1 (내 생각에는)이며 256 비트 키를 반환합니다. 무차별 대입 공격을 수행하는 데 필요한 시간을 늘리기 위해 getSecretKey()암호, 솔트 및 카운터의 해시 ( PBE_ITERATION_COUNT여기서는 100에 주어진 반복 횟수까지)를 반복적으로 생성하여 키를 생성합니다 . 솔트의 길이는 최소한 생성되는 키만큼 길어야합니다 (이 경우에는 최소한 256 비트). 반복 횟수는 비합리적인 지연없이 가능한 한 길게 설정해야합니다. 키 파생의 솔트 및 반복 횟수에 대한 자세한 내용은 RFC2898의 섹션 4를 참조 하세요 .

그러나 Java PBE의 구현은 암호에 유니 코드 문자 (즉, 8 비트 이상을 표시해야하는 문자)가 포함 된 경우 결함이 있습니다. 에서 언급했듯이 PBEKeySpec"PKCS # 5에 정의 된 PBE 메커니즘은 각 문자의 하위 8 비트 만 살펴 봅니다." 이 문제를 해결하려면 암호를 PBEKeySpec. 예를 들어 "ABC"는 "004100420043"이됩니다. 또한 PBEKeySpec은 "암호를 문자 배열로 요청하므로 clearPassword()완료되면 [ ] 로 덮어 쓸 수 있습니다 ". ( "메모리의 문자열 보호"와 관련 하여이 질문을 참조하십시오 .) 그래도 문제는 없습니다.

암호화

키가 생성되면이를 사용하여 텍스트를 암호화하고 해독 할 수 있습니다.

구현 1에서 사용 된 암호 알고리즘 AES/CBC/PKCS5Padding은 PKCS # 5에 패딩이 정의 된 CBC (Cipher Block Chaining) 암호 모드의 AES입니다. (기타 AES 암호 모드에는 카운터 모드 (CTR), 전자 코드북 모드 (ECB) 및 Galois 카운터 모드 (GCM)가 포함됩니다. Stack Overflow 에 대한 또 다른 질문 에는 다양한 AES 암호 모드 및 사용 권장 모드에 대해 자세히 설명하는 답변이 포함되어 있습니다. 또한 CBC 모드 암호화에 대한 여러 공격이 있으며 그중 일부는 RFC 7457에 언급되어 있습니다.)

암호화 된 데이터의 무결성도 확인하는 암호화 모드를 사용해야합니다 (예 : RFC 5116에 설명 된 관련 데이터 를 사용한 인증 된 암호화 , AEAD). 그러나 AES/CBC/PKCS5Padding는 무결성 검사를 제공하지 않으므로 단독으로 권장되지 않습니다 . AEAD 목적의 경우 관련 키 공격을 피하기 위해 일반 암호화 키보다 두 배 이상 긴 비밀을 사용하는 것이 좋습니다. 첫 번째 절반은 암호화 키로, 나머지 절반은 무결성 검사를위한 키 역할을합니다. (즉,이 경우 암호와 솔트에서 하나의 비밀을 생성하고 그 비밀을 둘로 분할합니다.)

자바 구현

구현 1의 다양한 기능은 알고리즘에 대해 특정 공급자, 즉 "BC"를 사용합니다. 그러나 일반적으로 지원 부족, 코드 중복 방지 또는 기타 이유로 모든 Java 구현에서 모든 공급자를 사용할 수있는 것은 아니기 때문에 특정 공급자를 요청하는 것은 권장되지 않습니다. 이 조언은 2018 년 초에 Android P 미리보기가 출시 된 이후로 특히 중요해졌습니다. 'BC'제공 업체의 일부 기능이 지원 중단 되었기 때문입니다. Android 개발자 블로그의 'Android P의 암호화 변경'문서를 참조하세요. Oracle 공급자 소개를 참조하십시오 .

따라서 PROVIDER존재하지 않아야하며에서 문자열 -BC을 제거해야합니다 PBE_ALGORITHM. 구현 2는이 점에서 정확합니다.

메서드가 모든 예외를 포착하는 것은 부적절하지만 가능한 예외 만 처리하는 것은 부적절합니다. 귀하의 질문에 제공된 구현은 다양한 확인 된 예외를 throw 할 수 있습니다. 메서드는 확인 된 예외 만 CryptoException으로 래핑하도록 선택하거나 throws절 에서 확인 된 예외를 지정할 수 있습니다. 편의상 원래 예외를 CryptoException으로 래핑하는 것이 적절할 수 있습니다. 클래스가 throw 할 수있는 잠재적으로 많은 확인 된 예외가 있기 때문입니다.

SecureRandom Android에서

Android 개발자 블로그의 "Some SecureRandom Thoughts"기사에 자세히 설명 된대로 java.security.SecureRandom2013 년 이전에 Android 릴리스에서 구현 하면 제공하는 난수의 강도를 감소시키는 결함이 있습니다. 이 결함은 해당 기사에 설명 된대로 완화 할 수 있습니다.


이중 비밀 생성은 제 생각에 약간 낭비 적입니다. 생성 된 비밀을 두 개로 쉽게 분할하거나, 사용할 수있는 비트가 충분하지 않은 경우 카운터를 추가 할 수 있습니다 (첫 번째 키에 대해 1, 두 번째 키에 대해 2). 비밀을 유지하고 단일 해시를 수행합니다. 모든 반복을 두 번 수행 할 필요가 없습니다.
Maarten Bodewes 2011

HMAC와 소금에 대한 정보에 감사드립니다. 이번에는 HMAC를 사용하지 않겠지 만 나중에는 매우 유용 할 것입니다. 그리고 일반적으로 이것은 의심 할 여지없이 좋은 것입니다.
caw

모든 편집과 Java의 AES 암호화에 대한이 멋진 소개에 감사드립니다!
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를 수행하기위한 반복 횟수와 솔트 크기는 모두 짧은 편입니다. 또한 PBE 키 생성이 하드 코딩 된 값으로 256을 사용하기 때문에 AES-256에 해당하는 것으로 보입니다 (모든 상수 이후에 수치심). 그것은 최소한 당신이 기대하는 CBC와 PKCS5Padding을 사용합니다.

인증 / 무결성 보호가 완전히 누락되어 공격자가 암호 텍스트를 변경할 수 있습니다. 이는 클라이언트 / 서버 모델에서 패딩 오라클 공격이 가능함을 의미합니다. 또한 공격자가 암호화 된 데이터를 변경하려고 시도 할 수 있음을 의미합니다. 패딩이나 콘텐츠가 응용 프로그램에서 허용되지 않기 때문에 어딘가에서 오류가 발생할 수 있지만 원하는 상황은 아닙니다.

예외 처리 및 입력 유효성 검사를 향상시킬 수 있으며 예외를 잡는 것은 항상 내 책에서 잘못되었습니다. 게다가이 클래스는 내가 모르는 ICrypt를 구현합니다. 클래스에 부작용이없는 메서드 만있는 것이 조금 이상하다는 것을 알고 있습니다. 일반적으로 정적으로 만들 것입니다. Cipher 인스턴스 등의 버퍼링이 없으므로 모든 필수 개체가 생성됩니다. 그러나 정의에서 ICrypto를 안전하게 제거 할 수 있습니다.이 경우 코드를 정적 메서드로 리팩터링 할 수도 있습니다 (또는 원하는 객체 지향으로 다시 작성).

문제는 모든 래퍼가 항상 사용 사례에 대해 가정한다는 것입니다. 래퍼가 옳고 그름을 말하는 것은 그러므로 벙크입니다. 이것이 제가 항상 래퍼 클래스 생성을 피하려고하는 이유입니다. 그러나 적어도 명백히 잘못된 것 같지는 않습니다.


이 자세한 답변에 감사드립니다! 부끄러운 건 알지만 아직 코드 리뷰 섹션을 몰랐습니다. : D이 힌트에 감사드립니다. 확인해 보겠습니다. 그러나이 질문은 이러한 코드 스 니펫에 대한 검토를 원하지 않기 때문에 내 의견에도 적합합니다. 대신 Android에서 AES 암호화를 구현할 때 중요한 측면이 무엇인지 모두 묻고 싶습니다. 이 코드는 AES-256 용입니다. 그래서 이것이 일반적으로 AES-256의 안전한 구현이라고 말할 수 있습니까? 유스 케이스는 텍스트 정보를 데이터베이스에 안전하게 저장하고 싶다는 것입니다.
caw

1
괜찮아 보이지만 무결성 검사 및 인증이 없다는 생각은 나를 괴롭힐 것입니다. 충분한 공간이 있다면 암호문 위에 HMAC를 추가하는 것을 진지하게 고려할 것입니다. 즉, 단순히 기밀성을 추가하려고 할 것이므로 큰 장점이라고 생각하지만 직접 요구 사항은 아닙니다.
Maarten Bodewes 2011

하지만 다른 사람이 암호화 된 정보에 접근하지 못하도록 의도한다면 HMAC가 필요하지 않습니까? 그들이 암호문을 변경하고 암호 해독의 "잘못된"결과를 강요한다면, 실제 문제는 없습니까?
caw

그것이 귀하의 위험 시나리오에 없다면 괜찮습니다. 암호문 (패딩 오라클 공격)을 변경 한 후 시스템에서 반복적 인 암호 해독을 트리거 할 수 있다면 키를 몰라도 데이터를 해독 할 수 있습니다. 키가없는 시스템의 데이터를 단순히 확보하는 경우에는이를 수행 할 수 없습니다. 그러나 이것이 항상 HMAC를 추가하는 것이 가장 좋은 방법입니다. 개인적으로 나는 AES-128과 HMAC가없는 시스템이 AES-256이없는 시스템보다 더 안전하다고 생각할 것입니다.
Maarten Bodewes 2011

1
무결성을 원한다면 Galois / Counter-mode (AES-GCM)에서 AES를 사용하지 않는 이유는 무엇입니까?
Kimvais 2011

1

꽤 흥미로운 질문을하셨습니다. 모든 알고리즘과 마찬가지로 암호 키는 "비밀 소스"입니다. 일단 대중에게 알려지면 다른 모든 것도 마찬가지입니다. 따라서 Google에서이 문서에 대한 방법을 살펴 봅니다.

보안

Google 인앱 결제 외에도 통찰력있는 보안에 대한 생각을 제공합니다.

billing_best_practices


이 링크에 감사드립니다! "암호 키가 나오면 다른 모든 것도 나간다"는 것이 정확히 무엇을 의미합니까?
caw

내 말은 암호화 키가 안전해야한다는 것입니다. 누군가가 그것을 잡을 수 있다면 암호화 된 데이터는 일반 텍스트만큼 좋습니다. 이 정도 :-)에 대한 내 대답은 도움이되었다고 경우 찬성 투표하십시오
the100rabh

0

BouncyCastle Lightweight API를 사용하십시오. PBE 및 Salt와 함께 256 AES를 제공합니다.
다음은 파일을 암호화 / 복호화 할 수있는 샘플 코드입니다.

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는 Oracle의 Java 런타임 라이브러리에서 사용할 수 있으므로 알고리즘을 사용할 수없는 경우이를 사용하고 Bouncy Castle 라이브러리를 사용하는 것이 가장 좋습니다.
Maarten Bodewes

정의가 누락되었습니다 buf( 필드가 아니길 바랍니다 static). 또한 두 모양 encrypt()decrypt()입력이 1024 바이트의 배수 인 경우, 정확하게 최종 블록을 처리하는 데 실패 할 것이다.
tc.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.