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:
- RSA signatures are generated by encryption of hash data,
e.g. 20 bytes for SHA1.
- RSA encryption data size limitations are slightly
less than the key modulus size, depending on the actual padding scheme used
(e.g. with 1024 bit (128 byte) RSA key, the size limit is 117 bytes for PKCS#1
v 1.5 padding.
Other padding schemes will have slightly different size limitations.
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
.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