Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Encrypt a number to another number of the same length

I need a way to take a 12 digit number and encrypt it to a different 12 digit number (no characters other than 0123456789). Then at a later point I need to be able to decrypt the encrypted number back to the original number.

It is important that it isn't obvious if 2 encrypted numbers are in order. So for instance if I encrypt 0000000000001 it should look totally different when encrypted than 000000000002. It doesn't have to be the most secure thing in the world, but the more secure the better.

I've been looking around a lot but haven't found anything that seems to be a perfect fit. From what I've seen some type of XOR might be the easiest way to go, but I'm not sure how to do this.

Thanks, Jim

like image 530
Jim Avatar asked Jan 26 '11 19:01

Jim


1 Answers

I ended up solving this thanks to you guys using "FPE from a prefix cipher" from the wikipedia page http://en.wikipedia.org/wiki/Format-preserving_encryption. I'll give the basic steps below to hopefully be helpful for someone in the future.

NOTE - I'm sure any expert will tell you this is a hack. The numbers seemed random and it was secure enough for what I needed, but if security is a big concern use something else. I'm sure experts can point to holes in what I did. My only goal for posting this is because I would have found it useful when doing my search for an answer to the problem. Also only use this in situations where it couldn't be decompiled.

I was going to post steps, but its too much to explain. I'll just post my code. This is my proof of concept code I still need to clean up, but you'll get the idea. Note my code is specific to a 12 digit number, but adjusting for others should be easy. Max is probably 16 with the way I did it.

public static string DoEncrypt(string unencryptedString)
{
    string encryptedString = "";
    unencryptedString = new string(unencryptedString.ToCharArray().Reverse().ToArray());
    foreach (char character in unencryptedString.ToCharArray())
    {
        string randomizationSeed = (encryptedString.Length > 0) ? unencryptedString.Substring(0, encryptedString.Length) : "";
        encryptedString += GetRandomSubstitutionArray(randomizationSeed)[int.Parse(character.ToString())];
    }

    return Shuffle(encryptedString);
}

public static string DoDecrypt(string encryptedString)
{
    // Unshuffle the string first to make processing easier.
    encryptedString = Unshuffle(encryptedString);

    string unencryptedString = "";
    foreach (char character in encryptedString.ToCharArray().ToArray())
        unencryptedString += GetRandomSubstitutionArray(unencryptedString).IndexOf(int.Parse(character.ToString()));

    // Reverse string since encrypted string was reversed while processing.
    return new string(unencryptedString.ToCharArray().Reverse().ToArray());
}

private static string Shuffle(string unshuffled)
{
    char[] unshuffledCharacters = unshuffled.ToCharArray();
    char[] shuffledCharacters = new char[12];
    shuffledCharacters[0] = unshuffledCharacters[2];
    shuffledCharacters[1] = unshuffledCharacters[7];
    shuffledCharacters[2] = unshuffledCharacters[10];
    shuffledCharacters[3] = unshuffledCharacters[5];
    shuffledCharacters[4] = unshuffledCharacters[3];
    shuffledCharacters[5] = unshuffledCharacters[1];
    shuffledCharacters[6] = unshuffledCharacters[0];
    shuffledCharacters[7] = unshuffledCharacters[4];
    shuffledCharacters[8] = unshuffledCharacters[8];
    shuffledCharacters[9] = unshuffledCharacters[11];
    shuffledCharacters[10] = unshuffledCharacters[6];
    shuffledCharacters[11] = unshuffledCharacters[9];
    return new string(shuffledCharacters);
}

private static string Unshuffle(string shuffled)
{
    char[] shuffledCharacters = shuffled.ToCharArray();
    char[] unshuffledCharacters = new char[12];
    unshuffledCharacters[0] = shuffledCharacters[6];
    unshuffledCharacters[1] = shuffledCharacters[5];
    unshuffledCharacters[2] = shuffledCharacters[0];
    unshuffledCharacters[3] = shuffledCharacters[4];
    unshuffledCharacters[4] = shuffledCharacters[7];
    unshuffledCharacters[5] = shuffledCharacters[3];
    unshuffledCharacters[6] = shuffledCharacters[10];
    unshuffledCharacters[7] = shuffledCharacters[1];
    unshuffledCharacters[8] = shuffledCharacters[8];
    unshuffledCharacters[9] = shuffledCharacters[11];
    unshuffledCharacters[10] = shuffledCharacters[2];
    unshuffledCharacters[11] = shuffledCharacters[9];
    return new string(unshuffledCharacters);
}

public static string DoPrefixCipherEncrypt(string strIn, byte[] btKey)
{
    if (strIn.Length < 1)
        return strIn;

    // Convert the input string to a byte array 
    byte[] btToEncrypt = System.Text.Encoding.Unicode.GetBytes(strIn);
    RijndaelManaged cryptoRijndael = new RijndaelManaged();
    cryptoRijndael.Mode =
    CipherMode.ECB;//Doesn't require Initialization Vector 
    cryptoRijndael.Padding =
    PaddingMode.PKCS7;


    // Create a key (No IV needed because we are using ECB mode) 
    ASCIIEncoding textConverter = new ASCIIEncoding();

    // Get an encryptor 
    ICryptoTransform ictEncryptor = cryptoRijndael.CreateEncryptor(btKey, null);


    // Encrypt the data... 
    MemoryStream msEncrypt = new MemoryStream();
    CryptoStream csEncrypt = new CryptoStream(msEncrypt, ictEncryptor, CryptoStreamMode.Write);


    // Write all data to the crypto stream to encrypt it 
    csEncrypt.Write(btToEncrypt, 0, btToEncrypt.Length);
    csEncrypt.Close();


    //flush, close, dispose 
    // Get the encrypted array of bytes 
    byte[] btEncrypted = msEncrypt.ToArray();


    // Convert the resulting encrypted byte array to string for return 
    return (Convert.ToBase64String(btEncrypted));
}

private static List<int> GetRandomSubstitutionArray(string number)
{
    // Pad number as needed to achieve longer key length and seed more randomly.
    // NOTE I didn't want to make the code here available and it would take too longer to clean, so I'll tell you what I did. I basically took every number seed that was passed in and prefixed it and  postfixed it with some values to make it 16 characters long and to get a more unique result. For example:
    // if (number.Length = 15)
    //    number = "Y" + number;
    // if (number.Length = 14)
    //    number = "7" + number + "z";
    // etc - hey I already said this is a hack ;)

    // We pass in the current number as the password to an AES encryption of each of the
    // digits 0 - 9. This returns us a set of values that we can then sort and get a 
    // random order for the digits based on the current state of the number.
    Dictionary<string, int> prefixCipherResults = new Dictionary<string, int>();
    for (int ndx = 0; ndx < 10; ndx++)
        prefixCipherResults.Add(DoPrefixCipherEncrypt(ndx.ToString(), Encoding.UTF8.GetBytes(number)), ndx);

    // Order the results and loop through to build your int array.
    List<int> group = new List<int>();
    foreach (string key in prefixCipherResults.Keys.OrderBy(k => k))
        group.Add(prefixCipherResults[key]);

    return group;
}
like image 170
Jim Avatar answered Oct 03 '22 16:10

Jim