Limit Size of Encrypted Data using 3DES

487 Views Asked by At

I am trying to encrypt and decrypt a string of data using 3DES and it is working fine. However I want that the size of the data after encryption to be limited to length of 16 bits.

This is the code I am referring from https://gist.github.com/riversun/6e15306cd6e3b1b37687a0e5cec1cef1 :

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class DesedeCrypter {

private static final String CRYPT_ALGORITHM = "DESede";
private static final String PADDING = "DESede/CBC/NoPadding";
private static final String CHAR_ENCODING = "UTF-8";

private static final byte[] MY_KEY = "5oquil2oo2vb63e8ionujny6".getBytes();//24-byte
private static final byte[] MY_IV = "3oco1v52".getBytes();//8-byte

public static void main(String[] args) {

    String srcText = "M3A1B2C3D4HHG393";

    final DesedeCrypter crypter = new DesedeCrypter();

    String encryptedText = crypter.encrypt(srcText);

    System.out.println("sourceText=" + srcText + " -> encryptedText=" + encryptedText + "\n");

    System.out.println("encrypted-text=" + encryptedText + " -> decrypted-text(source text)="
            + crypter.decrypt(encryptedText));
}

public String encrypt(String text) {

    String retVal = null;

    try {

        final SecretKeySpec secretKeySpec = new SecretKeySpec(MY_KEY, CRYPT_ALGORITHM);

        final IvParameterSpec iv = new IvParameterSpec(MY_IV);

        final Cipher cipher = Cipher.getInstance(PADDING);

        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);

        final byte[] encrypted = cipher.doFinal(text.getBytes(CHAR_ENCODING));

        retVal = new String(encodeHex(encrypted));

    } catch (Exception e) {
        e.printStackTrace();
    }
    return retVal;
}

public String decrypt(String text) {

    String retVal = null;
    try {
        final SecretKeySpec secretKeySpec = new SecretKeySpec(MY_KEY, CRYPT_ALGORITHM);
        final IvParameterSpec iv = new IvParameterSpec(MY_IV);

        final Cipher cipher = Cipher.getInstance(PADDING);

        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);

        final byte[] decrypted = cipher.doFinal(decodeHex(text.toCharArray()));

        retVal = new String(decrypted, CHAR_ENCODING);

    } catch (Exception e) {
        e.printStackTrace();
    }
    return retVal;
}

private byte[] decodeHex(char[] data) throws Exception {

    int len = data.length;
    if ((len & 0x01) != 0) {
        throw new Exception("Odd number of characters.");
    }
    byte[] out = new byte[len >> 1];

    // two characters form the hex value.
    for (int i = 0, j = 0; j < len; i++) {

        int f = toDigit(data[j], j) << 4;
        j++;
        f = f | toDigit(data[j], j);
        j++;
        out[i] = (byte) (f & 0xFF);
    }
    return out;
}

private int toDigit(char ch, int index) throws Exception {
    int digit = Character.digit(ch, 16);
    if (digit == -1) {
        throw new Exception("Illegal hexadecimal character " + ch + " at index " + index);
    }
    return digit;
}

private char[] encodeHex(byte[] data) {

    final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    int l = data.length;
    char[] out = new char[l << 1];
    // two characters form the hex value.
    for (int i = 0, j = 0; i < l; i++) {
        out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
        out[j++] = DIGITS[0x0F & data[i]];
    }
    return out;
}
}

Currently this is the output I am getting:

sourceText=M3A1B2C3D4HHG393 -> encryptedText=afc1d48ea5cc703253cbc1a88a198103

encrypted-text=afc1d48ea5cc703253cbc1a88a198103 -> decrypted-text(source text)=M3A1B2C3D4HHG393

Is there any way that the size of the encryptedText be limited to 16 as I want to add the encrypted text back into a message which has 16 digits space for encrypted text.

Please suggest some way or any other change that is required to achieve this. Thanks !

1

There are 1 best solutions below

2
Bram On

For one, I highly recommend not supporting (3)DES anymore, as it's officially unsecure in favour of AES/ChaCha, which I must say before answering this question.

3DES has a block size of 64 bits (or 8 bytes). With that also comes that encryption ≠ compression. So if you want to encrypt 16 bytes, provide 16 bytes of input, unless:

  1. You apply a padding scheme (which it appears you're not doing)(taken from the DESede/CBC/NoPadding
  2. You apply a (random) initialisation vector (which it appears you're doing)

The latter one should, but I'm not to sure of Android's implementation, create a 64 bits (8 byte) iv, as the iv should be as big as the block size of the cipher (again, 64 bits for 3DES).

So if you want 16 bytes of output, you can, and should, only provide 8 bytes to encrypt. Now, if 8 bytes is not enough, you might choose to drop the iv from being in the ciphertext, which you could do if you use a fixed iv (such as an all zero iv) with random keys, as reusing the same key with iv is not secure.

Now if you do take security in consideration, keep in mind that AES has a block size of 16 bytes. AES and ChaCha come with the same constraints regarding the iv.

Then you might also might want to consider changing the message (protocol) instead, so it can take more than 16 bytes of data or use those 16 bytes in such a way that it indicates that there is more data to handle, as like an attachment to an e-mail.