3

I'm attempting to convert this C# encryption algorithm to Java; however, I keep retrieving slightly different encrypted results (haven't tried decryption yet). It may also be important to point out that I'm not able to change the C# code.

However when I call the encrypt function in C# on the string "test" it will return nmj8MjjO52y928Syqf0J+g== However in Java it'll return C6xyQjJCqVo=

The C#

private static String key = "012345678901234567890123";

public static string encrypt(String stringToEncrypt)
{
    TripleDES des = CreateDES(key);
    ICryptoTransform ct = des.CreateEncryptor();
    byte[] input = Encoding.Unicode.GetBytes(stringToEncrypt);
    byte[] output = ct.TransformFinalBlock(input, 0, input.Length);
    //return output;
    return Convert.ToBase64String(output);
}


public static String decrypt(string encryptedString)
{
    byte[] input = Convert.FromBase64String(encryptedString);
    TripleDES des = CreateDES(key);
    ICryptoTransform ct = des.CreateDecryptor();
    byte[] output = ct.TransformFinalBlock(input, 0, input.Length);
    return Encoding.Unicode.GetString(output);
}


public static TripleDES CreateDES(string key)
{
    MD5 md5 = new MD5CryptoServiceProvider();
    TripleDES des = new TripleDESCryptoServiceProvider();
    des.Key = md5.ComputeHash(Encoding.Unicode.GetBytes(key));
    des.IV = new byte[des.BlockSize / 8];
    return des;
}

My Attempt with converting to Java

private static String key = "012345678901234567890123";

public static void main(String[] args) throws Exception {
    String text = "test";
    String codedtext = encrypt(text);
    //String decodedtext = decrypt(codedtext);

    System.out.println(new String(codedtext));
    //System.out.println(decodedtext); 
}

public static String encrypt(String message) throws Exception {
    MessageDigest md = MessageDigest.getInstance("md5");
    byte[] digestOfPassword = md.digest(key.getBytes("unicode"));
    byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
    //for (int j = 0, k = 16; j < 8;) {
    //  keyBytes[k++] = keyBytes[j++];
    //}

    SecretKey key = new SecretKeySpec(keyBytes, "DESede");
    IvParameterSpec iv = new IvParameterSpec(new byte[8]);
    Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);

    byte[] plainTextBytes = message.getBytes();
    byte[] cipherText = cipher.doFinal(plainTextBytes);

    String output = Base64.encode(cipherText);

    return output;
}

public static String decrypt(String message) throws Exception {
    byte[] messageBytes = Base64.decode(message);

    MessageDigest md = MessageDigest.getInstance("md5");
    byte[] digestOfPassword = md.digest(key.getBytes());
    byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
    for (int j = 0, k = 16; j < 8;) {
        keyBytes[k++] = keyBytes[j++];
    }

    SecretKey key = new SecretKeySpec(keyBytes, "DESede");
    IvParameterSpec iv = new IvParameterSpec(new byte[8]);
    Cipher decipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
    decipher.init(Cipher.DECRYPT_MODE, key, iv);

    byte[] plainText = decipher.doFinal(messageBytes);

    return new String(plainText);
}

Does anyone see what I'm overseeing?

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
Jujunol
  • 457
  • 5
  • 18
  • have you tried a `Google Search on Java TripleDes` here is a link doing that search http://stackoverflow.com/questions/20227/how-do-i-use-3des-encryption-decryption-in-java compare what you have to what the accepted answer has here... good luck – MethodMan Nov 17 '15 at 19:46
  • 1
    It doesn't help that your Java code uses different encodings to convert the key to bytes before hashing. (And I suspect that both are different to the one in C# - that's the first thing you should check.) – Jon Skeet Nov 17 '15 at 19:47
  • @JonSkeet Both encodings are Unicode (unless you're noticing the one in the decrypt method, but the two encrypted strings are different) – Jujunol Nov 17 '15 at 19:51
  • @Jujunol: `Encoding.Unicode` is little-endian UTF-16. `"unicode"` in Java isn't one of the specified encodings, but you may well find it's not that. Certainly on my machine, those two don't give the same results. You should use `StandardCharsets.UTF_16LE` in Java to get the same results. – Jon Skeet Nov 17 '15 at 19:54
  • @JonSkeet Thanks, I wasn't aware that the two unicodes were different. You're right, it did change the result however it's `3P6itCSdfWs=` now instead. – Jujunol Nov 17 '15 at 19:57
  • You might be trucating the md5 of the key in byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24); An md5 would be 32 bytes in length so I think you would be using different ones in the C# and Java versions – Ehz Nov 17 '15 at 20:41
  • @OG. The reason that's there is because md.digest() returns 16 bytes, (which was a previous error I got saying it's an invalid length, because it must be 24 bytes for tripleDES). So, it's not that it's truncating rather making it the accepted length, I'm assuming that the C# class does this automatically ? – Jujunol Nov 17 '15 at 20:55
  • @Jujunol You should try to fill the last 8 bytes of the key in C# with zeroes to see if that behaves better. Note that Triple DES has different key layouts, so C# might use 16 which is passed as key1+key2 and apply it in an EDE fashion as key1+key2+key1 or key2+key1+key2, but Java would use 3 distinct keys with the last one being a zero filled key. – Artjom B. Nov 17 '15 at 22:22
  • @ArtjomB. By calling arraycopy, it will set the trailing bytes (because the original is 16 in size) with 0's. I confirmed this as well and set them to 0's with the same result. No, I was not aware that the C# could inherit one of these methods. Is there any way of telling which one it may be? – Jujunol Nov 17 '15 at 22:57
  • I wrote complete answer on https://stackoverflow.com/a/46861817/1140304 – Milad Ahmadi Oct 21 '17 at 09:49

1 Answers1

2

You are missing two things. You are using a 16 length key on the c# side since it is not padded like the Java version. By default if the key is 16 bytes in length it will be padded with the first 8 bytes of the key.

To make this match on the Java side you will have to uncomment that line that adds the padding to the key:

for (int j = 0, k = 16; j < 8;) {
  keyBytes[k++] = keyBytes[j++];
}

SecretKey secretKey = new SecretKeySpec(keyBytes, 0, 24, "DESede");

Also, on the java side there was a suggestion to make sure to use UTF-LE for the text. Make sure to use it for everything. So the lines:

byte[] digestOfPassword = md.digest(key.getBytes("UTF-16LE"));

byte[] plainTextBytes = clearText.getBytes("UTF-16LE");

In general I would make sure to set the c# params of all the tripledes object, and not depend on defaults.

Here are two versions that match in c# and java

Java

String key = "012345678901234567890123";
String clearText = "test";

MessageDigest md = MessageDigest.getInstance("md5");
byte[] digestOfPassword = md.digest(key.getBytes("UTF-16LE"));

byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
String byteText = Arrays.toString(keyBytes);

for (int j = 0, k = 16; j < 8;) {
  keyBytes[k++] = keyBytes[j++];
}

SecretKey secretKey = new SecretKeySpec(keyBytes, 0, 24, "DESede");

IvParameterSpec iv = new IvParameterSpec(new byte[8]);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");

cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);

byte[] plainTextBytes = clearText.getBytes("UTF-16LE");
byte[] cipherText = cipher.doFinal(plainTextBytes);

String output = Base64.encode(cipherText);

c#

string clearText = "test";
string key = "012345678901234567890123";
string encryptedText = "";

MD5 md5 = new MD5CryptoServiceProvider();
TripleDES des = new TripleDESCryptoServiceProvider();
des.KeySize = 128;
des.Mode = CipherMode.CBC;
des.Padding = PaddingMode.PKCS7;

byte[] md5Bytes = md5.ComputeHash(Encoding.Unicode.GetBytes(key));

byte[] ivBytes = new byte[8];


des.Key = md5Bytes;

des.IV = ivBytes;

byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);

ICryptoTransform ct = des.CreateEncryptor();
using (MemoryStream ms = new MemoryStream())
{
    using (CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write))
    {
        cs.Write(clearBytes, 0, clearBytes.Length);
        cs.Close();
    }
    encryptedText = Convert.ToBase64String(ms.ToArray());
}

Edited: Both versions now return the test case result "nmj8MjjO52y928Syqf0J+g=="

Ehz
  • 2,027
  • 1
  • 12
  • 11
  • Unfortunately, as the original question states, I'm unable to modify the existing C# code. Is there a way to bring your answer closer to the Java side? – Jujunol Nov 17 '15 at 22:47
  • You just need to edit the parameters on the c# side. On the java side you should restore the loop that pads the end of the array with the first 8 bytes – Ehz Nov 17 '15 at 22:58
  • @Jujunol - Could you post your final code? Pretty Please!!... I have a need to take Java DESede and turn it into C#. Your help would be greatly appreciated.. it is using DESede (which I think has 2 keys and 7 bits per byte )... in C# it my understanding that it uses 3 keys and 8 bits per byte... but I'm a little thrown... Thanks In Advance!!! – Danimal111 Oct 27 '16 at 22:45
  • @OG. I have a need to take Java DESede and turn it into C#. Your help would be greatly appreciated.. it is using DESede (which I think has 2 keys and 7 bits per byte )... in C# it my understanding that it uses 3 keys and 8 bits per byte... but I'm a little thrown... Thanks In Advance!!! – Danimal111 Oct 27 '16 at 22:46