5
\$\begingroup\$

I have implemented a symmetric (AES 128-bit) key diversification algorithm for Java following the NXP Notes. It works as expected but am not the best Java programmer around, I guess I have done quite a mess so I need help on how can improve the code and make it better?

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

/**
 *
 * @author Chris Skinner
 */
public class AES {

    static SecretKeySpec skeySpec = null;
    static public byte[] K = new byte[16]; // 128 bit key
    static public byte[] K1 = new byte[16]; // 128 bit sub key
    static public byte[] K2 = new byte[16]; // 128 bit sub key
    static final public byte[] Z16 = new byte[16]; // 128 bit zero
    static Cipher cipher = null;
    private static final IvParameterSpec ZERO_IV = new IvParameterSpec(new byte[16]);

    public static byte[] getDiversifiedKey() throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException // make K1 K2 from key
    {
        byte bRb = (byte) 0x87; // Rb for AES128
        // key must be 16 bytes

        //Step 1

        byte[] key = hexStringToByteArray("00112233445566778899AABBCCDDEEFF");
        try {
            skeySpec = new SecretKeySpec(key, "AES");
            if (cipher == null) {
                cipher = Cipher.getInstance("AES/ECB/NoPadding");
            }
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
            K1 = cipher.doFinal(Z16);
            //Step 2
            System.out.println("KO " + bytesToHex(K1));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex) // nosuchalgorithm, invalidkey,nosuchpadding...
        {
            System.out.println("\n error 400 AES   " + ex.getMessage());
        }
        boolean highL = ((K1[0] & 0x80) != 0);
        K1 = shl(K1);
        if (highL) {
            K1[15] = (byte)(K1[15] ^ bRb);
        }
        highL = ((K1[0] & 0x80) != 0);
        K2 = shl(K1);
        if (highL) {
            K2[15] = (byte)(K2[15] ^ bRb);
        }
        //Step 3
        System.out.println("K1 " + bytesToHex(K1));


        //Step 4
        System.out.println("K2 " + bytesToHex(K2));


        byte[] div_constant = hexStringToByteArray("01");

        //Step 5
        byte[] uid = hexStringToByteArray("04782E21801D80");

        //Step 6
        byte[] aid = hexStringToByteArray("3042F5");

        //Step 7
        byte[] sysid = hexStringToByteArray("4E585020416275");

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(div_constant);
        outputStream.write(uid);
        outputStream.write(aid);
        outputStream.write(sysid);

        //Step 8 & 9
        byte c[] = outputStream.toByteArray();


        //Step 10 & 11
        boolean padded = false;
        if (c.length < 32) {
            padded = true;
            byte[] eg = hexStringToByteArray("80");
            outputStream.write(eg);
            byte[] st = outputStream.toByteArray();

            byte[] end = new byte[32 - st.length];

            outputStream.write(end);

            c = outputStream.toByteArray();
            outputStream.close();

        }

        byte[] xor_component;
        if (padded) {
            xor_component = K2;
        } else {
            xor_component = K1;
        }

        int mz = c.length;
        int n = mz / 16;

        byte[] MLast = new byte[16];
        int lastn = (n - 1) * 16;
        int lastz = mz - lastn;
        System.arraycopy(c, lastn, MLast, 0, lastz);
        byte[] first = Arrays.copyOfRange(c, 0, 16);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        stream.write(first);
        stream.write(xor16(MLast, xor_component));
        byte[] xored_d = stream.toByteArray();

        //Step 12
        System.out.println(bytesToHex(xored_d));


        cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ZERO_IV);
        byte[] encrypted = cipher.doFinal(xored_d);
        byte[] dkey = Arrays.copyOfRange(encrypted, 0, 32);
        System.out.println(bytesToHex(dkey));
        System.out.println(bytesToHex(Arrays.copyOfRange(dkey, 16, 32)));
        return dkey;
    }
    final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();

    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }


    static byte[] xor16(byte[] ba, byte[] bb) {
        byte[] bout = new byte[ba.length];
        for (short j = 0; j < ba.length; j++) {
            bout[j] = (byte)(ba[j] ^ bb[j]);
        }
        return bout;
    }

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

    static byte[] shl(byte[] bin) // << 16 byte array
    {
        byte[] bout = new byte[16];
        for (short j = 0; j < 15; j++) // java b[0] is the highorder
        {
            int sot = ((bin[j + 1] & 0x80) >> 7);
            int sef = (bin[j] << 1) | sot;
            bout[j] = (byte) sef;
        }
        bout[15] = (byte)(bin[15] << 1);
        return bout;
    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
    static SecretKeySpec skeySpec = null;

Assigning null is never a good idea. Besides that, for fields (static or not) Java will automatically assign null anyway.

    static public byte[] K1 = new byte[16]; // 128 bit sub key
    static final public byte[] Z16 = new byte[16]; // 128 bit zero

Now for the keys you had an excuse, but is this not called ZERO_VECTOR? Z16 is not in the specification.

    public static byte[] getDiversifiedKey() throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException // make K1 K2 from key

A getter should never throw an IOException: handle the exception internally or name the method differently.

        byte bRb = (byte) 0x87; // Rb for AES128

Completely unclear naming, and seemingly using Hungarian notation, which Java programmers will never use.

hexStringToByteArray

Not sure why you had to program that yourself, but at the naming is spot on at least.

            K1 = cipher.doFinal(Z16);

This doesn't make sense, you don't set static fields that way. Just keep the variable local. Assigning the new array to a field just tosses away the previous array reference.

            System.out.println("KO " + bytesToHex(K1));

Use a logger, or use a field set to System.out or something similar, but never print to console from anything other than a UI class.

            System.out.println("\n error 400 AES   " + ex.getMessage());

After catching never print and continue. I default not to printStackTrace (like crappy IDE's do) but to

// TODO better exception
throw new IllegalStateException("Exception not handled", e);

These runtime exceptions don't need to be handled and will at least exit the method without running on with invalid state.

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

Consider ByteBuffer.allocate() initialized to the correct size instead, then put the byte arrays into that, and retrieve the backing array using array(). Saves you the IOExceptions. In this case an IOException will never happen, so convert it to IllegalStateException again.

    final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();

Always keep to the right order: protected static final .... However, in this case private static final ... makes a lot more sense.

    public static String bytesToHex(byte[] bytes) {

Why is this method named rather differently than hexStringToByteArray?

    static byte[] xor16(byte[] ba, byte[] bb) {

What's the 16 for?

    static byte[] shl(byte[] bin) // << 16 byte array

Ah, another 16 this time. But in this case it is required. Please document things like the input size in the JavaDoc, and not in an end of line comment. Methods like these should be private (without any access specifier, methods and fields are visible from within the package).


General remarks:

  • If you use names from a specification (which is good) then point out the spec in the documentation of the class - use clear names otherwise;

  • Generally we don't use static anything, especially not for values that will change. None of the static values are necessary (bar the one for the hex decoding, possibly).

  • If you have static bytes then don't decode them each time, either decode them once and assign them to a private static final constant / class field or assign them directly to the class field (using { (byte) 0x3B, (byte) 0x17, (byte) 0x35 }).

  • Please group the various steps in logical groups and implement them using separate methods.

\$\endgroup\$
1
  • \$\begingroup\$ Late answer, I hope you learned to program (Java) better than above in the mean time. Try a few "clean code" books otherwise, this is not just Java that's the problem. \$\endgroup\$ Commented Mar 3, 2020 at 0:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.