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

63 天前
 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();
}

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

4919 次点击
所在节点    Java
45 条回复
geelaw
63 天前
@orangie #1 找到之后还需要认真阅读,这个 RFC 里面说的是 (p. 4)

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.)

并且 (p. 9)

Input: P password, an octet string

文档里没有定义什么是 octet string ,自然的理解是指 byte string ,即字节组成的序列。

一般编程概念里 string 也不一定非要是 text string ,单纯是指某个枚举类型(比如 byte 、char 、uint32 之类的)的序列罢了。

@0xD800 #17 这是误读。
0xD800
63 天前
@geelaw
@orangie
感谢两位指正,我确实没有详细阅读。
cslive
63 天前
不用 string 而用 char[]是为了安全
xubeiyou
63 天前
底层是这样的 但是基本都是有对应包装工具类- - 说实话 Java 这么受欢迎还是因为生态好- - 但是价格上不来也 TMD 是因为生态好
zzl22100048
63 天前
从 #1 给的文档点进去,password 是 octet string ,也是就 bytes
也就是 java 库没按规范实现
yippees
63 天前
s = "48656c6c6f20576f726c64"
b = bytes.fromhex(s)
print(b)
b'Hello World'

顾名思义不是一个基本的 hex-str 转 byte 数组吗。。。。48-》 0X48 。。。

主题的错误实现代码 回复的歪楼 看得真是太欢乐了,,,
都想借用楼主的标题了,,,
miaotaizi
63 天前
你就不能味给 AI 让 AI 帮你实现一版 JAVA 的吗
zzl22100048
63 天前
@yippees
op 说的是 sun 实现的代码问题
PBKDF2 强制用 utf8 做 password
0xD800
63 天前
@yippees 其实是你没看懂我上面的代码哦
0xD800
63 天前
@miaotaizi 上面的翻译代码其实是 AI 给我的,我 debug 看了,改动不大我都准备自己改下了
geelaw
63 天前
@BiChengfei #4 我觉得“糟糕”“无语”都还好吧,“屁”这个应该是楼主的错字,原文“个屁”实际上应该是 gěrpì(常写作:嗝儿屁),语气和意思差不多都是“死翘翘”。
0xD800
63 天前
@zzl22100048 是的👍
0xD800
63 天前
@geelaw 是的,输入法的问题,我想打的是嗝屁了
pkoukk
63 天前
吐槽一个 java 更离谱的 API javax.crypto.spec.DESKeySpec(byte[] key)
Creates a DESKeySpec object using the first 8 bytes in key as the key material for the DES key.

当年对接一个 java 服务的 API ,他让我用 des 签名,然后给了我一个 16 位字符串的 key
我瞬间小脑萎缩了?你这 key 怎么放进去的?
然后他给了我 java 的 demo ,我才发现是标准库干的骚操作,我头上一万个问号,至今想不通为什么。
yankebupt
63 天前
@BiChengfei 个屁(嗝屁)是个地方方言,意思是完蛋了,这个不是粗口,可以暂缓 block
0xD800
63 天前
@pkoukk #34 哈哈 挺无语的
yusheng88
63 天前
对一个东西不懂时,最好保持谦虚学习态度。

python 类库底层怎么处理的你不看
PBKDF 定义你不看
jdk 的类库你不研究

ai 翻译的不合你的"以为",你又要喷。
通篇下来就凸显浮躁和无脑,没有现成类库喂饭到嘴就啥也不是。
0o0O0o0O0o
63 天前
无法评判 sun 的这个库到底实现得有没有问题,但逆向移植会经常遇到这类不一致,一般是找到的偏移也许并不对应应用开发人员直接写的代码,可能只是应用所用到的加密库里的某个环节,或者开发人员真的误用、魔改。

而且流行的加密库往往都搞一些密码学的最佳实践,加一些默认设置或者屏蔽掉一些功能,已经习惯了此路不通就立刻找一份实现改改,不死磕。

- https://stackoverflow.com/a/35536933
- https://stackoverflow.com/a/51230724
Rache1
63 天前
@pkoukk #34 有时候人的问题也占很大一部分,Java 有个工具库 hutool ,有人写接口或者加解密就用里面的 API 一把梭。

然后给文档的时候就说 AES/RSA 加密的,给个密钥。一问 IV 、具体算法、填充方式就抓瞎了……
0xD800
63 天前
@0o0O0o0O0o 哈哈 我并不是死磕,而是翻译代码的时候发现这个设计很奇葩,居然只允许用 char[],我用过其他的加密库都是允许传 byte[],这个操作我确实无法理解。😁

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

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

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

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

© 2021 V2EX