Skip to Main Content

Java Security

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

AES/Rijndael - Moving from .NET to Java

843811Dec 9 2008 — edited May 1 2010
I'm in the middle of converting a program from .NET to java, and I've been banging my head trying to get the java encryption algorithm to match the .NET algorithm. I'll admit, I know very little about .NET (and the guy who wrote the .NET code is long gone), so that's a big part of my problem.

I managed to get the password encryption converted to Java (an MD5 hash), but the credit card encryption piece is giving me fits. I'm sure I'm just missing some detail. I think it might have something to do with the PaddingMode defined in the .NET code, because I'm not really sure how to convert that over to Java-land. Could anyone help me out? Here's the .NET code below for the credit card decryption:

I can post the Java code I have so far if anyone thinks that'll help.

Thanks in advance!
public class Cryptographer {
	/// <summary>
	/// Use AES to encrypt data string. The output string is the encrypted bytes as a base64 string.
	/// The same password must be used to decrypt the string.
	/// </summary>
	/// <param name="data">Clear string to encrypt.</param>
	/// <param name="password">Password used to encrypt the string.</param>
	/// <returns>Encrypted result as Base64 string.</returns>
	public static string EncryptData(string data, string password, string salt) {
		if (data == null) throw new ArgumentNullException("data");
		if (password == null) throw new ArgumentNullException("password");

		byte[] encBytes = EncryptData(Encoding.UTF8.GetBytes(data), password, PaddingMode.ISO10126, salt);
		return Convert.ToBase64String(encBytes);
	}

	/// <summary>
	/// Decrypt the data string to the original string.  The data must be the base64 string
	/// returned from the EncryptData method.
	/// </summary>
	/// <param name="data">Encrypted data generated from EncryptData method.</param>
	/// <param name="password">Password used to decrypt the string.</param>
	/// <returns>Decrypted string.</returns>
	public static string DecryptData(string data, string password, string salt) {
		if (data == null) throw new ArgumentNullException("data");
		if (password == null) throw new ArgumentNullException("password");

		byte[] encBytes = Convert.FromBase64String(data);
		byte[] decBytes = DecryptData(encBytes, password, PaddingMode.ISO10126, salt);
		return Encoding.UTF8.GetString(decBytes);
	}

	public static byte[] EncryptData(byte[] data, string password, PaddingMode paddingMode, string salt) {
		if (data == null || data.Length == 0) throw new ArgumentNullException("data");
		if (password == null) throw new ArgumentNullException("password");

		PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, Encoding.UTF8.GetBytes(salt));
		RijndaelManaged rm = new RijndaelManaged();
		rm.Padding = paddingMode;
		ICryptoTransform encryptor = rm.CreateEncryptor(pdb.GetBytes(16), pdb.GetBytes(16));

		using (MemoryStream msEncrypt = new MemoryStream())
		using (CryptoStream encStream = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
			encStream.Write(data, 0, data.Length);
			encStream.FlushFinalBlock();
			return msEncrypt.ToArray();
		}
	}

	public static byte[] DecryptData(byte[] data, string password, PaddingMode paddingMode, string salt) {
		if (data == null || data.Length == 0) throw new ArgumentNullException("data");
		if (password == null) throw new ArgumentNullException("password");

		PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, Encoding.UTF8.GetBytes(salt));
		RijndaelManaged rm = new RijndaelManaged();
		rm.Padding = paddingMode;
		ICryptoTransform decryptor = rm.CreateDecryptor(pdb.GetBytes(16), pdb.GetBytes(16));

		using (MemoryStream msDecrypt = new MemoryStream(data))
		using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) {
			// Decrypted bytes will always be less then encrypted bytes, 
			//so len of encrypted data will be big enouph for buffer.
			byte[] fromEncrypt = new byte[data.Length];

			// Read as many bytes as possible.
			int read = csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
			if (read < fromEncrypt.Length) {
				// Return a byte array of proper size.
				byte[] clearBytes = new byte[read];
				Buffer.BlockCopy(fromEncrypt, 0, clearBytes, 0, read);
				return clearBytes;
			}
			return fromEncrypt;
		}
	}
}

Comments

843811
Since your .NET code uses ISO10126 padding and this is not available as part of the SunJCE provider, you will have to do the padding yourself or maybe find a provider that allows ISO10126. BouncyCastle provider have implemented ISO 10126-2 so it might be worth a look.
843811
Thanks for the info, sabre150.

I've installed BouncyCastle, and verified that BC is the provider being selected for "Rijndael/CBC/ISO10126Padding". I'm having trouble adding the padding correctly. Its probably something ignorant I'm doing.

I'm getting the following error. Line 228 is byte[] results = cipher.doFinal(encryptedBytes);
javax.crypto.BadPaddingException: pad block corrupted
	at org.bouncycastle.jce.provider.JCEBlockCipher.engineDoFinal(Unknown Source)
	at javax.crypto.Cipher.doFinal(DashoA12275)
	at com.****.util.CryptoUtil.decrypt5(CryptoUtil.java:228)
	at com.****.util.CryptoUtil.main(CryptoUtil.java:38)
Can anyone spot something obviously wrong with the code I'm using? I feel like I'm making this more complex than it needs to be.
public static String decrypt5(String encryptedText, String saltStr, String passwordStr) {
    String plainText = null;
    try {

        byte[] saltBytes = saltStr.getBytes("UTF8");
        byte[] passwordBytes = passwordStr.getBytes("UTF8");

        ISO10126d2Padding padding = new ISO10126d2Padding();
        SecureRandom random = new SecureRandom();
        padding.init(random);

        byte[] ivBytes = new byte[16];
        System.arraycopy(saltBytes, 0, ivBytes, 0, saltBytes.length < 16 ? saltBytes.length : 16);
        if (saltBytes.length < 16)
            padding.addPadding(ivBytes, saltBytes.length);

        byte[] keyBytes = new byte[16];
        System.arraycopy(passwordBytes, 0, keyBytes, 0, passwordBytes.length < 16 ? passwordBytes.length : 16);
        if (passwordBytes.length < 16)
            padding.addPadding(keyBytes, passwordBytes.length);

        Cipher cipher = Cipher.getInstance("Rijndael/CBC/ISO10126Padding");
        System.out.println("Using provider: " + cipher.getProvider().getName() + " - "
                + cipher.getProvider().getInfo());

        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "Rijndael");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivBytes));

        BASE64Decoder decoder = new BASE64Decoder();
        byte[] encryptedBytes = decoder.decodeBuffer(encryptedText);
        byte[] results = cipher.doFinal(encryptedBytes);

        plainText = new String(results, "UTF-8");
        System.out.println(plainText);

    } catch (Exception e) {
        // log.error("Error in decryption method", e);
        e.printStackTrace();
    }
    return plainText;
}
Thanks again.
843811
Your Java key generation is totally wrong. Your C# code uses the class PasswordDeriveBytes to form the bytes of the key from the password. Since you are using a 16byte AES key this class will use the PBKDF1 algorithm using the default iteration count and the default hash method. I can't remember what the default values are so you will need to establish what these are (they are attributes of PasswordDeriveBytes so you can print them out) before you go any further. Once you have established these values you will need to implement the PBKDF1 algorithm. This is very easy.

There may be other things wrong but until you can get the same key bytes from your C# code as you get from your Java implementation of the PBKDF1 algorithm there is no point at all in trying to go any further.
843811
Ok, I got some help on this from someone else, and we are very close to getting this working. We are able to decrypt data that was encrypted using the .NET code, but for some reason the first four characters are garbled. Does anyone have a clue as to why?

Method that does the decrypting:
    private static byte[] decryptData(byte[] data, String password, String paddingMode, String salt) throws Exception {
        if (data == null || data.length == 0)
            throw new IllegalArgumentException("data is empty");
        if (password == null || password == "")
            throw new IllegalArgumentException("password is empty");
        if (salt == null || salt == "")
            salt = ".";

        byte[] saltBytes = salt.getBytes("UTF8");
        byte[] passBytes = password.getBytes("UTF8");// new byte[16];

        PKCS5S1ParametersGenerator generator = new PasswordDeriveBytes(new SHA1Digest());
        generator.init(passBytes, saltBytes, 100);

        byte[] key = ((KeyParameter) generator.generateDerivedParameters(256)).getKey();
        passBytes = new byte[16];
        saltBytes = new byte[16];
        System.arraycopy(key, 0, passBytes, 0, 16);
        System.arraycopy(key, 16, saltBytes, 0, 16);

        Cipher cipher = Cipher.getInstance("Rijndael/CBC/" + paddingMode);
        SecretKeySpec keySpec = new SecretKeySpec(passBytes, "Rijndael");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(saltBytes));

        byte[] original = cipher.doFinal(data);
        return original;
    }
Clone of the PasswordDerivedBytes class from .NET:
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.generators.PKCS5S1ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;

public class PasswordDeriveBytes extends PKCS5S1ParametersGenerator {

    private final Digest d;

    private byte[]       output = null;

    public PasswordDeriveBytes(Digest d) {
        super(d);

        this.d = d;
    }

    public CipherParameters generateDerivedParameters(int keySize) {
        keySize = keySize / 8;

        byte[] result = new byte[keySize];
        int done = 0;
        int count = 0;
        byte[] b = null;

        while (done < result.length) {
            if (b == null) {
                b = generateInitialKey();
            } else if (++count < 1000) {
                b = generateExtendedKey(count);
            } else {
                throw new RuntimeException("Exceeded limit");
            }

            int use = Math.min(b.length, result.length - done);
            System.arraycopy(b, 0, result, done, use);
            done += use;
        }

        return new KeyParameter(result);
    }

    private byte[] generateOutput() {
        byte[] digestBytes = new byte[d.getDigestSize()];

        d.update(password, 0, password.length);
        d.update(salt, 0, salt.length);
        d.doFinal(digestBytes, 0);

        for (int i = 1; i < (iterationCount - 1); i++) {
            d.update(digestBytes, 0, digestBytes.length);
            d.doFinal(digestBytes, 0);
        }

        return digestBytes;
    }

    private byte[] generateInitialKey() {
        output = generateOutput();
        d.update(output, 0, output.length);

        byte[] digestBytes = new byte[d.getDigestSize()];
        d.doFinal(digestBytes, 0);
        return digestBytes;
    }

    private byte[] generateExtendedKey(int count) {
        byte[] prefix = Integer.toString(count).getBytes();
        d.update(prefix, 0, prefix.length);
        d.update(output, 0, output.length);

        byte[] digestBytes = new byte[d.getDigestSize()];
        d.doFinal(digestBytes, 0);
        return digestBytes;
    }
}
Sorry for all the code pasting. I'd love it if someone could shed some light on this, we're so close!

Thanks.
843811
Very odd. Since you are using CBC mode, this implies that the first 4 bytes of the IV is wrong, but the remaining 12 are correct. Perhaps the .NET IV is overwritten with a 4-byte length or something?
843811
ghstark wrote:
Perhaps the .NET IV is overwritten with a 4-byte length or something?
Not in my experience. I suspect the class PasswordDeriveBytes. The OP should print (in hex) the bytes of the IV in the .NET code and in the Java code and then compare the result.

P.S. I'm not at all keen on the re-use of the references 'passBytes' and 'saltBytes'. These should be new references named something like 'keyBytes' and 'ivBytes'. There is actually no need to copy the bytes to new arrays since both the key and the IV can be constructed from a slice (offset and length) of a byte array.

Edited by: sabre150 on Jan 1, 2009 5:58 PM
843811
Yes, I think your are correct. The bouncycastle class PKCS5S1ParametersGenerator is already the clone of the .Net PasswordDerivedBytes class; the OPs version is incorrect. I suspect he really needs be using the .NET Rfc2898DeriveBytes for his C# code and the equivalent bouncycastle PKCS5S2ParametersGenerator for his Java code.
843811
Hi,
I dont think so.
The key and the IV out from posted .NET and Java codes are the same! with the clone of PasswordDerivedPassword class of the .NET
so we are now reached a state that same inputs to the cipher but two different outputs
maybe the Rijndael algorithm implementation differs or another version?
correct me if I am wrong.
843811
darshkemo wrote:
Hi,
I dont think so.
The key and the IV out from posted .NET and Java codes are the same! with the clone of PasswordDerivedPassword class of the .NET
so we are now reached a state that same inputs to the cipher but two different outputs
maybe the Rijndael algorithm implementation differs or another version?
correct me if I am wrong.
I am in the early stages of writing an article on cross plarform/language encryption and have written Java, C++, Python and PHP versions of PasswordDeriveBytes. They all produce outputs which match exactly with the C# library version. Encryption and decryption using these also matches exactly.
843811
when I run the .NET code and the Java code I get IV's that differ in the first 4 bytes, which would explain the OP results. I'm unable to post the code because of the 5000 char limit on posts. I guess I can post it in pieces if it would be useful.

Edited by: ghstark on Jan 5, 2009 6:37 PM
843811
Hi
thanks for your info,
seems that the first 4 bytes are not same really
I would be thankful if you provided me with copy of your PasswordDerivedClass implementation in java?
or if you found any fault with the one sent by funkymike
Thanks
843811
One problem that I don't yet know how to fix is that in
ICryptoTransform decryptor = rm.CreateDecryptor(pdb.GetBytes(16), pdb.GetBytes(16));
there are two calls to pdb.GetBytes(16) - one for the key bytes and one for the IV bytes. It turns out that these two calls are not the same as taking the first 16 byte of pdb.GetBytes(32) as the key bytes and the second 16 bytes as the IV bytes. The difference is some change to the first 4 bytes of the second 16 bytes.

This feature applies also when getting 48 bytes and comparing with 3 lots of getting 16 bytes. The first 16 bytes are OK but the first 4 bytes of each of the subsequent set of 16 byte differ.

Some C# test code (provide your own hex utility)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using grm.codec;

namespace ConsoleApplication2 
{
    class Program
    {
        static byte[] slice(byte[] source, int start, int len)
        {
            byte[] result = new byte[len];
            for (int i = 0; i < len; i++)
                result[i] = source[start++];
            return result;
        }

        static void Main(string[] args)
        {
            string password = "The quick brown fox jumps over the lazy dog.";
            string salt = "Hello World";
            {
                PasswordDeriveBytes pdb = new PasswordDeriveBytes(Encoding.UTF8.GetBytes(password), Encoding.UTF8.GetBytes(salt), "sha1", 100);
                HexEncoder hexEncoder = new HexEncoder();
                byte[] result = pdb.GetBytes(48);
                Console.WriteLine(hexEncoder.EncodeToString(slice(result, 0, 16)));
                Console.WriteLine(hexEncoder.EncodeToString(slice(result, 16, 16)));
                Console.WriteLine(hexEncoder.EncodeToString(slice(result, 32, 16)));
            }
            {
                PasswordDeriveBytes pdb = new PasswordDeriveBytes(Encoding.UTF8.GetBytes(password), Encoding.UTF8.GetBytes(salt), "sha1", 100);
                HexEncoder hexEncoder = new HexEncoder();
                for (int i = 0; i < 3; i++)
                {
                    Console.WriteLine(hexEncoder.EncodeToString(pdb.GetBytes(16)));
                }
            }
        }
    }
}
which produces as output
3ce947a213dff44cb971455ce2d54743
ebdc05eebc853be3c62c24f703717c36
1f4ebb724ed6a2b40988214c31e863d2
3ce947a213dff44cb971455ce2d54743
*13dff44c*bc853be3c62c24f703717c36
03717c361f4ebb720988214c31e863d2

It is unfortunate that this means my implementations of PasswordDeriveBytes are flawed. I only tested them in the same way that the OP used his version of PasswordDeriveBytes in that I only checked the equality for a single large block of bytes.

Back to the drawing board.

Edited by: sabre150 on Jan 9, 2009 10:16 PM

I'm probably just talking to myself but I have convinced myself that the problem is actually a bug in the C# PasswordDeriveBytes.getBytes() method. My main reasoning is that since the generated bytes are all the result of a sha1 hash then either 20 bytes will be different or no bytes will be different. To get only the first 4 different when requesting the second 16 byte block implies that in some way the 20 bytes of the first hash are different the second time round. From the C# I can see that this is possible though how one would create the same 'feature' in the Java I have not yet determined. I'm not even sure I want to spend any time trying to emulate this since I have never before seen this unusual double use of the getBytes() method. As far as my article is concerned, for the moment, I shall just comment on this 'feature'. I may return to it later after giving it some thought.

As far as the OP's problem is concerned - he will probably do best to get the source of the C# class and convert it line by line to Java. I feel it will be difficult to modify the OP's current Java version of the PasswordDeriveBytes implementation to get the same results as the C# version.
843811
Thanks everyone. I agree that this looks like it may be a bug in the .NET implementation. I also agree that it doesn't really make since to duplicate a non-standard implementation in my Java code.

Luckily for me, the data I'm decrypting is all XML, so the first four bytes are just "<?xml", so I can easily work around those being garbled.

Thanks again everyone.
843811
FunkyMike wrote:
Thanks everyone. I agree that this looks like it may be a bug in the .NET implementation. I also agree that it doesn't really make since to duplicate a non-standard implementation in my Java code.

Luckily for me, the data I'm decrypting is all XML, so the first four bytes are just "<?xml", so I can easily work around those being garbled.
I took the c# source for PasswordDeriveBytes and implemented it in Java line by line. Though I hate the result and am not willing to publish it, I do get an exact match between the two when I call the getBytes() method twice.
843811
Mike,

I have a similar issue where I need to use AES/Rijndael to exchange a hash that is created (encrypted) on one side on UNIX/Java and read (decrypted) on the other side using .NET 2.0. We are not using PasswordDeriveBytes as you do but instead use Rfc2898DeriveBytes and our .NET code produces different results from our counterparts Java code. It is worth noting that a different counterpart has already done a successful implementation of the algorithm on UNIX/Java so I know it actually can be done. They just don't want to share it with their competitors. :)

I'm looking for a Java consultant that will be able to get us past the impasse. I have the requisite .NET experitise on my staff. Can you recommend such a consultant based on your own experience with this very similar issue?
843811
I am using PKCS5S2ParametersGenerator as an equivalent to Rfc2898DeriveBytes (as stated within an earlier posting).
Code posted below is intended to be a matching counterpart to DotNetCrypto example from CodeProject.
    private static BufferedBlockCipher getCipher(String password, boolean encrypt)
        throws Exception
    {
        byte[] pwd = password.getBytes("UTF8");
        
        // equivalent to .NET class 
        // Rfc2898DeriveBytes when initialising with
        // Rfc2898DeriveBytes pwdb = new Rfc2898DeriveBytes(password, GENERAL_SALT)
        PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator();        
        generator.init(pwd, GENERAL_SALT, 0x3e8);
        
        // compared to .NET pwdb.GetBytes(32), pwdb.GetBytes(16) we use bit sizes here
        ParametersWithIV iv = ((ParametersWithIV)generator.generateDerivedParameters(256,128));                
        
        // encrypt the stuff using rijndael
        // .NET would be this ...
        // Rijndael alg = Rijndael.Create();
        // alg.Key = pwdb.GetBytes(32);
        // alg.IV = pwdb.GetBytes(16);
        RijndaelEngine engine = new RijndaelEngine();
        BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));        
        
        // .NET
        // CryptoStream cs = new CryptoStream(ms, (encrypt ? alg.CreateEncryptor() : alg.CreateDecryptor()),
        // CryptoStreamMode.Write);                 
        cipher.init(encrypt, iv);
        
        return cipher;
    }
843811
Hi -

Is your problem solved. If so please help us as we are in the same situation.

As we have to decrypt a string (in java using Rijndael) that was encrypted in .net using Rijndael.

Thanks,
Venkat
843811
vpaturu wrote:
Hi -

Is your problem solved. If so please help us as we are in the same situation.

As we have to decrypt a string (in java using Rijndael) that was encrypted in .net using Rijndael.

Thanks,
Venkat
Please stop spamming. If you have a question, read this and then ask your question in a new topic.
1 - 18
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on May 29 2010
Added on Dec 9 2008
18 comments
8,382 views