6

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

Jim
  • 141
  • 1
  • 2
  • 5
  • I suppose a randomly generated yet static 1 trillion item dictionary is out of the question? – Anthony Pegram Jan 26 '11 at 19:31
  • 2
    possible duplicate of [Obscure / encrypt an order number as another number: symmetrical, "random" appearance?](http://stackoverflow.com/questions/611915/obscure-encrypt-an-order-number-as-another-number-symmetrical-random-appear) – erickson Jan 26 '11 at 19:31
  • http://stackoverflow.com/questions/611915/obscure-encrypt-an-order-number-as-another-number-symmetrical-random-appear#answer-612085 – Luke Hutton Jan 26 '11 at 19:39
  • 1
    Is it essential that your result be a numeric value? Also, what are your security requirements? Rule #1 of encryption is "do not rely on your own encryption" – Matthew Jan 26 '11 at 19:46
  • It is ideal that the result be a numeric value, but not an absolute requirement. The result will be printed on packaging and entered into a website. If alphanumeric was the only option, then that would be okay but could only include a-z and 0-9 no special characters. As for security requirements, I don't think we'll have a team of hackers trying to crack this routine. We're not really protecting anything critical, so I'd say its more important that its not easily distinguishable that the numbers are in order (as mentioned above) than it being totally secure. – Jim Jan 26 '11 at 21:15
  • I did see the other post and it looks good, maybe I should have just commented there. It didn't include a code sample and I didn't know how to code it so I was afraid to commit a lot of time to it when I wasn't positive it was the answer. Sounds like it is so I'll try to figure it out. – Jim Jan 26 '11 at 21:25
  • This question has been asked a dozen times. The keyword to search for is "format preserving encryption". You can safely ingore any other proposals here. They are trash. – Accipitridae Jan 27 '11 at 09:14
  • Do you have to map every number between 0 and 999999999999? – geofftnz Jan 28 '11 at 00:38

6 Answers6

5

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;
}
Ral Zarek
  • 1,058
  • 4
  • 18
  • 25
Jim
  • 141
  • 1
  • 2
  • 5
  • Is there any way we could add some `salt` for different users? Like user A salt: 8875. user B salt: 5525. by this then it could encrypt and decrypt correctly also safe. – qakmak Nov 06 '16 at 08:55
2

What you're talking about is kinda like a one-time pad. A key the same length as the plaintext and then doing some modulo math on each individual character.

A xor B = C
C xor B = A

or in other words

A xor B xor B = A

As long as you don't use the same key B on multiple different inputs (e.g. B has to be unique, every single time you encrypt), then in theory you can never recover the original A without knowing what B was. If you use the same B multiple times, then all bets are off.

comment followup:

You shouldn't end up with more bits aftewards than you started with. xor just flips bits, it doesn't have any carry functionality. Ending up with 6 digits is just odd... As for code:

$plaintext = array(digit1, digit2, digit3, digit4, digit5, digit6);
$key = array(key1, key2, key3, key4, key5, key6);
$ciphertext = array()

# encryption
foreach($plaintext as $idx => $char) {
   $ciphertext[$idx] = $char xor $key[$idx];
}

# decryption
foreach($ciphertext as $idx => $char) {
   $decrypted[$idx] = $char xor $key[$idx];
}

Just doing this as an array for simplicity. For actual data you'd work on a per-byte or per-word basis, and just xor each chunk in sequence. You can use a key string shorter than the input, but that makes it easier to reverse engineer the key. In theory, you could use a single byte to do the xor'ing, but then you've just basically achieved the bit-level equivalent of rot-13.

Marc B
  • 356,200
  • 43
  • 426
  • 500
  • So when I Xor 2 5 character numbers (for example) sometimes I get a 6 digit result. It just shouldn't be this tough. I'm either doing the Xor wrong or sometimes they add up to greater than 9. Any chance you could put in a 5 line code sample? – Jim Jan 26 '11 at 22:26
2

One more way for simple encryption, you can just substruct each number from 10.

For example initial numbers: 123456

10-1 = 9 10-2 = 8 10-3 = 7 etc.

and you will get 987654

You can combine it with XOR for more secure encryption.

Disposer
  • 667
  • 1
  • 7
  • 23
  • 1
    i think it doesn't work for numbers end with 0. For eg.12340 Becomes 987610 so we face problems while decryption . – kasinavijay Jun 05 '14 at 08:15
0

For example you can add digits of your number with digits some const (214354178963...whatever) and apply "~" operator (reverse all bits) this is not safely but ensure you can decrypt your number allways.

Eugene Gluhotorenko
  • 3,094
  • 2
  • 33
  • 52
0

anyone with reflector or ildasm will be able to hack such an encryption algorithm.

I don't know what is your business requirement but you have to know that.

Steve B
  • 36,818
  • 21
  • 101
  • 174
  • That's a good point. In this case the code doing the encrypting and decrypting will all be run on a website so it shouldn't be accessible. – Jim Jan 26 '11 at 21:27
0

If there's enough wriggle-room in the requirements that you can accept 16 hexadecimal digits as the encrypted side, just interpret the 12 digit decimal number as a 64bit plaintext and use a 64 bit block cipher like Blowfish, Triple-DES or IDEA.

caf
  • 233,326
  • 40
  • 323
  • 462