0

I am using this AES Encrption and decryption method for encrypting my data. There is no problem with udp but when i use tcp i get this error "javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher"

AES Encryption/Decryption Code:

    public class AESEncDec {

     private static final String ALGO = "AES";
    private static final byte[] keyValue =  new byte[] { 'T', 'h', 'e', 'B','e', 's', 't','S', 'e', 'c', 'r','e', 't', 'K', 'e', 'y' };


public static String encrypt(String Data) throws Exception {
        Key key = generateKey();
        Cipher c = Cipher.getInstance(ALGO);
        c.init(Cipher.ENCRYPT_MODE, key);
        byte[] encVal = c.doFinal(Data.getBytes());
        String encryptedValue = new BASE64Encoder().encode(encVal);
        System.err.println("encVal: "+encryptedValue.length());

        return encryptedValue;
    }

    public static String decrypt(String encryptedData) throws Exception {
        Key key = generateKey();
        Cipher c = Cipher.getInstance(ALGO);
        c.init(Cipher.DECRYPT_MODE, key);
        byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
        byte[] decValue = c.doFinal(decordedValue);
        //byte[] decValue = c.doFinal(encryptedData.getBytes());
        String decryptedValue = new String(decValue);
        System.err.println("decVal: "+decryptedValue.length());

        return decryptedValue;
    }
    private static Key generateKey() throws Exception {
        Key key = new SecretKeySpec(keyValue, ALGO);
        return key;
}

}

TCP SERVER CODE:

    class TCPServer
    {
      public static void main(String argv[]) throws Exception
      {
          AESEncDec edData= new AESEncDec();
           // AES edData= new AES();

         String msg="Message_";
         String clientSentence="";
         String capitalizedSentence;
         ServerSocket welcomeSocket = new ServerSocket(6789);
         Socket connectionSocket = welcomeSocket.accept();
          for (int i = 0; i < 10; i++) 
         {
            clientSentence=edData.encrypt(msg+i)+"\n";
            DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());
            outToClient.writeBytes(clientSentence);
            Thread.sleep(100);
         }
      }
}

TCP CLIENT CODE:

    class TCPClient {

    public static void main(String argv[]) throws Exception {

        AESEncDec edData= new AESEncDec();
        String modifiedSentence;
        String DecData="";
        Socket clientSocket = new Socket("localhost", 6789);
        while(true){
         BufferedReader inFromServer = new BufferedReader(new  InputStreamReader(clientSocket.getInputStream()));
         modifiedSentence = inFromServer.readLine();
         DecData=edData.decrypt(modifiedSentence);
         System.out.println("FROM SERVER: " + DecData);   
        }

        //clientSocket.close();
    }
    }

For the Same code when the message is small it gets decrypted correctly. but when the message is long i get the illegalBlocksize Exception. I tried to use AES/CBC/PKCS5Padding so that the message gets padded and the blocksize comes in a multiple of 16. But still i get either the same error or BadPaddingException. if am specifying PKCS5Padding as the padding technique then the message should be padded correctly and shouldn't be giving this error. why isn't that working. What can i do to decrypt the data correctly. please help..

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
Kiara
  • 117
  • 1
  • 1
  • 11
  • General advice: **Always use a fully qualified Cipher string.** `Cipher.getInstance("AES");` may result in different ciphers depending on the default security provider. It most likely results in `"AES/ECB/PKCS5Padding"`, but it doesn't have to be. If it changes, you'll lose compatibility between different JVMs. For reference: [Java default Crypto/AES behavior](http://stackoverflow.com/q/6258047/1816580) – Artjom B. Sep 14 '16 at 20:12
  • **Never use [ECB mode](http://crypto.stackexchange.com/q/14487/13022)**. It's deterministic and therefore not semantically secure. You should at the very least use a randomized mode like [CBC](http://crypto.stackexchange.com/q/22260/13022) or [CTR](http://crypto.stackexchange.com/a/2378/13022). It is better to authenticate your ciphertexts so that attacks like a [padding oracle attack](http://crypto.stackexchange.com/q/18185/13022) are not possible. This can be done with authenticated modes like GCM or EAX, or with an [encrypt-then-MAC](http://crypto.stackexchange.com/q/202/13022) scheme. – Artjom B. Sep 14 '16 at 20:12
  • @Woot4Moo No, decrypting doesn't mean breaking the encryption. It is synonymous to deciphering. Still, I've rolled back your edit, because you've changed the wording of the error message which means that future readers won't find this question if they look for this error message – Artjom B. Sep 14 '16 at 20:17
  • @ArtjomB. The words mean two totally different things, its fine that you have decided to continue using the wrong words. It is not fine to spread the wrong information. Additionally, you will note I left the stack trace alone and the method name so that others could still find it. – Woot4Moo Sep 14 '16 at 20:18
  • @Kiara What package is `BASE64Encoder` from? I really hope you're not using the private `sun.*` classes. – Artjom B. Sep 14 '16 at 20:27
  • @ArtjomB. - yes it is sun.* – Kiara Sep 14 '16 at 20:36
  • @Kiara You really should switch to another Base64 implementation. You can copy one from one of the answers here on Stack Overflow or include an apache commons-codec library which has more control over the encoding process. – Artjom B. Sep 14 '16 at 20:38
  • @ArtjomB. That's what I suspected from the other post from SO. I just encoded 7MB of String using java8's built-in encoder. I didn't see a new line in it (output was approximately 10MB). – blackpen Sep 14 '16 at 20:53
  • @ArtjomB. Even when I inserted hundreds of newlines into the input data, the encoding came out Ok (without any new lines). – blackpen Sep 14 '16 at 20:58
  • @Kiara, Please use the Java8's built-in encoding and see how it works out. Refer [here](https://docs.oracle.com/javase/8/docs/api/java/util/Base64.html) for documentation. Example: String asB64 = Base64.getEncoder().encodeToString(data.getBytes("utf-8")); – blackpen Sep 14 '16 at 21:00

2 Answers2

1

The problem is that TCP gives you a stream. If you read a chunk and try to decrypt it, it may fail, because the size of the chunk may not be in multiples of 16 (or 128). AES works on 16 byte or 128 byte chunks of data. So, you may have to wait for a while till you gather that much amount of data before you decrypt.

Since UDP is message oriented, it doesn't face such problem.

@Kiara, Please use the Java8's built-in encoding and see how it works out. Refer here for documentation. Example:

String asB64 = Base64.getEncoder().encodeToString(data.getBytes("utf-8")); 

That was tested to work good with messages of lengths as much as 7 megabytes. It didn't introduce newlines into the encoded messages.

Kiara
  • 117
  • 1
  • 1
  • 11
blackpen
  • 2,339
  • 13
  • 15
  • OP decrypts by full line, so this doesn't seem like the correct answer. – Artjom B. Sep 14 '16 at 20:07
  • @ArtjomB.Yes, I saw that just now. What happens if a new line happens to be there in the encrypted data? I am not sure (since the encrypted data is binary/bytes). – blackpen Sep 14 '16 at 20:15
  • @blackpen There won't be a byte that can be interpreted as a newline character, because the encryption actually produces a Base64-encoded string which usually doesn't contain newline characters. Now that I think about it. Some Base64 implementations insert newline characters at specific locations. There must be a flag to disable this. – Artjom B. Sep 14 '16 at 20:19
  • @blackpen- the message is of variable length so i can't define particular range for that. but yes, It would not be greater than 255bytes. – Kiara Sep 14 '16 at 20:22
  • @ArtjomB.Agree with you,. – blackpen Sep 14 '16 at 20:23
  • @ArtjomB.Most examples state that the readLine blocks till it gets a newLine character. So, the OP's code should have worked (except the uncertainity of embedded newLine as you pointed out). – blackpen Sep 14 '16 at 20:28
  • @blackpen- Actually i added it so that i the data gets printed at every new line instead of a single line. – Kiara Sep 14 '16 at 20:30
  • @Kiara, we are talking about what happens if **encoding procedure** itself puts new lines in the encoded data (apart from the new line that you have inserted for better comprehension). ArtjomB is wondering about options to control such new lines. – blackpen Sep 14 '16 at 20:32
  • @ArtjomB. - when i removed the newline character from the server program the packets were not sent and i get a socketException. This means adding newline is necessary before sending the data. – Kiara Sep 14 '16 at 20:42
  • @Kiara Yes, newlines are important in this case, because that is how you distinguish multiple ciphertext. You could rewrite this where you would prepend the length of the ciphertext before the actual ciphertext and then you could send actual bytes instead of encoded ciphertext. Remember that Base64 encoding inflates the amount of transmitted bytes by 33%. – Artjom B. Sep 14 '16 at 20:45
  • @ArtjomB. - k so is this the reason for the illegalBlockSizeException as it changes the length of the data sent.ultimately my question is how to decrypt it correctly. – Kiara Sep 14 '16 at 20:48
  • @blackpen This worked!!!!! Thank you so much for the help :) I just changed the encoding and decoding code as per java8's built-in encoding and now i get the data perfectly. **Changes i made in the encrypting side:** `String encryptedValue = Base64.getEncoder().encodeToString(encVal);` **Changes in the decrypting code:** `byte[] decordedValue =Base64.getDecoder().decode(encryptedData);` Hope this helps somebody else... Thank you all :) – Kiara Sep 18 '16 at 14:20
  • @blackpen But can i know the reason why was that happening earlier , both are doing the same encoding/decoding work. What is the reason that java 8's builtin technique is better then the first one? – Kiara Sep 18 '16 at 14:30
  • @blackpen Also there is no link provided in your comment for java 8's builitin encoding documentation. – Kiara Sep 18 '16 at 14:33
  • @blackpen Also if i am working in java7 then how can i do the encoding in that case. Is there any api or something that can help me do the same thing as java8's encoding. sorry for these many questions but i would like to know about these things.. Thanks in advance :) – Kiara Sep 18 '16 at 14:38
  • @Kiara, Good to see that you worked it out. Good question about Java7/8. This [post](http://stackoverflow.com/questions/14413169/which-java-library-provides-base64-encoding-decoding) explains how to make it work on Java6. If it works on Java6, it is a safe bet that it will work on Java7 and Java8. – blackpen Sep 18 '16 at 15:17
1

AES is a block cipher and can only encrypt exactly 16 bytes to 16 bytes. If you want to encrypt arbitrary inputs, you need a mode of operation and a padding scheme. The default mode is usually ECB and the default padding scheme is PKCS#5 padding (actually PKCS#7 padding).

That means that a single encryption must also be decrypted in exactly the same way, because the padding must be removed during decryption. It is important that multiple ciphertexts don't overlap and are not broken up (chunked / wrapped).
That is what your newline characters are supposed to do during encryption of multiple messages, because you're reading the ciphertexts linewise at the receiving side.

The problem is that the default sun.misc.BASE64Encoder#encode method actually introduces newlines on its own. Every 76 characters there is a newline inserted, but the receiver cannot know which newline actually separates the message ciphertexts and which newline separates the wrapped lines in a single message ciphertext.

The easiest fix would be to remove the newlines after Base64-encoding:

String encryptedValue = new BASE64Encoder().encode(encVal).replaceAll("\\s", "");

This replaces all whitespace in a single message ciphertext.

Since you're using the private sun.misc.* classes, it is better to move to another Base64 implementations, because those classes don't have to be available in every JVM. You should use some known library like Apache's commons-codec which provides an implementation where you can disable wrapping/chunking:

String encryptedValue = Base64.encodeBase64(encVal, false);

which is equivalent to

String encryptedValue = Base64.encodeBase64(encVal);
Artjom B.
  • 61,146
  • 24
  • 125
  • 222