Java密码加密

时间:2008-12-16 18:49:31

标签: java encryption passphrase

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

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

7 个答案:

答案 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. 从用户那里获取密码和明文,并将它们转换为字节数组。
  2. 生成安全随机salt
  3. 将salt附加到密码并计算其加密hash。重复这几次。
  4. 使用生成的哈希作为initialization vector和/或秘密key加密明文。
  5. 保存盐和生成的密文。

答案 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);
    }
}