将 .Net 中的 TripleDES 算法应用到 Java 时出错

Error applying TripleDES algorith from .Net to Java

对于我们当前的项目,我们需要将 TripleDES MD5 CBC 模式的加密数据发送给另一家公司。 pass、salt 和 init vector 的值由公司给出,与此处给出的值不同(但长度相同)。

他们已经向我们发送了用于加密的代码(在 .Net C# 中),如下所示:

//Encription parameter def
static private string _pass = "12345678901234";
static private string _salt = "123456";
static private string _alg = "MD5";
static private string _iv = "1234567890123456";

public static string EncriptString(string textPlain)
{
    return EncriptString(textPlain, _pass, _salt, _alg, 1, _iv, 128); 
}

public static string EncriptString(string textPlain, string passBase, string saltValue, string hashAlgorithm, int passwordIterations, string initVector, int keySize)
{
    byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
    byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
    byte[] plainTextBytes = Encoding.UTF8.GetBytes(textPlain);

    PasswordDeriveBytes password = new PasswordDeriveBytes(passBase, saltValueBytes, hashAlgorithm, passwordIterations);
    byte[] keyBytes = password.GetBytes(keySize / 8);
    RijndaelManaged symmetricKey = new RijndaelManaged();
    symmetricKey.Mode = CipherMode.CBC;
    ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
    MemoryStream memoryStream = new MemoryStream();
    CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
    cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
    cryptoStream.FlushFinalBlock();
    byte[] cipherTextBytes = memoryStream.ToArray();
    memoryStream.Close();
    cryptoStream.Close();

    string cipherText = Convert.ToBase64String(cipherTextBytes);
    return cipherText;
}

我一直在 Google 上搜索一天,Whosebug 试图将其翻译成 JAVA 但我做不到。这是我目前的做法:

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * @author alvaro
 */
public class Encriptor {

    // Constants -----------------------------------------------------

    private static final String PASS_PHRASE = "12345678901234";//says wrong length
    private static final String SALT_VALUE = "123456";
    private static final int PASSWORD_ITERATIONS = 1;
    private static final String INIT_VECTOR = "1234567890123456";
    private static final int KEY_SIZE = 128;

    // Attributes ----------------------------------------------------

    // Static --------------------------------------------------------

    // Constructors --------------------------------------------------

    // Public --------------------------------------------------------

    public String encrypt(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException {
        MessageDigest digest = MessageDigest.getInstance(DIGEST);
        digest.update(SALT_VALUE.getBytes());
        byte[] bytes = digest.digest(PASS_PHRASE.getBytes(ENCODING));
        SecretKey password = new SecretKeySpec(bytes, "AES");

        //Initialize objects
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] IV = INIT_VECTOR.getBytes();
        IvParameterSpec ivParamSpec = new IvParameterSpec(IV);
        cipher.init(Cipher.ENCRYPT_MODE, password, ivParamSpec);
        byte[] encryptedData = cipher.doFinal(text.getBytes(ENCODING));
        return new BASE64Encoder().encode(encryptedData).replaceAll("\n", "");
    }

    // Package protected ---------------------------------------------

    // Protected -----------------------------------------------------

    // Private -------------------------------------------------------

    // Inner classes -------------------------------------------------

}

问题是 MD5 哈希在 Java 中的处理方式与在 C# 中的处理方式不同。

测试代码:

import org.junit.Test;

import static org.junit.Assert.*;

public class EncriptorTest {

    @Test
    public void shouldEncriptTextCorrectly() {
        // GIVEN
        String input = '<xml><oficina>1234</oficina><empleado>123456</empleado></xml>'
        String expected = 'Lz1aG3CFYoyzjGcMzJXDB7DQgscrv9scP+d5JY8/fiUN6LV2RsnSPqDU/E5BGKz3QbeSl3RyhUgnYyN3uBBRJA=='
        // WHEN
        String output = new Encriptor().encrypt(input)
        //THEN
        assertEquals('Wrong encription', expected, output)
    }
}

解决方案

最后我使用了另一个 中给出的解决方案,其中给出了 Java 的 PasswordDeriveBytes 端口。

您与对方沟通不畅。

您说您正在尝试使用 Triple DES、MD5、CBC;但是他们使用的 .Net 代码是这样说的:

RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.Mode = CipherMode.CBC;

Rijndael 是被选为 AES 的算法的名称;他们给你的例子是使用 AES,而不是 Triple DES。

Triple DES 密钥占用 3*56 位(扩展为 3*64),即 168 位或 192 位,具体取决于您如何计算位数; API 可能消耗并期望 168 位。 AES 仅支持 128、192 和 256 位密钥大小,这解释了为什么您可能会收到 wrong size 错误。

你需要弄清楚他们是打算使用 Triple DES 还是 AES 并修复两边的代码。

顺便说一句:

  • 众所周知,单一 DES 非常不安全;在 Amazon EC2 上花费几美元就可以破解 DES。然而,三重 DES 大致是安全的,但不鼓励进行新的开发。

  • 众所周知,MD5 非常不安全,特别是它很容易受到长度扩展攻击、原像攻击、碰撞攻击等的攻击。请考虑使用 SHA 1、2 或 3。