创建基于密码的加密密钥
本文討論了創(chuàng)建基于密碼的加密PBE密鑰。
首先提醒您以前的要點–通常,在實際操作中,應(yīng)將PBE密鑰用作主密鑰,該主密鑰僅用于解鎖工作密鑰。 這具有三個主要優(yōu)點:
- 您可以有多個密碼,例如,托管的恢復(fù)密鑰,
- 您無需更改密碼即可更改密碼,
- 您可以更改工作密鑰,而不必強行更改密碼。
我將在以后的文章中討論在數(shù)據(jù)庫加密中使用工作密鑰。
使用PBKDF2WithHmacSHA1的基于密碼的加密密鑰生成
Java過去沒有創(chuàng)建PBE密鑰的標(biāo)準(zhǔn)方法。 各個密碼提供者提供了自己的生成器,但是識別和使用與您的密碼匹配的生成器是一個痛苦的過程。
Java 7對此進行了更改。現(xiàn)在,所有JCE實現(xiàn)中都提供了一種標(biāo)準(zhǔn)的密鑰生成算法。 它絕對可以用于生成AES密鑰。 我已經(jīng)看到了一個示例,該示例用于生成任意長度的密鑰,但是我無法復(fù)制該行為–這可能是非標(biāo)準(zhǔn)擴展。
該算法采用四個參數(shù)。 第一個是密鑰長度– AES密鑰使用128。 其他可能的值是192和256位。 第二個是迭代次數(shù)。 您的wifi路由器使用4096次迭代,但是現(xiàn)在很多人建議至少進行10,000次迭代。
第三個參數(shù)是“鹽”。 wifi路由器使用SSID,許多站點使用一個小文件,下面我將討論另一種方法。 鹽應(yīng)足夠大,以使熵大于密鑰長度。 例如,如果要使用128位密鑰,則應(yīng)該(至少)具有128位隨機二進制數(shù)據(jù)或大約22個隨機字母數(shù)字字符。
最后一個參數(shù)是密碼。 同樣,熵應(yīng)大于密鑰長度。 在Webapp中,密碼通常是由應(yīng)用服務(wù)器通過JNDI提供的。
最后,我們既需要密碼密鑰,又需要IV,而不僅僅是密碼密鑰。 缺乏IV或使用較弱的IV是不熟悉密碼學(xué)的人最常見的錯誤之一。 (請參閱: 不使用具有密碼塊鏈接模式的隨機初始化矢量 [owasp.org]。)一種常見的方法是生成隨機鹽,并將其添加到密文中以供解密期間使用,但是我將討論另一種使用密碼和鹽。
現(xiàn)在的代碼。 首先,我們了解如何從密碼和鹽創(chuàng)建密碼密鑰和IV。 (我們待會兒討論鹽。)
public class PbkTest {private static final Provider bc = new BouncyCastleProvider();private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(PbkTest.class.getName());private SecretKey cipherKey;private AlgorithmParameterSpec ivSpec;/*** Create secret key and IV from password.* * Implementation note: I've believe I've seen other code that can extract* the random bits for the IV directly from the PBEKeySpec but I haven't* been able to duplicate it. It might have been a BouncyCastle extension.* * @throws Exception*/public void createKeyAndIv(char[] password) throws SecurityException,NoSuchAlgorithmException, InvalidKeySpecException {final String algorithm = "PBKDF2WithHmacSHA1";final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);final int derivedKeyLength = 128;final int iterations = 10000;// create saltfinal byte[][] salt = feistelSha1Hash(createSalt(), 1000);// create cipher keyfinal PBEKeySpec cipherSpec = new PBEKeySpec(password, salt[0],iterations, derivedKeyLength);cipherKey = factory.generateSecret(cipherSpec);cipherSpec.clearPassword();// create IV. This is just one of many approaches. You do// not want to use the same salt used in creating the PBEKey.try {final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", bc);cipher.init(Cipher.ENCRYPT_MODE, cipherKey, new IvParameterSpec(salt[1], 0, 16));ivSpec = new IvParameterSpec(cipher.doFinal(salt[1], 4, 16));} catch (NoSuchPaddingException e) {throw new SecurityException("unable to create IV", e);} catch (InvalidAlgorithmParameterException e) {throw new SecurityException("unable to create IV", e);} catch (InvalidKeyException e) {throw new SecurityException("unable to create IV", e);} catch (BadPaddingException e) {throw new SecurityException("unable to create IV", e);} catch (IllegalBlockSizeException e) {throw new SecurityException("unable to create IV", e);}} }我們可以簡單地加載包含隨機二進制數(shù)據(jù)的文件,但是使用Feistel密碼可以使我們混合來自兩個來源的熵。
/*** Create salt. Two values are provided to support creation of both a cipher* key and IV from a single password.* * The 'left' salt is pulled from a file outside of the app context. this* makes it much harder for a compromised app to obtain or modify this* value. You could read it as classloader resource but that's not really* different from the properties file used below. Another possibility is to* load it from a read-only value in a database, ideally one with a* different schema than the rest of the application. (It could even be an* in-memory database such as H2 that contains nothing but keying material,* again initialized from a file outside of the app context.)* * The 'right' salt is pulled from a properties file. It is possible to use* a base64-encoded value but administration is a lot easier if we just take* an arbitrary string and hash it ourselves. At a minimum it should be a* random mix-cased string of at least (120/5 = 24) characters.* * The generated salts are equally strong.* * Implementation note: since this is for demonstration purposes a static* string in used in place of reading an external file.*/public byte[][] createSalt() throws NoSuchAlgorithmException {final MessageDigest digest = MessageDigest.getInstance("SHA1");final byte[] left = new byte[20]; // fall back to all zeroesfinal byte[] right = new byte[20]; // fall back to all zeroes// load value from file or database.// note: we use fixed value for demonstration purposes.final String leftValue = "this string should be read from file or database";if (leftValue != null) {System.arraycopy(digest.digest(leftValue.getBytes()), 0, left, 0,left.length);digest.reset();}// load value from resource bundle.final String rightValue = BUNDLE.getString("salt");if (rightValue != null) {System.arraycopy(digest.digest(rightValue.getBytes()), 0, right, 0,right.length);digest.reset();}final byte[][] salt = feistelSha1Hash(new byte[][] { left, right },1000);return salt;}使用資源束(在類路徑中可見)和從文件系統(tǒng)或數(shù)據(jù)庫加載的字符串的實際實現(xiàn)是:
/*** Create salt. Two values are provided to support creation of both a cipher* key and IV from a single password.* * The 'left' salt is pulled from a file outside of the app context. this* makes it much harder for a compromised app to obtain or modify this* value. You could read it as classloader resource but that's not really* different from the properties file used below. Another possibility is to* load it from a read-only value in a database, ideally one with a* different schema than the rest of the application. (It could even be an* in-memory database such as H2 that contains nothing but keying material,* again initialized from a file outside of the app context.)* * The 'right' salt is pulled from a properties file. It is possible to use* a base64-encoded value but administration is a lot easier if we just take* an arbitrary string and hash it ourselves. At a minimum it should be a* random mix-cased string of at least (120/5 = 24) characters.* * The generated salts are equally strong.* * Implementation note: since this is for demonstration purposes a static* string in used in place of reading an external file.*/public byte[][] createSalt() throws NoSuchAlgorithmException {final MessageDigest digest = MessageDigest.getInstance("SHA1");final byte[] left = new byte[20]; // fall back to all zeroesfinal byte[] right = new byte[20]; // fall back to all zeroes// load value from file or database.// note: we use fixed value for demonstration purposes.final String leftValue = "this string should be read from file or database";if (leftValue != null) {System.arraycopy(digest.digest(leftValue.getBytes()), 0, left, 0,left.length);digest.reset();}// load value from resource bundle.final String rightValue = BUNDLE.getString("salt");if (rightValue != null) {System.arraycopy(digest.digest(rightValue.getBytes()), 0, right, 0,right.length);digest.reset();}final byte[][] salt = feistelSha1Hash(new byte[][] { left, right },1000);return salt;}最后,我們可以通過兩種測試方法在實踐中看到它:
/*** Obtain password. Architectually we'll want good "separation of concerns"* and we should get the cipher key and IV from a separate place than where* we use it.* * This is a unit test so the password is stored in a properties file. In* practice we'll want to get it from JNDI from an appserver, or at least a* file outside of the appserver's directory.* * @throws Exception*/@Beforepublic void setup() throws Exception {createKeyAndIv(BUNDLE.getString("password").toCharArray());}/*** Test encryption.* * @throws Exception*/@Testpublic void testEncryption() throws Exception {String plaintext = BUNDLE.getString("plaintext");Cipher cipher = Cipher.getInstance(BUNDLE.getString("algorithm"), bc);cipher.init(Cipher.ENCRYPT_MODE, cipherKey, ivSpec);byte[] actual = cipher.doFinal(plaintext.getBytes());assertEquals(BUNDLE.getString("ciphertext"),new String(Base64.encode(actual), Charset.forName("UTF-8")));}/*** Test decryption.* * @throws Exception*/@Testpublic void testEncryptionAndDecryption() throws Exception {String ciphertext = BUNDLE.getString("ciphertext");Cipher cipher = Cipher.getInstance(BUNDLE.getString("algorithm"), bc);cipher.init(Cipher.DECRYPT_MODE, cipherKey, ivSpec);byte[] actual = cipher.doFinal(Base64.decode(ciphertext));assertEquals(BUNDLE.getString("plaintext"),new String(actual, Charset.forName("UTF-8")));}- 完整的源代碼可從http://code.google.com/p/invariant-properties-blog/source/browse/pbekey獲取 。
- 另請參閱: NIST SP 800-132,基于密碼的密鑰派生建議 ,第5.3節(jié)。
- 另請參閱: http : //stackoverflow.com/questions/2465690/pbkdf2-hmac-sha1/2465884#2465884 ,有關(guān)創(chuàng)建WPA2網(wǎng)絡(luò)主密鑰的討論。
翻譯自: https://www.javacodegeeks.com/2013/10/creating-password-based-encryption-keys.html
總結(jié)
以上是生活随笔為你收集整理的创建基于密码的加密密钥的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 稀硫酸除铁锈的化学方程式 稀硫酸除铁锈的
- 下一篇: 环比下降率怎么算公式 环比下降率如何算公