The eTender REST API generates unique reservation codes like GmTvbTmsmGi, bQ1EaiwTd1d, etc after a client creates a reservation. These reservation code alone are enough to cancel or update a reservation, so it should be unique and impossible to guess or tamper with using a brute force attack. In this blog post I describe how we generate these id’s.
My technique is an improvement on this blogpost by Mads Kristensen. In his blogpost he describes how he Base64 encodes a guid and replaces the characters that are not supported in an url.
Instead of Base64 encoding and replacing some characters after the encoding, it’s better to Base58 encode the Guid, and skip the characters we don’t want right away in the encoding phase. My Base58 encoding algorithm is based on the one used in this github gist to encode bitcoin addresses.
This wiki page lists the following advantages of Base58 encoding over Base64 encoding:
The problem with using a GUID, is that GUID’s are guaranteed to be unique, but are not guaranteed to be random. So there is still a risk, that someone can guess reservation codes with a brute force attack.
I found the solution in a comment of this question on stackoverflow. Here the RNGCryptoServiceProvider class is used to generate the bytes for a GUID, hereby making sure that the GUID is guaranteed random and secure.
Combining both solutions lead to the below code, which gives us completely secure and random keys that can easily be used on the internet.
public class SecretCodeGeneratorService : ISecretCodeGeneratorService
{
private const string digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
// static to guarantee crypthographic randomness of the generated bytes
private static readonly RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
public string Generate(int length)
{
var data = new byte[length];
rng.GetBytes(data);
// Decode byte[] to BigInteger
BigInteger intData = 0;
for (int i = 0; i < data.Length; i++)
{
intData = intData * 256 + data[i];
}
// Encode BigInteger to Base58 string
var result = string.Empty;
while (intData > 0)
{
var remainder = (int)(intData % 58);
intData /= 58;
result = digits[remainder] + result;
}
// Append `1` for each leading 0 byte
for (int i = 0; i < data.Length && data[i] == 0; i++)
{
result = '1' + result;
}
return result;
}
}
Because I wanted shorter codes that can more easily be shared and read, I settled for a byte array with a length of 8 instead of 16. This generates short codes like GmTvbTmsmGi, bQ1EaiwTd1d, etc. These shorter codes are ofcourse less secure then the longer ones, but more then secure enought for reservation codes which are valid only for a short period of time and are not used for extremely sensitive data.