Java中与.NET TripleDESCryptoServiceProvider等效的内容

4
请不要问我为什么,我只是有一个.NET代码可以加密/解密数据字符串。现在我需要在Java中做出'完全相同'的功能。我尝试了几个DESede加密示例,但没有一个像.NET中的这个类一样给出相同的结果。
我甚至考虑过在.NET Web服务后面使用SSL来提供这两种在.NET中编写的方法,但在没有穷尽所有可能性之前,这样做太愚蠢了。
也许你们中与该领域更相关的Java人员会很快想到如何实现。
谢谢!
public class Encryption
{
  private static byte[] sharedkey = {...};
  private static byte[] sharedvector = {...};

  public static String Decrypt(String val)
  {
    TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
    byte[] toDecrypt = Convert.FromBase64String(val);
    MemoryStream ms = new MemoryStream();
    CryptoStream cs = new CryptoStream(ms, tdes.CreateDecryptor( sharedkey, sharedvector ), CryptoStreamMode.Write);

    cs.Write(toDecrypt, 0, toDecrypt.Length);
    cs.FlushFinalBlock();
    return Encoding.UTF8.GetString(ms.ToArray());
  }

  public static String Encrypt(String val)
  {
    TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
    byte[] toEncrypt = Encoding.UTF8.GetBytes(val);
    MemoryStream ms = new MemoryStream();
    CryptoStream cs = new CryptoStream(ms, tdes.CreateEncryptor( sharedkey, sharedvector ), CryptoStreamMode.Write);
    cs.Write(toEncrypt, 0, toEncrypt.Length);
    cs.FlushFinalBlock();
    return Convert.ToBase64String(ms.ToArray());
  }
}

示例输入/输出

String plain = "userNameHere:passwordHere";
Console.WriteLine("plain: " + plain);


String encrypted = Encrypt(plain);
Console.WriteLine("encrypted: " + encrypted);
// "zQPZgQHpjxR+41Bc6+2Bvqo7+pQAxBBVN+0V1tRXcOc="

String decripted = Decrypt(encrypted);
Console.WriteLine("decripted: " + decripted); 
// "userNameHere:passwordHere"

1
这是可以做到的。提供一些测试输入和输出。 - erickson
我添加了示例输入/输出,谢谢! - David Hofmann
4个回答

8

以下是代码,但首先有几点需要注意:

  1. 每个消息都必须选择不同的初始化向量。硬编码初始化向量没有意义。IV应该与密文一起发送给消息接收者(它不是秘密)。
  2. 我使用了自己的实用程序类进行base-64编码。您可以使用sun.misc.BASE64Encodersun.misc.BASE64Decoder,也可以使用第三方库如BouncyCastle,或编写自己的。
  3. 您正在使用2键三重DES,其中第一个密钥和第三个密钥相同。我修改了sharedkey以反映这一点,因为Java DESede密码始终需要192位密钥;由密钥生成器处理密钥选项。
  4. CBC IV仅为64位。我只使用了sharedvector的前64位。

这个类应该与C#版本互操作。

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

public class Encryption
{

  private static byte[] sharedkey = {
    0x01, 0x02, 0x03, 0x05, 0x07, 0x0B, 0x0D, 0x11, 
    0x12, 0x11, 0x0D, 0x0B, 0x07, 0x02, 0x04, 0x08, 
    0x01, 0x02, 0x03, 0x05, 0x07, 0x0B, 0x0D, 0x11
  };

  private static byte[] sharedvector = {
    0x01, 0x02, 0x03, 0x05, 0x07, 0x0B, 0x0D, 0x11
  };

  public static void main(String... argv)
    throws Exception
  {
    String plaintext = "userNameHere:passwordHere";
    String ciphertext = encrypt(plaintext);
    System.out.println(ciphertext);
    System.out.println(decrypt(ciphertext));
  }

  public static String encrypt(String plaintext)
    throws Exception
  {
    Cipher c = Cipher.getInstance("DESede/CBC/PKCS5Padding");
    c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sharedkey, "DESede"), new IvParameterSpec(sharedvector));
    byte[] encrypted = c.doFinal(plaintext.getBytes("UTF-8"));
    return Base64.encode(encrypted);
  }

  public static String decrypt(String ciphertext)
    throws Exception
  {
    Cipher c = Cipher.getInstance("DESede/CBC/PKCS5Padding");
    c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sharedkey, "DESede"), new IvParameterSpec(sharedvector));
    byte[] decrypted = c.doFinal(Base64.decode(ciphertext));
    return new String(decrypted, "UTF-8");
  }

}

输出:

zQPZgQHpjxR+41Bc6+2Bvqo7+pQAxBBVN+0V1tRXcOc=

这里填写用户名:这里填写密码


哇!我完全不知道你在说什么 :D 但它起作用了。我保证会阅读有关加密算法的主题,以便更好地理解。再次感谢!!! - David Hofmann

2

你有一些问题:

  1. 如果要在.NET和Java上生成相同的密钥材料,则必须使用24个字节的密钥。
  2. IV必须是块大小,Triple DES的块大小为8个字节。
  3. 在Java中,您需要指定默认模式和填充方式,即“DESede / CBC / NoPadding”。

一旦您进行了这些更改,您应该能够在Java端解密它。


但是你能加密他的明文吗?SunJCE会拒绝任何不是8个字节倍数的输入。 - erickson
1
在JCE术语中,NoPadding等同于零填充。在CBC模式下,它将被填充到块大小。假设您在填充后有3个额外的字节。NoPadding将用“00 00 00”填充它。PKCS5将采用“03 03 03”。在测试解密时,我总是首先使用NoPadding,它总是有效的。然后您可以找出所使用的填充方式。 - ZZ Coder

0
尝试以下操作。实际使用时,我建议获取一个类似于commons codec的base64库或使用BouncyCastle自带的编解码器。
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;

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

public class Encryption {

    private static SecretKey sharedkey;
    private static byte [] sharedvector;

    static {
        int keySize = 168;
        int ivSize = 8;
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
            keyGenerator.init(keySize);
            sharedkey = keyGenerator.generateKey();

            sharedvector = new byte [ivSize];
            byte [] data = sharedkey.getEncoded();

            int half = ivSize / 2;
            System.arraycopy(data, data.length-half, sharedvector, 0, half);
            System.arraycopy(sharedvector, 0, sharedvector, half, half);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    public static void main(String [] args) throws Exception {
        System.out.println(Decrypt(Encrypt("Hello World")));

    }

    public static String Encrypt(String val) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, sharedkey, new IvParameterSpec(sharedvector));

        return new sun.misc.BASE64Encoder().encode(cipher.doFinal(val.getBytes()));
    }

    public static String Decrypt(String val) throws GeneralSecurityException, IOException {
        Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, sharedkey, new IvParameterSpec(sharedvector));

        return new String(cipher.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(val)));
    }

}

0
你确定.NET代码使用了与Java代码相同的填充方式吗?我在.NET代码中没有看到指定填充方式,所以我想问一下。
你有Java代码的源代码吗?这将有助于找出错误。

1
我实际上不记得我尝试了多少次,肯定我甚至没有接近正确的代码。所以最好是有人知道如何从头开始制作它。 - David Hofmann
你没有Java代码吗?另外,我们还需要Java代码的输入和输出样例。 - Kyle Rosendo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接