Java - 使用 HMACSHA256 作为 PRF 的 PBKDF2

Posted

技术标签:

【中文标题】Java - 使用 HMACSHA256 作为 PRF 的 PBKDF2【英文标题】:Java - PBKDF2 with HMACSHA256 as the PRF 【发布时间】:2012-02-27 04:21:09 【问题描述】:

我的任务是为我们的项目创建一个登录 API,我应该使用带有 HMACSHA256 的 PBKDF2 作为 PRF。使用 MD5 对纯文本密码进行哈希处理,然后将其输入 PBKDF2 以生成派生密钥。问题是,我无法获得与项目文档告诉我的相同的输出。

这是 PBKDF2 的 Java 实现:

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, "HmacSHA256" );
        Mac prf = Mac.getInstance( "HmacSHA256" );
        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 () 


我使用在这里找到的测试向量 PBKDF2-HMAC-SHA2 test vectors 来验证实现的正确性,并且全部检查出来。我不知道为什么我不能使用 MD5 哈希密码获得相同的结果。

参数:

Salt: 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
Iterations Count: 1000
DKLen: 16 (128-bit derived key)

使用“foobar”作为明文密码,预期结果为:

PWHash = MD5(PlaintextPassword) = 3858f62230ac3c915f300c664312c63f
PWKey = PBKDF2(PWHash, Salt, IterationsCount, DKLen) = 33C37758EFA6780C5E52FAB3B50F329C

我得到了什么:

PWHash = 3858f62230ac3c915f300c664312c63f
PWKey = 0bd0c7d8339df2c66ce4b6e1e91ed3f1

【问题讨论】:

为什么你确定“预期”的结果是正确的? 我不知道它们是否是,但项目文档中的其余身份验证过程 + 客户端-服务器通信在其示例中使用此预期结果来创建与 AES-ECB 进行通信的加密消息与服务器,所以我认为它是正确的。 您的方法将字节数组作为密码参数。您如何将文本“foobar”转换为字节? 你可能会注意到 4096 = 0x1000 @androidNoob: 0x10004096 一样是整数。 【参考方案1】:

迭代次数应该是 4096,而不是 1000。

【讨论】:

【参考方案2】:

int l 的生成似乎是错误的。您已指定 dkLenhLen 之间的最大值,但规范说 l = CEIL (dkLen / hLen) 带有

CEIL (x) 是“天花板”函数,即大于或等于 x 的最小整数。

我认为l 更准确地定义为l = (int)Math.ceil( (double)dkLen / (double)hLen )

【讨论】:

以上是关于Java - 使用 HMACSHA256 作为 PRF 的 PBKDF2的主要内容,如果未能解决你的问题,请参考以下文章

微信小程序 HMACSHA256 哈希加密

hmac sha256和sha256的区别

JWT Web 令牌加密 - SecurityAlgoritms.HmacSha256 与 SecurityAlgoritms.HmacSha256Signature

HmacSHA256 算法

HmacSHA256签名加Base64编码加URL编码

如何使用Python3生成HmacSHA256加密