简体   繁体   English

生成AES和HMAC密钥需要太多时间

[英]Generate the AES & HMAC keys take too much time

I am using this method in an Android app to generate the AES & HMAC keys. 我在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);
}

The issue now that I am facing is, this method takes too much time. 我现在面临的问题是,此方法需要太多时间。 It approximately takes two seconds on my device. 在我的设备上大约需要两秒钟。 And as my profile, it's caused by this line of code: 作为我的个人资料,这是由以下代码行引起的:

byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();

I don't have much experience with cipher/ encryption/ decryption. 我在密码/加密/解密方面没有太多经验。 Please help to give me some advice, how would I speed up this method? 请帮忙给我一些建议,我将如何加快这种方法? Or are there any equivalence approach that I should follow instead of this method. 还是我应该遵循任何等效的方法来代替这种方法。

Your support will be very appreciated 非常感谢您的支持

Thanks. 谢谢。

TL;DR: use a better password and bring back the iterations and use a KBKDF2 to derive the AES and HMAC keys. TL; DR:使用更好的密码并返回迭代,并使用KBKDF2派生AES和HMAC密钥。


PBKDF2 is an algorithm that explicitly contains a work factor, in this case an iteration count . PBKDF2是一种显式包含工作因数的算法,在这种情况下为迭代计数 It has been designed to securely derive a key from a password. 它旨在安全地从密码派生密钥。 The idea is that even strong passwords are generally not secure enough and that an attacker has to perform the required work to derive the key. 这个想法是,即使强密码也通常不够安全,并且攻击者必须执行所需的工作才能得出密钥。

Unfortunately you'd have to perform the same work, possibly on a much slower device such as a smart phone. 不幸的是,您可能必须在速度较慢的设备(例如智能手机)上执行相同的工作。 10K is about the minimum of iterations that you'd perform nowadays. 10K大约是您现在执行的最小迭代次数。 However, if you have an alphabet of 70 characters then you could just extend your password with 2-3 fully random characters to achieve the same thing. 但是,如果您的字母由70个字符组成,则只需将密码扩展为2-3个完全随机的字符即可实现相同目的。 You'd still need to use PBKDF2 and a salt, but you can use a much lower iteration count. 您仍然需要使用PBKDF2和盐,但是您可以使用低得多的迭代次数。


PBKDF2 has a design to deliver any number of bits as output. PBKDF2的设计可提供任意数量的位作为输出。 It however has a design flaw where additional bits require that the entire work has to be performed all over again. 但是,它有一个设计缺陷,其中需要额外的位,因此必须重新执行整个工作。 As Math.ceil((128.0 + 256.0) / 160.0) = 3 you're basically performing the PBKDF2 function three times . Math.ceil((128.0 + 256.0) / 160.0) = 3您基本上要执行PBKDF2函数3次 The attacker can however validate the first (AES) key by just performing it once. 但是,攻击者只需执行一次即可验证第一个(AES)密钥。 So you have 3 times less the performance than the attacker, with no gain. 因此,您的性能是攻击者的三倍,却没有收获。

Rather than using PBKDF2 in this kind of way you should request 160 bits from it, and then append a label to it. 而不是以这种方式使用PBKDF2,您应该从中请求160位,然后在其上附加标签。 Eg a counter in 4 bytes: 00000001 (hex). 例如,以4个字节为单位的计数器: 00000001 (十六进制)。 Then you can use SHA-256 or 512 to generate a key from it. 然后,您可以使用SHA-256或512从中生成密钥。 For the next key you'd use 00000002 as a label (etc.). 对于下一个键,您将使用00000002作为标签(等等)。 Or you can get fancy and use one of the KBKDF's (Key Based Key Derivation Functions) in Bouncy Castle. 或者您也可以在Bouncy Castle中使用KBKDF的一种(基于密钥的密钥派生函数)。

The KBKDF with the counter I specified above is called KDF2 (or KDF1, they just differ wrt the counter value, which doesn't matter wrt security). 具有上面指定的计数器I的KBKDF称为KDF2(或KDF1,它们与计数器值不同,而与安全性无关)。 HKDF is much more complex and has a better security argument behind it. HKDF更为复杂,其背后的安全论据也更好。


Here is an example that uses HMAC and a textual label instead of the hash and counter specified above: 这是一个使用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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM