分享一个 Java 中非常糟糕的 API 设计

237 天前
 0xD800

python 代码如下:

hashlib.pbkdf2_hmac('sha1', bytes.fromhex('******'), bytes.fromhex('00000000000000000000000000000000'), 64000, 32)

需要用 Java 实现一版,但是发现 java 的 password 参数要传 char[],然后底层转 bytes ,代码如下:

// com.sun.crypto.provider.PBKDF2KeyImpl#getPasswordBytes
private static byte[] getPasswordBytes(char[] passwd) {
    CharBuffer cb = CharBuffer.wrap(passwd);
    ByteBuffer bb = UTF_8.encode(cb);

    int len = bb.limit();
    byte[] passwdBytes = new byte[len];
    bb.get(passwdBytes, 0, len);
    bb.clear().put(new byte[len]);

    return passwdBytes;
}

真无语了,这么写相当于密码只能用字符串转 char[]了,不能用二进制的 password ,如果 password 是非法字符序列就个屁了。

/**
 * hashlib.pbkdf2_hmac('sha1', password, salt, iterations, key_length)
 */
private static byte[] generateKey(byte[] password, byte[] salt, int iterationCount, int keyLength) throws Exception {
    // 由于 password 非字符序列导致 new String 后数据失真,底层无法还原会原始 bytes 。
    char[] encoded = new String(password, StandardCharsets.UTF_8).toCharArray();

    // 创建密钥规范
    KeySpec spec = new PBEKeySpec(encoded, salt, iterationCount, keyLength * 8);


    // 使用 PBKDF2WithHmacSHA1 算法创建密钥工厂
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

    // 生成密钥
    SecretKey secretKey = factory.generateSecret(spec);
    return secretKey.getEncoded();
}

这是一个微信聊天记录数据库算法。。

5336 次点击
所在节点    Java
45 条回复
orangie
237 天前
搜了一下 PBKDF2 的文档: https://www.ietf.org/rfc/rfc2898.txt 其中 password 是一个 string ,那么实现的时候使用 String 对应的 char[]应该是没有问题的。另外既然名称是 password ,使用用户可见的字符来表示也是更合理的。使用字节的密码应该叫做 key 。
lsk569937453
237 天前
java 的类库这么多,找一个符合你要求的就可以了。
pocketz
237 天前
那么为什么不直接将 byte[] 转为 char[] 呢
BiChengfei
237 天前
hashlib.pbkdf2_hmac('sha1', password, salt, iterations, key_length)
我理解你是想使用 sha1 算法对 password 进行 hash 运算,salt 表示加盐,iterations 表示计算次数,key_length 表示 hash 后的长度
非法字符序列,举个例子啊,是指不在 UTF_8 中的字符吗,上来就是”糟糕“、”无语“、”屁“,已 block
0xD800
237 天前
@orangie 请教下 java 有没有用 key 的类 0.0
0xD800
237 天前
@BiChengfei Hex: AC3C90034CF34804A7859144129CA9AEB6B90D07CA874172A374F2000000CAE5 ,麻烦指导一下用 jdk 的 API 算一下 key 出来呗
0xD800
237 天前
@lsk569937453 你说得对 0.0
siweipancc
237 天前
看得我一脸懵逼,啥跟啥这是
0xD800
237 天前
@siweipancc 一个计算微信聊天记录数据库密钥的算法,来源: https://mp.weixin.qq.com/s/4DbXOS5jDjJzM2PN0Mp2JA
orangie
237 天前
@0xD800 按照文档标准设计的应该用字符 password ,不会用字节 key 。PBKDF2 这个名字就是 Password-Based Key Derivation Function 。想要用字节的话,合理的方式不是换个库,而是换个加密方案。如果非要用 PBKDF 系列,那么可以自己把第三方的类库复制一份魔改,注意 license 。
InkStone
237 天前
这是 password 不是 crypto key 。你传个二进制数据进去本来就不符合用法……

你要传二进制用普通的 hmac 别用 pbkdf_hmac 啊
gadfly3173
237 天前
我不懂 python 也不懂 c ,但是按照 python 源码里这个实现,char *也不能用来放非字符吧?
https://github.com/python/cpython/blob/8b56d82c59c2983b4292a7f506982f2cab352bb2/Modules/_hashopenssl.c#L1323C59-L1323C67
0xD800
237 天前
@orangie 真的非常感激你的回复,因为这个是解密微信的本地聊天记录数据库,所以没办法换加密方案,只能换个库或者自己实现这个 SPI 了。
orangie
237 天前
@gadfly3173 C 语言字符和字节的没有区别,C 的 char 完全就是其它语言中的 byte 而没有真正的字符 char 。C 里 char 存的就是字节,完全不限制字符是否合法。
0xD800
237 天前
@gadfly3173 感谢回复,python 的 hashlib 中 password 是直接传入 bytes 的,不是传入 string ,因此没有转换的问题。而 java 是传入 UTF8 编码的字符数组,所以出现了问题,JAVA 底层还是把 char 转成了 byte[],我觉得这个设计不太合理,应该支持直接传入 byte[]好一些
0xD800
237 天前
@InkStone 微信用的是这个 我也没办法. 我要解密就必须得用 单纯吐槽下这个 API 而已
0xD800
237 天前
@orangie 刚刚看了规范里面确实 P 和 S 都是 string ,这么说怪不得 Java 了,只能说其他语言太灵活了。
```text
Input: P password, an octet string
S salt, an octet string
```
AoEiuV020JP
237 天前
以前就听说有一种加密学防破解的手段是使用非标准加密算法,一直没见过实际应用,你这个就是了,
合理怀疑 python 这个 pbkdf2_hmac 不是因为设计优秀才支持 bytes 的,而是 python 没有 chat[]?
0xD800
237 天前
@AoEiuV020JP 哈哈 但是 python 有字符串啊,标准就是传字符串,java 设计出 char[]可能是防止字符串在常量池中,被扫出来吧
orangie
237 天前
@0xD800 不对。这里说的 octet string 就是字节串的意思,而文档中的如下段落才是真正使用 char[]的原因:

Throughout this document, a password is considered to be an octet
string of arbitrary length whose interpretation as a text string is
unspecified. In the interest of interoperability, however, it is
recommended that applications follow some common text encoding rules.
ASCII and UTF-8 [27] are two possibilities. (ASCII is a subset of
UTF-8.)

这个 password 是字节串,但是仍然推荐使用符合 ASCII 或者 utf-8 等编码的兼容的表示方法来保持兼容性。我也是现学的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1036966

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX