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 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.

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*; import java.math.BigInteger; import*; import*; import*; 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: " +, 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