我正在尝试学习如何使用Java进行基于密码短语的加密。我在网上找到几个例子,但Stack Overflow上没有(还)。这些例子对我来说有点解释,特别是关于算法选择。似乎有许多传递字符串来说明要使用什么算法,但很少有关于字符串来自何处及其含义的文档。并且似乎不同的算法可能需要KeySpec类的不同实现,所以我不确定哪些算法可以使用我正在查看的PBEKeySpec类。此外,这些示例似乎都有点过时,许多要求您获得一个旧的加密包,以前不是JDK的一部分,甚至是第三方实现。
有人可以直接介绍我需要做些什么来实现加密(字符串数据,字符串密码)和解密(byte []数据,字符串密码)?
答案 0 :(得分:11)
我会对从论坛提供或接受与安全相关的建议持谨慎态度......细节非常复杂,并且经常会过时。
话虽如此,我认为Sun的Java Cryptography Architecture (JCA) Reference Guide是一个很好的起点。查看随附的code example说明基于密码的加密(PBE)。
顺便说一句,标准的JRE只为PBE提供了一些开箱即用的选项(“PBEWithMD5AndDES”就是其中之一)。要获得更多选择,您需要“强加密包”或某些第三方提供商,例如Bouncy Castle。另一种方法是使用JRE中提供的散列和密码算法实现您自己的PBE。您可以通过这种方式使用SHA-256和AES-128实现PBE(sample encrypt/decrypt methods)。
简而言之,PBE的加密方法可能涉及以下步骤:
答案 1 :(得分:4)
使用RFC2898从密码生成密钥。据我所知,这不包含在JRE或JCE中,但它包含在J2EE服务器中,如JBoss,Oracle和WebSphere。它也包含在.NET基类库(Rfc2898DeriveBytes)中。
Java中有一些LGPL实现,但快速查看this one看起来有点复杂。还有一个很好的javascript version。 (我制作了a modified version of that one并将其打包为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 () {}
}
答案 2 :(得分:3)
在Cheeso上面非常有用的答案中,存在一个糟糕的性能错误。
该行
int l = Math.max( dkLen, hLen)
不应计算最大值,而应计算除法的上限,所以
int l = ((dkLen - 1) / hLen) + 1; // >= ceil(dkLen / hLen), == for dkLen =>1
这将使16字节密钥的计算速度提高20倍。
答案 3 :(得分:2)
您需要一个加密库,它会告诉您如何设置它 我碰巧喜欢bouncycastle.org的内容。你可以找到他们如何here 5.1示例中引用的DES是它们提供的加密之一。实际字符串的含义取决于提供者。 基本上你加载了库。
Security.addProvider(new BouncyCastleProvider());
然后仅使用JCE接口做任何你想做的事情:
keyGen = KeyGenerator.getInstance("DES", "BC");
Java为您处理库和接口的绑定,您不必这样做。 如果您有任何问题,我会更乐意解释更多。不幸的是,此刻我患有“我不记得我是如何学习它”的疾病,所以请随意问。
答案 4 :(得分:1)
您可以使用哈希算法(必要时多次)从密码短语中获取一些可用作密钥的原始数据(如果算法需要,则为初始化向量)。
然后您可以将该密钥用于任何对称算法 - 例如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
答案 5 :(得分:0)
如果您不需要解密密码短语,只需根据密码/密码短语生成加密密钥,则可以使用JCE Cipher和MessageDigest类来实现PKCS#5 standard。
答案 6 :(得分:0)
在加密期间将字符串转换为字节数组。解密后转换回字符串。
/**
* 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);
}
}