我在Android应用中使用此方法来生成AES& HMAC密钥。
private static final int PBE_ITERATION_COUNT = 10000;
private static final int AES_KEY_LENGTH_BITS = 128;
private static final int HMAC_KEY_LENGTH_BITS = 256;
private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int AES_KEY_LENGTH_BYTES = AES_KEY_LENGTH_BITS >> 3;
private static final int HMAC_KEY_LENGTH_BYTES = HMAC_KEY_LENGTH_BITS >> 3;
public static AesHmacKeyPair generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
PrngFixes.fixPrng();
//Get enough random bytes for both the AES key and the HMAC key:
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
PBE_ITERATION_COUNT, AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance(PBE_ALGORITHM);
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
// Split the random bytes into two parts:
byte[] confidentialityKeyBytes = copyOfRange(keyBytes, 0, AES_KEY_LENGTH_BYTES);
byte[] integrityKeyBytes = copyOfRange(keyBytes, AES_KEY_LENGTH_BYTES, AES_KEY_LENGTH_BYTES + HMAC_KEY_LENGTH_BYTES);
//Generate the AES key
SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
//Generate the HMAC key
SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
return new AesHmacKeyPair(confidentialityKey, integrityKey);
}
现在我面临的问题是,这种方法需要花费太多时间。我的设备大约需要两秒钟。 而作为我的个人资料,它是由这行代码引起的:
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
我在密码/加密/解密方面没有太多经验。 请帮忙给我一些建议,我该如何加快这种方法? 或者是否有任何等价方法,我应该遵循这种方法。
非常感谢您的支持
感谢。
答案 0 :(得分:2)
TL; DR:使用更好的密码并恢复迭代并使用KBKDF2来获取AES和HMAC密钥。
PBKDF2是一种明确包含工作因子的算法,在本例中为迭代计数。它旨在从密码安全地获取密钥。这个想法是,即使是强密码通常也不够安全,攻击者必须执行所需的工作才能获得密钥。
不幸的是,您必须执行相同的工作,可能是在速度较慢的设备上,例如智能手机。 10K大约是您现在执行的最小迭代次数。但是,如果您有一个70个字符的字母,那么您可以使用2-3个完全随机字符扩展您的密码以实现相同的目的。您仍然需要使用PBKDF2和盐,但您可以使用更低的迭代次数。
PBKDF2的设计可以提供任意数量的位作为输出。然而,它有一个设计缺陷,其中额外的位需要整个工作必须重新执行。作为Math.ceil((128.0 + 256.0) / 160.0) = 3
,您基本上执行了三次的PBKDF2功能。但是,攻击者可以通过执行一次来验证第一个(AES)密钥。因此,您的性能比攻击者低3倍,没有任何收获。
不是以这种方式使用PBKDF2,而是应该从中请求160位,然后为其附加标签。例如。一个4字节的计数器:00000001
(十六进制)。然后,您可以使用SHA-256或512从中生成密钥。对于下一个键,您可以使用00000002
作为标签(等)。或者你可以在Bouncy Castle中使用KBKDF(基于密钥的关键衍生函数)。
带有上面指定的计数器的KBKDF称为KDF2(或KDF1,它们只是与计数器值不同,这与安全性无关)。 HKDF更为复杂,背后有更好的安全论据。
以下是使用HMAC和文本标签而不是上面指定的哈希和计数器的示例:
private static final int PBE_ITERATION_COUNT = 10000;
private static final int AES_KEY_LENGTH_BITS = 128;
private static final int HMAC_KEY_LENGTH_BITS = 256;
private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int AES_KEY_LENGTH_BYTES = AES_KEY_LENGTH_BITS >> 3;
private static final int HMAC_KEY_LENGTH_BYTES = HMAC_KEY_LENGTH_BITS >> 3;
private static final String CIPHER = "AES";
private static final String HMAC_ALGORITHM = "HMACSHA256";
private static final int MASTER_KEY_LENGTH_BITS = 160; // max for PBKDF2 configured with SHA-1
public static AesHmacKeyPair generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
// PrngFixes.fixPrng(); <-- needs to be put back
//Get enough random bytes for just the master key:
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
PBE_ITERATION_COUNT, MASTER_KEY_LENGTH_BITS);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance(PBE_ALGORITHM);
byte[] masterKeyBytes = keyFactory.generateSecret(keySpec).getEncoded();
//Generate the AES key
byte[] confidentialityKeyBytes = kdf(masterKeyBytes, "ENC", AES_KEY_LENGTH_BYTES);
SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
//Generate the HMAC key
byte[] integrityKeyBytes = kdf(masterKeyBytes, "MAC", HMAC_KEY_LENGTH_BYTES);
SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
return new AesHmacKeyPair(confidentialityKey, integrityKey);
}
private static byte[] kdf(byte[] inputKeyMaterial, String label, int outputKeyBytes) {
try {
Mac mac = Mac.getInstance("HMACSHA256");
mac.init(new SecretKeySpec(inputKeyMaterial, "HMACSHA256"));
mac.update(label.getBytes(StandardCharsets.US_ASCII));
byte[] confidentialityKeyBytes = mac.doFinal();
return Arrays.copyOf(confidentialityKeyBytes, outputKeyBytes);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("HMAC operation doesn't work", e);
}
}