C#/Java Cryptography Equivalence

Introduction

Cryptography isn't supposed to be particular to a certain programming language but the notion of "supposed to" isn't veru useful when it comes to computers in general. Since I've been working on a web service in Java EE and a C# application that uses it and encryption using RSA and AES is required on both ends, I thought it wise to test if Java and C# have interoperable cryptographic functions.

Note that the Java code requires Apache's commons-codec-1.7 library and that the C# code requires the Bouncy Castle C# library! The code and some examples of the files generated by the programs are available here.

Structure

Two programs have been made, one in Java using NetBeans and one in C# using Visual Studio 2010. The core of both programs are the classes RSATester and AESTester. They also have a lot of ancillary functionality to read from and write to XML files as that is how the two programs exchange data. Here are the four kinds of data files generated by the two implementations.

File for an RSA key pair:

	
<KeyPair type="RSA">
  <PrivateKey encoding="PKCS#8">Base64 encoded version of a private key in PKCS#8 format.</PrivateKey>
  <PublicKey encoding="X509">Base64 encoded version of a public key in X.509 format.</PublicKey>
</KeyPair>
	

Data encrypted with RSA is formatted as shown below. Note that we don't give a reference to which keys were used. The programs are simple and we assume that we only have one RSA key pair generated by each program.

	
<Message type="RSA">
  <PlainText>This is the message that we later encrypt.</PlainText>
  <Encrypted>Base64 encoded version of the encrypted message.</Encrypted>
</Message>
	

We also have a format for RSA signatures. Again we assume that there is only one RSA key pair to be considered.

	
<SignedData type="RSA">
  <Data>Base64 encoded version of the byte array we have signed.</Data>
  <Signature>Base64 encoded version of the signature.</Signature>
</SignedData>
	

AES is a symmetric cipher so we include the password and initialization vector(IV) in the file containing the message.

	
<Message type="AES">
  <Password>Base64 encoded version of the 16-byte array password.</Password>
  <IV>Base64 encoded IV</IV>
  <PlainText>The message is in ordinary text so no need to base64 encode it.</PlainText>
  <Encrypted>Base64 encoded version of the message after it has been encrypted.</Encrypted>
</Message>
	

Running the examples

Both programs follow the convention "<name of program> generate|verify". So to run the Java program to generate four files we write on the command line "java -jar CryptoCompatibility.jar generate". To make the C# program do the same thing we run "CryptoCompatibility.exe generate". The Java implementation will generate one of the kind of file as explained above, named rsa_keypair_from_java.xml, encrypted_rsa_from_java.xml, signed_rsa_from_java.xml and encrypted_aes_from_java.xml. The C# implementation will generate files with the names rsa_keypair_from_csharp.xml, encrypted_rsa_from_csharp.xml, signed_rsa_from_csharp.xml and encrypted_aes_from_csharp.xml. Graphically:

To verify that the files produced by the C# implementation is correctly decoded by the Java implementation we copy the files generated by the C# program to the directory where the Java program runs and write on the command line "java -jar CryptoCompatibility.jar verify". It will then read the file rsa_keypair_from_csharp.xml to be able to verify the files containing data encrypted and signed by the C# program. Similarly the C# program will look for the files ending with the word java when we run "CryptoCompatibility.exe verify".

Below is the output we get when we first run the Java program to generate four files and then verify four files coming from the C# program.


F:\Programming\Java\CryptoCompatibility\dist>java -jar CryptoCompatibility.jar g
enerate
 - - - - - - - - - - - - - - - - - -
Private key in PKCS base64: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIpg
VkUKTxkFIYvu5zpnE7fGQyER7RplxU92ywNW677OJ4dZRtu/JFVLHo7V1cC5XRJ13gJO8d0CfdJtLStf
tpNeyYEZvEDqC1GbhWxLetAuAlCfWvHYuMykzTXUvoqASn59khPKjlY1dKFo5zr+VycYqaFBet8DbtWx
N1qPAv0HAgMBAAECgYAMcMTQyDB8S13MutnHAmVfbE+0tWvfp66pikCOOE5RhS9Al+Iq93BIyswgg1EI
VqHrevVOt9I+0G6HcJFTCPPdAgL1QLVv4LxBS8dLk9KEoCNEAKoElLe847BFFSUeZhzK75plu4YpRqwL
nuf6W3Z2apHaPK2X8Z8Rm/7ulM+wgQJBAMtYSLtW95fcN8hw5Glz6ncvnyj6yGqcvWSeY2K5i/ToscRA
wa3Z3CJBqnNkLjtoTz+JtWgeztB/1rYf8qmKYzECQQCuNUr6sbsP9lr/Bk6+IlZkRm8/49gmJQbu+GQG
K8U5u8eqqTP2TQi5dqUlq6m4/bU31M6Ko0rNTnFg6Wl4wCW3AkEAi7t4B1xkubM3lgKVwy1Hgm10Fqn2
jOR2dS0uKIpOdZZtienh7cCWt+ed5LuD1YkBZC0SPqFlBvvCJNbhXsXdEQJAJV8DXbJA6dS0gp7sx3cA
R3SgqvkKxmwNB7i7ZGOBZLg2xhKUNhMiev7LwMAJocizVm3NfQo4osBgLCUGXkQCEwJBAJoaZ20vNgIl
s+BHOAN+kGJDwBBJrN/KSwkUNU1rwQalPBxEm3bafI73yt3cVSzlYlqIFfFjSKI00gWNknL3Ru0=
 - - - - - - - - - - - - - - - - - -
Public key in X509 base64: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCKYFZFCk8ZBSGL7
uc6ZxO3xkMhEe0aZcVPdssDVuu+zieHWUbbvyRVSx6O1dXAuV0Sdd4CTvHdAn3SbS0rX7aTXsmBGbxA6
gtRm4VsS3rQLgJQn1rx2LjMpM011L6KgEp+fZITyo5WNXShaOc6/lcnGKmhQXrfA27VsTdajwL9BwIDA
QAB
Original string to be encrypted using RSA: 'This is a message used to show RSA e
ncryption in Java.'
Encoded and encrypted message: 'JlFZI2FPM6+6AJO4kaYKtBdJMdzjXeCDB1iOEXpZK1bUXdzd
b0PQUJ6FQCFnjp4bROxy3Lkbgv7EGDA7TX3Y212hqOJwcWgmolIOXXFnk+hisg+FYG5cFPp2Lzbxsnkj
vRbGwE5OnBh62vq3U7zeGt+HnaEd64ZOX9NCSLuWLN4='
-------------------------------------------------------------
Message to be signed: This is a message used to show RSA encryption in Java.
Encoded signature: 'UjgoX6vDqqjJJWe4fjIJAjqUmI9K4hQRg2RA7p1+0OnQsWrOyd/yCs0vvJy8
7DaQsmHPixnqXb6G45F0lPoPtE23X472Qx5KxU/7b6eCfAlnmjbnJgIc3/x2zk2GxaOM9iMdsMwdK8EX
n5eo33x4weAXnzzvZAUA0GX6bpsFwA8='
-------------------------------------------------------------
Original message: This is a message used to show AES encryption in Java. It is m
eant to be decrypted in an equivalent C# program.
Encoded and encrypted message: '3ThGVhpY8NrFdxXDY3QjGLeQxnUAdY77VUwIXqxYHYivASgr
QcSf1VTGF4uIy4HghVr4VbFAq2ezTZZ73G5B6lyCuJRx7Zhh/cejlv64DRRypOQxhreCmZnKrbXCy8nA
6RiYVkWlQKELWUYW234stw=='
-------------------------------------------------------------

F:\Programming\Java\CryptoCompatibility\dist>java -jar CryptoCompatibility.jar v
erify
Original message: This is a message used to show RSA encryption in C#.
Encoded encrypted message: It2gUe/+MNW4UDPr+HOjVuzOYc8/H+fFR6V/vprRlmBnV0m8fnc18
knHYAVkaOkHANynVMhmGrLBeuMHwsnGgkEWWDvxHRslGt4YD1zgWpmWODymiDNTtHHBAeJsAwTRhtOd5
2K2tX9JRjngvhwcZf8FpWDosPEvICKBO3/4mZs=
Decrypted message: '
                This is a message used to show RSA encryption in C#.'
-------------------------------------------------------------
Original message: This is a message used to show RSA encryption in C#.
Encoded sig: Est5Jq2plHySFMkB/OvQT87q3phgGbVvBx825e2ACMt2wclfg0WfO9f+nv0CmQM7rnj
iQ1Z5SHZJ4PDMF2uIYPquPbsxDa1DdKFY4dHvdJc1W3Rqu14V82qzppdqrgVfhGk8iKLRsqC3GoDDvTL
70bbyH3ajeWhjnCuLfUrT3fQ=
VerSig, key: RSA
VerSig, data: [B@21a2fefd
Signature was valid!
-------------------------------------------------------------
Encoded encrypted message: 9CMjEjyV4Jc3u7RKNJyoGk/5fXxsbPgCbyMCfHJzEBdcPztXnPZPD
aoiUikVgsTN5PjdPKNxxFn/6oB6MrHYT82cRgGfi6lvpkELh3biUofkKk9WYyu2Zr/iwHhbwNj+EGkDo
k4Rg3RSxjmCUcCX6w==
Encoded password: kKUKT62cQvv1dROHHGKaMQ==
Encoded IV: iqbnJL/qMcYlKAXU2A7KOw==
Original message: This is a message used to show AES encryption in C#. It is mea
nt to be decrypted in an equivalent Java program.
Decrypted message: 'This is a message used to show AES encryption in C#. It is m
eant to be decrypted in an equivalent Java program.'
-------------------------------------------------------------

The runner code

The code for generating and verifying files is perhaps easier to understand than the explanation above. It has been slightly modified to remove formatting code and such stuff.

Java


String java_rsa_key_filename = "rsa_keypair_from_java.xml";
String java_rsa_encrypted_filename = "encrypted_rsa_from_java.xml";
String java_rsa_signed_filename = "signed_rsa_from_java.xml";
String java_aes_encrypted_filename = "encrypted_aes_from_java.xml";

String csharp_rsa_key_filename = "rsa_keypair_from_csharp.xml";
String csharp_rsa_encrypted_filename = "encrypted_rsa_from_csharp.xml";
String csharp_rsa_signed_filename = "signed_rsa_from_csharp.xml";
String csharp_aes_encrypted_filename = "encrypted_aes_from_csharp.xml";
				
if(args[0].compareToIgnoreCase("generate") == 0) {

	String messageRSA1 = "This is a message used to show RSA encryption in Java.";
	String messageAES1 = "This is a message used to show AES encryption in Java. "
			+ "It is meant to be decrypted in an equivalent C# program.";

	dumpKeys(java_rsa_key_filename);
	encryptDataRSA(messageRSA1, java_rsa_key_filename, java_rsa_encrypted_filename);
	signData(messageRSA1, java_rsa_key_filename, java_rsa_signed_filename);
	encryptDataAES(messageAES1, java_aes_encrypted_filename);
} else if(args[0].compareToIgnoreCase("verify") == 0) {

	decryptFileRSA(csharp_rsa_key_filename, csharp_rsa_encrypted_filename);
	verifySig(csharp_rsa_key_filename, csharp_rsa_signed_filename);
	decryptFileAES(csharp_aes_encrypted_filename);
} else {
	System.out.println("Usage: java -jar CryptoCompatibility.jar generate|verify");
}

The C# runner code is very similar:

C#



String java_rsa_key_filename = "rsa_keypair_from_java.xml";
String java_rsa_encrypted_filename = "encrypted_rsa_from_java.xml";
String java_rsa_signed_filename = "signed_rsa_from_java.xml";
String java_aes_encrypted_filename = "encrypted_aes_from_java.xml";

String csharp_rsa_key_filename = "rsa_keypair_from_csharp.xml";
String csharp_rsa_encrypted_filename = "encrypted_rsa_from_csharp.xml";
String csharp_rsa_signed_filename = "signed_rsa_from_csharp.xml";
String csharp_aes_encrypted_filename = "encrypted_aes_from_csharp.xml";

if(args[0].CompareTo("generate") == 0) {

	String messageRSA1 = "This is a message used to show RSA encryption in C#.";
	String messageAES1 = "This is a message used to show AES encryption in C#. "
			+ "It is meant to be decrypted in an equivalent Java program.";

	AsymmetricCipherKeyPair ackp = RSATester.MakeKeyPair();
	XMLHandler.WriteKeyPair(ackp, csharp_rsa_key_filename);
	encryptFileRSA(messageRSA1, csharp_rsa_key_filename, csharp_rsa_encrypted_filename);
	signData(messageRSA1, csharp_rsa_key_filename, csharp_rsa_signed_filename);
	encryptFileAES(messageAES1, csharp_aes_encrypted_filename);
} else if(args[0].CompareTo("verify") == 0) {

	decryptFileRSA(java_rsa_key_filename, java_rsa_encrypted_filename);
	verifySig(java_rsa_key_filename, java_rsa_signed_filename);
	decryptFileAES(java_aes_encrypted_filename);
} else {
	Console.WriteLine("Usage: CryptoCompatibility.exe generate|verify");
}


RSA key pairs

Let's first look at RSA key generation. It is to a large extent because of this step that I use the Bouncy Castle cryptographic library for C#, as the native C# library doesn't like exporting keys to other languages. Generating the keys in Java is simple enough. I've put it in a static function in RSATester:

Java


public static KeyPair generatePair() {
	KeyPairGenerator keyGen = null;
	
	try {
		keyGen = KeyPairGenerator.getInstance("RSA", "SunJSSE");
		SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
		keyGen.initialize(1024, random);
	} catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
		System.err.println(ex);
	}
	
	KeyPair pair = keyGen.generateKeyPair();
	
	return pair;
}

We have to specify some things concerning providers(underlying implementations, here stock Java stuff) and algorithms in the first two lines in the try-catch block. A combination of Google searches and trial-and-error will show you how to change these values to use different algorithms and providers. Doing the same thing in C# is similar(though here all classes are from Bouncy Castle):

C#


public static AsymmetricCipherKeyPair MakeKeyPair()
{
	RsaKeyPairGenerator rsaGen = new RsaKeyPairGenerator();

	rsaGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024));
	AsymmetricCipherKeyPair keyPair = rsaGen.GenerateKeyPair();

	return keyPair;
}

So in both cases we create some thingamajig that will generate the keys, create a source of randomness, feed the randomness into the thingamajig and make it generate keys 1024 bits long. Note the methods used to export the keys. In Java we see this in XMLWrapper.java on line 42 and down:

Java


byte[] privBytes = privKey.getEncoded();
String privEncoding = privKey.getFormat();
String encodedPriv = base64.encodeToString(privBytes); 

Then the same is done for the public key. In C# it's slightly different as we use a pair of KeyParameters most of the time. So to export a private key in C# we extract the private key parameter, create a standard key and then get a byte array through the GetEncoded() call. In this case the encoding for Java and C# is both PKCS#8 if memory serves me right.

C#


AsymmetricKeyParameter akpPriv = (AsymmetricKeyParameter) keyPair.Private;
PrivateKeyInfo privFact = PrivateKeyInfoFactory.CreatePrivateKeyInfo(akpPriv);
byte[] pkcsPriv = privFact.GetEncoded();
String privPkcsBase64 = Utilities.toBase64(pkcsPriv);

Note that we use encoding upon encoding! A PrivateKeyInfo object is a C#-specific data structure that is internally stored like some array of bytes. When we say "privFact.GetEncoded()" we get another array of bytes only this time in a standardized format. Both we can't write raw byte arrays in XML files so we give it a base64 encoding so we can write the data in standard characters.

RSA

RSA is an asymmetric cipher that can also be used to sign and authenticate agents. I won't explain it any further here except to point out that RSA can't encrypt large blocks of data(the size of the data block it can encrypt in one go is based on the key size) and must pad small blocks of data so as to get the right size. To test RSA encryption we first have to create an instance of the RSATester class. Let's start with Java where we have this code:

Java


XMLReader.KeyHolder kh = XMLReader.getKeyPair(keyFileName);
RSATester rsa = new RSATester(kh.privKey, kh.pubKey);

A KeyHolder is just a Java class serving the role of a tuple. The keys are of referenced in the code as types PrivateKey and PublicKey respectively but if you were to try to print out they types they would probably be something like RsaPrivCert and RsaPubKey. The actual encryption function is mainly made up of exception declarations. As we're already in the Java implementation we can go through the decryption, signing and signature verification code as well.

Java


public byte[] encryptRSA(byte[] data) throws 
            NoSuchAlgorithmException, 
            NoSuchPaddingException, 
            InvalidKeyException, 
            IllegalBlockSizeException, 
            BadPaddingException 
{

	Cipher ciph = Cipher.getInstance("RSA/ECB/NoPadding");
	ciph.init(Cipher.ENCRYPT_MODE, pubKey);
			
	return ciph.doFinal(data);
}

public byte[] decryptRSA(byte[] data) throws 
            NoSuchAlgorithmException, 
            NoSuchPaddingException, 
            InvalidKeyException, 
            IllegalBlockSizeException, 
            BadPaddingException 
{

	Cipher ciph = Cipher.getInstance("RSA/ECB/NoPadding");
	ciph.init(Cipher.DECRYPT_MODE, privKey);
			
	return ciph.doFinal(data);
}

public byte[] sign(byte[] data) throws 
            SignatureException, 
            InvalidKeyException, 
            NoSuchAlgorithmException 
{
	Signature sig = Signature.getInstance("SHA1withRSA");
	sig.initSign(privKey);
	sig.update(data);
	byte[] signature = sig.sign();
	
	return signature;
}

public Boolean verifySignature(byte[] data, byte[] signature) throws 
		NoSuchAlgorithmException, 
		SignatureException, 
		InvalidKeyException 
{
	Signature sig = Signature.getInstance("SHA1withRSA");
	sig.initVerify(pubKey);
	sig.update(data);
	
	return sig.verify(signature);
}

So the encryption and decryption functions are simple and almost identical. The key difference is that in the decryption function we have ciph.init(Cipher.DECRYPT_MODE, privKey); instead of ENCRYPT_MODE. The signature code I got from a stackoverflow question(using-sha1-and-rsa-with-java-security-signature-vs-messagedigest-and-cipher). Obviously we use the private key to sign and the public key to verify. One important point: the choice of encryption mode "RSA/ECB/NoPadding" is simply because that is the default method used in the C# Bouncy Castle RSA implementation. Speaking of which, to fetch a key pair using the C# program:

C#


XMLHandler.ParamHolder provider = XMLHandler.FetchKeyPair(keyFilename);
RSATester test = new RSATester(provider.PrivKey, provider.PubKey);

Here the private and public keys are referenced more specifically, as RsaPrivateCrtKeyParameters for the private key and the public key is referenced as type RsaKeyParameters. I'm sure you could use a more generic type but this is how I did it. The actual functions are as follows:

C#


public byte[] encrypt(byte[] data)
{
	RsaEngine engine = new RsaEngine();
	engine.Init(true, PubKey);

	return engine.ProcessBlock(data, 0, data.Length);
}

public byte[] decrypt(byte[] data)
{
	RsaEngine engine = new RsaEngine();
	engine.Init(false, PrivKey);

	return engine.ProcessBlock(data, 0, data.Length);
}

public byte[] sign(byte[] data)
{
	ISigner sig = SignerUtilities.GetSigner("SHA1withRSA");
	sig.Init(true, PrivKey);

	sig.BlockUpdate(data, 0, data.Length);

	return sig.GenerateSignature();
}

public Boolean verify(byte[] data, byte[] signature)
{
	ISigner sig = SignerUtilities.GetSigner("SHA1withRSA");
	sig.Init(false, PubKey);

	sig.BlockUpdate(data, 0, data.Length);

	return sig.VerifySignature(signature);
}

I had problem with ProcessBytes in the C# Bouncy Castle library for AES and I would encourage anyone using this code as a basis for actual software to check if the RSA engine's ProcessBlock function returns the entire result or if it has just done some part of the encryption/decryption you wanted. Just a caveat.

AES

AES is a symmetric cipher that can't be used to authenticate parties as neatly as RSA. It is however much more efficient than RSA for encryption. To mirror the way the RSA code was presented, let's first look at the constructor for the AESTester in Java:

Java


public AESTester(byte[] password, byte[] iv) {
	this.password = password;
	this.iv = iv;
}

Simple enough. The "password" and the iv are both 16 byte arrays. The two functions are then as follows:

Java


public byte[] encryptAES(byte[] data) throws 
		InvalidKeyException, 
		NoSuchAlgorithmException, 
		NoSuchPaddingException, 
		IllegalBlockSizeException, 
		BadPaddingException, 
		InvalidAlgorithmParameterException 
{
	Cipher crypto = Cipher.getInstance("AES/CBC/PKCS5Padding");
	Key key = new SecretKeySpec(password, "AES");
	IvParameterSpec ivSpec = new IvParameterSpec(iv);
	crypto.init(Cipher.ENCRYPT_MODE, key, ivSpec);
	
	return crypto.doFinal(data);
}

public byte[] decryptAES(byte[] data) throws 
		InvalidKeyException, 
		NoSuchAlgorithmException, 
		NoSuchPaddingException, 
		IllegalBlockSizeException, 
		BadPaddingException, 
		InvalidAlgorithmParameterException 
{
	Cipher crypto = Cipher.getInstance("AES/CBC/PKCS5Padding");
	Key key = new SecretKeySpec(password, "AES");
	IvParameterSpec ivSpec = new IvParameterSpec(iv);
	crypto.init(Cipher.DECRYPT_MODE, key, ivSpec);
	
	return crypto.doFinal(data);
}

Java tends by default doesn't deal with the IV overtly which caused me trouble when trying to make it work with C# in the past. C# insists on the use of an IV or salt so we have to make Java take note of the IV as well. The C# version of AESTester is constructed the same way as the Java version and here are the two functions that do the actual work:

C#


public byte[] encrypt(byte[] data)
{
	IBufferedCipher c = CipherUtilities.GetCipher("AES/CBC/PKCS7Padding");
	KeyParameter key = new KeyParameter(Password);
	ParametersWithIV withIV = new ParametersWithIV(key, IV);
	c.Init(true, withIV);

	byte[] encrypted = c.DoFinal(data);

	return encrypted;
}

public byte[] decrypt(byte[] data)
{
	IBufferedCipher c = CipherUtilities.GetCipher("AES/CBC/PKCS7Padding");
	KeyParameter key = new KeyParameter(Password);
	ParametersWithIV withIV = new ParametersWithIV(key, IV);
	c.Init(false, withIV);

	byte[] decrypted = c.DoFinal(data);

	return decrypted;
}

Note that we use PKCS7Padding in C# and PKCS5Padding in Java! This works fine but looks weird. Otherwise the main difference between the two versions is that we create a composite key ParametersWithIV using both the password-based key and the IV. As I'm sure you understand c.Init takes as its first argument whether or not we are encrypting.

Conclusion

This has been a kind of hackish exploration of making RSA and AES interoperate between Java and C#. If you want to use different modes for RSA and AES you can easily change the code and see what happens.

Slightly off-topic is my observation how horrid exception handling gets in many of these Java examples. It's familiar from Java EE whenever you do non-trivial things. I don't mind having the option to catch 7 different exceptions but should I really have to? Is "AlgorithmNotFoundException" really recoverable? Under what circumstance would that be? In these examples C# has more of a laissez-faire approach which I appreciate.