开发者

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'v开发者_JAVA百科e 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


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;
}


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.


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.


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜