Simple interoperable encryption in Java and .net

It is quite common to require encryption of data that is being sent between different systems. More often than not, the scenario is also a simple point-to-point communication. In these cases, a public key approach adds significant complexity to the solution and could be replaced by an equally secure alternative based on symmetric encryption. This article describes a simple approach to such a solution that also demonstrates interoperability between Java and .net environments.

Based on the scenario described above, the following criteria have been identified for selection of the encryption algorithm:

  • The algorithm should build on open standards as this generally is good for interoperability.
  • The algorithm should not be protected by patents.
  • The algorithm should have no severe known vulnerabilities.
  • The encryption should build on a symmetric cipher with a shared secret, which is simple and generally good from a performance perspective.

Additional requirements on the overall solution are:

  • Every encrypted message should utilize an initialization vector (IV) in order to avoid ever producing the same ciphertext, even if the source message could be identical (which could be quite common in a system-to-system communication scenario).
  • The encryption key should be generated from a shared password. This is practical when agreeing on the key, since it can be performed without passing the key in binary form between the communicating parties.

The above criteria can be met, for example, by using the AES (Rijndael) encryption algorithm, which is a modern, well-tested and high performing block cipher. Another option could be Triple DES which has been around for a long time and is very well-tested to withstand cryptanalysis, but not very efficient in software implementations. This leaves AES as the choice.

As with pretty much all block ciphers, AES uses a binary key for encryption and decryption. This binary key needs to be derived from a password, as was one of the requirements for the solution. There are several methods of deriving binary key data from a password, and they are typically employing different hashing algorithms. One such method is described in RFC2898 and will work for our purposes.

The algorithm-specific parameters chosen for the solution are:

Encryption and decryption

  • Algorithm: AES.
  • Key length: 128 bits (which gives good protection and at the same time works well with US export restrictions).
  • Block size: 128 bits.
  • Operation modus: CBC (Cipher Block Chaining – which prevents repeated input data from producing repeated cipher text).
  • Padding: PKCS#7 (Padding is used for filling the block with data if the plaintext does not fit an even number of blocks. PKCS#7 and PKCS#5 are the same for practical purposes, but PKCS#5 is formally only defined for 64 bit block sizes)

Key generation

  • Key generator: RFC2898
  • Method: PBKDF2
  • Pseudorandom function (PRF): Hmac with SHA-1
  • Number of iterations: 1024

When the encrypted information is transferred between sender and receiver, it needs to be stored in a way that enforces interoperability. Binary information is always tricky and can be interpreted differently on different platforms. ASCII is a lot easier to work with, and in order to use ASCII as the carrier, Base64 encoding and decoding is used by the sender and receiver.

Implementation

These implementation examples shows Java and C# code. The C# examples have been verified using the Mono platform – an open source implementation of the CLR and C# language which is binary compatible with the .net framework. Mono is cross-platform and runs on various Linux platforms as well as MacOS and Windows.

Key generation in Java

The following code example creates an AES key from a password. The password should be known only to the sender and receiver. The salt used can be communicated openly and is only used to prevent keys from being reverse looked up using rainbow table attacks (in real world implementations, the salt should be configurable though, not hard-coded as in this example).

String password = "sOme*ShaREd*SecreT";
byte[] salt = new byte[]{-84, -119, 2556, -100, 100, -120, -45, 84, 67, 96, 10, 24111, 112, -119, 3};
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1024, 128);
SecretKey tmp = factory.generateSecret(spec);
secret = new SecretKeySpec(tmp.getEncoded(), "AES");
System.out.println("Key:" + Base64.encode(secret.getEncoded()));

Key generation in C#

The following code creates an AES key from a password in the same way as the previous, Java-based, example. The resulting key is identical. Please note that the salt is the same in both examples, the difference is that Java’s bytes are always signed, whereas C# uses unsigned bytes by default.

byte[] salt = new byte[]{172, 137, 25, 56156100136, 211, 84, 67, 96, 10, 24111112, 137, 3};
int iterations = 1024;
var rfc2898 =
new
System.Security.Cryptography.Rfc2898DeriveBytes("sOme*ShaREd*SecreT", salt, iterations);
byte[] key = rfc2898.GetBytes(16);
String keyB64 = Convert.ToBase64String(key);
System.Console.WriteLine("Key: " + keyB64);

Encryption in Java

The following example encrypts a message in the form of a string stored in the variable cleartext. By not initializing the algorithm with an IV, a unique byte sequence will be generated for every invocation of the code. This IV needs to be sent with the encrypted message in order for the receiving system to decrypt the message. The variable secret contains the binary secret key generated in the previous example.

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
iv = params.getParameterSpec(IvParameterSpec.class).getIV();
ciphertext = cipher.doFinal(cleartext.getBytes("UTF-8"));
System.out.println("IV:" + Base64.encode(iv));
System.out.println("Cipher text:" + Base64.encode(ciphertext));

Encryption in C#

The following example encrypts a message in the form of a string stored in the variable cleartext. The variable secret contains the binary secret key generated in the previous example.

AesManaged aesCipher = new AesManaged();
aesCipher.KeySize = 128;
aesCipher.BlockSize = 128;
aesCipher.Mode = CipherMode.CBC;
aesCipher.Padding = PaddingMode.PKCS7;
aesCipher.Key = key;
byte[] b = System.Text.Encoding.UTF8.GetBytes(cleartext);
ICryptoTransform encryptTransform = aesCipher.CreateEncryptor();
byte[] ctext = encryptTransform.TransformFinalBlock(b, 0, b.Length);
System.console.WriteLine("IV:" + Convert.ToBase64String(aesCipher.IV));
System.Console.WriteLine("Cipher text: " + Convert.ToBase64String(ctext));

Decryption in Java

Decryption is performed by initializing the algorithm with the same IV as was used during encryption and specifying the decryption mode of operation. In the example below, the variable iv is assumed to contain the correct initialization vector:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");
System.out.println(plaintext);

Decryption in C#

Decryption is performed asuuming that aesCipher is initialized with the same parameters as earlier and that the IV used during encryption is stored in the variable iv. The encrypted message is stored in the varible cipherText:

aesCipher.IV = iv;
ICryptoTransform decryptTransform = aesCipher.CreateDecryptor();
byte[] plainText = decryptTransform.TransformFinalBlock(cipherText, 0, cipherText.Length);
System.Console.WriteLine("Decrypted: " + System.Text.Encoding.UTF8.GetString(plainText));

JavaScript (added 2018-03-12)

The same functionality can be implemented in JavaScript as well. I am using the crypto library that is available by default in node.js. The following example will create the key, perform a decryption of the outputs of the previous examples and then perform encryption again.

var crypto = require('crypto')

// Derive a key
var salt = new Uint8Array([172, 137, 25, 56, 156, 100, 136, 211, 84, 67, 96, 10, 24, 111, 112, 137, 3]);
var password = "sOme*ShaREd*SecreT";
var iterations = 1024;
var prng = "sha1"
var keylen = 128/8;
var derivedKey = crypto.pbkdf2Sync(password, salt, iterations, keylen, prng)
console.log();
console.log("--------- Key generation ------------");
console.log("Key length: " + keylen*8 + " bits")
console.log("Salt: " + salt);
console.log("Password: " + password + "(ASCII)");
console.log("Iterations: " + iterations);
console.log("Algorithm: " + prng);
console.log("Encoding: BASE64");
console.log("-------------------------------------");
console.log("Key: " + Buffer.from(derivedKey).toString('base64'));
console.log("-------------------------------------");
console.log();

// Do some decryption
var ivb64 = 'TZuWY0W5Yn9l9F2DEiU0hg==';
var iv = Buffer.from(ivb64, 'base64');
ciphertextb64 = 'eq1sel7Muoz/nOO8YFNLG629iM0qis+oDhvzT9pmvW8=';
var ciphertext = Buffer.from(ciphertextb64, 'base64');
var ciphername = 'aes-128-cbc';
var decipher = crypto.createDecipheriv(ciphername, derivedKey, iv);
let decrypted = decipher.update(ciphertext);
decrypted += decipher.final('utf8');
console.log("----------- Decryption --------------");
console.log("Method: " + ciphername);
console.log("IV: " + ivb64 + " (BASE64)");
console.log("Key: " + Buffer.from(derivedKey).toString('base64') + " (BASE64)");
console.log("Ciphertext: " + ciphertextb64 + " (BASE64)");
console.log("-------------------------------------");
console.log("Plain text: " + decrypted);
console.log("-------------------------------------");
console.log();

// Do some encryption
var cipher = crypto.createCipheriv('aes-128-cbc', derivedKey, iv);
var plaintext = '*** Top secret ***';
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
encrypted += cipher.final('base64');
console.log("----------- Encryption --------------");
console.log("Method: " + ciphername);
console.log("IV: " + ivb64 + " (BASE64)");
console.log("Key: " + Buffer.from(derivedKey).toString('base64') + " (BASE64)");
console.log("Plain text: " + plaintext);
console.log("-------------------------------------");
console.log("Ciphertext: " + encrypted + " (BASE64)");
console.log("-------------------------------------");
console.log();

In order to run the JavaScript example, you should have node.js installed. Save the contents into a file, e.g. app.js and run with node app.js

If you wish to run the code in a browser (which does not implement the crypto library from node), you can install the browserify module:

npm install -g browserify

Then “browserify” the node.js code:

browserify app.js -o bundle.js

Finally, add a simple HTML wrapper (e.g. index.html) for the script:

<html>
 <head>
 </head>
 <body>
 <script src="bundle.js"></script>
 Open the JS console to view output
 </body>
</html>

Open the html file in the browser and then view the output in the debug console. Unfortunately the “browserified” output gets fairly large (a few hundred kilobytes), so it might not be a solution for everyone.

Final words

The above examples can easily be generalized into production code which can be used to ensure confidentiality between two systems in many types of integration scenarios. The code has been verified to work in both Java and .net environments, but the full code has been omitted (imports etc.) in the above examples for readbility. A working code example can be found here (ODT document with both a Java and C# class).

References

20 thoughts on “Simple interoperable encryption in Java and .net

    • You sure can – when you generate the key, the constructor for PBEKeySpec takes the key length as the fourth parameter: PBEKeySpec(char[] password, byte[] salt, int iterationCount,int keyLength).

  1. Pingback: Chiffrement interopérable en Java, PL/SQL, Javascript, et avec des progiciels | Mossroy

  2. Hi steelmon,
    Very nice article, cleared many of my concepts about encryption and decryption in c# and java.
    In my scenario, i am creating a encrypted text using c#, which i need to decrypt in java.
    In java IV element is needed, for decryption which is generated while encryption only, in java.
    How can I decrypt that string, without using IV parameter in java.

    Any help shall be appreciated.
    Manoj.Maity

    • Hello Manoj,
      Thank you for your feedback! In order to decrypt a message produced from c# in Java, you would need the IV output from the c# program together with the encrypted output.

      To get the IV from your c# program:

      String ivB64 = Convert.ToBase64String(aesCipher.IV);
      System.Console.WriteLine("IV: " + ivB64);

      …and read it into your Java program:

      byte[] iv = Base64.decode("TZuWY0W5Yn9l9F2DEiU0hg=="); // Replace B64 string with the output of the c# program

      Next, decipher (assuming the shared key in variable “secret” and the encrypted message in “ciphertext”:

      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
      cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
      String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");

      Hope this helps. You can see the full code example in the open office (odt) document linked to in the last section of the article.

      • Thanks a lot stellan.
        One more doubt, whether java supports 256 length key AES encryption.

        Thanks Again,
        Manoj Maity

      • Hi Manoj. Yes – the Java AES implementation supports 256 bits. There may be some restrictions on key length however, depending on which country you are in due to US export regulations.

    • Hi Abydsangel and thank you. The salt is not secret – if you have computed a salt in C#, just send it openly to the receiving side and pass it to the Java program as a byte array. Hope this helps!

  3. Hi
    Thanks for the article. I’m a bit curious on how do you transfer the IV to the receiver? Is it safe to send it together with the encrypted data, or does that make the use of IV pointless?

    • Hi Markus. The IV is not secret at all – the purpose of the IV is to always produce a unique ciphertext, even if you would send the same message more than once, so you should always generate a random IV with every message exchange and pass it in the clear together with the ciphertext.

  4. Hi Stellan! Thank you for this great and helpful post. Is there a way to decrypt an already encrypted text (by java or c#) in java script for showing it on a webpage and within a hybrid mobile app?

    Thanks,
    Alfred

    • Hi Alfred. You certainly can use JavaScript to both encrypt and decrypt using the same standards. I have just updated the article with a node.js example, as well as information on how you can transform from node to a pure browser implementation.

  5. HI Stellan, I can see the the code is working when I copied and pasted your sample code in my visual studio. However, when I break this into class I always gets this error: ‘Padding is invalid and cannot be removed.’. From your working example in C# found insde odt file, you are decrypting ‘cipherText’ on the same instance it was encrypted which I think that this will not happen in live. Here’s a sample of what I’ve created: https://drive.google.com/file/d/1nqrwV4_JEZPCR7REDxsta49H-BDgEE__/view?usp=sharing

Leave a reply to steelmon Cancel reply