11

How would I code a reversible shuffle algorithm in C# which uses a key to shuffle and can be reversed to the original state?

For instance, I have a string: "Hello world", how can I shuffle it so that later I could be able to reverse the shuffled string back to "Hello world".

Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
Tush
  • 153
  • 3
  • 13
  • 2
    What do you mean by "shuffle"? Is "dlrow olleH" shuffle? Or do you mean encryption? – Albin Sunnanbo Aug 22 '10 at 11:58
  • A text shuffle which shuffles randomly with formula and a key/password, and it can be reversed when we use the same key. – Tush Aug 22 '10 at 12:10
  • 1
    Do you mean the characters must be the same as in the original but shuffled (e.g. "hello world" --> "ehwl llodor") or encrypted (e.g. "hello world" --> "%.7$£-@f+=|") ? – digEmAll Aug 22 '10 at 12:11
  • To be shuffled randomly but based entirely on a special key. The key would restore the original state of the string. – Tush Aug 22 '10 at 12:41
  • 1
    possible duplicate of [Encrypt/Decrypt string in .NET](http://stackoverflow.com/questions/202011/encrypt-decrypt-string-in-net) – Hans Passant Aug 22 '10 at 13:17
  • @Steve: But shuffle+key = encryption – H H Aug 22 '10 at 16:06
  • 6
    I disagree. shuffle + key is a deterministic shuffle (as opposed to a true random one). A shuffle is never encryption, regardless of how you choose which permutation to apply. Tush repeatedly says "shuffle", including choosing "shuffle" rather than "encrypt" when offered the choice between the two by digEmAll. So I don't think he wants encryption, and if he does he should ask for it instead of refusing it in favour of something else ;-) – Steve Jessop Aug 22 '10 at 16:17
  • 1
    A reversible shuffle *is* a kind of encryption, although not very strong. It's the mode of operation of the so-called "permutation ciphers" and it's also the mode of operation of a P-Box found in modern ciphers. If the shuffle/permutation works at the bit level, it can be tricky to reverse. – Yolanda Ruiz Feb 22 '14 at 11:00

4 Answers4

18

Look at Fisher-Yates shuffle for a way to permute the string based on a key. Feed the key as the seed into a PRNG, use that to generate the random numbers used by the shuffle.

Now, how to reverse the process? Fisher-Yates works by swapping certain pairs of elements. So to reverse the process you can feed the same key into the same PRNG, then run through the Fisher-Yates algorithm as if you were shuffling an array the size of your string. But actually you don't move anything, just record the indexes of the elements that would be swapped at each stage.

Once you've done this, run through your list of swaps in reverse, applying them to your shuffled string. The result is the original string.

So for example, suppose we've shuffled the string "hello" using the following swaps (I haven't used a PRNG here, I rolled dice, but the point about a PRNG is it gives you the same sequence of numbers given the same seed):

(4,0): "hello" -> "oellh"
(3,3): "oellh" -> "oellh"
(2,1): "oellh" -> "olelh"
(1,0): "olelh" -> "loelh"

So, the shuffled string is "loelh".

To deshuffle, I generate the same series of "random" numbers, 0, 3, 1, 0. Then apply the swaps in reverse order:

(1,0): "loelh" -> "olelh"
(2,1): "olelh" -> "oellh"
(3,3): "oellh" -> "oellh"
(4,0): "oellh" -> "hello"

Success!

The downside of this of course is that it uses a lot of memory for the deshuffle: an array of indexes as long as your original array of chars. So for truly huge arrays, you might want to choose a PRNG (or anyway a sequence-generation function) that can be stepped either forwards or backwards without having to store all the output. This rules out hash-based cryptographically secure PRNGs, but LFSRs are reversible.

Btw, why do you want to do this?

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • It is in practice what I wrote in my answer; what it changes is basically the random exchanges generation part :) – digEmAll Aug 22 '10 at 15:29
  • 1
    Yes fair point. I looked at your code and saw it was doing something fairly similar, but `GetShuffledIndexes` was tl;dr, and Sort is O(N log N) when we only really need O(N), so I thought I'd go for an English explanation :-) – Steve Jessop Aug 22 '10 at 15:34
  • Just that I didn't have any incentive to know exactly what it does. It's going to shuffle the indexes 0 ... N-1. I expect the questioner cares how you do that, or someone maintaining your code, and it looks readable enough. But I don't and I'm not, so I didn't ;-) – Steve Jessop Aug 22 '10 at 15:42
  • Hi! I am making my research on Hash based Super Data-Compression which would require these kind of mathematically intense string functions. – Tush Aug 24 '10 at 11:56
  • @Tush - This isn't compression this is shuffleing. Compression is a whole other question. – Wes Aug 25 '10 at 13:26
  • Thanks, Steve... I needed to know how to do this for random bit interleaving to improve my Forward Error Correction algorithm... and that explanation should be a massive help. – Luke Oct 04 '12 at 15:59
9

Here's a simple implementation of what you need (if I got it well):

public static class ShuffleExtensions
{
    public static int[] GetShuffleExchanges(int size, int key)
    {
        int[] exchanges = new int[size - 1];
        var rand = new Random(key);
        for (int i = size - 1; i > 0; i--)
        {
            int n = rand.Next(i + 1);
            exchanges[size - 1 - i] = n;
        }
        return exchanges;
    }

    public static string Shuffle(this string toShuffle, int key)
    {
        int size = toShuffle.Length;
        char[] chars = toShuffle.ToArray();
        var exchanges = GetShuffleExchanges(size, key);
        for (int i = size - 1; i > 0; i--)
        {
            int n = exchanges[size - 1 - i];
            char tmp = chars[i];
            chars[i] = chars[n];
            chars[n] = tmp;
        }
        return new string(chars);
    }

    public static string DeShuffle(this string shuffled, int key)
    {
        int size = shuffled.Length;
        char[] chars = shuffled.ToArray();
        var exchanges = GetShuffleExchanges(size, key);
        for (int i = 1; i < size; i++)
        {
            int n = exchanges[size - i - 1];
            char tmp = chars[i];
            chars[i] = chars[n];
            chars[n] = tmp;
        }
        return new string(chars);
    }
}

usage:

var originalString = "Hello world";
var shuffled = originalString.Shuffle(123);
var deShuffled = shuffled.DeShuffle(123);

// shuffled = "lelooH rwld";
// deShuffled = "Hello world";

The key must be an integer, if you need to use a string as password, just call GetHashCode() on it:

var shuffled = originalString.Shuffle(myStringKey.GetHashCode());

EDIT:

Now is exactly a Fisher–Yates shuffle algorithm implementation. Thanks to Jeff for the code

digEmAll
  • 56,430
  • 9
  • 115
  • 140
  • Errors are coming in return statements. Can't compile. Error 14 'string' does not contain a definition for 'Shuffle' and no extension method 'Shuffle' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) public static string Shuffle(this string toShuffle, int key) { return new string(toShuffle.Shuffle(key)); } public static string DeShuffle(this string toShuffle, int key) { return new string(toShuffle.DeShuffle(key)); } – Tush Aug 22 '10 at 17:20
  • It's really strange because it works for me...have you set the extensions methods in a static class ? – digEmAll Aug 22 '10 at 18:09
  • Ok, stimulated by Steve Jessop's comment about Sort complexity, I've rewritten the code. Now it's exactly Fisher-Yates with O(n) computational complexity in both Shuffling and Deshuffling. – digEmAll Aug 22 '10 at 20:58
1

A java question also redirects here, so here is the full cryptographic-strength java implementation:

import java.security.*;
import java.util.*;

/** Cryptographic strength reversible random shuffle. To be truly secure, the passKey arguments should be 20 chars or more and (obviously) not guessable. */
public class SecureShuffle
{
    public static int[] getShuffleExchanges(int size, String passKey)
    {
        int[] exchanges = new int[size - 1];
        SecureRandom rand = new SecureRandom(passKey.getBytes());
        for (int i = size - 1; i > 0; i--)
        {
            int n = rand.nextInt(i + 1);
            exchanges[size - 1 - i] = n;
        }
        return exchanges;
    }

    public static void shuffle(byte[] toShuffle, String passKey)
    {
        int size = toShuffle.length;
        int[] exchanges = getShuffleExchanges(size, passKey);
        for (int i = size - 1; i > 0; i--)
        {
            int n = exchanges[size - 1 - i];
            byte tmp = toShuffle[i];
            toShuffle[i] = toShuffle[n];
            toShuffle[n] = tmp;
        }
    }

    public static void deshuffle(byte[] shuffled, String passKey)
    {
        int size = shuffled.length;
        int[] exchanges = getShuffleExchanges(size, passKey);
        for (int i = 1; i < size; i++)
        {
            int n = exchanges[size - i - 1];
            byte tmp = shuffled[i];
            shuffled[i] = shuffled[n];
            shuffled[n] = tmp;
        }
    }

    public static void shuffle(char[] toShuffle, String passKey)
    {
        int size = toShuffle.length;
        int[] exchanges = getShuffleExchanges(size, passKey);
        for (int i = size - 1; i > 0; i--)
        {
            int n = exchanges[size - 1 - i];
            char tmp = toShuffle[i];
            toShuffle[i] = toShuffle[n];
            toShuffle[n] = tmp;
        }
    }

    public static void deshuffle(char[] shuffled, String passKey)
    {
        int size = shuffled.length;
        int[] exchanges = getShuffleExchanges(size, passKey);
        for (int i = 1; i < size; i++)
        {
            int n = exchanges[size - i - 1];
            char tmp = shuffled[i];
            shuffled[i] = shuffled[n];
            shuffled[n] = tmp;
        }
    }

    public static void shuffle(int[] toShuffle, String passKey)
    {
        int size = toShuffle.length;
        int[] exchanges = getShuffleExchanges(size, passKey);
        for (int i = size - 1; i > 0; i--)
        {
            int n = exchanges[size - 1 - i];
            int tmp = toShuffle[i];
            toShuffle[i] = toShuffle[n];
            toShuffle[n] = tmp;
        }
    }

    public static void deshuffle(int[] shuffled, String passKey)
    {
        int size = shuffled.length;
        int[] exchanges = getShuffleExchanges(size, passKey);
        for (int i = 1; i < size; i++)
        {
            int n = exchanges[size - i - 1];
            int tmp = shuffled[i];
            shuffled[i] = shuffled[n];
            shuffled[n] = tmp;
        }
    }

    public static void shuffle(long[] toShuffle, String passKey)
    {
        int size = toShuffle.length;
        int[] exchanges = getShuffleExchanges(size, passKey);
        for (int i = size - 1; i > 0; i--)
        {
            int n = exchanges[size - 1 - i];
            long tmp = toShuffle[i];
            toShuffle[i] = toShuffle[n];
            toShuffle[n] = tmp;
        }
    }

    public static void deshuffle(long[] shuffled, String passKey)
    {
        int size = shuffled.length;
        int[] exchanges = getShuffleExchanges(size, passKey);
        for (int i = 1; i < size; i++)
        {
            int n = exchanges[size - i - 1];
            long tmp = shuffled[i];
            shuffled[i] = shuffled[n];
            shuffled[n] = tmp;
        }
    }

    public static void main(String[] args)
    {
        String passphrase = "passphrase";
        String text = "The rain in Spain stays mainly on the plain";

        char[] chars = text.toCharArray();
        shuffle(chars, passphrase);
        System.out.println(new String(chars));

        deshuffle(chars, passphrase);
        System.out.println(new String(chars));

        byte[] bytes = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
        shuffle(bytes, passphrase);
        System.out.println(Arrays.toString(bytes));

        deshuffle(bytes, passphrase);
        System.out.println(Arrays.toString(bytes));
    }

}
barneypitt
  • 979
  • 9
  • 11
1

You may look at the following question and it's answers. Encrypt/Decrypt string in .NET

Community
  • 1
  • 1
Albin Sunnanbo
  • 46,430
  • 8
  • 69
  • 108