Java密码加密

我正在尝试学习如何使用Java进行基于密码的加密。 我在网上找到几个例子,但Stack Overflow上没有(还)。 这些例子对我来说有点解释,特别是关于算法选择。 似乎有很多传递字符串来说明要使用什么算法,但很少有关于字符串来自何处及其含义的文档。 并且似乎不同的算法可能需要KeySpec类的不同实现,所以我不确定哪些算法可以使用我正在查看的PBEKeySpec类。 此外,这些示例似乎都过时了,许多要求您获得旧的加密包,以前不是JDK的一部分,甚至是第三方实现。

有人可以直接介绍我需要做什么来实现加密(字符串数据,字符串密码)和解密(字节[]数据,字符串密码)?

我会对从论坛提供或接受安全相关的建议持谨慎态度……细节非常复杂,并且经常会过时。

话虽如此,我认为Sun的Java Cryptography Architecture(JCA)参考指南是一个很好的起点。 查看随附的代码示例,说明基于密码的加密(PBE)。

顺便说一句,标准的JRE只为PBE提供了一些开箱即用的选项(“PBEWithMD5AndDES”就是其中之一)。 要获得更多选择,您需要“强加密包”或某些第三方提供商,如Bouncy Castle 。 另一种方法是使用JRE中提供的散列和密码算法实现您自己的PBE。 您可以通过这种方式使用SHA-256和AES-128实现PBE( 样本加密/解密方法 )。

简而言之,PBE的加密方法可能涉及以下步骤:

  1. 从用户获取密码和明文,并将它们转换为字节数组。
  2. 生成安全的随机盐 。
  3. 将salt附加到密码并计算其加密哈希值 。 重复这几次。
  4. 使用生成的哈希作为初始化向量和/或密钥加密明文。
  5. 保存盐和生成的密文。

使用RFC2898从密码生成密钥。 据我所知,这不包含在JRE或JCE中,但它包含在JBoss ,Oracle和WebSphere等J2EE服务器中。 它也包含在.NET基类库( Rfc2898DeriveBytes )中。

Java中有一些LGPL实现,但快速看一下, 这看起来有点复杂。 还有一个很好的JavaScript版本 。 (我制作了那个的修改版本并将其打包为Windows脚本组件)

由于缺乏适当的许可证,我从Mattias Gartner打包了一些代码。 这是完整的代码。 简短,简单,易懂。 它是根据MS Public License许可的 。

// PBKDF2.java // ------------------------------------------------------------------ // // RFC2898 PBKDF2 in Java. The RFC2898 defines a standard algorithm for // deriving key bytes from a text password. This is sometimes // abbreviated "PBKDF2", for Password-based key derivation function #2. // // There's no RFC2898-compliant PBKDF2 function in the JRE, as far as I // know, but it is available in many J2EE runtimes, including those from // JBoss, IBM, and Oracle. // // It's fairly simple to implement, so here it is. // // Created Sun Aug 09 01:06:57 2009 // // last saved: // Time-stamp: <2009-August-09 02:19:50> // ------------------------------------------------------------------ // // code thanks to Matthias Gartner // // ------------------------------------------------------------------ package cheeso.examples; import java.security.NoSuchAlgorithmException; import java.security.InvalidKeyException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public class PBKDF2 { public static byte[] deriveKey( byte[] password, byte[] salt, int iterationCount, int dkLen ) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException { SecretKeySpec keyspec = new SecretKeySpec( password, "HmacSHA1" ); Mac prf = Mac.getInstance( "HmacSHA1" ); prf.init( keyspec ); // Note: hLen, dkLen, l, r, T, F, etc. are horrible names for // variables and functions in this day and age, but they // reflect the terse symbols used in RFC 2898 to describe // the PBKDF2 algorithm, which improves validation of the // code vs. the RFC. // // dklen is expressed in bytes. (16 for a 128-bit key) int hLen = prf.getMacLength(); // 20 for SHA1 int l = Math.max( dkLen, hLen); // 1 for 128bit (16-byte) keys int r = dkLen - (l-1)*hLen; // 16 for 128bit (16-byte) keys byte T[] = new byte[l * hLen]; int ti_offset = 0; for (int i = 1; i <= l; i++) { F( T, ti_offset, prf, salt, iterationCount, i ); ti_offset += hLen; } if (r < hLen) { // Incomplete last block byte DK[] = new byte[dkLen]; System.arraycopy(T, 0, DK, 0, dkLen); return DK; } return T; } private static void F( byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex ) { final int hLen = prf.getMacLength(); byte U_r[] = new byte[ hLen ]; // U0 = S || INT (i); byte U_i[] = new byte[S.length + 4]; System.arraycopy( S, 0, U_i, 0, S.length ); INT( U_i, S.length, blockIndex ); for( int i = 0; i < c; i++ ) { U_i = prf.doFinal( U_i ); xor( U_r, U_i ); } System.arraycopy( U_r, 0, dest, offset, hLen ); } private static void xor( byte[] dest, byte[] src ) { for( int i = 0; i < dest.length; i++ ) { dest[i] ^= src[i]; } } private static void INT( byte[] dest, int offset, int i ) { dest[offset + 0] = (byte) (i / (256 * 256 * 256)); dest[offset + 1] = (byte) (i / (256 * 256)); dest[offset + 2] = (byte) (i / (256)); dest[offset + 3] = (byte) (i); } // ctor private PBKDF2 () {} } 

在Cheeso上面非常有用的答案中,有一个糟糕的性能错误。

这条线

 int l = Math.max( dkLen, hLen) 

不应该计算最大值,而是分割的上限,所以

 int l = ((dkLen - 1) / hLen) + 1; // >= ceil(dkLen / hLen), == for dkLen =>1 

这将使16字节密钥的计算速度提高20倍。

您需要一个加密库,它将告诉您如何设置它。
我碰巧喜欢bouncycastle.org的内容。 您可以在这里找到它们的方法5.1示例中引用的DES是它们提供的加密之一。 实际字符串的含义取决于提供者。 基本上你加载库。

 Security.addProvider(new BouncyCastleProvider()); 

然后使用JCE接口做任何你想做的事情:

  keyGen = KeyGenerator.getInstance("DES", "BC"); 

Java为您处理库和接口的绑定,您不必这样做。 如果您有任何疑问,我会更乐意解释更多。 不幸的是,此刻我患有“我不记得我是如何学习它”的疾病,所以请随意问。

您可以使用哈希算法(如果需要,多次)从密码短语中获取一些可用作密钥的原始数据(如果算法需要,则为初始化向量)。

然后,您可以将该密钥与任何对称算法一起使用 – 例如3DES-CBC或AES-CBC(DES现在被认为已过时)。

根据您提供的JCE,您可以使用不同的算法,但AES可能就是您想要的。 然而,选择算法以及如何使用它在某种程度上是一个宗教问题,你不应该尝试自己动手,甚至尝试使用标准算法构建自己的加密方案。 如果你没有研究它,你几乎肯定会弄错,也许即使你有。

如果安全对您来说非常重要,那么您正在考虑加密,那么您应该考虑一下安全工程书,如Bruce Schneier的应用密码学或Ross Anderson的安全工程 – 有很多实现方面的缺陷。 例如,首先使用密码作为密钥并不是一个好主意,因为它实际上减少了密钥的大小。

您还可以查看其他人已经完成的设计,IETF有很多设计,例如: http : //tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha1-00

如果您不需要解密密码短语,只需根据密码/密码短语生成加密密钥,则可以使用JCE Cipher和MessageDigest类来实现PKCS#5标准 。

在加密期间将字符串转换为字节数组。 解密后转换回字符串。

 /** * Creates a cipher for encryption or decryption. * * @param algorithm PBE algorithm like "PBEWithMD5AndDES" or "PBEWithMD5AndTripleDES". * @param mode Encyrption or decyrption. * @param password Password * @param salt Salt usable with algorithm. * @param count Iterations. * @return Ready initialized cipher. * @throws GeneralSecurityException Error creating the cipher. */ private static Cipher createCipher(final String algorithm, final int mode, final char[] password, final byte[] salt, final int count) throws GeneralSecurityException { final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm); final PBEKeySpec keySpec = new PBEKeySpec(password); final SecretKey key = keyFactory.generateSecret(keySpec); final Cipher cipher = Cipher.getInstance(algorithm); final PBEParameterSpec params = new PBEParameterSpec(salt, count); cipher.init(mode, key, params); return cipher; } /** * Encrypts some data based on a password. * @param algorithm PBE algorithm like "PBEWithMD5AndDES" or "PBEWithMD5AndTripleDES" * @param data Data to encrypt * @param password Password * @param salt Salt usable with algorithm * @param count Iterations. * @return Encrypted data. */ public static byte[] encryptPasswordBased(final String algorithm, final byte[] data, final char[] password, final byte[] salt, final int count) { Validate.notNull(algorithm); Validate.notNull(data); Validate.notNull(password); Validate.notNull(salt); try { final Cipher cipher = createCipher(algorithm, Cipher.ENCRYPT_MODE, password, salt, count); return cipher.doFinal(data); } catch (final Exception ex) { throw new RuntimeException("Error encrypting the password!", ex); } } /** * Decrypts some data based on a password. * @param algorithm PBE algorithm like "PBEWithMD5AndDES" or "PBEWithMD5AndTripleDES" * @param encryptedData Data to decrypt * @param password Password * @param salt Salt usable with algorithm * @param count Iterations. * @return Encrypted data. */ public static byte[] decryptPasswordBased(final String algorithm, final byte[] encryptedData, final char[] password, final byte[] salt, final int count) { Validate.notNull(algorithm); Validate.notNull(encryptedData); Validate.notNull(password); Validate.notNull(salt); try { final Cipher cipher = createCipher(algorithm, Cipher.DECRYPT_MODE, password, salt, count); return cipher.doFinal(encryptedData); } catch (final Exception ex) { throw new RuntimeException("Error decrypting the password!", ex); } }