/* * Copyright 2013 Robert von Burg <eitch@eitchnet.ch> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package li.strolch.utils.helper; import java.text.MessageFormat; /** * <p> * This class implements the encoding and decoding of RFC 4648 <a>https://tools.ietf.org/html/rfc4648</a>. * </p> * * <p> * The following implementations are supported: * <ul> * <li>Base64</li> * <li>Base64 URL safe</li> * <li>Base32</li> * <li>Base32 HEX</li> * <li>Base16 / HEX</li> * </ul> * </p> * * <p> * As a further bonus, it is possible to use the algorithm with a client specified alphabet. In this case the client is * responsible for generating the alphabet for use in the decoding * </p> * * <p> * This class also implements a number of utility methods to check if given data is in a valid encoding * </p> * * @author Robert von Burg <eitch@eitchnet.ch> */ public class BaseEncoding { // private static final Logger logger = LoggerFactory.getLogger(BaseEncoding.class); private static final int PADDING_64 = 2; private static final int PADDING_32 = 6; public static final byte PAD = '='; static final byte[] BASE_16 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; static final byte[] BASE_32 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7' }; static final byte[] BASE_32_HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V' }; static final byte[] BASE_32_DMEDIA = { '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y' }; static final byte[] BASE_32_CROCKFORD = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' }; static final byte[] BASE_64 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; static final byte[] BASE_64_SAFE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; // these reverse base encoding alphabets were generated from the actual alphabet static final byte[] REV_BASE_16 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static final byte[] REV_BASE_32 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static final byte[] REV_BASE_32_CROCKFORD = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, -1, 18, 19, -1, 20, 21, -1, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static final byte[] REV_BASE_32_DMEDIA = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, -1, -1, -1, -1, -1, -1, -1, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static final byte[] REV_BASE_32_HEX = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static final byte[] REV_BASE_64 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; static final byte[] REV_BASE_64_SAFE = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; public static byte[] toBase64(byte[] bytes) { return toBase64(BASE_64, bytes); } public static String toBase64(String data) { return toBase64(BASE_64, data); } public static byte[] toBase64Safe(byte[] bytes) { return toBase64(BASE_64_SAFE, bytes); } public static String toBase64Safe(String data) { return toBase64(BASE_64_SAFE, data); } public static String toBase64(byte[] alphabet, String data) { return new String(toBase64(alphabet, data.getBytes())); } public static byte[] toBase32(byte[] bytes) { return toBase32(BASE_32, bytes); } public static String toBase32(String data) { return toBase32(BASE_32, data); } public static byte[] toBase32Hex(byte[] bytes) { return toBase32(BASE_32_HEX, bytes); } public static String toBase32Hex(String data) { return toBase32(BASE_32_HEX, data); } public static byte[] toBase32Dmedia(byte[] bytes) { return toBase32(BASE_32_DMEDIA, bytes); } public static String toBase32Dmedia(String data) { return toBase32(BASE_32_DMEDIA, data); } public static byte[] toBase32Crockford(byte[] bytes) { return toBase32(BASE_32_CROCKFORD, bytes); } public static String toBase32Crockford(String data) { return toBase32(BASE_32_CROCKFORD, data); } public static String toBase32(byte[] alphabet, String data) { return new String(toBase32(alphabet, data.getBytes())); } public static byte[] toBase16(byte[] bytes) { return toBase16(BASE_16, bytes); } public static String toBase16(String data) { return toBase16(BASE_16, data); } public static String toBase16(byte[] alphabet, String data) { return new String(toBase16(alphabet, data.getBytes())); } public static byte[] fromBase64(byte[] bytes) { return fromBase64(REV_BASE_64, bytes); } public static String fromBase64(String data) { return fromBase64(REV_BASE_64, data); } public static String fromBase64Safe(String data) { return fromBase64(REV_BASE_64_SAFE, data); } public static String fromBase64(byte[] alphabet, String data) { return new String(fromBase64(alphabet, data.getBytes())); } public static byte[] fromBase32(byte[] bytes) { return fromBase32(REV_BASE_32, bytes); } public static String fromBase32(String data) { return fromBase32(REV_BASE_32, data); } public static byte[] fromBase32Hex(byte[] bytes) { return fromBase32(REV_BASE_32_HEX, bytes); } public static String fromBase32Hex(String data) { return fromBase32(REV_BASE_32_HEX, data); } public static byte[] fromBase32Dmedia(byte[] bytes) { return fromBase32(REV_BASE_32_DMEDIA, bytes); } public static String fromBase32Dmedia(String data) { return fromBase32(REV_BASE_32_DMEDIA, data); } public static byte[] fromBase32Crockford(byte[] bytes) { return fromBase32(REV_BASE_32_CROCKFORD, bytes); } public static String fromBase32Crockford(String data) { return fromBase32(REV_BASE_32_CROCKFORD, data); } public static String fromBase32(byte[] alphabet, String data) { return new String(fromBase32(alphabet, data.getBytes())); } public static byte[] fromBase16(byte[] bytes) { return fromBase16(REV_BASE_16, bytes); } public static String fromBase16(String data) { return fromBase16(REV_BASE_16, data); } public static String fromBase16(byte[] alphabet, String data) { return new String(fromBase16(alphabet, data.getBytes())); } public static boolean isBase64(byte[] bytes) { return isEncodedByAlphabet(REV_BASE_64, bytes, PADDING_64); } public static boolean isBase64(String data) { return isEncodedByAlphabet(REV_BASE_64, data, PADDING_64); } public static boolean isBase64Safe(byte[] bytes) { return isEncodedByAlphabet(REV_BASE_64_SAFE, bytes, PADDING_64); } public static boolean isBase64Safe(String data) { return isEncodedByAlphabet(REV_BASE_64_SAFE, data, PADDING_64); } public static boolean isBase32(byte[] bytes) { return isEncodedByAlphabet(REV_BASE_32, bytes, PADDING_32); } public static boolean isBase32(String data) { return isEncodedByAlphabet(REV_BASE_32, data, PADDING_32); } public static boolean isBase32Hex(byte[] bytes) { return isEncodedByAlphabet(REV_BASE_32_HEX, bytes, PADDING_32); } public static boolean isBase32Hex(String data) { return isEncodedByAlphabet(REV_BASE_32_HEX, data, PADDING_32); } public static boolean isBase32Crockford(byte[] bytes) { return isEncodedByAlphabet(REV_BASE_32_CROCKFORD, bytes, PADDING_32); } public static boolean isBase32Crockford(String data) { return isEncodedByAlphabet(REV_BASE_32_CROCKFORD, data, PADDING_32); } public static boolean isBase32Dmedia(byte[] bytes) { return isEncodedByAlphabet(REV_BASE_32_DMEDIA, bytes, PADDING_32); } public static boolean isBase32Dmedia(String data) { return isEncodedByAlphabet(REV_BASE_32_DMEDIA, data, PADDING_32); } public static boolean isBase16(byte[] bytes) { return isEncodedByAlphabet(REV_BASE_16, bytes, 0); } public static boolean isBase16(String data) { return isEncodedByAlphabet(REV_BASE_16, data, 0); } public static boolean isEncodedByAlphabet(byte[] alphabet, String data, int padding) { return isEncodedByAlphabet(alphabet, data.getBytes(), padding); } /** * @param alphabet * @param bytes * @param maxPadding * * @return */ public static boolean isEncodedByAlphabet(byte[] alphabet, byte[] bytes, int maxPadding) { if (bytes.length == 0) return true; int paddingStart = 0; for (int i = 0; i < bytes.length; i++) { byte b = bytes[i]; if (b < 0 || b > alphabet.length) return false; byte c = alphabet[b]; if (c == -1) { if (b == PAD && maxPadding != 0) { if (paddingStart == 0) paddingStart = i; continue; } return false; } } if (paddingStart != 0 && paddingStart < (bytes.length - maxPadding)) return false; return true; } /** * Encodes the given data to a 64-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet * * @param alphabet * the 64-bit alphabet to use * @param bytes * the bytes to encode * * @return the encoded data */ public static byte[] toBase64(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) return new byte[0]; if (alphabet.length != 64) { String msg = MessageFormat.format("Alphabet does not have expected size 64 but is {0}", alphabet.length); //$NON-NLS-1$ throw new RuntimeException(msg); } // 6 bits input for every 8 bits (1 byte) output // least common multiple of 6 bits input and 8 bits output = 24 // and output multiple is then lcm(6, 8) / 6 = 4 // thus we need to write multiples of 4 bytes of data int bitsIn = 6; int outputMultiple = 4; // first convert to bits int nrOfInputBytes = bytes.length; int nrOfInputBits = nrOfInputBytes * Byte.SIZE; // calculate number of bits missing for multiples of bitsIn int inputPadding = nrOfInputBits % bitsIn; int nrOfOutputBytes; if (inputPadding == 0) nrOfOutputBytes = nrOfInputBits / bitsIn; else nrOfOutputBytes = (nrOfInputBits + (bitsIn - (inputPadding))) / bitsIn; // calculate number of bits missing for multiple of bitsOut int nrOfBytesPadding = outputMultiple - (nrOfOutputBytes % outputMultiple); if (nrOfBytesPadding == outputMultiple) nrOfBytesPadding = 0; // actual result array is multiples of bitsOut/8 thus sum of: int txtLength = nrOfOutputBytes + nrOfBytesPadding; // logger.info(String.format("Input: %d bytes, Output: %d bytes, Padding: %d bytes, TextLength: %d", // nrOfInputBytes, nrOfOutputBytes, nrOfBytesPadding, txtLength)); byte[] txt = new byte[txtLength]; long bits; int bytesPos = 0; int txtPos = 0; while (bytesPos < bytes.length) { int remaining = bytes.length - bytesPos; // get up to 24 bits of data in 3 bytes bits = 0; if (remaining >= 3) { bits = ((long) (bytes[bytesPos++] & 0xff) << 16) // | ((long) (bytes[bytesPos++] & 0xff) << 8) // | ((bytes[bytesPos++] & 0xff)); } else if (remaining == 2) { bits = ((long) (bytes[bytesPos++] & 0xff) << 16) // | ((long) (bytes[bytesPos++] & 0xff) << 8); } else if (remaining == 1) { bits = ((long) (bytes[bytesPos++] & 0xff) << 16); } // always start at 24. bit int bitPos = 23; // always write 24 bits (6 bits * 4), but this will also write into the padding // we will fix this by writing the padding as has been calculated previously while (bitPos >= 0) { int index = 0; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; bitPos--; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; bitPos--; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; bitPos--; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; bitPos--; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; bitPos--; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; bitPos--; byte character = alphabet[index]; txt[txtPos] = character; txtPos++; } } // write any padding that was calculated if (nrOfBytesPadding != 0) { int paddingPos = txtPos - nrOfBytesPadding; for (; paddingPos < txtLength; paddingPos++) { txt[paddingPos] = PAD; } } return txt; } /** * Encodes the given data to a 32-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet * * @param alphabet * the 32-bit alphabet to use * @param bytes * the bytes to encode * * @return the encoded data */ public static byte[] toBase32(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) return new byte[0]; if (alphabet.length != 32) { String msg = MessageFormat.format("Alphabet does not have expected size 32 but is {0}", alphabet.length); //$NON-NLS-1$ throw new RuntimeException(msg); } // 5 bits input for every 8 bits (1 byte) output // least common multiple of 5 bits input and 8 bits output = 40 // and output multiple is then lcm(5, 8) / 5 = 8 // thus we need to write multiples of 8 bytes of data int bitsIn = 5; int outputMultiple = 8; // first convert to bits int nrOfInputBytes = bytes.length; int nrOfInputBits = nrOfInputBytes * Byte.SIZE; // calculate number of bits missing for multiples of bitsIn int inputPadding = nrOfInputBits % bitsIn; int nrOfOutputBytes; if (inputPadding == 0) nrOfOutputBytes = nrOfInputBits / bitsIn; else nrOfOutputBytes = (nrOfInputBits + (bitsIn - (inputPadding))) / bitsIn; // calculate number of bits missing for multiple of bitsOut int nrOfBytesPadding = outputMultiple - (nrOfOutputBytes % outputMultiple); if (nrOfBytesPadding == outputMultiple) nrOfBytesPadding = 0; // actual result array is multiples of bitsOut/8 thus sum of: int txtLength = nrOfOutputBytes + nrOfBytesPadding; // logger.info(String.format("Input: %d bytes, Output: %d bytes, Padding: %d bytes, TextLength: %d", // nrOfInputBytes, nrOfOutputBytes, nrOfBytesPadding, txtLength)); byte[] txt = new byte[txtLength]; long bits; int bytesPos = 0; int txtPos = 0; while (bytesPos < bytes.length) { int remaining = bytes.length - bytesPos; // get up to 40 bits of data in 5 bytes bits = 0; if (remaining >= 5) { bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // | ((long) (bytes[bytesPos++] & 0xff) << 24) // | ((long) (bytes[bytesPos++] & 0xff) << 16) // | ((long) (bytes[bytesPos++] & 0xff) << 8) // | ((bytes[bytesPos++] & 0xff)); } else if (remaining == 4) { bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // | ((long) (bytes[bytesPos++] & 0xff) << 24) // | ((long) (bytes[bytesPos++] & 0xff) << 16) // | ((long) (bytes[bytesPos++] & 0xff) << 8); } else if (remaining == 3) { bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // | ((long) (bytes[bytesPos++] & 0xff) << 24) // | ((long) (bytes[bytesPos++] & 0xff) << 16); } else if (remaining == 2) { bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // | ((long) (bytes[bytesPos++] & 0xff) << 24); } else if (remaining == 1) { bits = ((long) (bytes[bytesPos++] & 0xff) << 32); } // always start at 40. bit int bitPos = 39; // always write 40 bits (5 bytes * 8 multiples), but this will also write into the padding // we will fix this by writing the padding as has been calculated previously while (bitPos >= 0) { int index = 0; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; bitPos--; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; bitPos--; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; bitPos--; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; bitPos--; index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; bitPos--; byte character = alphabet[index]; txt[txtPos] = character; txtPos++; } } // write any padding that was calculated if (nrOfBytesPadding != 0) { int paddingPos = txtPos - nrOfBytesPadding; for (; paddingPos < txtLength; paddingPos++) { txt[paddingPos] = PAD; } } return txt; } /** * Encodes the given data to a 16-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet * * @param alphabet * the 16-bit alphabet to use * @param bytes * the bytes to encode * * @return the encoded data */ public static byte[] toBase16(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) return new byte[0]; if (alphabet.length != 16) { String msg = MessageFormat.format("Alphabet does not have expected size 16 but is {0}", alphabet.length); //$NON-NLS-1$ throw new RuntimeException(msg); } // calculate output text length int nrOfInputBytes = bytes.length; int nrOfOutputBytes = nrOfInputBytes * 2; int txtLength = nrOfOutputBytes; // logger.info(String.format("Input: %d bytes, Output: %d bytes, TextLength: %d", nrOfInputBytes, nrOfOutputBytes, // txtLength)); byte[] txt = new byte[txtLength]; byte bits; int bytesPos = 0; int txtPos = 0; while (bytesPos < bytes.length) { // get 8 bits of data (1 byte) bits = bytes[bytesPos++]; // now write the 8 bits as 2 * 4 bits // output byte 1 int index = (bits >>> 4) & 0xf; byte character = alphabet[index]; txt[txtPos] = character; txtPos++; // output byte 2 index = bits & 0xf; character = alphabet[index]; txt[txtPos] = character; txtPos++; } return txt; } /** * Decodes the given Base64 encoded data to the original data set * * @param alphabet * the 64-bit alphabet to use * @param bytes * the bytes to decode * * @return the decoded data */ public static byte[] fromBase64(byte[] alphabet, byte[] bytes) { int inputLength = bytes.length; if (inputLength == 0) return new byte[0]; if ((inputLength % 4) != 0) { String msg = MessageFormat.format( "The input bytes to be decoded must be multiples of 4, but is multiple of {0}", //$NON-NLS-1$ (inputLength % 4)); throw new RuntimeException(msg); } if (alphabet.length != 128) { String msg = MessageFormat.format("Alphabet does not have expected size 128 but is {0}", alphabet.length); //$NON-NLS-1$ throw new RuntimeException(msg); } if (!isEncodedByAlphabet(alphabet, bytes, PADDING_64)) throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); //$NON-NLS-1$ // find how much padding we have int nrOfBytesPadding = 0; if (bytes[inputLength - 1] == PAD) { int end = inputLength - 1; while (bytes[end] == PAD) end--; if (end != inputLength - 1) nrOfBytesPadding = inputLength - 1 - end; } int inputDataLength = inputLength - nrOfBytesPadding; int dataLengthBits = inputDataLength * 6; // 6 bits data for every 8 bits inputs // multiples of 6 required // truncating is no problem due to the input having padding to have multiples of 32 bits dataLengthBits = dataLengthBits - (dataLengthBits % 8); int dataLengthBytes = dataLengthBits / 8; // f => Zg== // fo => Zm8= // foo => Zm9v // we want to write as much as 24 bits in multiples of 6. // these multiples of 6 are read from multiples of 8 // i.e. we discard 2 bits from every 8 bits input // thus we need to read 24 / 6 = 4 bytes byte[] data = new byte[dataLengthBytes]; int dataPos = 0; // but we simply ignore the padding int bytesPos = 0; while (bytesPos < inputDataLength) { int remaining = inputDataLength - bytesPos; long bits; if (remaining >= 4) { bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6) // | (alphabet[bytes[bytesPos++]] & 63); } else if (remaining == 3) { bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6); } else if (remaining == 2) { bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12); // // long b; // byte a; // a = bytes[0]; // logger.info("1 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); // b = (byte) (alphabet[a] & 63); // logger.info("1 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); // a = bytes[1]; // logger.info("2 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); // b = (byte) (alphabet[a] & 63); // logger.info("2 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); } else if (remaining == 1) { bits = ((alphabet[bytes[bytesPos++]] & 63) << 18); } else { bits = 0L; } // we can truncate to 8 bits int toWrite = remaining >= 4 ? 3 : remaining * 6 / 8; // max is always 3 bytes data from 4 bytes input // logger.info("toWrite: " + toWrite + ", remaining: " + remaining); // logger.info("bits: " + ByteHelper.asBinary(bits)); // always start at 24. bit int bitPos = 23; // always write 24 bits (8 bits * n bytes) for (int i = 0; i < toWrite; i++) { byte value = 0; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; bitPos--; data[dataPos] = value; dataPos++; } } return data; } /** * Decodes the given Base32 encoded data to the original data set * * @param alphabet * the 32-bit alphabet to use * @param bytes * the bytes to decode * * @return the decoded data */ public static byte[] fromBase32(byte[] alphabet, byte[] bytes) { int inputLength = bytes.length; if (inputLength == 0) return new byte[0]; if ((inputLength % 8) != 0) { String msg = "The input bytes to be decoded must be multiples of 8, but is multiple of {0}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, (inputLength % 8)); throw new RuntimeException(msg); } if (alphabet.length != 128) { String msg = MessageFormat.format("Alphabet does not have expected size 128 but is {0}", alphabet.length); //$NON-NLS-1$ throw new RuntimeException(msg); } if (!isEncodedByAlphabet(alphabet, bytes, PADDING_32)) throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); //$NON-NLS-1$ // find how much padding we have int nrOfBytesPadding = 0; if (bytes[inputLength - 1] == PAD) { int end = inputLength - 1; while (bytes[end] == PAD) end--; if (end != inputLength - 1) nrOfBytesPadding = inputLength - 1 - end; } int inputDataLength = inputLength - nrOfBytesPadding; int dataLengthBits = inputDataLength * 5; // 5 bits data for every 8 bits inputs // multiples of 8 required // truncating is no problem due to the input having padding to have multiples of 40 bits dataLengthBits = dataLengthBits - (dataLengthBits % 8); int dataLengthBytes = dataLengthBits / 8; // logger.info("Input " + inputLength + " bytes, InputData " + inputDataLength + " bytes, Padding: " // + nrOfBytesPadding + " bytes, dataLength: " + dataLengthBits + " bits, dataLengthBytes: " // + dataLengthBytes + " bytes"); // logger.info(ByteHelper.asBinary(bytes)); // we want to write as much as 40 bits in multiples of 5. // these multiples of 5 are read from multiples of 8 // i.e. we discard 3 bits from every 8 bits input // thus we need to read 40 / 5 = 8 bytes byte[] data = new byte[dataLengthBytes]; int dataPos = 0; // but we simply ignore the padding int bytesPos = 0; while (bytesPos < inputDataLength) { int remaining = inputDataLength - bytesPos; long bits; if (remaining >= 8) { bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5) // | (alphabet[bytes[bytesPos++]] & 31); } else if (remaining >= 7) { bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5); } else if (remaining == 6) { bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10); } else if (remaining == 5) { bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15); } else if (remaining == 4) { bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20); } else if (remaining == 3) { bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25); } else if (remaining == 2) { bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30); } else if (remaining == 1) { bits = ((alphabet[bytes[bytesPos++]] & 31) << 35); } else { bits = 0L; } // we can truncate to 8 bits int toRead = remaining >= 8 ? 5 : remaining * 5 / 8; // max is always 5 bytes data from 8 bytes input // always start at 40. bit int bitPos = 39; // always write 40 bits (5 bytes * 8 bits) for (int i = 0; i < toRead; i++) { byte value = 0; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; bitPos--; value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; bitPos--; data[dataPos] = value; dataPos++; } } return data; } /** * Decodes the given Base16 encoded data to the original data set * * @param alphabet * the 16-bit alphabet to use * @param bytes * the bytes to decode * * @return the decoded data */ public static byte[] fromBase16(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) return new byte[0]; if ((bytes.length % 2) != 0) { String msg = "The input bytes to be decoded must be multiples of 4, but is multiple of {0}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, (bytes.length % 4)); throw new RuntimeException(msg); } if (alphabet.length != 128) { String msg = MessageFormat.format("Alphabet does not have expected size 128 but is {0}", alphabet.length); //$NON-NLS-1$ throw new RuntimeException(msg); } if (!isEncodedByAlphabet(alphabet, bytes, 0)) throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); //$NON-NLS-1$ int dataLength = bytes.length / 2; byte[] data = new byte[dataLength]; for (int i = 0; i < bytes.length;) { byte b1 = bytes[i++]; byte b2 = bytes[i++]; String msgOutOfRange = "Value at index {0} is not in range of alphabet (0-127){1}"; //$NON-NLS-1$ if (b1 < 0) { msgOutOfRange = MessageFormat.format(msgOutOfRange, (i - 2), b1); throw new IllegalArgumentException(msgOutOfRange); } if (b2 < 0) { msgOutOfRange = MessageFormat.format(msgOutOfRange, (i - 1), b2); throw new IllegalArgumentException(msgOutOfRange); } byte c1 = alphabet[b1]; byte c2 = alphabet[b2]; String msgIllegalValue = "Value at index {0} is referencing illegal value in alphabet: {1}"; //$NON-NLS-1$ if (c1 == -1) { msgIllegalValue = MessageFormat.format(msgIllegalValue, (i - 2), b1); throw new IllegalArgumentException(msgIllegalValue); } if (c2 == -1) { msgIllegalValue = MessageFormat.format(msgIllegalValue, (i - 2), b2); throw new IllegalArgumentException(msgIllegalValue); } int dataIndex = (i / 2) - 1; int value = ((c1 << 4) & 0xff) | c2; data[dataIndex] = (byte) value; } return data; } }