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();
}
这是一个微信聊天记录数据库算法。。
解决方案如下:
// 指定一个自定义的 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 ,搜了下好像是清理用的,我没处理这个直接注释了。
1
orangie 237 天前
搜了一下 PBKDF2 的文档: https://www.ietf.org/rfc/rfc2898.txt 其中 password 是一个 string ,那么实现的时候使用 String 对应的 char[]应该是没有问题的。另外既然名称是 password ,使用用户可见的字符来表示也是更合理的。使用字节的密码应该叫做 key 。
|
2
lsk569937453 237 天前
java 的类库这么多,找一个符合你要求的就可以了。
|
3
pocketz 237 天前
那么为什么不直接将 byte[] 转为 char[] 呢
|
4
BiChengfei 237 天前 2
hashlib.pbkdf2_hmac('sha1', password, salt, iterations, key_length)
我理解你是想使用 sha1 算法对 password 进行 hash 运算,salt 表示加盐,iterations 表示计算次数,key_length 表示 hash 后的长度 非法字符序列,举个例子啊,是指不在 UTF_8 中的字符吗,上来就是”糟糕“、”无语“、”屁“,已 block |
6
0xD800 OP @BiChengfei Hex: AC3C90034CF34804A7859144129CA9AEB6B90D07CA874172A374F2000000CAE5 ,麻烦指导一下用 jdk 的 API 算一下 key 出来呗
|
7
0xD800 OP @lsk569937453 你说得对 0.0
|
8
siweipancc 237 天前 via iPhone
看得我一脸懵逼,啥跟啥这是
|
9
0xD800 OP @siweipancc 一个计算微信聊天记录数据库密钥的算法,来源: https://mp.weixin.qq.com/s/4DbXOS5jDjJzM2PN0Mp2JA
|
10
orangie 237 天前
@0xD800 按照文档标准设计的应该用字符 password ,不会用字节 key 。PBKDF2 这个名字就是 Password-Based Key Derivation Function 。想要用字节的话,合理的方式不是换个库,而是换个加密方案。如果非要用 PBKDF 系列,那么可以自己把第三方的类库复制一份魔改,注意 license 。
|
11
InkStone 237 天前
这是 password 不是 crypto key 。你传个二进制数据进去本来就不符合用法……
你要传二进制用普通的 hmac 别用 pbkdf_hmac 啊 |
12
gadfly3173 237 天前 via Android
我不懂 python 也不懂 c ,但是按照 python 源码里这个实现,char *也不能用来放非字符吧?
https://github.com/python/cpython/blob/8b56d82c59c2983b4292a7f506982f2cab352bb2/Modules/_hashopenssl.c#L1323C59-L1323C67 |
14
orangie 237 天前
@gadfly3173 C 语言字符和字节的没有区别,C 的 char 完全就是其它语言中的 byte 而没有真正的字符 char 。C 里 char 存的就是字节,完全不限制字符是否合法。
|
15
0xD800 OP @gadfly3173 感谢回复,python 的 hashlib 中 password 是直接传入 bytes 的,不是传入 string ,因此没有转换的问题。而 java 是传入 UTF8 编码的字符数组,所以出现了问题,JAVA 底层还是把 char 转成了 byte[],我觉得这个设计不太合理,应该支持直接传入 byte[]好一些
|
17
0xD800 OP @orangie 刚刚看了规范里面确实 P 和 S 都是 string ,这么说怪不得 Java 了,只能说其他语言太灵活了。
```text Input: P password, an octet string S salt, an octet string ``` |
18
AoEiuV020JP 237 天前
以前就听说有一种加密学防破解的手段是使用非标准加密算法,一直没见过实际应用,你这个就是了,
合理怀疑 python 这个 pbkdf2_hmac 不是因为设计优秀才支持 bytes 的,而是 python 没有 chat[]? |
19
0xD800 OP @AoEiuV020JP 哈哈 但是 python 有字符串啊,标准就是传字符串,java 设计出 char[]可能是防止字符串在常量池中,被扫出来吧
|
20
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 等编码的兼容的表示方法来保持兼容性。我也是现学的。 |
21
geelaw 237 天前
@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 这是误读。 |
23
cslive 237 天前
不用 string 而用 char[]是为了安全
|
24
xubeiyou 237 天前 1
底层是这样的 但是基本都是有对应包装工具类- - 说实话 Java 这么受欢迎还是因为生态好- - 但是价格上不来也 TMD 是因为生态好
|
25
zzl22100048 237 天前
从 #1 给的文档点进去,password 是 octet string ,也是就 bytes
也就是 java 库没按规范实现 |
26
yippees 237 天前 1
s = "48656c6c6f20576f726c64"
b = bytes.fromhex(s) print(b) b'Hello World' 顾名思义不是一个基本的 hex-str 转 byte 数组吗。。。。48-》 0X48 。。。 主题的错误实现代码 回复的歪楼 看得真是太欢乐了,,, 都想借用楼主的标题了,,, |
27
miaotaizi 237 天前
你就不能味给 AI 让 AI 帮你实现一版 JAVA 的吗
|
28
zzl22100048 237 天前
|
31
geelaw 237 天前 via iPhone
@BiChengfei #4 我觉得“糟糕”“无语”都还好吧,“屁”这个应该是楼主的错字,原文“个屁”实际上应该是 gěrpì(常写作:嗝儿屁),语气和意思差不多都是“死翘翘”。
|
32
0xD800 OP @zzl22100048 是的👍
|
34
pkoukk 237 天前
吐槽一个 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 ,我才发现是标准库干的骚操作,我头上一万个问号,至今想不通为什么。 |
35
yankebupt 237 天前
@BiChengfei 个屁(嗝屁)是个地方方言,意思是完蛋了,这个不是粗口,可以暂缓 block
|
37
yusheng88 237 天前 via Android
对一个东西不懂时,最好保持谦虚学习态度。
python 类库底层怎么处理的你不看 PBKDF 定义你不看 jdk 的类库你不研究 ai 翻译的不合你的"以为",你又要喷。 通篇下来就凸显浮躁和无脑,没有现成类库喂饭到嘴就啥也不是。 |
38
0o0O0o0O0o 237 天前
无法评判 sun 的这个库到底实现得有没有问题,但逆向移植会经常遇到这类不一致,一般是找到的偏移也许并不对应应用开发人员直接写的代码,可能只是应用所用到的加密库里的某个环节,或者开发人员真的误用、魔改。
而且流行的加密库往往都搞一些密码学的最佳实践,加一些默认设置或者屏蔽掉一些功能,已经习惯了此路不通就立刻找一份实现改改,不死磕。 - https://stackoverflow.com/a/35536933 - https://stackoverflow.com/a/51230724 |
39
Rache1 237 天前
@pkoukk #34 有时候人的问题也占很大一部分,Java 有个工具库 hutool ,有人写接口或者加解密就用里面的 API 一把梭。
然后给文档的时候就说 AES/RSA 加密的,给个密钥。一问 IV 、具体算法、填充方式就抓瞎了…… |
40
0xD800 OP @0o0O0o0O0o 哈哈 我并不是死磕,而是翻译代码的时候发现这个设计很奇葩,居然只允许用 char[],我用过其他的加密库都是允许传 byte[],这个操作我确实无法理解。😁
|
41
0xD800 OP @yusheng88 #37
回复: 1. 上面有朋友发了 CPython 的实现,password 是允许字节流的 2. PBKDF 定义没看,但是可以参考#21 的回复,规范定义是字节流,只是建议用 ASCII 或 UTF8 序列 3. JDK 的类库我是研究了才发现这个奇葩的设计的呢 所以您有什么更好的解决方案吗?请指教。 另外我英文水平不是很好,无法直接阅读上面那些规范,自然不愿意去细读,那个网页的排版也差。 |
42
0xD800 OP |
43
DefoliationM 236 天前 via Android
转成 base64 转一下。。
|
44
0xD800 OP @DefoliationM #43 很遗憾 不行的。。。
|
45
0xD800 OP 解决方案如下:
// 指定一个自定义的 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 ,搜了下好像是清理用的,我没处理这个直接注释了。 |