background

My .NET Stuff

Wednesday, July 4, 2007

Crypto interop between .NET & Java

Of late I have been searching for ways to encrypt text in Java and decrypt the same in .NET. I couldn't find anything that met my needs. The closest one I found was in Java forums: http://forum.java.sun.com/thread.jspa?threadID=734949&messageID=4227225

I decided to write a sample that has both encryption and decryption in both Java and .NET. I am no crypto expert. However I did some research and found that the default .NET encryption configuration is set to high. On the Java side, I did not find any out of the box encryption libraries with JDK SE 1.6 that are good enough for .NET. BouncyCastle has a great library for encryption. You can find that at: http://www.bouncycastle.org/. They also do have a port for .NET (C#).

Anyways, here's my take at the sample:


.NET Code





    1 /*****************************************************

    2  * Author   : Charles Prakash Dasari

    3  * Copyright © 2007 Charles Prakash Dasari.

    4  * 

    5  * THE AUTHOR DOES NOT ASSUME ANY RESPONSIBILITY FOR ANY KIND OF USAGE OF THE PROVIDED

    6  * SOURCE CODE. THE CODE IS PROVIDED AS IS.

    7  * 

    8  * ---------------------------------------------------

    9  * 

   10  * You are free to use and modify this code to suit your needs as long as

   11  * you acknowledge the author and waive the author of any responsibility whatsoever.

   12  * You must include the above statement as is.

   13  * 

   14  * ****************************************************/

   15 

   16 using System;

   17 using System.Collections.Generic;

   18 using System.Text;

   19 using System.Security.Cryptography;

   20 

   21 namespace crypttest

   22 {

   23     public class CryptFinal

   24     {

   25         static string passPhrase = "abcdefghijklmnopqrstuvwx";

   26         static byte[] salt = {

   27             (byte)0x49, (byte)0x76, (byte)0x61, (byte)0x6e, (byte)0x20, (byte)0x4d, 

   28             (byte)0x65, (byte)0x64, (byte)0x76, (byte)0x65, (byte)0x64, (byte)0x65,

   29             (byte)0x76

   30         };

   31         static int iterationCount = 1000;

   32         static int keyLength = 256;         // 32 bytes

   33         static int ivLength = 128;          // 16 bytes

   34 

   35         public static void Main(string[] args)

   36         {

   37             string password;

   38             Console.Write("Password to encrypt: ");

   39             password = Console.ReadLine();

   40 

   41             string encrypted = Encrypt(password);

   42             string decrypted = Decrypt(encrypted);

   43 

   44             Console.WriteLine("Encrypted: " + encrypted);

   45             Console.WriteLine("Decrypted: " + decrypted);

   46         }

   47 

   48         static ICryptoTransform ConstructCipher(bool forEncryption)

   49         {

   50             byte[] passPhraseBytes = ToUtf8ByteArray(passPhrase);

   51             Rfc2898DeriveBytes paramGen = new Rfc2898DeriveBytes(

   52                 passPhraseBytes,

   53                 salt,

   54                 iterationCount

   55             );

   56             Rijndael algorithm = Rijndael.Create();

   57             byte[] key = paramGen.GetBytes(keyLength / 8);

   58             byte[] iv  = paramGen.GetBytes(ivLength / 8);

   59 

   60             return (forEncryption ? algorithm.CreateEncryptor(key, iv)

   61                 : algorithm.CreateDecryptor(key, iv));

   62         }

   63         static byte[] ProcessBytesWithCipher(ICryptoTransform cipher, byte[] input)

   64         {

   65             return cipher.TransformFinalBlock(input, 0, input.Length);

   66         }

   67         static string Encrypt(string input)

   68         {

   69             return ToBase64String(

   70                 ProcessBytesWithCipher(

   71                     ConstructCipher(true),

   72                     ToUtf8ByteArray(input)

   73                 )

   74             );

   75         }

   76         static string Decrypt(string input)

   77         {

   78             return FromUtf8ByteArray(

   79                 ProcessBytesWithCipher(

   80                     ConstructCipher(false),

   81                     FromBase64String(input)

   82                 )

   83             );

   84         }

   85         static string ToBase64String(byte[] input)

   86         {

   87             return Convert.ToBase64String(input);

   88         }

   89         static byte[] FromBase64String(string input)

   90         {

   91             return Convert.FromBase64String(input);

   92         }

   93         static byte[] ToUtf8ByteArray(string input)

   94         {

   95             return Encoding.UTF8.GetBytes(input);

   96         }

   97         static string FromUtf8ByteArray(byte[] input)

   98         {

   99             return Encoding.UTF8.GetString(input);

  100         }

  101     }

  102 }




Java Code




    1 /**

    2  * Author   : Charles Prakash Dasari

    3  * Copyright © 2007 Charles Prakash Dasari.

    4  * 

    5  * THE AUTHOR DOES NOT ASSUME ANY RESPONSIBILITY FOR ANY KIND OF USAGE OF THE PROVIDED

    6  * SOURCE CODE. THE CODE IS PROVIDED AS IS.

    7  * 

    8  * ---------------------------------------------------

    9  * 

   10  * You are free to use and modify this code to suit your needs as long as

   11  * you acknowledge the author and waive the author of any responsibility whatsoever.

   12  * You must include the above statement as is.

   13  * 

   14  */

   15 

   16 import java.io.BufferedReader;

   17 import java.io.IOException;

   18 import java.io.InputStreamReader;

   19 import java.io.UnsupportedEncodingException;

   20 

   21 import org.bouncycastle.crypto.BufferedBlockCipher;

   22 import org.bouncycastle.crypto.CipherParameters;

   23 import org.bouncycastle.crypto.engines.AESEngine;

   24 import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;

   25 import org.bouncycastle.crypto.modes.CBCBlockCipher;

   26 import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;

   27 

   28 import sun.misc.BASE64Decoder;

   29 import sun.misc.BASE64Encoder;

   30 

   31 /**

   32  * @author prakashd

   33  *

   34  */

   35 public class CryptFinal {

   36 

   37     static String passPhrase = "abcdefghijklmnopqrstuvwx";

   38     static byte[] salt = {

   39         (byte)0x49, (byte)0x76, (byte)0x61, (byte)0x6e, (byte)0x20, (byte)0x4d, 

   40         (byte)0x65, (byte)0x64, (byte)0x76, (byte)0x65, (byte)0x64, (byte)0x65,

   41         (byte)0x76

   42     };

   43     static int iterationCount = 1000;

   44     static int keyLength = 256;     // 32 bytes

   45     static int ivLength = 128;      // 16 bytes

   46 

   47   /**

   48    * @param args

   49    */

   50   public static void main(String[] args) {

   51         try {

   52             String password = "";

   53 

   54             System.out.print("Password to encrypt: ");

   55             BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

   56             password = reader.readLine();

   57 

   58             String encrypted = encrypt(password);

   59             String decrypted = decrypt(encrypted);

   60 

   61             System.out.println("Encrypted: " + encrypted);

   62             System.out.println("Decrypted: " + decrypted);

   63 

   64         } catch (Throwable e) {

   65             e.printStackTrace();

   66         }

   67   }

   68     static BufferedBlockCipher constructCipher(boolean forEncryption) {

   69         byte[] passPhraseBytes = toUtf8ByteArray(passPhrase);

   70 

   71         PKCS5S2ParametersGenerator paramGen = new PKCS5S2ParametersGenerator();

   72         paramGen.init(passPhraseBytes, salt, iterationCount);

   73         CipherParameters cipherParam = paramGen.generateDerivedParameters(

   74             keyLength,

   75             ivLength

   76         );

   77 

   78         BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(

   79             new CBCBlockCipher(new AESEngine())

   80         );

   81 

   82         cipher.init(forEncryption, cipherParam);

   83 

   84         return cipher;

   85     }

   86     static byte[] processBytesWithCipher(BufferedBlockCipher cipher, byte[] input) {

   87         try {

   88             int expectedLength = cipher.getOutputSize(input.length);

   89 

   90             byte[] processed = new byte[expectedLength];

   91 

   92             int length = cipher.processBytes(input, 0, input.length, processed, 0);

   93             length += cipher.doFinal(processed, length);

   94 

   95             byte[] output = new byte[length];

   96 

   97             for (int i = 0; i < length; i++) output[i] = processed[i];

   98             return output;

   99         } catch (Throwable e) {

  100             // TODO: handle exception

  101             e.printStackTrace();

  102         }

  103 

  104         return null;

  105     }

  106     static String encrypt(String input) {

  107         return toBase64String(

  108             processBytesWithCipher(

  109                 constructCipher(true),

  110                 toUtf8ByteArray(input)

  111             )

  112         );

  113     }

  114     static String decrypt(String input) {

  115         return fromUtf8ByteArray(

  116                 processBytesWithCipher(

  117                     constructCipher(false),

  118                     fromBase64String(input)

  119                 )

  120             );

  121     }

  122     static String toBase64String(byte[] input) {

  123         // TODO: Since the below class is deprecated, find an alternate

  124         // for Base64 encoding

  125         return new BASE64Encoder().encode(input);

  126     }

  127     static byte[] fromBase64String(String input) {

  128         // TODO: Since the below class is deprecated, find an alternate

  129         // for Base64 encoding

  130         try {

  131             return new BASE64Decoder().decodeBuffer(input);

  132         } catch (IOException e) {

  133             // TODO Auto-generated catch block

  134             e.printStackTrace();

  135         }

  136 

  137         return null;

  138     }

  139     static byte[] toUtf8ByteArray(String input) {

  140         try {

  141             return input.getBytes("UTF8");

  142         } catch (UnsupportedEncodingException e) {

  143             // TODO Auto-generated catch block

  144             e.printStackTrace();

  145         }

  146 

  147         return null;

  148     }

  149     static String fromUtf8ByteArray(byte[] input) {

  150         try {

  151             return new String(input, "UTF8");

  152         } catch (UnsupportedEncodingException e) {

  153             // TODO Auto-generated catch block

  154             e.printStackTrace();

  155         }

  156 

  157         return null;

  158     }

  159 }




Notes:

  • This sample code fails to comply with the security best practices for encryption – like having the password phrase in the code, and probably more. When you use this code, you have to incorporate into your system adhering to the security practices that are followed within that system.
  • There is a known issue. If the input is longer than 47 characters, we see extra carriage return and line feed characters (\r\n) in the encoded output. My suspicion is that the base-64 encoder is buggy. The best way to identify that is to compare the produced bytes both in Java and in .NET before they are base-64 encoded. I didn't get the time to do the same. Any confirmation on the same will be appreciated.
  • This sample uses the 256-bit encryption using Rijndael algorithm (aka AES in BouncyCastle terminology).
  • This sample uses Java's deprecated sun.misc.BASE64Encoder/Decoder classes. You have to use your own base-64 encoding helpers.


Enjoy!

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]



<< Home