【问题标题】:Reversible shuffle algorithm using a key使用密钥的可逆洗牌算法
【发布时间】:2011-04-02 06:23:56
【问题描述】:

如何在 C# 中编写可逆的随机播放算法,该算法使用密钥进行随机播放并且可以反转到原始状态?

例如,我有一个字符串:“Hello world”,我怎样才能将它打乱,以便稍后我可以将打乱的字符串反转回“Hello world”。

【问题讨论】:

  • “随机播放”是什么意思?是“dlrow olleH”洗牌吗?还是说加密?
  • 你的意思是字符必须和原来的一样,但是被打乱了(例如“hello world”-->“ehwl llodor”)还是加密的(例如“hello world”-->“% .7$£-@f+=|") ?
  • Encrypt/Decrypt string in .NET 的可能重复项
  • 我不同意。 shuffle + key 是一种确定性的随机播放(而不是真正的随机随机播放)。无论您如何选择应用哪种排列,随机播放都不是加密。 Tush 反复说“shuffle”,包括在 digEmAll 提供两者之间的选择时选择“shuffle”而不是“encrypt”。所以我不认为他想要加密,如果他想要加密,他应该要求它而不是拒绝它以支持其他东西;-)
  • 可逆洗牌一种加密,虽然不是很强大。这是所谓的“置换密码”的操作模式,也是现代密码中的 P-Box 的操作模式。如果 shuffle/permutation 在位级别起作用,那么反转可能会很棘手。

标签: c# algorithm string key shuffle


【解决方案1】:

查看Fisher-Yates shuffle 以了解基于键置换字符串的方法。将密钥作为种子输入到 PRNG 中,使用它来生成 shuffle 使用的随机数。

现在,如何逆转这个过程? Fisher-Yates 通过交换某些元素对来工作。因此,要反转该过程,您可以将相同的密钥提供给相同的 PRNG,然后运行 ​​Fisher-Yates 算法就好像您正在改组一个字符串大小的数组。但实际上你并没有移动任何东西,只是记录了每个阶段要交换的元素的索引。

完成此操作后,逆向遍历你的交换列表,将它们应用到你的洗牌字符串。结果是原始字符串。

例如,假设我们使用以下交换对字符串“hello”进行了洗牌(我在这里没有使用 PRNG,我掷骰子,但关于 PRNG 的重点是它为您提供相同的数字序列给定相同的种子):

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

所以,洗牌后的字符串是“loelh”。

为了去洗牌,我生成了相同系列的“随机”数字,0、3、1、0。然后以相反的顺序应用交换:

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

成功了!

当然,这样做的缺点是它使用大量内存进行洗牌:索引数组与原始字符数组一样长。因此,对于真正巨大的数组,您可能想要选择一个 PRNG(或者无论如何是序列生成函数),它可以向前或向后步进,而无需存储所有输出。这排除了基于哈希的加密安全 PRNG,但 LFSR 是可逆的。

顺便说一句,你为什么要这样做?

【讨论】:

  • 这实际上是我在回答中所写的;它改变的基本上是随机交换生成部分:)
  • 是的,公平点。我查看了您的代码,发现它正在做一些非常相似的事情,但是 GetShuffledIndexes 是 tl;dr,当我们只需要 O(N) 时,排序是 O(N log N),所以我想我会去英文解释:-)
  • 只是我没有任何动力去确切地知道它的作用。它将对索引 0 ... N-1 进行洗牌。我希望提问者关心你如何做到这一点,或者有人在维护你的代码,并且它看起来足够可读。但我没有,我也不是,所以我没有;-)
  • 嗨!我正在研究基于哈希的超级数据压缩,这需要这种数学上密集的字符串函数。
  • @Tush - 这不是压缩,而是随机播放。压缩是另一个问题。
【解决方案2】:

这是您需要的一个简单实现(如果我做得好的话):

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

用法:

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

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

密钥必须是整数,如果需要使用字符串作为密码,只需调用GetHashCode()即可:

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

编辑:

现在正是 Fisher-Yates shuffle 算法的实现。 感谢 Jeff the code

【讨论】:

  • 返回语句中出现错误。无法编译。错误 14 'string' 不包含 'Shuffle' 的定义,并且找不到接受类型为 'string' 的第一个参数的扩展方法 'Shuffle'(您是否缺少 using 指令或程序集引用?) public static string Shuffle (这个字符串 toShuffle, int key) { return new string(toShuffle.Shuffle(key)); } public static string DeShuffle(这个字符串 toShuffle, int key) { return new string(toShuffle.DeShuffle(key)); }
  • 这真的很奇怪,因为它对我有用......你有没有在静态类中设置扩展方法?
  • 好吧,受 Steve Jessop 关于排序复杂性的评论的启发,我重写了代码。现在它完全是 Fisher-Yates,在洗牌和去洗牌中具有 O(n) 的计算复杂度。
【解决方案3】:

您可以查看以下问题及其答案。 Encrypt/Decrypt string in .NET

【讨论】:

    【解决方案4】:

    一个java问题也重定向到这里,所以这里是完整的加密强度java实现:

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

    【讨论】:

    • 这行不通。刚刚测试了一下,没有返回预期的结果
    猜你喜欢
    • 2014-08-07
    • 2011-01-19
    • 2014-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-22
    相关资源
    最近更新 更多