java - Java 中的 Blowfish 实现

标签 java algorithm encryption blowfish

我正在用 Java 构建 Blowfish 密码算法。我使用 these 测试 vector 来查看它是否有效,我发现了最糟糕的情况 - 它适用于某些输入而不适用于其他输入。

我使用 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];

key 扩展

布鲁斯说:

(1) 先初始化P-array,再初始化四个S-box,依次用固定的字符串。此字符串由 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) P1 与 key 的前 32 位异或,P2 与 key 的后 32 位异或,依此类推 key 的所有位(可能直到 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;
}

由于 key 长度可变(最多 56 个字节),我想出了最简单的方法来传递它 是一个字符串。也许我应该使用 BigInteger?


布鲁斯说:

(3) 使用 Blowfish 算法加密全零字符串,使用 步骤 (1) 和 (2) 中描述的子项。

(4) 将 P1 和 P2 替换为步骤 (3) 的输出。

(5) 使用 Blowfish 算法加密步骤 (3) 的输出 修改子项。

(6) 将 P3 和 P4 替换为步骤 (5) 的输出。

(7) 继续这个过程,替换 P 数组的所有条目,并且 然后按顺序排列所有四个 S-box,输出为 连续变化的 Blowfish 算法。


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;
}

加密

布鲁斯说:

Blowfish 是一个由 16 轮组成的 Feistel 网络。输入是 64 位数据元素 x。

将 x 分成两个 32 位的一半:xL、xR

对于 i = 1 到 16:

xL = xL XOR Pi

xR = F(xL) XOR xR

交换 xL 和 xR

下一个

交换 xL 和 xR(撤消上次交换。)

xR = xR XOR P17

xL = xL XOR 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;
    }
}

测试

我只是针对上述测试 vector 运行此代码,如下所示:

  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 为短键工作,我将 reading from key 改成了这样:

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/

相关文章:

python - 对 HTML 文档执行拼写检查的高效算法

c++ - 基于 256 位 key 对矩阵进行加扰的方法有哪些?

java - EntityManager contains 或 findById after persist 来了解 persist 方法是否有效

python - "Three sums"问题空间复杂度 - 为什么是 O(n)?

python - 我的算法的运行时间复杂度 - 我如何计算它并进一步优化算法?

javascript - Python Javascript CryptoJS

javascript - 聊天系统架构 : Where to decrypt the messages?

java - 如何强制我在 Spring 中的所有页面使用 https 协议(protocol)

java - Excel 到 Java 中的 XML 代码?

java - 在面向服务的体系结构中,映射器调用服务是一种好的做法吗?