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

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

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

4930 次点击
所在节点    Java
45 条回复
0xD800
65 天前
@yusheng88 #37
回复:
1. 上面有朋友发了 CPython 的实现,password 是允许字节流的
2. PBKDF 定义没看,但是可以参考#21 的回复,规范定义是字节流,只是建议用 ASCII 或 UTF8 序列
3. JDK 的类库我是研究了才发现这个奇葩的设计的呢

所以您有什么更好的解决方案吗?请指教。
另外我英文水平不是很好,无法直接阅读上面那些规范,自然不愿意去细读,那个网页的排版也差。
0xD800
65 天前
@Rache1 说到这个我其实还是会点,至少对填充方式,一些数论基础,RSA 加解密原理,ECC 加解密原理都是熟悉的。
不过 IV 之类的了解还挺少,用的不多,我觉得也不难吧。

填充算法也简单。
DefoliationM
65 天前
转成 base64 转一下。。
0xD800
64 天前
@DefoliationM #43 很遗憾 不行的。。。
0xD800
64 天前
解决方案如下:

// 指定一个自定义的 Provider
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1", new PBEProvider());

// PBEProvider
public class PBEProvider extends Provider {
public PBEProvider() {
super("PBEProvider", 1.0, "MyProvider v1.0: Custom SecretKeyFactorySpi Implementation");
put("SecretKeyFactory.PBKDF2WithHmacSHA1", PBESecretKeyFactorySpi.class.getName());
}
}

// PBESecretKeyFactorySpi
public class PBESecretKeyFactorySpi extends SecretKeyFactorySpi {

String prfAlgo = "HmacSHA1";

@Override
protected SecretKey engineGenerateSecret(KeySpec spec) throws InvalidKeySpecException {
if (spec instanceof PBEKeySpec pksp) {
return new PBKDF2KeyImpl(pksp, this.prfAlgo);
} else {
throw new InvalidKeySpecException("Unsupported KeySpec");
}
}
// ...省略其他方法
}


// 自己实现一个 PBKDF2KeyImpl
// 重写 getPasswdBytes 方法
// 将每个 byte 直接转成 char 传入,然后再强转还原 byte[]即可
private static byte[] getPasswordBytes(char[] passwd) {
byte[] result = new byte[passwd.length];

for (int i = 0; i < passwd.length; i++) {
result[i] = (byte) passwd[i];
}

return result;
}


其中遇到一个问题,JDK 的 PBKDF2KeyImpl 里面有 CleanFactory ,搜了下好像是清理用的,我没处理这个直接注释了。

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

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

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

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

© 2021 V2EX