将 C# 密码学转换为 Java

Converting C# cryptography to Java

我的任务是将 C# 加密方法转换为 Java,但卡住了。我知道 C# 代码可以工作,但我无法让我的 Java 代码工作。

这是 C# 代码:

private const int Nb = 4; // Legal values:  4 = 128-bit blocks

public static void Decrypt(byte[] input, Stream output)
    { 
        var s1 = new MemoryStream(input);
        const int BufferSize = 1024;
        byte[] buffer = new byte[BufferSize];

        input.Read(buffer, 0, 4);
        int pad = buffer[3];

        RijndaelManaged rijndael = new RijndaelManaged();
        rijndael.BlockSize = Nb * 32;
        rijndael.KeySize = buffer[1] * 32;

        rijndael.Mode = CipherMode.ECB;
        rijndael.Padding = PaddingMode.None;

        byte[] key = GetKey(buffer[1]);
        ICryptoTransform decryptor = rijndael.CreateDecryptor(key, GetIV());

        int bytes;
        while ((bytes = input.Read(buffer, 0, BufferSize)) > 0)
        {
            for (int i = 0; i < bytes; i += rijndael.BlockSize)
            {
                decryptor.TransformBlock(buffer, i, rijndael.BlockSize, buffer, i);
            }
            output.Write(buffer, 0, bytes);
        }
        output.SetLength(output.Length - pad - 4);
    }

这是我目前在 Java 中的尝试:

public static String decrypt(byte[] input) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
    byte[] key = getKey(input[1]);
    SecretKey secretKey = new SecretKeySpec(key, 0, key.length, "AES/ECB/NoPadding");
    cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(getIV()));
    // remove first 4 since C# code reads past those
    byte[] finalDecoded = Arrays.copyOfRange(input, 4, input.length);
    byte[] decryptedVal = cipher.doFinal(finalDecoded);
    return new String(decryptedVal);
}

更多信息

Java 代码哪里出错了?

您收到错误 ECB mode cannot use IV 因为 ECB 不执行链接,所以 IV 没有意义。不同之处在于 Java 会引发错误,而 C# 只会忽略 IV。

When I remove this code : new IvParameterSpec(getIV()) I get this error: Wrong algorithm:AES or Rijndaelrequired

If I change the algorithm to only AES or only Rijndael I get this error: Input length must be multiple of 16 when decrypting with padded cipher.

你的想法是对的,但你做得太过分了。此错误仅与 SecretKeySpec 有关,它不关心模式,只关心算法。 Cipher 是您指定模式的地方。此外,Rijndael 和 AES 也不是一回事。

因此,首先将前几行更改为:

Cipher cipher = Cipher.getInstance("Rijndael/ECB/NoPadding");
byte[] key = getKey(input[1]);
SecretKey secretKey = new SecretKeySpec(key, 0, key.length, "Rijndael");
cipher.init(Cipher.DECRYPT_MODE, secretKey);

请注意,由于您使用的是整个 key,因此不需要偏移量和长度参数,因此您可以直接执行

SecretKey secretKey = new SecretKeySpec(key, "Rijndael");

原始 C# 代码有一些不太明显的行为:

while ((bytes = input.Read(buffer, 0, BufferSize)) > 0)
{
    for (int i = 0; i < bytes; i += rijndael.BlockSize)
    {
        decryptor.TransformBlock(buffer, i, rijndael.BlockSize, buffer, i);
    }
    output.Write(buffer, 0, bytes);
}

当循环到达 input 的末尾时,它会将剩余的内容复制到 buffer 中。除非最后一个 Read 恰好是 1024 字节,否则在输入结束后会有前一个循环的残留物(或者如果它通过一个 Read 操作获得整个 input 则来自初始化) .

内循环一次解密一个 16 字节的块。对于 420 字节的示例,最后一个块将包含剩余的 4 个字节的输入 和另外 12 个字节的垃圾 。但这没关系,因为 output.Write 只写入 bytes 字节数来截断垃圾。您必须在 Java 代码中复制此行为。


旁注:您一定要使用 ECB 吗?不是很安全...