Pet*_*jor 9 java security encryption cryptography
在阅读了几个关于实现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;
}
}
Run Code Online (Sandbox Code Playgroud)
虽然这样可以正常工作,但我不确定它是否尽可能安全......我现在对以下事情感到很失落:
更新:基于此处收到的建议,我重写了我的实现:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
计划将生成salt安装时间,然后它将对所有加密/解密操作保持相同.我假设这应该提供足够的保护,防止彩虹表攻击.
更新2:我必须意识到我的MAC验证码不是最优的:MAC已经是SHA-1哈希,所以创建另一个SHA1摘要没有意义.我还调整了MAC验证,因此它不再使用Arrays.equals,因为它容易受到时序攻击.
在为用户输入密钥(例如键入的密码)时,应始终使用密钥拉伸算法.关键的拉伸做了一些不错的事情.首先,它重新分配你的密钥的熵(SHA1也这样做)使得密钥看起来更随机(它实际上不是更随机,熵保持不变),其次它使得强制密钥更加计算密集(增加)显然迭代).随机盐的使用也排除了使用预先计算的查找表.
你应该绝对使用标准算法,如PBKDF2.在Java中,您可以获得此通道的关键工厂SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
如果在您不控制的环境中存储加密数据,还应在IV +密文上生成MAC并将其与密文一起存储.您可以使用类似于存储IV的方式将其添加到明文中.在解密之前验证MAC,你应该首先通过散列MAC来间接验证(这里简单的SHA1工作),以便不创建定时攻击向量.
诸如HMACSHA1的MAC算法需要类似于密码的秘密密钥.您不应使用相同的密钥来加密和生成MAC.您可以使用键拉伸算法生成足够长的键,您可以将其用作密码的一部分,也可以用作MAC的一部分.
附录:如果您使用的是Java 7(或支持它的外部JCA提供程序),请使用GCM模式在您的AES密码中包含MAC.GCM模式下的AES是一种经过身份验证的加密形式,可将完整性验证为密码的一部分.实现MAC生成和验证有各种各样的缺陷需要避免(例如我提到的定时攻击或使用单独的密钥)并将其滚入密码是一件不小的事情.
创建安全的加密系统并不是一项简单的任务,有很多方法可以将其搞砸并使整个过程变得不安全.通过将各种加密原语放在一起而不是创建自己的加密系统,通常使用更高级别的库来处理cookie加密和数据存储或SSL/TLS等传输中的数据更好.
| 归档时间: |
|
| 查看次数: |
8987 次 |
| 最近记录: |