6

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

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

4

6 回答 6

11

我会谨慎地从论坛提供或接受与安全相关的建议……细节非常复杂,而且经常很快就过时了。

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

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

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

  1. 从用户那里获取密码和明文,并将它们转换为字节数组。
  2. 生成一个安全的随机salt
  3. 将盐附加到密码并计算其加密哈希。重复多次。
  4. 使用生成的哈希作为初始化向量和/或密钥加密明文。
  5. 保存盐和生成的密文。
于 2008-12-17T06:33:17.570 回答
4

使用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 () {}

}
于 2009-08-09T07:34:25.867 回答
3

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

线

int l = Math.max( dkLen, hLen)

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

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

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

于 2012-02-22T08:15:21.713 回答
2

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

Security.addProvider(new BouncyCastleProvider());

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

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

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

于 2008-12-16T21:00:41.593 回答
1

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

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

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

如果安全性对您来说非常重要以至于您正在考虑加密,那么您还应该考虑查看安全工程书籍,例如 Bruce Schneier 的 Applied Cryptography 或 Ross Anderson 的 Security Engineering - 存在很多实现陷阱。例如,首先使用密码作为密钥并不是一个好主意,因为它从本质上减小了密钥的大小。

您还可以查看其他人所做的设计,IETF 有很多,例如: https ://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha1-00

于 2008-12-16T21:51:01.977 回答
0

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

于 2008-12-22T23:05:53.160 回答