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.
精彩评论