Thursday, July 16, 2009

Triple DES Encryption implementation compatible between Java and .NET!

One problem that I have had to solve in the past was making encryption between Java and .NET possible.  Specifically, I was charged to read an encrypted query string parameter from a Java system in my ASP.NET application.  After a few iterations of incompatible implementations, I created an implementation in both .NET (c#) and Java to ensure compatibility.

Since this caused me so much pain, I thought I would share my implementation with you in hopes that it will work for you as well.

Let me know if you have any questions or want to provide a decrypt implementation for the java class.

DOWNLOAD C# CLASS FILE

DOWNLOAD JAVA CLASS FILE

 

.NET (C#) Implementation

/// <summary>
/// Encryption utility class that implements Triple DES algorithm
/// </summary>
public class TripleDESImplementation
{
    //Encryption Key
    private byte[] EncryptionKey { get; set; }
    // The Initialization Vector for the DES encryption routine
    private byte[] IV { get; set; }

    /// <summary>
    /// Constructor for TripleDESImplementation class
    /// </summary>
    /// <param name="encryptionKey">The 24-byte encryption key (24 character ASCII)</param>
    /// <param name="IV">The 8-byte DES encryption initialization vector (8 characters ASCII)</param>
    public TripleDESImplementation(string encryptionKey, string IV)
    {
        if (string.IsNullOrEmpty(encryptionKey))
        {
            throw new ArgumentNullException("'encryptionKey' parameter cannot be null.", "encryptionKey");
        }
        if (string.IsNullOrEmpty(IV))
        {
            throw new ArgumentException("'IV' parameter cannot be null or empty.", "IV");
        }

        EncryptionKey = Encoding.ASCII.GetBytes(encryptionKey);
        // Ensures length of 24 for encryption key
        Trace.Assert(EncryptionKey.Length == 24, "Encryption key must be exactly 24 characters of ASCII text (24 bytes)");

        this.IV = Encoding.ASCII.GetBytes(IV);
        // Ensures length of 8 for init. vector
        Trace.Assert(IV.Length == 8, "Init. vector must be exactly 8 characters of ASCII text (8 bytes)");
    }

    /// <summary>
    /// Encrypts a text block
    /// </summary>
    public string Encrypt(string textToEncrypt)
    {
        TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
        tdes.Key = EncryptionKey;
        tdes.IV = IV;

        byte[] buffer = Encoding.ASCII.GetBytes(textToEncrypt);
        return Convert.ToBase64String(tdes.CreateEncryptor().TransformFinalBlock(buffer, 0, buffer.Length));
    }

    /// <summary>
    /// Decrypts an encrypted text block
    /// </summary>
    public string Decrypt(string textToDecrypt)
    {
        byte[] buffer = Convert.FromBase64String(textToDecrypt);

        TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
        des.Key = EncryptionKey;
        des.IV = IV;

        return Encoding.ASCII.GetString(des.CreateDecryptor().TransformFinalBlock(buffer, 0, buffer.Length));
    }
}

Java Implementation

public class TripleDesImplementation {
    private String key;
    private String initializationVector;
    public TripleDesImplementation(String key, String initializationVector)
    {
        this.key = key;
        this.initializationVector = initializationVector;
    }

    public String encryptText(String plainText) throws Exception{
    //----  Use specified 3DES key and IV from other source --------------
      byte[] plaintext = plainText.getBytes();
      byte[] tdesKeyData = key.getBytes();

      byte[] myIV = initializationVector.getBytes();

      Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
      SecretKeySpec    myKey = new SecretKeySpec(tdesKeyData, "DESede");
      IvParameterSpec ivspec = new IvParameterSpec(myIV);

      c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
      byte[] cipherText = c3des.doFinal(plaintext);

      return Base64Coder.encodeString(new String(cipherText));
    }
}

11 comments:

strokov said...

Hey great info, I am also dealing with this problem, but I am doing it from C# Desktop application to a Java servlet, so I need to decrypt on the Java side, I tried to replicate the C# code for decrypt in Java but I only get a 40% of the message decrypted fine, any idea?

For example I sent this as the text to encrypt "_2G4WS52M6W1459365" and I get this "_2G4WS52J���iS�5_2G4WS52J���iS�5"

Sam Walker said...

From first glance, it looks like an encoding issue, but I'd have to see your code to make a better guess.

Yann said...

Hi Sam, I've tried your code and it seems to work excepted when you get an encryted text from java wi th at least one "/" . The C# method can't decrypt the message.Have you already face to this problem ?

Yann said...

Sorry, in fact it's not due to to the "/"

I tried to encrypt this random string "AeRTYhgFD678de4" with these key "4d89g13j4j91j27c582ji693" and iv "zerocool".

In Java i got : cANU9X90FvhHP5wH3t9VMQ==

In C# I got:cANU9X90FvhHgZwH3t9VMQ==

So I can't decrypt the encrypted text from java.

Sam Walker said...

Yann, I haven't experienced this problem before, but I'll take a look. In the mean time, if you end up figuring out the solution, make sure you post it. Thanks!

Sam

cawineguy said...

When I found this post I got excited, because this is precisely the problem I'm trying to solve, except the other direction. Here is the .NET code I want to convert to java:
Decryptor dec = new Decryptor(EncryptionAlgorithm.TripleDes);

dec.IV = Encoding.ASCII.GetBytes(ssIV);
byte[] key = Encoding.ASCII.GetBytes(ssKEY);

byte[] plainText = dec.Decrypt(Convert.FromBase64String(encryptedCCNumber), key);
return Encoding.ASCII.GetString(plainText);

The issue is that the key is 16 characters, and that is an illegal length for the java decryption. Am I using the wrong algorithm on the java side?

Sam Walker said...

cawineguy, I'm afraid I'm not much of a java expert, so I'm afraid I can't tell you what's going on right off the bat. I'll take a look at it and get back to you if I find anything.

cawineguy said...

Sam - thanks, I found it in the article you originally referenced. The problem is that Java requires a 24 byte key; so I needed to take the 16 byte password, and pad with the first 8 bytes of the password. now it works like a champ!

Mr.Madoogun.Com said...

So Many Thank Thank Thank.

Very good topic

Mark D said...

Sam,
THANK YOU VERY MUCH!!! This is 75% of what I have been looking high and low for. Can you please post the Java Decryptor function for the remaining 25% of the functionality I need??? I am not a java developer, so am having a hard time with this.
Thanks a lot for this great post...

Author Nitin said...

have modified the java code somewhat, but it works fine for me, I encrypting the string in .net and decrypting it on java... hope it'll be helpful for you.

package com.bay.bizlogic;

/**
* Created by Nitin Gaur.
* User: admin
* Date: 4 Feb, 2010
* Time: 12:46:23 PM
* To change this template use File | Settings | File Templates.
*/
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class TripleDES {
private String key;
private String initializationVector;
public TripleDES(String key, String initializationVector)
{
this.key = key;
this.initializationVector = initializationVector;

}

public String encryptText(String plainText) throws Exception{
//---- Use specified 3DES key and IV from other source --------------
byte[] plaintext = plainText.getBytes();
byte[] tdesKeyData = key.getBytes();

// byte[] myIV = initializationVector.getBytes();

Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(initializationVector.getBytes());

c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
byte[] cipherText = c3des.doFinal(plaintext);

return new sun.misc.BASE64Encoder().encode(cipherText);
}
public String decryptText(String cipherText)throws Exception{
byte[] encData = new sun.misc.BASE64Decoder().decodeBuffer(cipherText);
Cipher decipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
byte[] tdesKeyData = key.getBytes();
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(initializationVector.getBytes());
decipher.init(Cipher.DECRYPT_MODE, myKey, ivspec);
byte[] plainText = decipher.doFinal(encData);
return new String(plainText);
}
}


Thanks...
India