Encode data into user-typed code, like an old game's password-save system
-
18-06-2021 - |
Question
I tried searching for information on this, but must not know the right terms to use...
What would be the simplest way to store information in a human-readable and -writeable passcode, with enough padding to make randomly guessed codes unlikely to be valid? Not unlike 8-bit console games' password-save systems.
In my particular case, I'd like to be able to encode three unsigned integers (with a maximum length of probably about 20 bits each) into a passcode under Java 1.4.2, and decrypt them on a web server. I was thinking about also adding a random or date-based value in an attempt to make passcodes unique.
The code could use numbers, mixed-case letters and potentially some simple symbols, but should probably exclude easily-confused characters like 1lI and O0. Since the user needs to type it, obviously shorter is better.
Thanks!
Solution
- Choose the alphabet that you want to use. It can have as many symbols as you like, in any order. An example alphabet would be
ABCDEFGHJKLMNPQRSTUVWXYZ0123456789
which contains 34 symbols. - Encode the data you want to save as a single integer by using bitshifts and bitwise or, or multiplication and addition. This shouldn't be a problem if you have about 20 bits of data as you say.
- Encode the integer from step 1 in base-N, where N is the number of symbols in your alphabet, then print it using symbols from the alphabet.
- Add a "check digit" on the end. Adding all of the output digits mod N is a simple and useful system. A check digit isn't real crypto and won't stop anyone who's putting any effort into forging a save code, but it limits the chances of a randomly chosen code working. If your alphabet has 34 symbols then there's only a 1-in-34 chance of the check digit matching on a random code.
- To decode, first validate the check digit in the received code, then decode the base-N representation (reverse step 3), then extract the individual values from the integer value (reverse step 2).
The number of symbols needed to represent k
bits of data in base n
plus one check digit is ceil(k * log(2) / log(n)) + 1
, which is only 5 for k
=20 bits and n
=34.
OTHER TIPS
You can encode your properties as Base64. The only restriction is that you must have a fixed size of your properties. You may want to apply MD5 algorithm or similar to the encoded string.
Take a look at this snipplet using apache commons codec for java 1.4
public class Base64Encoder {
/**
* Base64 game properties encoded
* @param properties list of properties as Strings
* @return base64 encoded properties
*/
public String encodeProperties(List properties){
StringBuffer buffer = new StringBuffer();
Iterator iter = properties.iterator();
while(iter.hasNext()){
buffer.append(iter.next().toString());
}
return Base64.encodeBase64String(buffer.toString().getBytes());
}
/**
* Decodes a based64 properties
* @param propertiesSize list of Integer with each property size
* @param encodedProperties base64 encoded properties
* @return List of properties as String
*/
public List decodeProperties(List propertiesSize, String encodedProperties){
List decodedProperties = new ArrayList();
Iterator iter = propertiesSize.iterator();
String decoded = new String(Base64.decodeBase64(encodedProperties.getBytes()));
int current = 0;
while(iter.hasNext()){
Integer propertySize = new Integer(iter.next().toString());
String property = decoded.substring(current, current + propertySize.intValue());
decodedProperties.add(property);
current += propertySize.intValue();
}
return decodedProperties;
}
public static void main(String[] args){
String points = "1450";
String level = "12";
String lifes = "2";
Base64Encoder encoder = new Base64Encoder();
List properties = new ArrayList();
properties.add(points);
properties.add(level);
properties.add(lifes);
String encodedProperties = encoder.encodeProperties(properties); //MTQ1MDEyMg==
System.out.println(encodedProperties);
List propertiesSize = new ArrayList();
propertiesSize.add(Integer.valueOf(points.length()));
propertiesSize.add(Integer.valueOf(level.length()));
propertiesSize.add(Integer.valueOf(lifes.length()));
System.out.println(encoder.decodeProperties(propertiesSize, encodedProperties)); //[1450, 12, 2]
}
}