/* * Base32.java - et-otp: GPL Java TOTP soft token by Bernd Eckenfels. */ package org.signserver.validationservice.server; import java.util.HashMap; import java.util.Locale; import java.util.Map; /** * Encodes arbitrary byte arrays as case-insensitive BASE32 strings. *

* This is based on the Google code from Steve Weis and Neal Gafter * http://code.google.com/p/google-authenticator/source/browse/mobile/android/src/com/google/android/apps/authenticator/Base32String.java */ public class Base32 { private static final Base32 INSTANCE = new Base32(); private final char[] DIGITS; private final int MASK; private final int SHIFT; private final Map CHAR_MAP; protected Base32() { DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".toCharArray(); // RFC 4668/3548 MASK = DIGITS.length - 1; SHIFT = Integer.numberOfTrailingZeros(DIGITS.length); CHAR_MAP = new HashMap(DIGITS.length); for (int i = 0; i < DIGITS.length; i++) { CHAR_MAP.put(DIGITS[i], i); } } public static Base32 getInstance() { return INSTANCE; } public static byte[] decode(String encoded) throws DecodingException { return getInstance().decodeInternal(encoded); } protected byte[] decodeInternal(String encodedArg) throws DecodingException { // Remove all '-', ' ', '.' (seperators, trimming). Might throw NPE if encodedArg is null String encoded = encodedArg.replaceAll("-? ?\\.?", ""); // Canonicalize to all upper case encoded = encoded.toUpperCase(Locale.ENGLISH); encoded = encoded.replaceAll("8", "B").replaceAll("1", "I"); if (encoded.length() == 0) { return new byte[0]; } int encodedLength = encoded.length(); int outLength = encodedLength * SHIFT / 8; byte[] result = new byte[outLength]; int buffer = 0; int next = 0; int bitsLeft = 0; for (char c : encoded.toCharArray()) { Integer i = CHAR_MAP.get(c); if (i==null) { throw new DecodingException("Illegal character: " + c); } buffer <<= SHIFT; buffer |= i.intValue() & MASK; bitsLeft += SHIFT; if (bitsLeft >= 8) { result[next++] = (byte) (buffer >> (bitsLeft - 8)); bitsLeft -= 8; } } // We'll ignore leftover bits for now. // if (next != outLength || bitsLeft >= SHIFT) { // throw new DecodingException("Bits left: " + bitsLeft);} return result; } public static String encode(byte[] data) { return getInstance().encodeInternal(data); } protected String encodeInternal(byte[] data) { if (data.length == 0) { return ""; } // SHIFT is the number of bits per output character, so the length of the // output is the length of the input multiplied by 8/SHIFT, rounded up. if (data.length >= (1 << 28)) { // The computation below will fail, so don't do it. throw new IllegalArgumentException(); } int outputLength = (data.length * 8 + SHIFT - 1) / SHIFT; StringBuilder result = new StringBuilder(outputLength); int buffer = data[0]; int next = 1; int bitsLeft = 8; while (bitsLeft > 0 || next < data.length) { if (bitsLeft < SHIFT) { if (next < data.length) { buffer <<= 8; buffer |= (data[next++] & 0xff); bitsLeft += 8; } else { int pad = SHIFT - bitsLeft; buffer <<= pad; bitsLeft += pad; } } int index = MASK & (buffer >> (bitsLeft - SHIFT)); bitsLeft -= SHIFT; result.append(DIGITS[index]); } return result.toString(); } public static class DecodingException extends Exception { /** field serialVersionUID */ private static final long serialVersionUID = 20111119L; public DecodingException(String message) { super(message); } } }