Java 使用 JCE 的 3DES/DES 加密 - 生成可接受的密钥

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4436397/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 17:27:59  来源:igfitidea点击:

3DES/DES encryption using the JCE - generating an acceptable key

java.netjce

提问by darkphoenix

I'm working on a project that requires3DES encryption in Java. The issue is that I've been (and will continue to be) supplied with a 128-bit hex key like "0123456789ABCDEF0123456789ABCDEF". Conversion to bytes is no issue. What isthe issue, however, is that the Java Cryptographic Extensions API will choke on this key, saying it is invalid. I gather that the MSB of each byte is merely a parity bit, so the JCE expects me to remove those (or so I think). In .NET, however, I can specify the key as supplied, and it quietly handles the encryption/decryption with no complaints.

我正在从事一个需要在 Java 中进行 3DES 加密的项目。问题是我已经(并将继续)提供了一个 128 位十六进制密钥,如“0123456789ABCDEF0123456789ABCDEF”。转换为字节没有问题。什么问题,然而,就是在Java加密扩展API会呛此键,称这是无效的。我认为每个字节的 MSB 只是一个奇偶校验位,因此 JCE 希望我删除那些(或者我认为)。但是,在 .NET 中,我可以指定所提供的密钥,并且它可以安静地处理加密/解密,没有任何抱怨。

Is there any way I can generate the kind of key the JCE expects from the kind of key I'm supplied?

有什么方法可以生成 JCE 期望从我提供的密钥类型中得到的密钥类型?

I've found that the JCE allows you specify an 8-byte key for DES encryption, so I tried implementing 3DES as DES EDE using half of the supplied key. However, I'm still getting inconsistent results with .NET.

我发现 JCE 允许您为 DES 加密指定一个 8 字节的密钥,因此我尝试使用一半提供的密钥将 3DES 实现为 DES EDE。但是,我仍然得到与 .NET 不一致的结果。

Here's the Java code:

这是Java代码:

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;

public class Main{
    public static void main(String[] args) throws Exception {
        byte [] plain = "I eat fish every day".getBytes("utf-8");

        byte [] keyBytes = new byte [] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
            };

        byte [] key2Bytes = new byte [] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0  }; // actual keys replaced with dummies.

        SecretKey keySpec = new SecretKeySpec(keyBytes, "DES");
        SecretKey keySpec2 = new SecretKeySpec(key2Bytes, "DES");

        IvParameterSpec iv = new IvParameterSpec(new byte[8]);

        Cipher e_cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");

        e_cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
        cipher.init(Cipher.DECRYPT_MODE, keySpec2, iv);

        byte [] cipherText = e_cipher.doFinal(plain);
        cipherText = cipher.doFinal(cipherText);
        cipherText = e_cipher.doFinal(cipherText);

        System.out.println("Ciphertext: " + new sun.misc.BASE64Encoder().encode(cipherText));
    }
}

and here's the .NET code:

这是 .NET 代码:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace EncryptionDemo
{
    class Program
    {
    public static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");

        // TODO: Implement Functionality Here
        var plainBytes = Encoding.UTF8.GetBytes("I eat fish every day");
        var keyBytes = new byte [] { 0x00, 0x00, 0x00, 0x00,
         0x00, 0x00, 0x00, 0x00,
         0x00, 0x00, 0x00, 0x00,
         0x00,  0x00, 0x00, 0x00  };

        var tripleDES = TripleDESCryptoServiceProvider.Create();
        var transform = tripleDES.CreateEncryptor(keyBytes, new byte [8]);

        var memStream = new MemoryStream();
        var cStream = new CryptoStream(memStream, transform, CryptoStreamMode.Write);

        cStream.Write(plainBytes, 0, plainBytes.Length);
        cStream.FlushFinalBlock();

        //memStream.Position = 0;
        var cipherBytes = memStream.ToArray();

        Console.WriteLine("Ciphertext: " + Convert.ToBase64String(cipherBytes));

        Console.Write("Press any key to continue . . . ");
        Console.ReadKey(true);
    }
}

Both produce different outputs (some characters in the Base64 string are the same)

两者都产生不同的输出(Base64 字符串中的某些字符是相同的)

采纳答案by darkphoenix

the Sun provider doesn't accept 16-byte 3DES keys, but the BouncyCastle provider does. I just tried it out and it works like a charm - it produces the same output as the .NET code!

Sun 提供程序不接受 16 字节 3DES 密钥,但 BouncyCastle 提供程序接受。我刚刚尝试了它,它就像一个魅力 - 它产生与 .NET 代码相同的输出!

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.Security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class Main{
    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        byte [] plain = "I eat fish every day".getBytes("utf-8");

        byte [] keyBytes = new byte [] { (byte) 0xC1, (byte) 0x57, (byte) 0x45, (byte) 0x08,
            (byte) 0x85, (byte) 0x02, (byte) 0xB0, (byte) 0xD3,
            (byte) 0xA2, (byte) 0xEF, (byte) 0x68, (byte) 0x43,
            (byte) 0x5E, (byte) 0xE6, (byte) 0xD0, (byte) 0x75 };


        SecretKey keySpec = new SecretKeySpec(keyBytes, "DESede");

        IvParameterSpec iv = new IvParameterSpec(new byte[8]);

        Cipher e_cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding", "BC");

        e_cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);

        byte [] cipherText = e_cipher.doFinal(plain);

        System.out.println("Ciphertext: " + new sun.misc.BASE64Encoder().encode(cipherText));
    }
}

回答by erickson

3DES keys are 192 bits long.

3DES 密钥的长度为 192 位。

How are you creating the SecretKeyinstance? What error message to you get?

你是如何创建SecretKey实例的?你得到什么错误信息?



The Java code in your questionis using DES, not "Triple DES". The algorithm name should be "DESede/CBC/PKCS5Padding". The code in your answerprobably works because you got the algorithm right, not because you switched providers. The SunJCE provider in Java 6 will accept 128-bit keys (and use keying option 2). I am not sure about older versions.

问题中的 Java 代码使用的是 DES,而不是“三重 DES”。算法名称应该是"DESede/CBC/PKCS5Padding". 您答案中的代码可能有效,因为您的算法是正确的,而不是因为您切换了提供者。Java 6 中的 SunJCE 提供程序将接受 128 位密钥(并使用密钥选项 2)。我不确定旧版本。

回答by darkphoenix

In the jPOSproject, the problem is worked around by always using either single-length (8-byte) or triple-length (24-byte) keys. Let's say your clear double-length key (in bytes) is AAAAAAAA BBBBBBBB. All code in the jPOS project I've seen so far that uses the JCE appends the first 8 bytes again to the clear key, so it becomes a triple-length key as such: AAAAAAAA BBBBBBBB AAAAAAAA. It seems the Sun provider does accept this material for creating a SecreKeySpec, as it is 192 bits long, as @erickson mentioned.

jPOS项目中,通过始终使用单长度(8 字节)或三长度(24 字节)密钥来解决该问题。假设您的明确双长度密钥(以字节为单位)是 AAAAAAAA BBBBBBBB。到目前为止,我看到的 jPOS 项目中的所有代码都使用 JCE 将前 8 个字节再次附加到清除密钥,因此它变成了一个三重长度的密钥:AAAAAAAA BBBBBBBB AAAAAAA。正如@erickson 所提到的,Sun 提供商似乎确实接受了这种材料来创建 SecreKeySpec,因为它有 192 位长。