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;
}
}