Why I ask this question:
为什么我问这个问题:
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.
我知道有很多关于AES加密的问题,即使对于Android也是如此。如果你在网上搜索,有很多代码片段。但是在每个页面上,在每个Stack Overflow问题中,我发现另一个具有重大差异的实现。
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!
所以我创建了这个问题以找到“最佳实践”。我希望我们可以收集最重要的要求列表,并建立一个非常安全的实施!
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?
我读到了初始化载体和盐。并非我发现的所有实现都具有这些功能。所以你需要它吗?它是否会增加安全性?你是如何实现的?如果加密数据无法解密,算法是否应该引发异常?或者这是不安全的,它应该只返回一个不可读的字符串?算法可以使用Bcrypt而不是SHA吗?
What about these two implementations I found? Are they okay? Perfect or some important things missing? What of these is secure?
我发现这两个实现怎么样?他们还好吗?完美或一些重要的事情缺失?这些是安全的吗?
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.
算法应该使用字符串和“密码”进行加密,然后使用该密码加密字符串。输出应该是一个字符串(十六进制或base64?)。当然,解密也应该是可能的。
What is the perfect AES implementation for Android?
什么是Android的完美AES实现?
Implementation #1:
实施#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
Implementation #2:
实施#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));
}
}
Source: 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
5 个解决方案
#1
35
Keys and Hashes
键和哈希
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.
我将开始讨论基于密码的系统与盐。盐是随机生成的数字。它不是“推断”的。实现1包括generateSalt()方法,该方法生成加密强随机数。因为盐对安全很重要,所以一旦生成它就应该保密,尽管它只需要生成一次。如果这是一个网站,保持盐的秘密相对容易,但对于已安装的应用程序(对于台式机和移动设备),这将更加困难,因为假设这些应用程序不保守秘密。
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.
方法getHash()返回给定密码和salt的哈希值,连接成一个字符串。使用的算法是SHA-512,它返回512位散列。此方法返回一个对检查字符串完整性很有用的哈希值,因此它也可以通过只使用密码或只是一个盐调用getHash()来使用,因为它只是连接两个参数。由于此方法不会用于基于密码的加密系统,因此我不再进一步讨论。
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.
方法getSecretKey(),从generateSalt()返回的密码的char数组和十六进制编码的salt中派生一个键。使用的算法是来自PKCS5的PBKDF1(我认为),其中SHA-256作为哈希函数,并返回256位密钥。 getSecretKey()通过重复生成密码,盐和计数器的哈希值来生成密钥(最多为PBE_ITERATION_COUNT中给出的迭代计数,此处为100),以便增加安装暴力攻击所需的时间。 salt的长度应该至少与生成的密钥一样长,在这种情况下,至少为256位。迭代计数应尽可能长,而不会造成不合理的延迟。有关密钥派生中的salt和迭代计数的更多信息,请参阅RFC2898中的第4节。
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". I don't see any problems, though, with representing a salt as a hex-encoded string.
但是,如果密码包含Unicode字符,即需要表示超过8位的字符,则Java PBE中的实现存在缺陷。如PBEKeySpec中所述,“PKCS#5中定义的PBE机制仅查看每个字符的低8位”。若要解决此问题,您可以尝试生成密码中所有16位字符的十六进制字符串(将只包含8位字符),然后将其传递给PBEKeySpec。例如,“ABC”变为“004100420043”。实际上,您也可以使用char数组作为密码的参数,因为出于安全考虑,PBEKeySpec“将密码请求为char数组,因此在完成后可以覆盖[使用clearPassword()]”。但是,我没有看到任何问题,将salt表示为十六进制编码的字符串。
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.)
生成密钥后,我们可以使用它来加密和解密文本。在实现方式1中,使用的密码算法是AES / CBC / PKCS5Padding,即密码块链接(CBC)密码模式中的AES,其中填充在PKCS#5中定义。 (其他AES密码模式包括计数器模式(CTR),电子密码本模式(ECB)和伽罗瓦计数器模式(GCM)。有关Stack Overflow的另一个问题包含详细讨论各种AES密码模式和推荐使用的模式的答案。 )
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.)
如果加密文本可供外人使用,则建议将加密数据(以及可选的附加参数)应用消息验证代码或MAC,以保护其完整性。这里流行的是基于散列的MAC或HMAC,它们基于SHA-1,SHA-256或其他安全散列函数。但是,如果使用MAC,则建议使用至少是普通加密密钥长度的两倍的秘密,以避免相关的密钥攻击:前半部分用作加密密钥,后半部分用作密钥。苹果电脑。 (也就是说,在这种情况下,从密码和盐生成一个秘密,并将该秘密分成两个。)
Java Implementation
Java实现
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.
实现1中的各种功能使用特定的提供者,即“BC”,用于其算法。但是,一般情况下,不建议请求特定的提供程序,因为并非所有提供程序都可用于所有Java实现,请参阅Oracle提供程序简介。
Thus, PROVIDER
should not exist and the string -BC
should probably be removed from PBE_ALGORITHM
. Implementation 2 is correct in this respect.
因此,PROVIDER不应该存在,并且可能应该从PBE_ALGORITHM中删除字符串-BC。在这方面,实施2是正确的。
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.
方法捕获所有异常是不合适的,而是仅处理它可以的异常。您的问题中给出的实现可以抛出各种已检查的异常。方法可以选择仅使用CryptoException包装那些已检查的异常,或者在throws子句中指定那些已检查的异常。为方便起见,在此处使用CryptoException包装原始异常可能是合适的,因为类可能会抛出许多已检查的异常。
#2
15
#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.
永远不应该使用#2,因为它只对密码使用“AES”(这意味着对文本进行ECB模式加密,这是一个很大的禁忌)。我只想谈谈#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.
第一个实现似乎遵循加密的最佳实践。常数通常是正常的,尽管盐的大小和执行PBE的迭代次数都是短边。此外,似乎是AES-256,因为PBE密钥生成使用256作为硬编码值(在所有这些常量之后是一种耻辱)。它使用CBC和PKCS5Padding,这至少是你所期望的。
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.
完全缺少任何身份验证/完整性保护,因此攻击者可以更改密文。这意味着在客户端/服务器模型中可以填充oracle攻击。这也意味着攻击者可以尝试更改加密数据。这可能会导致某些错误,因为应用程序不接受填充或内容,但这不是您想要的情况。
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).
可以增强异常处理和输入验证,在我的书中捕获异常总是错误的。此外,该课程实现了ICrypt,我不知道。我知道在课堂上只有没有副作用的方法有点奇怪。通常,你会使那些静态。 Cipher实例等没有缓冲,因此每个必需的对象都会被创建。但是,您可以安全地从似乎的定义中删除ICrypto,在这种情况下,您还可以将代码重构为静态方法(或者将其重写为更面向对象的,您的选择)。
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.
问题是任何包装器总是对用例做出假设。因此说包装是对还是错是非常重要的。这就是我总是试图避免生成包装类的原因。但至少它似乎并没有明显错误。
#3
1
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
你问过一个非常有趣的问题。与所有算法一样,密码密钥是“秘密酱”,因为一旦公众知道,其他一切也是如此。因此,您可以通过Google查看此文档的方法
安全
Besides Google In-App Billing also gives thoughts on security which is insightful as well
除了Google In-App Billing,还会提供有关安全性的想法,这也是很有见地的
billing_best_practices
#4
0
Use BouncyCastle Lightweight API. It provides 256 AES With PBE and Salt.
Here sample code, which can encrypt/decrypt files.
使用BouncyCastle轻量级API。它提供256个带PBE和盐的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();
}
}
#5
0
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
我在这里找到了一个不错的实现:http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html和https://github.com/nelenkov/android-pbe这也很有帮助在我的Android中寻求一个足够好的AES实现
#1
35
Keys and Hashes
键和哈希
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.
我将开始讨论基于密码的系统与盐。盐是随机生成的数字。它不是“推断”的。实现1包括generateSalt()方法,该方法生成加密强随机数。因为盐对安全很重要,所以一旦生成它就应该保密,尽管它只需要生成一次。如果这是一个网站,保持盐的秘密相对容易,但对于已安装的应用程序(对于台式机和移动设备),这将更加困难,因为假设这些应用程序不保守秘密。
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.
方法getHash()返回给定密码和salt的哈希值,连接成一个字符串。使用的算法是SHA-512,它返回512位散列。此方法返回一个对检查字符串完整性很有用的哈希值,因此它也可以通过只使用密码或只是一个盐调用getHash()来使用,因为它只是连接两个参数。由于此方法不会用于基于密码的加密系统,因此我不再进一步讨论。
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.
方法getSecretKey(),从generateSalt()返回的密码的char数组和十六进制编码的salt中派生一个键。使用的算法是来自PKCS5的PBKDF1(我认为),其中SHA-256作为哈希函数,并返回256位密钥。 getSecretKey()通过重复生成密码,盐和计数器的哈希值来生成密钥(最多为PBE_ITERATION_COUNT中给出的迭代计数,此处为100),以便增加安装暴力攻击所需的时间。 salt的长度应该至少与生成的密钥一样长,在这种情况下,至少为256位。迭代计数应尽可能长,而不会造成不合理的延迟。有关密钥派生中的salt和迭代计数的更多信息,请参阅RFC2898中的第4节。
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". I don't see any problems, though, with representing a salt as a hex-encoded string.
但是,如果密码包含Unicode字符,即需要表示超过8位的字符,则Java PBE中的实现存在缺陷。如PBEKeySpec中所述,“PKCS#5中定义的PBE机制仅查看每个字符的低8位”。若要解决此问题,您可以尝试生成密码中所有16位字符的十六进制字符串(将只包含8位字符),然后将其传递给PBEKeySpec。例如,“ABC”变为“004100420043”。实际上,您也可以使用char数组作为密码的参数,因为出于安全考虑,PBEKeySpec“将密码请求为char数组,因此在完成后可以覆盖[使用clearPassword()]”。但是,我没有看到任何问题,将salt表示为十六进制编码的字符串。
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.)
生成密钥后,我们可以使用它来加密和解密文本。在实现方式1中,使用的密码算法是AES / CBC / PKCS5Padding,即密码块链接(CBC)密码模式中的AES,其中填充在PKCS#5中定义。 (其他AES密码模式包括计数器模式(CTR),电子密码本模式(ECB)和伽罗瓦计数器模式(GCM)。有关Stack Overflow的另一个问题包含详细讨论各种AES密码模式和推荐使用的模式的答案。 )
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.)
如果加密文本可供外人使用,则建议将加密数据(以及可选的附加参数)应用消息验证代码或MAC,以保护其完整性。这里流行的是基于散列的MAC或HMAC,它们基于SHA-1,SHA-256或其他安全散列函数。但是,如果使用MAC,则建议使用至少是普通加密密钥长度的两倍的秘密,以避免相关的密钥攻击:前半部分用作加密密钥,后半部分用作密钥。苹果电脑。 (也就是说,在这种情况下,从密码和盐生成一个秘密,并将该秘密分成两个。)
Java Implementation
Java实现
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.
实现1中的各种功能使用特定的提供者,即“BC”,用于其算法。但是,一般情况下,不建议请求特定的提供程序,因为并非所有提供程序都可用于所有Java实现,请参阅Oracle提供程序简介。
Thus, PROVIDER
should not exist and the string -BC
should probably be removed from PBE_ALGORITHM
. Implementation 2 is correct in this respect.
因此,PROVIDER不应该存在,并且可能应该从PBE_ALGORITHM中删除字符串-BC。在这方面,实施2是正确的。
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.
方法捕获所有异常是不合适的,而是仅处理它可以的异常。您的问题中给出的实现可以抛出各种已检查的异常。方法可以选择仅使用CryptoException包装那些已检查的异常,或者在throws子句中指定那些已检查的异常。为方便起见,在此处使用CryptoException包装原始异常可能是合适的,因为类可能会抛出许多已检查的异常。
#2
15
#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.
永远不应该使用#2,因为它只对密码使用“AES”(这意味着对文本进行ECB模式加密,这是一个很大的禁忌)。我只想谈谈#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.
第一个实现似乎遵循加密的最佳实践。常数通常是正常的,尽管盐的大小和执行PBE的迭代次数都是短边。此外,似乎是AES-256,因为PBE密钥生成使用256作为硬编码值(在所有这些常量之后是一种耻辱)。它使用CBC和PKCS5Padding,这至少是你所期望的。
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.
完全缺少任何身份验证/完整性保护,因此攻击者可以更改密文。这意味着在客户端/服务器模型中可以填充oracle攻击。这也意味着攻击者可以尝试更改加密数据。这可能会导致某些错误,因为应用程序不接受填充或内容,但这不是您想要的情况。
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).
可以增强异常处理和输入验证,在我的书中捕获异常总是错误的。此外,该课程实现了ICrypt,我不知道。我知道在课堂上只有没有副作用的方法有点奇怪。通常,你会使那些静态。 Cipher实例等没有缓冲,因此每个必需的对象都会被创建。但是,您可以安全地从似乎的定义中删除ICrypto,在这种情况下,您还可以将代码重构为静态方法(或者将其重写为更面向对象的,您的选择)。
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.
问题是任何包装器总是对用例做出假设。因此说包装是对还是错是非常重要的。这就是我总是试图避免生成包装类的原因。但至少它似乎并没有明显错误。
#3
1
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
你问过一个非常有趣的问题。与所有算法一样,密码密钥是“秘密酱”,因为一旦公众知道,其他一切也是如此。因此,您可以通过Google查看此文档的方法
安全
Besides Google In-App Billing also gives thoughts on security which is insightful as well
除了Google In-App Billing,还会提供有关安全性的想法,这也是很有见地的
billing_best_practices
#4
0
Use BouncyCastle Lightweight API. It provides 256 AES With PBE and Salt.
Here sample code, which can encrypt/decrypt files.
使用BouncyCastle轻量级API。它提供256个带PBE和盐的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();
}
}
#5
0
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
我在这里找到了一个不错的实现:http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html和https://github.com/nelenkov/android-pbe这也很有帮助在我的Android中寻求一个足够好的AES实现