6

I have a following code in C#. It does encoding an array of bytes with an AES symmetric algorithm. I need to write Java equivalent of this code.

class Program
{
    static void Main(string[] args)
    {
        string a = "ABCDEFGHIJKLMNOP";
        byte[] bytes = Encoding.ASCII.GetBytes(a);
        byte[] cipher = encode(bytes, "1111111122222222111111112222222211111111222222221111111122222222", "66666666555555556666666655555555");
    }

    private static byte[] encode(byte[] toEncrypt, string sKey, string sIV)
    {
        byte[] IV = new byte[16];
        byte[] key = new byte[32];
        byte[] array = new byte[toEncrypt.Length];
        string s;

        for (int i = 0; i < IV.Length; ++i)
        {
            s = sIV.Substring(i * 2, 2);
            IV[i] = Convert.ToByte(s, 16);
        }

        for (int i = 0; i < key.Length; ++i)
        {
            s = sKey.Substring(i * 2, 2);
            key[i] = Convert.ToByte(s, 16);
        }

        MemoryStream filecrypt = new MemoryStream(array);

        AesManaged encrypt = new AesManaged();
        encrypt.Mode = CipherMode.CBC;
        encrypt.Padding = PaddingMode.None;
        encrypt.BlockSize = 128;
        encrypt.KeySize = 256;

        CryptoStream cs = new CryptoStream(filecrypt, encrypt.CreateEncryptor(key, IV), CryptoStreamMode.Write);
        cs.Write(toEncrypt, 0, toEncrypt.Length);
        cs.Close();

        return array;
    }
}

This is my attempt of writing this in Java. The code looks fine, but the output is different, something must be wrong.

public class Main {

    public static void main(String [] args) {
        byte [] code = encode("ABCDEFGHIJKLMNOP".getBytes(), "1111111122222222111111112222222211111111222222221111111122222222", "66666666555555556666666655555555");
    }

    private static byte[] toByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        int a;
        int b;
        for (int i = 0; i < len; i += 2) {
            a = (Character.digit(s.charAt(i), 16) << 4);
            b = Character.digit(s.charAt(i+1), 16);
            int n = (Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16);
                data[i / 2] = (byte) (n);
        }
        return data;
    }

    private static byte[] encode(byte[] toEncrypt, String skey, String siv)
    {
        byte[] key = toByteArray(skey);
        byte[] iv = toByteArray(siv);

        byte[] array = new byte[toEncrypt.length];

        Cipher cipher;

        try {
            cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE,  new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
            array = cipher.doFinal(array);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return array;
    }
}

Any clues and ideas will be very appreciated.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
Someone
  • 369
  • 2
  • 8
  • 24
  • How are you looking at the output in each case? – Jon Skeet Nov 27 '15 at 09:42
  • In the debuggers (I keep in mind that bytes in Java are signed). – Someone Nov 27 '15 at 09:42
  • 1
    It would be much easier to help you if you'd provide a short but complete program in each language, so that we can see it for ourselves - include the input and output in each case. I'd also *strongly* recommend you use more descriptive names than `b` and `a` to represent the key and IV... – Jon Skeet Nov 27 '15 at 09:44
  • 1
    Please don't change your question completely after receiving answers. I reverted your question to an earlier revision. The edit you made introduces OFB for some reason which would make all the given answers obsolete. You should ask a new question if you have problems with OFB. – Artjom B. Dec 12 '15 at 11:29

4 Answers4

4

I don't know C# pretty well but in general you want multiple consecutive encryption results to be different. This is why you specify an initial IV for the AES algorithm. An encryption code could look like the following:

  public String encrypt( String stringToEncrypt, IvParameterSpec ivSpec ) {
    if ( stringToEncrypt == null ) {
      return null;
    }
    try {
      Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding");
      SecretKeySpec keySpec = new SecretKeySpec( key, "AES" );
      cipher.init( Cipher.ENCRYPT_MODE, keySpec, ivSpec );
      byte[] data = cipher.doFinal( stringToEncrypt.getBytes( "UTF-8" ) );
      return String.format( "%s:%s", Base64.encode( ivSpec.getIV() ), Base64.encode( data ) );
    } catch ( Exception e ) {
      throw new RuntimeException( "Unable to encrypt the string", e );
    }
  }

Your key and your IV should be generated using SecureRandom as this provides the best entropy in java:

byte[] iv = new byte[32];
random.nextBytes( iv );
byte[] key = new byte[32];
random.nextBytes( key );

Furthermore, you might want to calculate an HMAC afterwards - java also supports multiple solutions here. By checking the HMAC on the receiver side you can prevent a padding oracle attack.

To compare different encryption results I would compare them base64 encoded.

Note: It is ok to save the IV next to the ciphertext - it is just there to protect against pre computation attacks.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
2

You're initializing byte[] array = new byte[toEncrypt.length]; for some reason, but you never write the contents of toEncrypt into it before encryption. You could use System.arraycopy(toEncrypt, 0, array, 0, array.length);, but it would be easier to just use

byte[] array;
...
array = cipher.doFinal(toEncrypt);
...
return array;
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • stupid mistake. That was it, thank you. I couldn't see it myself. – Someone Dec 02 '15 at 14:22
  • The questioner may have not given you the kudos you deserved by marking that answer as final, but I shall do it with a nice bounty. Enjoy! – Flame_Phoenix Dec 15 '15 at 15:57
  • @Flame_Phoenix Thanks! But I'm leaning towards closing the question, because it's more or less a typo and I don't think I *earned* the bounty you offered. Btw, why did you have the need to offer the bounty? – Artjom B. Dec 15 '15 at 16:05
0

Personally, if your objective is to simply get AES encryption with Java you should not base your code on a C# class. Sure they may be similar, but Java has strong libraries for that already.

With that aside, I wish I had my encryption book here to explain it to you, but unfortunately the best i can do now is only to provide you with a good examples that someone else tried:

I hope these links help you achieve your objective.

Also, regarding your specific c# code, I fail to see where you are specifying the following code in Java:

encrypt.BlockSize = 128;
encrypt.KeySize = 256;

In the second tutorial I suggest you have an example where they specify the key size. I Hope I helped!

Community
  • 1
  • 1
Flame_Phoenix
  • 16,489
  • 37
  • 131
  • 266
  • I need to base my code on C#, the Java code must work exactly the same. I don't need to specify BlockSize and KeySize in Java (those are defaults). – Someone Nov 27 '15 at 10:03
0
public String notify(String message, String encryptionKey) {
    Security.addProvider(new BouncyCastleProvider());
    // System.out.println(message);
    byte[] key = Base64.decode(encryptionKey);
    SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
    byte[] data = Base64.decode(message);
    String decryptedString = "";
    try {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(data);
        decryptedString = new String(decrypted);
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println(decryptedString);
    return decryptedString;
}

This code should decrypt the encrypted message adding the required padding - default 128bit and you shall need to provide encryption key though. However this is my C# version of the same code

 void DecryptMessage(string message)
    {
        var deserializedMessage = JsonConvert.DeserializeObject<List<string>>(message.ToString());
        byte[] decodedEncryptionKey = Convert.FromBase64String(encryptkey);
        byte[] data = Convert.FromBase64String(deserializedMessage[0]);
        byte[] iv = new byte[16];
        AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
        aes.BlockSize = 128;
        aes.KeySize = 128;
        aes.Mode = CipherMode.ECB;
        aes.Padding = PaddingMode.PKCS7;

        using (ICryptoTransform decrypt = aes.CreateDecryptor(decodedEncryptionKey, iv))
        {
            byte[] dest = decrypt.TransformFinalBlock(data, 0, data.Length);
            decrypt.Dispose();
            Console.WriteLine(Encoding.UTF8.GetString(dest));
        }
    }