1. Overview
1.概述
In this article, we’ll deep dive into the purpose of keys in AES or Ciphers in general. We’ll go over the best practices to keep in mind while generating one.
在这篇文章中,我们将深入探讨AES或一般密码器中密钥的用途。我们将讨论生成密钥时需要注意的最佳做法。
Finally, we’ll look at the various ways to generate one and weigh them against the guidelines.
最后,我们将看一下产生一个的各种方法,并根据准则进行权衡。
2. AES
2.AES
Advanced Encryption Standard (AES) is the successor of the Data Encryption Standard(DES), published in 2001 by the National Institute of Standards and Technology(NIST). It’s classified as a symmetric block cipher.
高级加密标准(AES)是数据加密标准(DES)的继承者,由美国国家标准与技术研究所(NIST)于2001年发布。它被归类为对称块密码。
A symmetric cipher uses the same secret key for both encryption and decryption. A block cipher means it works on 128 bits blocks of the input Plaintext:
对称密码在加密和解密时使用相同的秘钥。区块密码指的是它对输入的128比特的明文块进行工作。
2.1. AES Variants
2.1.AES的变体
Based on the key size, AES supports three variants: AES-128 (128 bits), AES-192 (192 bits), and AES-256 (256 bits). Increasing the key size increases the strength of encryption as a larger key size means the number of possible keys is larger. Consequently, the number of rounds to be performed during the execution of the algorithm increases as well and hence the compute required:
基于密钥大小,AES支持三种变体。AES-128(128位)、AES-192(192位)和AES-256(256位)。增加密钥大小会增加加密的强度,因为更大的密钥大小意味着可能的密钥数量更大。因此,在执行算法的过程中,需要执行的回合数也会增加,因此需要的计算量也会增加。
Key Size | Block Size | # Rounds |
---|---|---|
128 | 128 | 10 |
192 | 128 | 12 |
256 | 128 | 14 |
2.2. How Secure Is AES?
2.2.AES的安全性如何?
The AES algorithm is public information – it’s the AES key that is a secret and must be known to successfully decipher. So, it boils down to cracking the AES keys. Assuming the key is securely preserved, an attacker would have to try to guess the key.
AES算法是公共信息–AES密钥才是一个秘密,必须知道才能成功破译。所以,归根结底就是破解AES密钥。假设密钥是安全保存的,攻击者就必须尝试猜测密钥。
Let’s see how the brute-force approach fares in guessing the key.
让我们看看暴力方法在猜测钥匙方面的表现。
AES-128 keys are 128 bits, which means there are 2^128 possible values. It’d take a humongous and infeasible amount of time and money to search through this. Hence, AES is practically unbreakable by a brute-force approach.
AES-128密钥为128位,这意味着有2^128个可能值。这需要大量的、不可行的时间和金钱来搜索。因此,AES实际上是无法通过暴力手段破解的。
There have been a few non-brute-force approaches but these could only reduce the possible key lookup space by a couple of bits.
已经有一些非蛮力方法,但这些方法只能将可能的密钥查询空间减少几位。
All this means is that with zero knowledge about the key, AES is practically impossible to break.
所有这一切意味着,在对密钥一无所知的情况下,AES几乎不可能被破解。
3. Properties of a Good Key
3.好钥匙的属性
Let’s now look at some of the important guidelines to follow while generating an AES key.
现在让我们来看看生成AES密钥时需要遵循的一些重要准则。
3.1. Key Size
3.1.关键尺寸
Since AES supports three key sizes, we should choose the right key size for the use case. AES-128 is the most common choice in commercial applications. It offers a balance between security and speed. National Governments typically make use of AES-192 and AES-256 to have maximum security. We can use AES-256 if we want to have an extra level of security.
由于AES支持三种密钥大小,我们应该根据使用情况选择合适的密钥大小。AES-128是商业应用中最常见的选择。它在安全性和速度之间提供了一个平衡。国家政府通常使用AES-192和AES-256来获得最大的安全性。如果我们想有一个额外的安全级别,我们可以使用AES-256。
The quantum computers do pose a threat of being able to reduce the compute required for large keyspaces. Hence, having an AES-256 key would be more future-proof, although as of now, they’re out of the reach of any threat actors of commercial applications.
量子计算机确实构成了一种威胁,能够减少大型密钥空间所需的计算量。因此,拥有AES-256密钥会更有未来性,尽管到现在为止,它们还不在任何商业应用的威胁者的范围内。
3.2. Entropy
3.2. 熵
Entropy refers to randomness in the key. If the generated key isn’t random enough and has some co-relation with being time-dependent, machine-dependent, or a dictionary word, for example, it becomes vulnerable. An attacker would be able to narrow down the key search space, robbing the strength of AES. Hence, it’s of utmost importance that the keys are truly random.
熵是指钥匙的随机性。如果生成的密钥不够随机,并且与随时间变化、随机器变化或随字典变化的单词等有一定的关联,那么它就会变得脆弱。攻击者将能够缩小密钥的搜索空间,抢夺AES的强度。因此,最重要的是,密钥必须是真正随机的。
4. Generating AES Keys
4.生成AES密钥
Now, armed with the guidelines for generating an AES key, let’s see the various approaches to generating them.
现在,有了生成AES密钥的指南,让我们看看生成密钥的各种方法。
For all the code snippets, we define our cipher as:
对于所有的代码片断,我们将我们的密码定义为。
private static final String CIPHER = "AES";
4.1. Random
4.1.Random(随机)
Let’s use the Random class in Java to generate the key:
让我们使用Java中的Random类来生成密钥。
private static Key getRandomKey(String cipher, int keySize) {
byte[] randomKeyBytes = new byte[keySize / 8];
Random random = new Random();
random.nextBytes(randomKeyBytes);
return new SecretKeySpec(randomKeyBytes, cipher);
}
We create a byte array of desired key size and fill it with random bytes obtained from random.nextBytes(). The random byte array is then used to create a SecretKeySpec.
我们创建一个所需密钥大小的字节数组,并用从random.nextBytes()获得的随机字节填充它。然后,这个随机字节数组被用来创建一个SecretKeySpec。
The Java Random class is a Pseudo-Random Number Generator (PRNG), also known as Deterministic Random Number Generator (DRNG). This means it’s not truly random. The sequence of random numbers in a PRNG can be completely determined based on its seed. Java doesn’t recommend using Random for cryptographic applications.
Java Random 类是一个伪随机数发生器(PRNG),也被称为确定性随机数发生器(DRNG)。这意味着它不是真正的随机。PRNG中的随机数序列可以根据其种子完全确定。Java不建议在加密应用中使用Random。
With that said, NEVER use Random for generating keys.
也就是说,千万不要使用Random来生成密钥。
4.2. SecureRandom
4.2.安全随机(SecureRandom)
We’ll now use SecureRandom class in Java to generate the key:
现在我们将使用Java中的SecureRandom类来生成密钥。
private static Key getSecureRandomKey(String cipher, int keySize) {
byte[] secureRandomKeyBytes = new byte[keySize / 8];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(secureRandomKeyBytes);
return new SecretKeySpec(secureRandomKeyBytes, cipher);
}
Similar to the previous example, we instantiate a byte array of the desired key size. Now, instead of using Random, we use SecureRandom to generate the random bytes for our byte array. SecureRandom is recommended by Java for generating a random number for cryptographic applications. It minimally complies with FIPS 140-2, Security Requirements for Cryptographic Modules.
与之前的例子类似,我们实例化了一个所需密钥大小的字节数组。现在,我们不再使用Random,而是使用SecureRandom来为我们的字节数组生成随机字节。SecureRandom被Java推荐用于为加密应用程序生成随机数。它在最小程度上符合FIPS 140-2,加密模块的安全要求。
Clearly, in Java, SecureRandom is the de-facto standard for obtaining randomness. But is it the best way to generate keys? Let’s move on to the next approach.
显然,在Java中,SecureRandom是获得随机性的事实标准。但它是生成密钥的最佳方式吗?让我们继续讨论下一个方法。
4.3. KeyGenerator
4.3.密钥生成器
Next, let’s generate a key using the KeyGenerator class:
接下来,让我们使用KeyGenerator 类来生成一个密钥。
private static Key getKeyFromKeyGenerator(String cipher, int keySize) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(cipher);
keyGenerator.init(keySize);
return keyGenerator.generateKey();
}
We get an instance of KeyGenerator for the cipher we’re working with. We then initialize the keyGenerator object with the desired keySize. Finally, we invoke the generateKey method to generate our secret key. So, how’s it different from the Random and SecureRandom approaches?
我们为我们正在使用的密码获得一个KeyGenerator的实例。然后我们用所需的keySize初始化keyGenerator对象。最后,我们调用generateKey方法来生成我们的秘密密钥。那么,它与Random和SecureRandom方法有何不同?
There are two crucial differences worth highlighting.
有两个关键的区别值得强调。。
For one, neither the Random nor SecureRandom approach can tell whether we’re generating keys of the right sizes as per the Cipher specification. It’s only when we go for encryption that we’ll encounter exceptions if the keys are of an unsupported size.
首先,无论是Random还是SecureRandom方法都无法判断我们是否按照Cipher规范生成了正确大小的密钥。只有当我们去加密时,如果密钥的大小不受支持,我们才会遇到异常。
Using SecureRandom with invalid keySize throws an exception when we initialize the cipher for encryption:
使用SecureRandom与无效的keySize在我们初始化加密的密码时抛出一个异常。
encrypt(plainText, getSecureRandomKey(CIPHER, 111));
java.security.InvalidKeyException: Invalid AES key length: 13 bytes
at java.base/com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:90)
at java.base/com.sun.crypto.provider.GaloisCounterMode.init(GaloisCounterMode.java:321)
at java.base/com.sun.crypto.provider.CipherCore.init(CipherCore.java:592)
at java.base/com.sun.crypto.provider.CipherCore.init(CipherCore.java:470)
at java.base/com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:322)
at java.base/javax.crypto.Cipher.implInit(Cipher.java:867)
at java.base/javax.crypto.Cipher.chooseProvider(Cipher.java:929)
at java.base/javax.crypto.Cipher.init(Cipher.java:1299)
at java.base/javax.crypto.Cipher.init(Cipher.java:1236)
at com.baeldung.secretkey.Main.encrypt(Main.java:59)
at com.baeldung.secretkey.Main.main(Main.java:51)
Using KeyGenerator, on the other hand, fails during key generation itself, allowing us to handle it more appropriately:
另一方面,使用KeyGenerator,在密钥生成过程中会失败,允许我们更恰当地处理它。
encrypt(plainText, getKeyFromKeyGenerator(CIPHER, 111));
java.security.InvalidParameterException: Wrong keysize: must be equal to 128, 192 or 256
at java.base/com.sun.crypto.provider.AESKeyGenerator.engineInit(AESKeyGenerator.java:93)
at java.base/javax.crypto.KeyGenerator.init(KeyGenerator.java:539)
at java.base/javax.crypto.KeyGenerator.init(KeyGenerator.java:516)
at com.baeldung.secretkey.Main.getKeyFromKeyGenerator(Main.java:89)
at com.baeldung.secretkey.Main.main(Main.java:58)
The other key difference is the default use of SecureRandom. The KeyGenerator class is part of Java’s crypto package javax.crypto, which ensures the usage of SecureRandom for randomness. We can see the definition of the init method in the KeyGenerator class:
另一个关键的区别是对SecureRandom的默认使用。KeyGenerator类是Java的加密包javax.crypto的一部分,它确保使用SecureRandom进行随机性。我们可以看到init方法在KeyGenerator类中的定义。
public final void init(int keysize) {
init(keysize, JCAUtil.getSecureRandom());
}
Hence, using a KeyGenerator as a practice ensures we never use a Random class object for key generation.
因此,使用KeyGenerator的做法可以确保我们从不使用Random类对象来生成密钥。
4.4. Password-Based Key
4.4.基于密码的钥匙
So far, we’ve been generating keys from random and not-so-human-friendly byte arrays. Password-Based Key (PBK) offers us the ability to generate a SecretKey based on a human-readable password:
到目前为止,我们一直在从随机的、不那么人性化的字节数组中生成密钥。基于密码的密钥(PBK)为我们提供了基于人类可读密码生成SecretKey的能力。
private static Key getPasswordBasedKey(String cipher, int keySize, char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] salt = new byte[100];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password, salt, 1000, keySize);
SecretKey pbeKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(pbeKeySpec);
return new SecretKeySpec(pbeKey.getEncoded(), cipher);
}
We’ve got quite a few things going on here. Let’s break it down.
我们这里有相当多的事情要做。让我们把它分解一下。
We start with our human-readable password. This is a secret and must be protected. The password guidelines must be followed, such as a minimum length of 8 characters, the use of special characters, the combination of uppercase and lowercase letters, digits, and so on. Additionally, OWASP guidelines suggest checking against already exposed passwords.
我们从人类可读的密码开始。这是个秘密,必须加以保护。必须遵循密码准则,如至少8个字符的长度,使用特殊字符,大写和小写字母、数字的组合,等等。此外,OWASP指南建议对照已经暴露的密码进行检查。
A user-friendly password doesn’t have enough entropy. Hence, we add additional randomly generated bytes called a salt to make it harder to guess. The minimum salt length should be 128 bits. We used SecureRandom to generate our salt. The salt isn’t a secret and is stored as plaintext. We should generate salt in pairs with each password and not use the same salt globally. This’ll protect from Rainbow Table attacks, which use lookups from a precomputed hash table for cracking the passwords.
一个用户友好的密码并没有足够的熵。因此,我们添加了额外的随机生成的字节,称为salt,以使其更难猜测。 最小的盐长度应该是128比特。我们使用SecureRandom来生成我们的盐。盐并不是一个秘密,而是以明文形式存储。我们应该与每个密码成对地生成盐,而不是在全球范围内使用相同的盐。这将防止彩虹表攻击,这种攻击使用预先计算的哈希表的查询来破解密码。
The iteration count is the number of times the secret generation algorithm applies the transformation function. It should be as large as feasible. The minimum recommended iteration count is 1,000. A higher iteration count increases the complexity for the attacker while performing a brute-force check for all possible passwords.
迭代次数是指秘密生成算法应用转换函数的次数。它应该是尽可能大的。推荐的最小迭代次数为1,000。更高的迭代次数会增加攻击者的复杂性,同时对所有可能的密码进行暴力检查。
The key size is the same we discussed earlier, which can be 128, 192, or 256 for AES.
密钥大小与我们前面讨论的一样,对于AES来说,可以是128、192或256。
We’ve wrapped all the four elements discussed above into a PBEKeySpec object. Next, using the SecretKeyFactory, we get an instance of PBKDF2WithHmacSHA256 algorithm to generate the key.
我们已经将上面讨论的所有四个元素包装成一个PBEKeySpec对象。接下来,使用SecretKeyFactory,我们得到一个PBKDF2WithHmacSHA256算法的实例来生成密钥。
Finally, invoking generateSecret with the PBEKeySpec, we generate a SecretKey based on a human-readable password.
最后,用PBEKeySpec调用generateSecret,我们生成一个基于人类可读密码的SecretKey。
5. Conclusion
5.总结
There are two primary bases for generating a key. It could be a random key or a key based on a human-readable password. We’ve discussed three approaches to generating a random key. Among them, KeyGenerator provides true randomness and also offers checks and balances. Hence, KeyGenerator is a better option.
有两个主要的基础来生成一个密钥。它可以是一个随机密钥或基于人类可读密码的密钥。我们已经讨论了生成随机密钥的三种方法。其中,KeyGenerator提供了真正的随机性,也提供了检查和平衡。因此,KeyGenerator是一个更好的选择。
For a key based on a human-readable password, we can use SecretKeyFactory along with a salt generated using SecureRandom and high iteration count.
对于基于人类可读密码的密钥,我们可以使用SecretKeyFactory以及使用SecureRandom和高迭代计数生成的盐。
As always, the complete code is available over on GitHub.
一如既往,完整的代码可在GitHub上获得。