我正在用Java构建一个Blowfish密码算法我使用these测试向量来查看它是否有效,我发现了最坏的可能情况——它对某些输入有效,而对其他输入无效。
我使用Blowfish paper作为创建实现的指南。下面是它的相关部分。
public class Blowfish {
// Binary Digits of Pi
// ...
private static final int N = 16;
private final long S[][] = new long[4][256];
private final long P[] = new long[N + 2];
密钥扩展
布鲁斯说:
(1)先初始化P数组,然后按顺序初始化四个S盒,使用固定字符串。此字符串由pi的十六进制数字组成(减去开头的3)
public void keyExpansion(String key) {
System.arraycopy(p, 0, P, 0, N + 2);
for (int i = 0; i < 4; i++) {
System.arraycopy(s[i], 0, S[i], 0, 256);
}
我意识到这不是真正可读的,但是小写
p[]
和s[][]
包含固定值(pi的十六进制数字),大写P[]
和S[][]
是Blowfish对象的属性,这些属性将发生变化。布鲁斯说:
(2)键的前32位是XOR P1,键的后32位是XOR P2,依此类推,键的所有位(可能高达P14)重复循环键位,直到整个p数组与键位异或。
for (int i = 0; i < N + 2; i++) {
int begin = (i * 8) % key.length();
int end = ((i + 1) * 8) % key.length();
String keySubstring;
if (begin < end) {
keySubstring = key.substring(begin, end);
} else {
keySubstring = key.substring(begin) + key.substring(0, end);
}
long keyChunk = Long.parseLong(keySubstring, 16);
P[i] ^= keyChunk;
}
由于密钥的长度是可变的(最多56个字节),所以我找到了传递它的最简单方法
就像一根绳子。也许我应该用大整数?
布鲁斯说:
(3)使用blowfish算法加密全零字符串,使用
在步骤(1)和(2)中描述的子键。
(4)用步骤(3)的输出替换p1和p2。
(5)使用Blowfish算法加密步骤(3)的输出
修改的子键。
(6)用步骤(5)的输出替换p3和p4。
(7)继续这个过程,替换P数组的所有条目,并且
然后,所有四个S-box按顺序排列,输出
不断变化的河豚算法。
long e = 0;
for (int i = 0; i < N + 2; i += 2) {
e = encrypt(e);
long eL = trim(e >>> 32);
long eR = trim(e);
P[i] = eL;
P[i + 1] = eR;
}
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 256; j += 2) {
e = encrypt(e);
long eL = trim(e >>> 32);
long eR = trim(e);
S[i][j] = eL;
S[i][j + 1] = eR;
}
}
这是一个很好的时机,因为任何提到我的助手函数
trim
删除更高的32位。private static long trim(long value) {
return value & 0xFFFFFFFFL;
}
加密
布鲁斯说:
河豚是一个由16发组成的网输入是64位数据元素x。
将x分为两个32位半部分:xL,xR
当i=1至16时:
xL=xL异或π
XR=F(XL)异或XR
交换xL和xR
下一个我
交换XL和XR(撤消上次交换。)
xR=xR异或P17
XL=XL异或p18
重新组合XL和XR
public long encrypt(long plaintext) {
long xL = trim(plaintext >>> 32);
long xR = trim(plaintext);
for (int i = 0; i < N; i++) {
xL = xL ^ P[i];
xR = F(xL) ^ xR;
//swap
xL = xL + xR;
xR = xL - xR;
xL = xL - xR;
}
//swap
xL = xL + xR;
xR = xL - xR;
xL = xL - xR;
//final step
xR = xR ^ P[N];
xL = xL ^ P[N + 1];
return (xL << 32) | xR;
}
}
测试
我只是针对前面提到的测试向量运行此代码,如下所示:
public void testBlowfish() {
long key[] = {
...
};
long clear[] = {
...
};
long cipher[] = {
...
};
Blowfish b = new Blowfish();
for (int i = 0; i < 34; i++) {
b.keyExpansion(Long.toHexString(key[i]));
long result = b.encrypt(clear[i]);
if (result == cipher[i]) {
System.out.println("Test " + i + " passed!");
} else {
System.out.println("Test " + i + " failed: ");
System.out.println("\tKey:\t" + Long.toHexString(key[i]));
System.out.println("\tClear:\t" + Long.toHexString(clear[i]));
System.out.println("\tCipher:\t" + Long.toHexString(cipher[i]));
System.out.println("\tResult:\t" + Long.toHexString(result));
}
}
}
以下是测试结果:
Test 0 passed!
Test 1 passed!
Test 2 passed!
Test 3 passed!
Test 4 failed:
Key: 123456789abcdef
Clear: 1111111111111111
Cipher: 61f9c3802281b096
Result: 65a2dfe88702a6bf
Test 5 passed!
Test 6 passed!
Test 7 passed!
Test 8 passed!
Test 9 failed:
Key: 131d9619dc1376e
Clear: 5cd54ca83def57da
Cipher: b1b8cc0b250f09a0
Result: fbd7e706e563d21a
Test 10 failed:
Key: 7a1133e4a0b2686
Clear: 248d43806f67172
Cipher: 1730e5778bea1da4
Result: 7d11a2563bbf76f1
Test 11 passed!
Test 12 failed:
Key: 4b915ba43feb5b6
Clear: 42fd443059577fa2
Cipher: 353882b109ce8f1a
Result: d747d42ef2bc89c0
Test 13 failed:
Key: 113b970fd34f2ce
Clear: 59b5e0851cf143a
Cipher: 48f4d0884c379918
Result: c559acf605825008
Test 14 failed:
Key: 170f175468fb5e6
Clear: 756d8e0774761d2
Cipher: 432193b78951fc98
Result: 8761d08e81a796d
Test 15 passed!
Test 16 failed:
Key: 7a7137045da2a16
Clear: 3bdd119049372802
Cipher: 2eedda93ffd39c79
Result: db47a054a5a0d496
Test 17 failed:
Key: 4689104c2fd3b2f
Clear: 26955f6835af609a
Cipher: d887e0393c2da6e3
Result: 40f96e5f9f3affe1
Test 18 passed!
Test 19 passed!
Test 20 passed!
Test 21 failed:
Key: 25816164629b007
Clear: 480d39006ee762f2
Cipher: 7555ae39f59b87bd
Result: a6cb030922383650
Test 22 passed!
Test 23 passed!
Test 24 passed!
Test 25 failed:
Key: 18310dc409b26d6
Clear: 1d9d5c5018f728c2
Cipher: d1abb290658bc778
Result: d45634df2f8eb002
Test 26 passed!
Test 27 failed:
Key: 101010101010101
Clear: 123456789abcdef
Cipher: fa34ec4847b268b2
Result: 30ce63f436ff5475
Test 28 passed!
Test 29 passed!
Test 30 passed!
Test 31 passed!
Test 32 failed:
Key: 123456789abcdef
Clear: 0
Cipher: 245946885754369a
Result: 291c4bd096af70e5
Test 33 passed!
正如我所说,有些输入有效,有些则无效。我不明白为什么,这在我看来是武断的,我拒绝生活在计算机可以做出武断决定的世界里换句话说,错误在哪里?
最佳答案
如前所述,我在转换中丢失了前导零。实现很好,但在测试中
b.keyExpansion(Long.toHexString(key[i]));
导致错误替换为
b.keyExpansion(String.format("%016x", key[i]));
工作。
现在看来很明显了,因为每个失败的测试至少有一个领先零。
编辑
出于某种原因,我假设每个键都会超过8个字节为了让
keyExpansion
工作于短按键,我将按键的读数改为如下:for (int i = 0; i < N + 2; i++) {
StringBuilder keySubstring = new StringBuilder();
for (int j = 0; j < 8; j++) {
int index = (i * 8 + j) % key.length();
keySubstring.append(key.charAt(index));
}
long keyChunk = Long.parseLong(keySubstring.toString(), 16);
P[i] ^= keyChunk;
}
关于java - Java中的Blowfish实现,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45520635/