RSA Encryption/Decryption: .NET, Java 2 and CryptoAPI


This article describes RSA PKCS#1 encryption interoperability between Java 2, .NET Framework 1.1 and CryptoAPI. The two samples show how to RSA encrypt in Java with RSA/ECB/PKCS1Padding using a certificate public key in a Sun JKS keystore file, (with the Bouncy Castle provider installed), and decrypting that content in .NET, specifying PKCS#1 v1.5 padding, using the matching RSA private key contained in a CryptoAPI key container. The RSA PKCS#1 type 2 encryption block is always the same size as the RSA public key modulus; e.g. 128 bytes for a 1024 bit RSA public key.

.NET RSA
.NET Framework 1.1 contains cryptographic capability via the System.Security.Cryptography.RSACryptoServiceProvider to generate RSA digital signatures (using RSA private key), and RSA data encryption (using RSA public key). Due to security issues and performance issues, RSA encryption and signature-generation can only be performed on small amounts of data:

Java 2 RSA
Standard Java 2 distribution includes security provider support for generation of RSA digital signatures, but does NOT contain a provider implementation for generating RSA encrypted data. An extra provider must be added to obtain this capability from standard Java 2, such as the Bouncy Castle Provider.

CryptoAPI RSA
Windows 2000+ supports RSA encryption with CryptEncrypt(). The encryption block buffer returned is in little-endian byte order (compared to big-endian for Java and .NET above). So, for example, to decrypt within .NET a PKCS#1 type 2 encryption block generated by CryptoAPI CryptEncrypt(), simply reverse the byte array order, using Array.Reverse(byte[] cryptencryptRSAblock), before decryption .


Java 2 RSA Encryption Sample (RSA/ECB/PKCS1Padding)

import java.io.*; import java.math.BigInteger; import java.security.*; import java.security.cert.*; import java.security.interfaces.*; import javax.crypto.Cipher; import org.bouncycastle.jce.provider.BouncyCastleProvider; class RSAJEncrypt { public static void main(String[] args) { if (args.length != 1) System.out.println("Usage: RSAEncrypt nameOfFileToEncrypt"); else try{ Security.addProvider(new BouncyCastleProvider()); /* Use existing keystore */ String ALIAS = "mykeyalias"; // keystore alias KeyStore keystore = KeyStore.getInstance("JKS"); keystore.load(new FileInputStream(".keystore"), null); X509Certificate cert = (X509Certificate)keystore.getCertificate(ALIAS); Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC"); rsaCipher.init(Cipher.ENCRYPT_MODE, cert); //------ Get the content data from file ------------- File f = new File(args[0]) ; int sizecontent = ((int) f.length()); byte[] data = new byte[sizecontent]; try { FileInputStream freader = new FileInputStream(f); System.out.println("\nContent Bytes: " + freader.read(data, 0, sizecontent)); freader.close(); } catch(IOException ioe) { System.out.println(ioe.toString()); return; } byte[] encrypteddata = rsaCipher.doFinal(data); RSAJEncrypt.displayData(encrypteddata); /* Save the signature in a file */ FileOutputStream sigfos = new FileOutputStream("rsaJencrypted"); sigfos.write(encrypteddata); sigfos.close(); } catch (Exception e) { System.err.println("Caught exception " + e.toString()); } } private static void displayData(byte[] data) { System.out.println("Size of encrypted data: " + data.length) ; int bytecon = 0; //to get unsigned byte representation for(int i=0; i<data.length ; i++){ bytecon = data[i] & 0xFF ; // byte-wise AND converts signed byte to unsigned. if(bytecon<16) System.out.print("0" + Integer.toHexString(bytecon).toUpperCase() + " "); // pad on left if single hex digit. else System.out.print(Integer.toHexString(bytecon).toUpperCase() + " "); // pad on left if single hex digit. } } }

.NET Framework 1.1 RSA Decryption Sample (PKCS#1 v1.5 padding)
using System; using System.Text; using System.Security.Cryptography; using System.IO; class RSANetDecrypt { const string ContainerName = "{myCryptoAPIkeycontainername}" ; const int AT_KEYEXCHANGE = 1; const int AT_SIGNATURE = 2; static void Main(string[] args) { CspParameters cp = new CspParameters(); cp.KeyContainerName = ContainerName; cp.KeyNumber = AT_KEYEXCHANGE; RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider(cp); Console.WriteLine("Got CSP"); byte[] encdata = GetFileBytes(args[0]); // If RSA encryption block created with CryptoAPI CryptEncrypt(), reverse the bytes // Array.Reverse(encdata) ; if(encdata==null) return; Console.WriteLine("Size of encrypted data {0} bytes", encdata.Length) ; try{ byte[] decrypteddata = rsaCSP.Decrypt( encdata, false); Console.WriteLine("Decrypted data size: {0}", decrypteddata.Length); DisplayBytes(decrypteddata); } catch(Exception exc){ Console.WriteLine("Couldn't decrypt file\n{0}", exc.Message); } } private static byte[] GetFileBytes(String filename){ if(!File.Exists(filename)) return null; Stream stream=new FileStream(filename,FileMode.Open); int datalen = (int)stream.Length; byte[] filebytes =new byte[datalen]; stream.Seek(0,SeekOrigin.Begin); stream.Read(filebytes,0,datalen); stream.Close(); return filebytes; } private static void DisplayBytes(byte[] data) { for(int i=1; i<=data.Length; i++){ Console.Write("{0:X2} ", data[i-1]) ; if(i%16 == 0) Console.WriteLine(""); } Console.WriteLine(); } }


Michel I. Gallant
neutron@istar.ca