package rabbitescape.engine.util; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * Obfuscate strings like a champion. * <p> * Swaps characters within the string around, swaps well-known characters with * each other, and transposes the string by exchanging characters with other * characters. * <p> * The MegaCoder is licensed here for use as part of rabbit-escape, but is also * declared public domain, so that it may be used, without attribution, by * anyone who needs obfuscation slightly more powerful than rot13, but less * portable than base64. (For clarity, the MegaCoder is this single file). * <p> * The MegaCoder shuffle algorithm: * <pre> * Input: An array of length l, A integer key k * i = 0 * while at least two array entries remain unswapped: * if array entry i has not been swapped: * j = (floor(length / 2) + 3i + k) mod l * while array entry j has already been swapped: * j = (j + 1) mod l * swap entries i and j in the array * i = i + 1 * Output: The modified array * </pre> * The MegaCoder replace algorithm: * <pre> * Input: An key array k of length l, An array of characters x * for i in [0, floor(l / 2)]: * a = entry i in k; b = entry (l - 1 - i) in k * replace all instances of a with b, and b with a, in the array x * Output: The modified array x * </pre> * The MegaCoder uniquify algorithm: * <pre> * Input: An array of characters x * Output: The set of characters in x sorted by value * </pre> * The MegaCoder encode algorithm: * <pre> * Input: An array of characters x of length l * k1 = uniquify(x) * x = replace(k1, x) * k2 = shuffle(C, l) where C is the array of ASCII characters from space (0x20) to tilde (0x7e) * x = replace(k2, x) * x = shuffle(x, 0) * Output: The modified array x * </pre> * The MegaCoder decode algorithm: * <pre> * Input: An array of characters x of length l * x = shuffle(x, 0) * k2 = shuffle(C, l) where C is the array of ASCII characters from space (0x20) to tilde (0x7e) * x = replace(k2, x) * k1 = uniquify(x) * x = replace(k1, x) * Output: The modified array x * </pre> * * @author tttppp */ public class MegaCoder { /** A list of common ASCII characters that can be swapped with each other. */ private static final char[] COMMON_CHARACTERS = new char[] { ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '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', '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', '{', '|', '}', '~' }; /** Private constructor for util class. */ private MegaCoder() { } /** * Encode a string using the MegaCoder obfuscation routine. * * @param input * The plaintext to encode. * @return The obfuscated ciphertext. */ public static String encode( String input ) { char[] charArray = input.toCharArray(); char[] sortedCharsUsed = getSortedUniqueChars( charArray ); charArray = replaceUsingCharacterList( sortedCharsUsed, charArray ); charArray = replaceUsingCharacterList( shuffle( COMMON_CHARACTERS, charArray.length ), charArray ); charArray = shuffle( charArray, 0 ); return String.valueOf( charArray ); } /** * Decode a string using the MegaCoder obfuscation routine. * * @param input * The ciphertext to decode. * @return The plaintext. */ public static String decode( String input ) { char[] charArray = input.toCharArray(); charArray = shuffle( charArray, 0 ); charArray = replaceUsingCharacterList( shuffle( COMMON_CHARACTERS, charArray.length ), charArray ); char[] sortedCharsUsed = getSortedUniqueChars( charArray ); charArray = replaceUsingCharacterList( sortedCharsUsed, charArray ); return String.valueOf( charArray ); } /** * Create a new character array containing every character that appears in a * given array once. The new array will be sorted by natural order. * * @param charArray * The character array to process. * @return An array containing the sorted set of characters in the input. */ private static char[] getSortedUniqueChars( char[] charArray ) { // Create a sorted copy of the input array. char[] sortedCharsUsed = copyArray( charArray ); Arrays.sort( sortedCharsUsed ); // Handle the edge case of an empty array. if ( sortedCharsUsed.length == 0 ) { return sortedCharsUsed; } // Create a 'uniquified' copy of the array. int uniqueCharCount = 1; for ( int i = 0; i < sortedCharsUsed.length - 1; i++ ) { if ( sortedCharsUsed[i] != sortedCharsUsed[i + 1] ) { uniqueCharCount++; } } char[] uniqueSortedCharsUsed = new char[uniqueCharCount]; uniqueSortedCharsUsed[0] = sortedCharsUsed[0]; int uniqueArrayIndex = 1; for ( int i = 0; i < sortedCharsUsed.length - 1; i++ ) { if ( sortedCharsUsed[i] != sortedCharsUsed[i + 1] ) { uniqueSortedCharsUsed[uniqueArrayIndex] = sortedCharsUsed[i + 1]; uniqueArrayIndex++; } } return uniqueSortedCharsUsed; } /** * Replace characters in a string using a key. * * @param charsListKey * A list of distinct characters. The first will be swapped with * the last, and so on. If there is an odd number of characters * then the middle character will not be swapped at all. * @param input * The string to replace the characters in. */ private static char[] replaceUsingCharacterList( char[] charsListKey, char[] input ) { char[] output = input; int distinctCharacters = charsListKey.length; for ( int i = 0; i < distinctCharacters / 2; i++ ) { output = exchangeCharacters( output, charsListKey[i], charsListKey[distinctCharacters - i - 1] ); } return output; } /** * Swap two characters with each other in a given characters list. * * @param chars * The list of characters to search through. * @param a * A character. * @param b * Another character. * @return The input, but with all occurrences of a and b exchanged. */ private static char[] exchangeCharacters( char[] chars, char a, char b ) { for ( int i = 0; i < chars.length; i++ ) { if ( chars[i] == a ) { chars[i] = b; } else if ( chars[i] == b ) { chars[i] = a; } } return chars; } /** * Shuffle the characters in the given string. * * @param input * The string to be shuffled. * @param key * A value that changes the shuffling algorithm. * @return The string with letters swapped around a bit. */ private static char[] shuffle( char[] input, int key ) { char[] output = copyArray( input ); Set<Integer> swappedIndexes = new HashSet<>(); int length = output.length; for ( int i = 0; swappedIndexes.size() < ( length / 2 ) * 2; i++ ) { if ( !swappedIndexes.contains( i ) ) { int j = ( ( length / 2 ) + i * 3 + key ) % length; while ( swappedIndexes.contains( j ) || j == i ) { j = ( j + 1 ) % length; } Character temp = output[i]; output[i] = output[j]; output[j] = temp; swappedIndexes.add( i ); swappedIndexes.add( j ); } } return output; } private static char[] copyArray( char[] input ) { char[] ret = new char[ input.length ]; System.arraycopy( input, 0, ret, 0, ret.length ); return ret; } }