问:使用AES加密在Android的最佳做法? [英] What are best practices for using AES encryption in Android?

查看:144
本文介绍了问:使用AES加密在Android的最佳做法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么我问这个问题:

我知道已经有很多关于AES加密的问题,甚至是为Android。而且有很多code片段,如果你在网上搜索。但是,每一个页面上,在每一个堆栈溢出的问题,我发现另一个与实施重大分歧。

I know there have been a lot of questions about AES encryption, even for Android. And there are lots of code snippets if you search the Web. But on every single page, in every Stack Overflow question, I find another implementation with major differences.

因此​​,我创建了这个问题找到一个最佳实践。我希望我们能收集的最重要的要求列表,并设置一个真正安全的实现!

So I created this question to find a "best practice". I hope we can collect a list of the most important requirements and set up an implementation that is really secure!

我读到的初始化向量和盐。不是所有的我发现实现了这些功能。所以,你需要它?是否增加安全很多吗?如何实现呢?应的算法产生异常,如果加密后的数据不能被解密?或者是不安全的,它应该只返回一个字符串不可读?能否算法使用Bcrypt代替SHA?

I read about initialization vectors and salts. Not all implementations I found had these features. So do you need it? Does it increase the security a lot? How do you implement it? Should the algorithm raise exceptions if the encrypted data cannot be decrypted? Or is that insecure and it should just return an unreadable string? Can the algorithm use Bcrypt instead of SHA?

那么这两种实现我发现了什么?难道他们还好吗?完美或者一些重要的东西不见了?这些什么是安全的?

What about these two implementations I found? Are they okay? Perfect or some important things missing? What of these is secure?

该算法应该采取一个字符串和密码进行加密,然后加密字符串的密码。输出应该是一个字符串(十六进制或Base64?)了。解密应该是当然可以的。

The algorithm should take a string and a "password" for encryption and then encrypt the string with that password. The output should be a string (hex or base64?) again. Decryption should be possible as well, of course.

什么是完美的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;
        }

}

来源:<一href="http://pocket-for-android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html">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));
    }

}

来源:<一href="http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml">http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml

推荐答案

键和散列

我将开始与盐讨论基于口令的系统。该盐是随机生成的数字。它不是推测。实施1包括 generateSalt()方法,生成保密性强的随机数。因为该盐是很重要的安全性,应当保持秘密一旦被产生,尽管它只需生成一次。如果这是一个网站,它的相对容易地保持盐秘密,但对于安装的应用程序(台式和移动设备),这将更加困难,因为这样的应用被假定不保守秘密。

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, as such applications are assumed not to keep secrets.

方法 getHash()返回给定的密码和盐,串连成一个字符串的哈希值。所使用的算法SHA-512,它返回一个512位的散列值。该方法返回一个哈希是用于检查一个字符串的完整性非常有用,所以它可能也被用于通过调用 getHash()只是一个密码,或者只是盐,因为它只是符连接这两个参数。

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.

方法 getSecretKey(),在另一方面,源于一个密码和盐的关键。所使用的算法是PBKDF1(我认为)从PKCS5与SHA-256散列函数,并返回一个256位的密钥。 getSecretKey()生成密钥的密码,盐重复生成散列和计数器(不超过 PBE_ITERATION_COUNT ,这里100),以增加安装一个强力攻击所需的时间。该盐的长度至少应为只要被生成的密钥,在这种情况下,至少256位。迭代次数应尽可能长时间地设置,而不会导致不合理的延误。它应该是1000或更大,但是如果高得多的数量不会导致不合理的延迟,它应该被使用。有关盐和迭代计数的密钥导出的更多信息,请参阅 RFC2898 的第4节。

The method getSecretKey(), on the other hand, derives a key from a password and salt. 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. It should be 1000 or more, although if a much higher number doesn't cause unreasonable delay, it should be used. For more information on salts and iteration counts in key derivation, see section 4 in RFC2898.

在Java的PBE的实施,但是,是有缺陷的,如果密码包含统一code字符,也就是那些需要超过8位重新presented。作为<一说href="http://docs.oracle.com/javase/7/docs/api/javax/crypto/spec/PBEKeySpec.html"><$c$c>PBEKeySpec, 在PKCS#5所限定的PBE机构着眼于仅低位8位每个字符。要解决这个问题,你可以尝试将其传递给 PBEKeySpec 前生成的密码,所有16位字符的十六进制字符串(其中仅包含8位字符) 。例如,ABC变成004100420043。事实上,你还不如用一个字符数组作为密码的参数,因为出于安全目的,PBEKeySpec要求的密码字符数组,因此它可以被覆盖[与 clearPassword()]时的情形。

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". In fact, you might as well use a char array as a parameter for the password, since for security purposes, PBEKeySpec "requests the password as a char array, so it can be overwritten [with clearPassword()] when done".

加密

在产生密钥,我们可以用它来加密和解密的文本。在实施1,使用的加密算法是 AES / CBC / PKCS5Padding ,即,AES的密码块链接加密模式,在PKCS#5中定义的填充。这个算法接受的128,192或256位密钥(尽管后者两种尺寸可能不总是在所有的实现可提供)。

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 cipher mode, with padding defined in PKCS#5. This algorithm accepts keys of 128, 192, or 256 bits (though the latter two sizes may not always be available in all implementations).

的AES支持几种密码模式,其中有一些是以下

AES supports several cipher modes, some of which are the following.

  • CBC是加密前与previous(加密的)块,其中每块明文结合(通过异或)一个加密模式,以及所述第一块进行组合(通过异或)用所谓的初始化向量(或IV)之前进行加密。在Java实现,随机IV产生(与IvParameterSpec类)和放置在密文的开始。 IV是16个字节(128位)长,如在IV_LENGTH给出。这是完全一样的长度在AES的块。
  • 的ECB模式(默认AES模式)不推荐,因为相同块将用同样的方式用相同的密钥来加密。这相当于使用没有第四可言,虽然它并形成供其他密码模式,其中大部分确实使用IV型
  • 在计数器模式(CTR),16字节的IV是一个计数器,每块后加1。对于每个块,计数器与该块以产生密文或明文进行加密,并组合(通过异或)。
  • 然而
  • 伽罗瓦计数器模式(GCM)提供完整性保护,它不是在所有的Java版本,严格来说是一个密码,而不是一个模式,并极力推荐一款独一无二的四以确保其安全性。
  • CBC is a cipher mode where each block of plaintext is combined (through XOR) with the previous (encrypted) block before encrypting, and the first block is combined (through XOR) with a so-called initialization vector (or IV) before encrypting. In the Java implementation, a random IV is generated (with the IvParameterSpec class) and placed at the beginning of the cipher text. The IV is 16 bytes (128 bits) long, as given in IV_LENGTH. This is exactly the same length as a block in AES.
  • The ECB mode (the default AES mode) is not recommended since identical blocks will be encrypted in the same way with the same key. This is equivalent to using no IV at all, though it does form the basis for other cipher modes, most of which do use IVs.
  • In counter mode (CTR), the 16-byte IV is a counter, which is increased by 1 after each block. For each block, the counter is encrypted and is combined (through XOR) with that block to produce the ciphertext or plaintext.
  • Galois counter mode (GCM) provides integrity protection, however it is not available in all Java versions, is strictly speaking a cipher, rather than a mode, and strongly recommends a "unique" IV to ensure its security.

如果加密的文本将提供给外人,然后将消息验证code或MAC,加密数据(与 任选附加的参数)被推荐,以保护其完整性。流行这里是基于散列的MAC,或者HMACs,是基于SHA-1,SHA-256,或其它安全散列函数。当MAC被使用,然而,使用秘密这至少两次,只要正常加密密钥被推荐的,以避免相关密钥攻击:上半场用作加密密钥,和第二半用作密钥苹果。 (即,在这种情况下,产生从一个密码和盐的单一的秘密,并分割的秘密中两项。)

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. Popular here are hash-based MACs, or HMACs, that are based on SHA-1, 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实现

在实施1的各种功能,使用特定的提供者,即BC, 其算法。在一般情况下,虽然,它不建议请求特定 供应商,因为不是所有的供应商都适用于所有的Java实现, 看到<一href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#Introduction">Introduction甲骨文提供。 因此,提供商不应该存在和字符串 -BC 或许应该被调离 PBE_ALGORITHM 。实施2是正确的在这方面。

The various functions in implementation 1 uses 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, see the Introduction to Oracle Providers. Thus, PROVIDER should not exist and the string -BC should probably be removed from PBE_ALGORITHM. Implementation 2 is correct in this respect.

这是不恰当的方法来捕获所有异常,而是只处理异常 它可以。各种加密类可以抛出各种检查异常。一种方法 可以选择用保鲜CryptoException只有那些检查异常,或者指定抛出子句中这些检查的异常。为方便起见,与CryptoException包装原始的异常可能是适当的在这里,因为有许多潜在的checked异常类可以抛出。

It is inappropriate for a method to catch all exceptions, but rather to handle only the exceptions it can. The various cryptography classes 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.

getHash 方法哈希一个UTF-8版的密码加上盐。因为它不会被使用的 基于口令的加密系统,我不会再讨论这个问题。

The getHash method hashes a UTF-8 version of the password plus salt. Since it won't be used in the password-based encryption system, I won't be discussing it further.

getSecretKey 的方法,但是,源于密码的字符阵列和一键十六进制恩codeD盐,从 generateSalt为返回的()。的问题,因为我刚才提到的PBEKeySpec,我建议有getSecretKey传递密码,而不是一个字符串的字符阵列, preferably与我上面提到的解决方法。我看不出有什么问题,但是,与重presenting 盐为十六进制恩codeD字符串。

The getSecretKey method, however, derives a key from a char array of the password and a hex-encoded salt, as returned from generateSalt(). Because of the problems I just mentioned with PBEKeySpec, I recommend having getSecretKey pass a char array of the password instead of a String, preferably with the workaround I mentioned above. I don't see any problems, though, with representing a salt as a hex-encoded string.

加密解密方法使用明文和回报UTF-8 EN codeD版十六进制恩codeD的密文。密文也可以是基64(因为确切的重presentation应该没有关系,只要它的加密,并用同样的方式由双方加密解密),但如果你使用的是Mac,我建议应用MAC的密文字节数组,而不是它的十六进制或碱64重新presentation。

The encrypt and decrypt methods use a UTF-8 encoded version of the plaintext and return a hex-encoded ciphertext. The ciphertext can also be base-64 (since the exact representation shouldn't matter as long as it's encrypted and is used the same way by both encrypt and decrypt) but if you use a MAC, I recommend applying the MAC to the ciphertext byte array rather than its hex or base-64 representation.

这篇关于问:使用AES加密在Android的最佳做法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆