Cryptopals first challenge – hexadecimal to base64

Posted on

Problem

I’ve just started the cryptopals-challenge, and now wanted to show my solution to the first challenge here:

public class Challenge1_1 {

    public static void main(String[] args) {
        String hex = "49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d";
        String base64 = hextobase64(hex);
        System.out.println(base64);
        String test = "SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t";
        boolean t = base64.equals(test);
        System.out.println(t);
    }

    public static String hextobase64(String hex) {

        String[] hex_array = new String[hex.length()/2];

        //Splits up the hex String into substring with length 2
        for(int i = 0; i < hex_array.length; i++) {
            int x = 2 * i;
            int y = x + 2;
            hex_array[i] = substring(hex, x, y);
        }

        //Conversion to binary system
        String binary = "";
        String bin = "";
        for(int i = 0; i < hex_array.length; i++) {
            bin = conversion(hex_array[i]);
            while(bin.length()<8){
                bin = "0" + bin;
            }
            binary = binary + bin;
        }

        //Split up to strings of length 6
        String[] binary6 = new String[binary.length()/6];
        for(int i = 0; i < binary6.length; i++) {
            int x = i * 6;
            int y = x + 6;
            binary6[i] = substring(binary, x, y);
        }

        //Conversion to base64
        String out = "";
        String character = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        String[] base64 = {
                "000000", "000001", "000010", "000011", "000100", "000101", "000110", "000111", "001000", "001001", "001010", "001011", "001100", "001101", "001110", "001111",
                "010000", "010001", "010010", "010011", "010100", "010101", "010110", "010111", "011000", "011001", "011010", "011011", "011100", "011101", "011110", "011111",
                "100000", "100001", "100010", "100011", "100100", "100101", "100110", "100111", "101000", "101001", "101010", "101011", "101100", "101101", "101110", "101111",
                "110000", "110001", "110010", "110011", "110100", "110101", "110110", "110111", "111000", "111001", "111010", "111011", "111100", "111101", "111110", "111111"
        };
        for(int i = 0; i < binary6.length; i++) {
            for(int j = 0; j < base64.length; j++) {
                if(binary6[i].equals(base64[j])){
                    out = out + character.charAt(j);
                }
            }
        }
        return(out);
    }

    public static String conversion(String hex) {
        //hex-system to decimal system
        String hex_numbers = "0123456789ABCDEF";
        hex = hex.toUpperCase();
        int value = 0;
        for (int i = 0; i < hex.length(); i++)
        {
            int d = hex_numbers.indexOf(hex.charAt(i));
            value = 16*value + d;
        }

        //decimal to binary
        String out = "";
        while(value != 0){
            int mod = value%2;
            String m = mod + "";
            value = value/2;
            out = m + out;

        }
        return out;

    }

    public static String substring(String str, int start, int end) {

        String out = "";
        if (start > end) {
            return out;
        }

        if (start < 0) {
            start = 0;
        }

        if (end > str.length() - 1) {
            end = str.length();
        }

        while (start < end) {
            out = out + str.charAt(start);
            start = start + 1;
        }

        return out;

    }
}

I’ve tried to all do it manually instead of using some kind of java-package.
I would appreciate any suggestions to improve the code.

Solution

That is a very pure solution that does not use any available feature. It is a solid solution.

However everything is String, even the conversion from a byte as two hexadecimal digits uses integer, but converts it back to a string.

The same code style of yours would allow immediately convert every hexadecimal digit to 4 bits.

    final String[] nibbles = { "0000", "0001", "0010", "0011",
            "0100", "0101", "0110", "0111",
            "1000", "1001", "1010", "1011",
            "1100", "1101", "1110", "1111" };
    int nbitsRaw = hex.length() * 4;

    // Make nbits a multiple of 6 
    int nbits += (6 - (nbitsRaw % 6)) % 6;
    StringBuilder sb = new StringBuilder(nbits);
    hex.codePoints()
            .forEach(hexdigit -> {
                int value = hexdigit <= '9' ? hexdigit - '0' : 9 + (hexdigit & 0xF); // 0-9A-Fa-F
                sb.append(nibbles[value]);
            });
    for (int i = nbitsRaw; i < nbits; ++i) {
        sb.append('0');
    }

Best of course would be using the bits in an int, not needing to juggle string constants of binary numbering. Especially the loops hurt. One would indeed far better use a Map<String, Character> but I understand your requirement of not using any higher construct.

The code is necessarily slow. You can try a longer input, and will probably have to wait for a result.

String concatenation is slow; char[] or StringBuilder would be advisable.
One can always do new String(charArray).

Stylistic:

  • hextobase64 by java camel case convention: hexToBase64
  • conversion no-namer: hexByteToBits
  • The constants could be fields private static final String[] BASE64 i.o. base64.

Leave a Reply

Your email address will not be published. Required fields are marked *