生成AES& HMAC键占用太多时间

时间:2018-04-22 10:17:03

标签: java android security encryption optimization

我在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();

我在密码/加密/解密方面没有太多经验。 请帮忙给我一些建议,我该如何加快这种方法? 或者是否有任何等价方法,我应该遵循这种方法。

非常感谢您的支持

感谢。

1 个答案:

答案 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);
    }
}