在阅读了几个关于实现AES的stackoverflow问题后,我想我已经开始理解基础知识了:
我的环境非常简单:
基于这些,我提出了以下Java代码:
public class SecureEncryption {
private static final String CONTENT = "thisneedstobestoredverysecurely";
private static final String PASSPHRASE = "mysuperstrongpassword";
private static final int IV_LENGTH = 16;
public static void main(String[] args) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
byte[] passphrase = digest.digest(PASSPHRASE.getBytes("UTF-8"));
Cipher instance = Cipher.getInstance("AES/CFB/NoPadding");
passphrase = Arrays.copyOf(passphrase, 16);
SecretKeySpec secretKey = new SecretKeySpec(passphrase, "AES");
byte[] iv = new byte[16];
SecureRandom sr = new SecureRandom();
sr.nextBytes(iv);
instance.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] encrypted = instance.doFinal(CONTENT.getBytes("UTF-8"));
byte[] result = addIVtoEncrypted(iv, encrypted);
System.arraycopy(result, 0, iv, 0, IV_LENGTH);
System.arraycopy(result, IV_LENGTH, encrypted, 0, result.length - IV_LENGTH);
instance.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] decrypted = instance.doFinal(encrypted);
System.out.println(new String(decrypted, "UTF-8"));
}
private static byte[] addIVtoEncrypted(byte[] iv, byte[] encrypted) {
byte[] ret = new byte[IV_LENGTH + encrypted.length];
System.arraycopy(iv, 0, ret, 0, IV_LENGTH);
System.arraycopy(encrypted, 0, ret, IV_LENGTH, encrypted.length);
return ret;
}
}
虽然这种方法很好,但我不确定它是否像它可以获得的那样安全。 关于以下事情,我现在有点迷失:
更新:根据此处收到的建议重新编写了我的实施内容:
public class SecureEncryption {
private static final String CONTENT = "thisneedstobestoredverysecurely";
private static final String PASSPHRASE = "mysuperstrongpassword";
private static final int IV_LENGTH = 16;
private static final int AES_KEY_LENGTH = 16;
private static final int MAC_KEY_LENGTH = 16;
private static final int MAC_LENGTH = 20;
private static final int ITERATION_COUNT = 4096;
private static final String AES = "AES";
private static final String CIPHER_ALGORITHM = "AES/CFB/NoPadding";
private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String MAC_ALGORITHM = "HmacSHA1";
public static void main(String[] args) throws Exception {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[16];
sr.nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
SecretKey secretKey = factory.generateSecret(new PBEKeySpec(PASSPHRASE.toCharArray(), salt, ITERATION_COUNT, 256));
byte[] secretBytes = secretKey.getEncoded();
byte[] iv = new byte[16];
sr.nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secretBytes, 0, AES_KEY_LENGTH, AES), new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(CONTENT.getBytes("UTF-8"));
byte[] result = concatArrays(iv, encrypted);
byte[] macResult = getMAC(secretBytes, result);
result = concatArrays(macResult, result);
System.arraycopy(result, 0, macResult, 0, MAC_LENGTH);
System.arraycopy(result, MAC_LENGTH, iv, 0, IV_LENGTH);
System.arraycopy(result, MAC_LENGTH + IV_LENGTH, encrypted, 0, result.length - IV_LENGTH - MAC_LENGTH);
if (!Arrays.equals(getDigest(getMAC(secretBytes, concatArrays(iv, encrypted))), getDigest(macResult))) {
System.out.println("Invalid MAC");
}
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secretBytes, 0, AES_KEY_LENGTH, AES), new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println(new String(decrypted, "UTF-8"));
}
private static byte[] getDigest(byte[] mac) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA1");
return digest.digest(mac);
}
private static byte[] getMAC(byte[] secretBytes, byte[] data) throws Exception {
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(new SecretKeySpec(secretBytes, AES_KEY_LENGTH, MAC_KEY_LENGTH, MAC_ALGORITHM));
return mac.doFinal(data);
}
private static byte[] concatArrays(byte[] first, byte[] second) {
byte[] ret = new byte[first.length + second.length];
System.arraycopy(first, 0, ret, 0, first.length);
System.arraycopy(second, 0, ret, first.length, second.length);
return ret;
}
}
计划将生成salt安装时间,然后对所有加密/解密操作保持相同。我假设这应该提供足够的保护,防止彩虹表攻击。
更新2:我必须意识到我的MAC验证码不是最优的:MAC已经是SHA-1哈希,所以创建另一个SHA1摘要没有意义。我还调整了MAC验证,因此它不再使用Arrays.equals,因为它很容易受到时间攻击。
答案 0 :(得分:5)
在为用户输入密钥(例如键入的密码)时,应始终使用密钥拉伸算法。关键的拉伸做了一些不错的事情。首先,它重新分配你的密钥的熵(SHA1也这样做)使得密钥看起来更随机(它实际上不是更随机,熵保持不变),其次它使得强制密钥更加计算密集(增加)显然迭代)。随机盐的使用也排除了使用预先计算的查找表。
您应该绝对使用标准算法,例如PBKDF2。在Java中,您可以通过SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
如果您将加密数据存储在您无法控制的环境中,则还应在IV + Ciphertext上生成MAC并将其与您的密文一起存储。您可以使用类似于存储IV的方式将其添加到明文中。在解密之前验证MAC,你应该首先通过散列MAC来间接验证(这里简单的SHA1工作),以便不创建定时攻击向量。
诸如HMACSHA1之类的MAC算法需要类似于密码的密钥。您不应使用相同的密钥来加密和生成MAC。您可以使用键拉伸算法生成足够长的键,您可以将其用作密码的一部分,也可以用作MAC的一部分。
附录: 如果您使用的是Java 7(或支持它的外部JCA提供程序),请使用GCM模式在您的AES密码中包含MAC。 GCM模式下的AES是一种经过身份验证的加密形式,可将完整性验证为密码的一部分。实现MAC生成和验证有许多需要避免的陷阱(例如我提到的时序攻击或使用单独的密钥)并将其滚入密码是一件不小的事情。
创建安全的加密系统并非易事,有很多方法可以搞砸它并使整个过程变得不安全。通过将各种加密原语放在一起来创建自己的加密系统,通常更好的做法是使用更高级别的库来处理cookie加密和数据存储或SSL / TLS等传输中的数据。