001package squidpony; 002 003import squidpony.squidmath.CrossHash; 004import squidpony.squidmath.CrossHash.Mist; 005 006/** 007 * Tools for garbling Strings (making them appear to be gibberish) and degarbling earlier outputs to get the original 008 * inputs. This is like a weak form of encryption, and is probably enough to stop random users from editing saved files 009 * effectively. This allows a key as a String, long, or long array to be used to affect the garbling process. 010 * <br> 011 * A minor step of obfuscation could be to run some combination of garble calls with different keys and then require 012 * they be degarbled by degarble calls (with the same keys as before) in the reverse order of the garble calls. 013 * This is made more efficient with the {@link #garble(String, long[])} and {@link #degarble(String, long[])} methods, 014 * which avoid allocating multiple temporary char arrays when multiple keys are used. A more major step of obfuscation 015 * would be to run any garbling on already-compressed text, which {@link LZSPlus} does by using this class. 016 * <br> 017 * Created by Tommy Ettinger on 5/22/2017. 018 */ 019public final class Garbler { 020 021 private Garbler() 022 { 023 } 024 025 /** 026 * Get a long from this with {@code splitMix64(z += 0x9E3779B97F4A7C15L)}, where z is a long to use as state. 027 * 0x9E3779B97F4A7C15L can be changed for any odd long if the same number is used across calls. 028 * @param state long, must be changed with each call; {@code splitMix64(z += 0x9E3779B97F4A7C15L)} is recommended 029 * @return a pseudo-random long; all are possible 030 */ 031 private static long splitMix64(long state) { 032 state = ((state >>> 30) ^ state) * 0xBF58476D1CE4E5B9L; 033 state = (state ^ (state >>> 27)) * 0x94D049BB133111EBL; 034 return state ^ (state >>> 31); 035 } 036 037 /** 038 * ThrustAltRNG.determine() in a stable form. Expects state to change by 1. 039 * @param state should change by 1 040 * @return a pseudo-random long; only about 2/3 of all 64-bit values are possible 041 */ 042 private static long ta(long state) { 043 return (state = ((state *= 0x6C8E9CF570932BD5L) ^ (state >>> 25)) * (state | 0xA529L)) ^ (state >>> 23); 044 } 045 046 /** 047 * Mizuchi.nextLong() in a stable form. 048 * Expects to be called with with {@code mizuchi(state = state * 0x369DEA0F31A53F85L + stream)} 049 * @param state should change with an LCG 050 * @return a pseudo-random long; all are possible 051 */ 052 private static long mizuchi(long state) 053 { 054 return (state = (state ^ state >>> 23 ^ state >>> 47) * 0xAEF17502108EF2D9L) ^ state >>> 25; 055 } 056 057 /** 058 * Garbles text with the default key. This can be degarbled with {@link #degarble(String)}, which also uses the 059 * default key. 060 * @param text the text to garble 061 * @return a new String that appears unrelated to text and should look like gibberish 062 */ 063 public static String garble(final String text) { 064 return garble(text, "HOWARD PHILLIPS LOVECRAFT, EVERYBODY!"); 065 } 066 067 /** 068 * Garbles text with the given keyText. This can be degarbled with {@link #degarble(String, String)}, which must be 069 * given the same keyText. 070 * @param text the text to garble 071 * @param keyText used to determine the key this will use to garble text 072 * @return a new String that appears unrelated to text and should look like gibberish 073 */ 074 public static String garble(final String text, final String keyText) 075 { 076 return garble(text, CrossHash.Water.hash64(keyText) ^ 0x7F4A7C15L); 077 } 078 079 /** 080 * Garbles text with the given key as a long. This can be degarbled with {@link #degarble(String, long)}, which must 081 * be given the same key. 082 * @param text the text to garble 083 * @param key the key this will use to garble text 084 * @return a new String that appears unrelated to text and should look like gibberish 085 */ 086 public static String garble(final String text, final long key) 087 { 088 final char[] cs = text.toCharArray(); 089 final int len = cs.length; 090 long state = ta(key); 091 final long increment = ta(state) | 1; 092 long wiggle; 093 for (int i = len - 1; i > 0; i--) { 094 wiggle = splitMix64(state += increment); 095 int r = (int) (((i+1L) * (wiggle & 0x7FFFFFFFL)) >>> 31);; 096 char c = cs[r]; 097 cs[r] = cs[i]; 098 cs[i] = (char) (c ^ wiggle >>> 59); 099 } 100 wiggle = splitMix64(state + increment); 101 cs[0] ^= wiggle >>> 59; 102 return String.valueOf(cs); 103 } 104 105 /** 106 * Given a garbled String that was produced by {@link #garble(String)} (using the default key), this reverses the 107 * garbling and gets the original String. 108 * @param garbled a String produced by a garble() method using the default key 109 * @return the original String before garbling, if the keys match 110 */ 111 public static String degarble(final String garbled) { 112 return degarble(garbled, "HOWARD PHILLIPS LOVECRAFT, EVERYBODY!"); 113 } 114 /** 115 * Given a garbled String that was produced by {@link #garble(String, String)} (using the given keyText), this 116 * reverses the garbling and gets the original String. 117 * @param garbled a String produced by a garble() method using the same keyText 118 * @param keyText the keyText that was used during garbling 119 * @return the original String before garbling, if the keys match 120 */ 121 public static String degarble(final String garbled, final String keyText) 122 { 123 return degarble(garbled,CrossHash.Water.hash64(keyText) ^ 0x7F4A7C15L); 124 } 125 126 /** 127 * Given a garbled String that was produced by {@link #garble(String, long)} (using the given key), this reverses 128 * the garbling and gets the original String. 129 * @param garbled a String produced by a garble() method using the same keyText 130 * @param key the key that was used during garbling 131 * @return the original String before garbling, if the keys match 132 */ 133 public static String degarble(final String garbled, final long key) 134 { 135 final char[] cs = garbled.toCharArray(); 136 final int len = cs.length - 1; 137 long state = ta(key); 138 final long increment = ta(state) | 1; 139 long wiggle = splitMix64(state += increment * (len+1)); 140 cs[0] ^= wiggle >>> 59; 141 for (int i = 1; i <= len; i++) { 142 wiggle = splitMix64(state -= increment); 143 int r = (int) (((i+1L) * (wiggle & 0x7FFFFFFFL)) >>> 31); 144 if(i == r) 145 { 146 cs[i] ^= wiggle >>> 59; 147 } 148 else { 149 char c = cs[r]; 150 cs[r] = (char) (cs[i] ^ wiggle >>> 59); 151 cs[i] = c; 152 } 153 } 154 return String.valueOf(cs); 155 } 156 157 /** 158 * Garbles text with the given keys as a long array, effectively garbling the same text one time per item in keys. 159 * This can seen as a way to improve the quality of the shuffle by adding more bits of state to the key(s). 160 * The result can be degarbled with {@link #degarble(String, long[])}, which must be given the same keys. This 161 * method is more efficient than calling garble() repeatedly because it only allocates one temporary char array 162 * for the whole batch of keys, as opposed to needing one temporary array per key with repeated calls. 163 * @param text the text to garble 164 * @param keys the key array this will use to garble text, as a long array 165 * @return a new String that appears unrelated to text and should look like gibberish 166 */ 167 public static String garble(final String text, final long[] keys) 168 { 169 if(keys == null) 170 return garble(text); 171 final char[] cs = text.toCharArray(); 172 final int len = cs.length; 173 for (int k = 0; k < keys.length; k++) { 174 final long key = keys[k]; 175 long state = ta(key); 176 final long increment = ta(state) | 1; 177 long wiggle; 178 for (int i = len - 1; i > 0; i--) { 179 wiggle = splitMix64(state += increment); 180 int r = (int) (((i+1L) * (wiggle & 0x7FFFFFFFL)) >>> 31); 181 char c = cs[r]; 182 cs[r] = cs[i]; 183 cs[i] = (char) (c ^ wiggle >>> 59); 184 } 185 wiggle = splitMix64(state + increment); 186 cs[0] ^= wiggle >>> 59; 187 } 188 return String.valueOf(cs); 189 } 190 /** 191 * Given a garbled String that was produced by {@link #garble(String, long[])} (using the given keys), this 192 * reverses the garbling and gets the original String. This is not the same as calling degarble() repeatedly, in 193 * part because this uses the keys in reverse order (just like every part of the degarbling process, it needs to be 194 * in reverse), and in part because this only creates one temporary char array for the whole batch of keys, instead 195 * of creating one new char array per repeated call. 196 * @param garbled a String produced by a garble() method using the same keyText 197 * @param keys the key array that was used during garbling 198 * @return the original String before garbling, if the keys match 199 */ 200 public static String degarble(final String garbled, final long[] keys) 201 { 202 if(keys == null) 203 return degarble(garbled); 204 final char[] cs = garbled.toCharArray(); 205 final int len = cs.length - 1; 206 for (int k = keys.length - 1; k >= 0; k--) { 207 final long key = keys[k]; 208 long state = ta(key); 209 final long increment = ta(state) | 1; 210 long wiggle = splitMix64(state += increment * (len + 1)); 211 cs[0] ^= wiggle >>> 59; 212 for (int i = 1; i <= len; i++) { 213 wiggle = splitMix64(state -= increment); 214 int r = (int) (((i+1L) * (wiggle & 0x7FFFFFFFL)) >>> 31); 215 if (i == r) { 216 cs[i] ^= wiggle >>> 59; 217 } else { 218 char c = cs[r]; 219 cs[r] = (char) (cs[i] ^ wiggle >>> 59); 220 cs[i] = c; 221 } 222 } 223 } 224 return String.valueOf(cs); 225 } 226 227 /** 228 * If you need to produce an long array as a key for {@link #garble(String, long[])} when you only have a String, 229 * you can use this method if the String isn't too small (at least 8 char Strings should be fine). This produces a 230 * diverse array of longs without the correlation between items that you would get if you just generated a sequence 231 * of random longs from one small seed, by using multiple different {@link Mist} objects to hash the text. 232 * @param size the size of the key array to produce; larger key arrays take proportionately longer to process 233 * @param keyText the String to use as a basis for generating random-seeming numbers for keys 234 * @return a long array that can be given to {@link #garble(String, long[])} and {@link #degarble(String, long[])} 235 */ 236 public static long[] makeKeyArray(final int size, final String keyText) 237 { 238 if(size <= 1) return new long[]{Mist.predefined[keyText.length() & 31].hash64(keyText)}; 239 long[] keys = new long[size]; 240 long ctr = keyText.length() * 181L + 0xB9A2842FL; 241 for (int i = 0; i < size; i++) { 242 ctr += (keys[i] = Mist.predefined[(int)splitMix64(ctr) & 31].hash64(keyText)) + 0xB9A2842FL; 243 keys[i] ^= ta(ctr); 244 } 245 return keys; 246 } 247}