题
我正在尝试学习如何使用 Java 进行基于密码的加密。我在网上找到了几个例子,但在 Stack Overflow 上还没有找到。这些例子对我来说解释起来有点简单,特别是关于算法选择。似乎有很多传递字符串来说明要使用什么算法,但很少有文档说明字符串的来源及其含义。而且似乎不同的算法可能需要 KeySpec 类的不同实现,因此我不确定哪些算法可以使用我正在查看的 PBEKeySpec 类。此外,所有示例似乎都有点过时,许多示例要求您获取以前不属于 JDK 一部分的旧加密包,甚至需要第三方实现。
有人可以简单介绍一下我需要做什么来实现加密(字符串数据,字符串密码)和解密(字节[]数据,字符串密码)吗?
解决方案
我会谨慎地在论坛上提供或接受与安全相关的建议......具体细节相当复杂,而且往往很快就会过时。
话虽如此,我认为孙 Java 加密体系结构 (JCA) 参考指南 是一个很好的起点。查看随附的 代码示例 说明基于密码的加密 (PBE)。
顺便说一句,标准 JRE 只为 PBE 提供了几个开箱即用的选项(“PBEWithMD5AndDES”就是其中之一)。要获得更多选择,您需要“强加密包”或一些第三方提供商,例如 充气城堡. 。另一种选择是使用 JRE 中提供的哈希和密码算法来实现您自己的 PBE。您可以通过这种方式使用 SHA-256 和 AES-128 实现 PBE(示例加密/解密方法).
简单来说,PBE的加密方法可以包括以下步骤:
其他提示
使用 RFC2898 从密码生成密钥。据我所知,这不包含在 JRE 或 JCE 中,但它包含在 J2EE 服务器中,例如 老板, 、甲骨文和 WebSphere. 。它还包含在 .NET 基类库中(Rfc2898DeriveBytes).
Java 中有一些 LGPL 实现,但快速浏览一下 这个 看起来有点过于复杂。还有一个不错的 JavaScript 版本. 。(我制作了 该版本的修改版本 并将其打包为 Windows 脚本组件)
由于缺乏适当许可证的良好实施,我从 Mattias Gartner 打包了一些代码。这是完整的代码。简短、简单、容易理解。它已获得许可 微软公共许可证.
// 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)
不应caculate最大,但分割的天花板上,这样
int l = ((dkLen - 1) / hLen) + 1; // >= ceil(dkLen / hLen), == for dkLen =>1
这将通过20倍16个字节键加快计算。
您需要一个加密库,它会告诉你如何设置它。结果 我碰巧喜欢从bouncycastle.org的东西。你可以找到他们如何这里 在DES的均指在5.1例子,是他们所提供的加密中的一个。什么实际的字符串意味着,将取决于供应商。 基本上你加载库。
Security.addProvider(new BouncyCastleProvider());
然后的只有的使用JCE接口做任何你想要的:
keyGen = KeyGenerator.getInstance("DES", "BC");
Java的处理库,并为您的接口的绑定,你不必这样做。 我会更快乐,然后解释更多,如果您有任何问题。不幸的是,在我患的那一刻:“我不记得我是如何学习的病”,所以请随意问。
您可以使用散列算法(多次如有必要)从密码去(如果该算法要求一个+的初始化向量),你可以作为重点用一些原始数据。
然后,可以使用任何对称算法该键 - 例如3DES-CBC或AES-CBC(DES被认为是过时这些天)
。根据JCE您有可用您可以在您的处置有不同的算法,但AES可能是你想要的东西。算法以及如何正确地使用它的选择是有点宗教问题,但是,你将是不明智的尝试,并推出自己的,甚至试图打造自己的使用标准算法的一些加密方案。你几乎肯定会得到它错了,如果你还没有研究过它,也许即使你有。
如果安全是对你很重要,你正在考虑加密,那么你也应该考虑在寻找一个安全工程本书像应用密码学由布鲁斯或安全工程罗斯安德森 - 有很多实现的陷阱。例如,使用密码作为密钥不是首先造成多大的想法,因为它本质上减少了您的键的大小。
您也可以看看其他人做设计,有很多在IETF,例如: http://tools.ietf.org/ HTML /草案-麦格鲁-aead-AES-CBC-HMAC-sha1-00
如果您不需要解密密码,而只是基于口令/密码的加密密钥,您可以实现的 PKCS#5标准时,使用密码JCE和类消息摘要
在加密过程中转换你字符串的字节数组。转换回串解密之后。
/**
* 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);
}
}