Programing

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

lottogame 2020. 9. 19. 11:54
반응형

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


내가이 질문을하는 이유 :

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


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

키 및 해시

I will start discussing the password-based system with salts. The salt is a randomly generated number. It is not "deduced". Implementation 1 includes a generateSalt() method that generates a cryptographically strong random number. Because the salt is important to security, it should be kept secret once it is generated, though it only needs to be generated once. If this is a Web site, it's relatively easy to keep the salt secret, but for installed applications (for desktop and mobile devices), this will be much more difficult.

The method getHash() returns a hash of the given password and salt, concatenated into a single string. The algorithm used is SHA-512, which returns a 512-bit hash. This method returns a hash that's useful for checking a string's integrity, so it might as well be used by calling getHash() with just a password or just a salt, since it simply concatenates both parameters. Since this method won't be used in the password-based encryption system, I won't be discussing it further.

The method getSecretKey(), derives a key from a char array of the password and a hex-encoded salt, as returned from generateSalt(). The algorithm used is PBKDF1 (I think) from PKCS5 with SHA-256 as the hash function, and returns a 256-bit key. getSecretKey() generates a key by repeatedly generating hashes of the password, salt, and a counter (up to the iteration count given in PBE_ITERATION_COUNT, here 100) in order to increase the time needed to mount a brute-force attack. The salt's length should be at least as long as the key being generated, in this case, at least 256 bits. The iteration count should be set as long as possible without causing unreasonable delay. For more information on salts and iteration counts in key derivation, see section 4 in RFC2898.

The implementation in Java's PBE, however, is flawed if the password contains Unicode characters, that is, those that require more than 8 bits to be represented. As stated in PBEKeySpec, "the PBE mechanism defined in PKCS #5 looks at only the low order 8 bits of each character". To work around this problem, you can try generating a hex string (which will contain only 8-bit characters) of all 16-bit characters in the password before passing it to PBEKeySpec. For example, "ABC" becomes "004100420043". Note also that PBEKeySpec "requests the password as a char array, so it can be overwritten [with clearPassword()] when done". (With respect to "protecting strings in memory", see this question.) I don't see any problems, though, with representing a salt as a hex-encoded string.

Encryption

Once a key is generated, we can use it to encrypt and decrypt text. In implementation 1, the cipher algorithm used is AES/CBC/PKCS5Padding, that is, AES in the Cipher Block Chaining (CBC) cipher mode, with padding defined in PKCS#5. (Other AES cipher modes include counter mode (CTR), electronic codebook mode (ECB), and Galois counter mode (GCM). Another question on Stack Overflow contains answers that discuss in detail the various AES cipher modes and the recommended ones to use. Be aware, too, that there are several attacks on CBC mode encryption, some of which are mentioned in RFC 7457.)

If the encrypted text will be made available to outsiders, then applying a message authentication code, or MAC, to the encrypted data (and optionally additional parameters) is recommended to protect its integrity (a technique known as authenticated encryption with associated data, AEAD, described in RFC 5116). Popular here are hash-based MACs, or HMACs, that are based on SHA-256 or other secure hash functions. If a MAC is used, however, using a secret that's at least twice as long as a normal encryption key is recommended, to avoid related key attacks: the first half serves as the encryption key, and the second half serves as the key for the MAC. (That is, in this case, generate a single secret from a password and salt, and split that secret in two.)

Java Implementation

The various functions in implementation 1 use a specific provider, namely "BC", for its algorithms. In general, though, it is not recommended to request specific providers, since not all providers are available on all Java implementations, whether for lack of support, to avoid code duplication, or for other reasons. This advice has especially become important since the release of Android P preview in early 2018, because some functionality from the "BC" provider has been deprecated there — see the article "Cryptography Changes in Android P" in the Android Developers Blog. See also the Introduction to Oracle Providers.

Thus, PROVIDER should not exist and the string -BC should be removed from PBE_ALGORITHM. Implementation 2 is correct in this respect.

It is inappropriate for a method to catch all exceptions, but rather to handle only the exceptions it can. The implementations given in your question can throw a variety of checked exceptions. A method can choose to wrap only those checked exceptions with CryptoException, or specify those checked exceptions in the throws clause. For convenience, wrapping the original exception with CryptoException may be appropriate here, since there are potentially many checked exceptions the classes can throw.

SecureRandom in Android

As detailed in the article "Some SecureRandom Thoughts", in the Android Developers Blog, the implementation of java.security.SecureRandom in Android releases before 2013 has a flaw that reduces the strength of random numbers it delivers. This flaw can be mitigated by passing an unpredictable and random block of data (such as the output of /dev/urandom) to that class's setSeed method.


#2 should never be used as it uses only "AES" (which means ECB mode encryption on text, a big no-no) for the cipher. I'll just talk about #1.

The first implementation seems to adhere to best practices for encryption. The constants are generally OK, although both the salt size and the number of iterations for performing PBE are on the short side. Futhermore, it seems to be for AES-256 since the PBE key generation uses 256 as a hard coded value (a shame after all those constants). It uses CBC and PKCS5Padding which is at least what you would expect.

Completely missing is any authentication/integrity protection, so an attacker can change the cipher text. This means that padding oracle attacks are possible in a client/server model. It also means that an attacker can try and change the encrypted data. This will likely result in some error somewhere becaues the padding or content is not accepted by the application, but that's not a situation that you want to be in.

Exception handling and input validation could be enhanced, catching Exception is always wrong in my book. Furhtermore, the class implements ICrypt, which I don't know. I do know that having only methods without side effects in a class is a bit weird. Normally, you would make those static. There is no buffering of Cipher instances etc., so every required object gets created ad-nauseum. However, you can safely remove ICrypto from the definition it seems, in that case you could also refactor the code to static methods (or rewrite it to be more object oriented, your choice).

The problem is that any wrapper always makes assumptions about the use case. To say that a wrapper is right or wrong is therefore bunk. This is why I always try to avoid generating wrapper classes. But at least it does not seem explicitly wrong.


You have asked a pretty interesting question. As with all algorithms the cipher key is the "secret sauce", since once that's known to the public, everything else is too. So you look into ways to this document by Google

security

Besides Google In-App Billing also gives thoughts on security which is insightful as well

billing_best_practices


Use BouncyCastle Lightweight API. It provides 256 AES With PBE and Salt.
Here sample code, which can encrypt/decrypt files.

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

I found a nice implementation here : http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html and https://github.com/nelenkov/android-pbe That was also helpful in my quest for a good enough AES Implementation for Android

참고URL : https://stackoverflow.com/questions/8622367/what-are-best-practices-for-using-aes-encryption-in-android

반응형