【问题标题】:Serial numbers generation without user data没有用户数据的序列号生成
【发布时间】:2011-06-02 18:55:21
【问题描述】:

这是this question 的后续。

接受的答案通常就足够了,但需要用户提供个人信息(例如姓名)来生成密钥。

我想知道是否有可能基于一个共同的种子生成不同的密钥,这样程序就可以验证这些密钥是否属于特定产品,但不会让最终用户清楚这一过程。

我的意思是它可能是产品 ID 的散列加上一些随机字符序列,但这将允许用户猜测潜在的新密钥。应该有某种算法难以猜测。

【问题讨论】:

    标签: .net copy-protection


    【解决方案1】:

    您可以使用由您的密钥生成的数字签名,并将公钥插入您的程序中。

    【讨论】:

      【解决方案2】:

      标准对称/非对称/哈希算法(MDx、SHAx、RSA 等)的问题是它们无法对非常短的字符串(如 20/30 字符长的产品密钥字符串)进行操作。如果你能给你的用户大约 1000 个字符长的字符串,那么我会选择这些算法。

      如果您不能,这里有一些 C# 代码能够构建“几乎完全随机”的短产品密钥,但其中包含一个隐藏字节。你可以在那个“隐藏字节”中放入任何你想要的东西,并且密钥遵循一些不容易猜到的算法。您可以使用 TryReadKey() 验证传入的字符串并重新读取字节,如下所示:

      string key = BuildKey(myHiddenByte); // give that to the end user
      ...
      byte hidden;
      if (!TryReadKey(key, out hidden))
      {
         Console.WriteLine("key is not ok");
      }
      else
      {
         Console.WriteLine("key is ok and hidden byte is " + hidden);
      }
      

      该算法使用Steganography 原则,当然不是万无一失的,可以改进,但可能有用。注意密钥生成可能需要一些时间,这很正常。它会产生这样的键(注意第一个单词是 6 个字符长,其他 5 个字符长):

      KZGMB0-XYJC2-YRKH3-8LD8G-5FUZG
      YKU93K-M34PD-E5PL0-QM91J-QLDZF
      DH27H9-NCW8E-EMGPL-YCJXJ-N2PRG
      WDAKDE-G56NR-6BA3R-0JE6U-625EB
      6D5EJ0-NJDAK-EMGZR-Z6ZDF-JHJGF
      ....
      

      代码如下:

          // need 32 characters, so we can use a 5-bit base encoding
          //                               0         1         2         3
          //                               01234567890123456789012345678901
          private const string KeyChars = "ABCDEFGHJKLMNPQRTUWXYZ0123456789";
          private const int MinSum = 2000;
          private const int Mask = 0xFFFFF;  // beware, this can have a dramatic influence on performance
      
          /// <summary>
          /// Builds a key and hides data in it.
          /// Key format is XXXXXX-XXXXX-XXXXX-XXXXX-XXXXX.
          /// </summary>
          /// <param name="hiddenData">The hidden data.</param>
          /// <returns>The build key</returns>
          public static string BuildKey(byte hiddenData)
          {
              int index;
              Guid guid;
              uint[] word = new uint[4];
              byte[] array;
              while (true)
              {
                  // we use guid initial randomness characteristics
                  guid = Guid.NewGuid();
                  array = guid.ToByteArray();
      
                  int sum = array.Aggregate(0, (current, b) => current + b);
      
                  // a simple check to make sure the guid is not filled with too much zeros...
                  if (sum < MinSum)
                      continue;
      
                  // build a 32-bit word array
                  for (int i = 0; i < 4; i++)
                  {
                      word[i] = BitConverter.ToUInt32(array, i * 4) & Mask;
                  }
      
                  // Here we check the guid follows some algorithm. if it doesn't we skip it
                  // the algorithm presented here is to check one of the 32-bit word is equal to the sum of the others
                  // (modulo a mask otherwise it takes too long!)
                  //
                  // This algorithm can be changed at your will (and change the TryReadKey as well)
      
                  if ((word[0] + word[1] + word[2]) == word[3])
                  {
                      index = 3;
                      break;
                  }
      
                  if ((word[0] + word[1] + word[3]) == word[2])
                  {
                      index = 2;
                      break;
                  }
      
                  if ((word[0] + word[3] + word[2]) == word[1])
                  {
                      index = 1;
                      break;
                  }
      
                  if ((word[3] + word[1] + word[2]) == word[0])
                  {
                      index = 0;
                      break;
                  }
              }
      
              // hidden info is also xor'd with other words hi order (except the one we masked)
              // so it's not too easy to guess
              hiddenData = (byte)(hiddenData ^ array[0] ^ array[4] ^ array[8] ^ array[12]);
      
              // rebuild words without mask
              for (int i = 0; i < 4; i++)
              {
                  word[i] = BitConverter.ToUInt32(array, i * 4);
              }
      
              // hidden info is stored in the block that is the sum of other blocks, in hi order byte (outside the mask)
              word[index] &= 0x00FFFFFF;
              word[index] |= ((uint)hiddenData) << 24;
      
              // rebuild the array back
              for (int i = 0; i < 4; i++)
              {
                  byte[] ui = BitConverter.GetBytes(word[i]);
                  for (int j = 0; j < 4; j++)
                  {
                      array[i * 4 + j] = ui[j];
                  }
              }
      
              // now use 5-bits encoding
              int encodingBitIndex = 0;
              StringBuilder key = new StringBuilder();
              while (encodingBitIndex < 128)
              {
                  byte encodingByte = Get5Bits(array, encodingBitIndex);
                  if ((key.Length > 0) && (key.Length % 6) == 0)
                  {
                      key.Append('-');
                  }
                  key.Append(KeyChars[encodingByte]);
                  encodingBitIndex += 5;
              }
              return key.ToString();
          }
      
          /// <summary>
          /// Validates the specified key and reads hidden data from it.
          /// </summary>
          /// <param name="key">The key.</param>
          /// <param name="hiddenData">The hidden data.</param>
          /// <returns>true if the key is valid and hidden data was read; false otherwise.</returns>
          public static bool TryReadKey(string key, out byte hiddenData)
          {
              hiddenData = 0;
              if (key == null)
                  return false;
      
              key = key.Replace("-", string.Empty);
              if (key.Length != 26)
                  return false;
      
              byte[] array = new byte[16];
              int encodingBitIndex = 0;
              foreach (char t in key)
              {
                  byte b = 255;
                  for (byte k = 0; k < KeyChars.Length; k++)
                  {
                      if (KeyChars[k] != t) continue;
                      b = k;
                      break;
                  }
                  if (b == 255) // char not found
                      return false;
      
                  Put5Bits(array, encodingBitIndex, b);
                  encodingBitIndex += 5;
              }
      
              // quick sum check
              int sum = array.Aggregate(0, (current, b) => current + b);
      
              // add 256 because we changed the hidden byte
              sum += 256;
              if (sum < MinSum)
                  return false;
      
              uint[] word = new uint[4];
              for (int i = 0; i < 4; i++)
              {
                  word[i] = BitConverter.ToUInt32(array, i * 4) & Mask;
              }
      
              // This must match BuildKey algorithm
      
              int index;
              if ((word[0] + word[1] + word[2]) == word[3])
              {
                  index = 3;
              }
              else if ((word[0] + word[1] + word[3]) == word[2])
              {
                  index = 2;
              }
              else if ((word[0] + word[3] + word[2]) == word[1])
              {
                  index = 1;
              }
              else if ((word[3] + word[1] + word[2]) == word[0])
              {
                  index = 0;
              }
              else
                  return false;
      
              // reread word & extract hidden byte back
              word[index] = BitConverter.ToUInt32(array, index * 4);
              hiddenData = (byte)(word[index] >> 24);
              hiddenData = (byte)(hiddenData ^ array[0] ^ array[4] ^ array[8] ^ array[12]);
              return true;
          }
      
          // get 5 bits from a byte buffer at an arbitrary bit index
          private static byte Get5Bits(byte[] buffer, int bitIndex)
          {
              int r = bitIndex % 8;
              if (r < 4)
                  return (byte)(((buffer[bitIndex / 8]) & (0xFF >> r)) >> (3 - r));
      
              byte b0 = (byte)((buffer[bitIndex / 8] & (0xFF >> r)) << (r - 3));
              if ((1 + (bitIndex / 8)) == buffer.Length) // last
                  return (byte)(buffer[buffer.Length - 1] & 0x7);
      
              if ((bitIndex / 8) < 16)
                  return (byte)(b0 | buffer[1 + (bitIndex / 8)] >> (11 - r));
      
              return b0;
          }
      
          // put 5 bits into a byte buffer at an arbitrary bit index
          private static void Put5Bits(byte[] buffer, int bitIndex, byte value)
          {
              int r = bitIndex % 8;
              if (r < 4)
              {
                  buffer[bitIndex / 8] |= (byte)((value << (3 - r)));
              }
              else
              {
                  if ((1 + (bitIndex / 8)) == buffer.Length) // last
                  {
                      buffer[buffer.Length - 1] |= (byte)(value & 0x7);
                  }
                  else if ((bitIndex / 8) < 16)
                  {
                      buffer[bitIndex / 8] |= (byte)((value >> (r - 3)));
                      buffer[1 + (bitIndex / 8)] |= (byte)((value << (11 - r)));
                  }
              }
          }
      

      【讨论】:

      • 这看起来像是我正在寻找的解决方案。谢谢西蒙!
      猜你喜欢
      • 1970-01-01
      • 2010-11-14
      • 1970-01-01
      • 2019-02-18
      • 2010-12-29
      • 2022-11-22
      • 2012-10-22
      • 2012-11-23
      • 1970-01-01
      相关资源
      最近更新 更多