Question

I'm quite a newbie regarding encryption and NIO, I have the following code for client:

    String key1 = "1234567812345678";
    byte[] key2 = key1.getBytes();
    SecretKeySpec secret = new SecretKeySpec(key2, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, secret);
    byte[] encrypted = cipher.doFinal(msg.getBytes());
    System.out.println("Encrypted info: " + encrypted);

    String send = encrypted.toString();
    bytebuf = ByteBuffer.allocate(48);
    bytebuf.clear();
    bytebuf.put(send.getBytes());

    bytebuf.flip();

    while(bytebuf.hasRemaining()) {
        nBytes += client.write(bytebuf);
    }

and the following code for server:

    // Server receives data and decrypts

    SocketChannel socket = (SocketChannel) key.channel();
    ByteBuffer buf = ByteBuffer.allocate(1024);
    nBytes = socket.read(buf);
    String data = new String(buf.array()).trim();
    String key1 = "1234567812345678";
    byte[] key2 = key1.getBytes();
    SecretKeySpec secret = new SecretKeySpec(key2, "AES");

    Cipher cipher = Cipher.getInstance("AES");

    cipher.init(Cipher.DECRYPT_MODE, secret);
    byte[] decrypted = cipher.doFinal(data.getBytes());
    System.out.println("Decrypted Info: " + new String(decrypted));

When a message is sent from the Client to the Server, "HELLO" for example is encrypted to [B@34d74aa5 and on the Server side I get *Data packet found as [B@34d74aa5.

Till here everything looks fine, but I get the following exception:

javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher

I suspect that I have some issue with the way the data is coming out of the buffer on the server side? Any ideas on this?

UPDATE:

**Based on Erickson's answer this is the final solution

javax.crypto.BadPaddingException: Given final block not properly padded

Client Code:

            String key1 = "1234567812345678";
        byte[] key2 = key1.getBytes();
            byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        IvParameterSpec ivspec = new IvParameterSpec(iv);
        SecretKeySpec secret = new SecretKeySpec(key2, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);

        byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
        String text = DatatypeConverter.printBase64Binary(encrypted);

        System.out.println("Encrypted info: " + text);

        bytebuf = ByteBuffer.allocate(32);
        bytebuf.clear();

        bytebuf.put(text.getBytes());

        bytebuf.flip();

        while(bytebuf.hasRemaining()) {
            nBytes += client.write(bytebuf);
        }

Server Code:

            LOGGER.info("Confirming write");
        String data = new String(buf.array());

        LOGGER.info("Data packet found as {}", data);

        /*******************************************************/
        byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        IvParameterSpec ivspec = new IvParameterSpec(iv);
        String key1 = "1234567812345678";
        byte[] key2 = key1.getBytes();
        SecretKeySpec secret = new SecretKeySpec(key2, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        cipher.init(Cipher.DECRYPT_MODE, secret, ivspec);

        byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
        byte[] decrypted = cipher.doFinal(encrypted);

        System.out.println("Decrypted Info: " + new String(decrypted, StandardCharsets.UTF_8));
Was it helpful?

Solution

Your cipher text, encrypted, is a byte[], and invoking toString() on an array doesn't render the array content, it returns type ([B) and hash code (@34d74aa5) information as described by Object.toString().

You can't just use new String(encrypted) either. When a byte array is decoded to text, the decoder will replace any invalid byte sequences with the replacement character, \uFFFD (�). Thus, information is lost and subsequent decryption will fail.

Use an encoding like base-64 to convert byte sequences to printable characters instead. Don't junk up your code with third-party libraries for this; you can use javax.xml.bind.DatatypeConverter.

/* Client: */
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
…

/* Server: */
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = Cipher.doFinal(encrypted);
System.out.println(new String(decrypted, StandardCharsets.UTF_8);

You should also be explicit in selecting your mode and padding (like "AES/CBC/PKCS5Padding") because there's no guarantee the recipient will use the same provider, or that the same provider will use the same defaults over time. Same goes for specifying character encodings, like UTF-8.

OTHER TIPS

The AES scheme is a "block cipher" it works on fixed-size blocks of data. You are creating a "raw" Cipher instance, which will expect you to make sure that every byte array that you pass to the cipher is aligned to the cipher's "native" block length. That's usually not what you want to do.

An additional problem that you are exposing yourself to in using the cipher "raw", although it's not causing an actual error, is that if you were to pass it the same block of data on separate occasions, each time, that block would be encrypted identically, therefore giving an attacker clues as to the structure of the data. Again, that's usually not what you want to do in a practical application.

So usually, you need to specify two extra things: a padding scheme, which determines what happens when sections of data are not exactly aligned to a block size, and a block mode, which determines what scheme the cipher will use to avoid identical input blocks being encrypted to identical output blocks. The block mode generally needs initialising with a "starting state" called the initialisation vector (you could use a default state of "all zero", but that's less secure).

So you need to do two things:

  • You need to initialise you cipher with a padding scheme and block mode, e.g. "AES/CBC/PKCS5PADDING"

  • For additional security, you would also usually set up (and transmit before the data) a random initialisation vector. See this example for more information.

You are converting the ciphertext, which is a byte[], to a String here:

byte[] encrypted = cipher.doFinal(msg.getBytes());
String send = encrypted.toString();

This is incorrect. You also cannot do new String(byte[]) because the byte[] is random, not a stream of character data in the platform default encoding assumed by new String(byte[]). You should convert the byte[] data to a String by using a hex or base64 encoding (I recommend Apache Commons Codec) e.g.

hexEncodedCipherText = new String(Hex.encodeHex(binaryCipherText))

On the server-side, use the opposite operation to convert the hex or base64 encoded data back to a byte[] before decryption e.g.

binaryCipherText = Hex.decodeHex(hexEncodedCipherText.toCharArray());

UPDATE:

The updated question is not working during decryption because of the incorrect use of the initialization vector. You don't specify an IV during encryption, which means Java will generate a random one. You need to obtain this random IV from the cipher by calling cipher.getIV() after the encryption (or specify it explicitly, though generating a random one is more secure). Then, during the decryption, create the IvParameterSpec using the IV created during encryption. In addition, you will need to encode/decode the IV in the same manner as the ciphertext, since it is also binary data.

UPDATE 2:

I see you have updated your question with the IV, but you are using a null IV. Generally, this is only "safe" when you have a unique key for every message you send. If your key is fixed or re-used for any significant length of time, you should generate a unique IV for each encryption/decryption. Otherwise, you are leaving yourself open to cryptanalysis based on multiple ciphertexts encrypted with the same key and IV.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top