Serial numbers generation without user data

后端 未结 2 845
遇见更好的自我
遇见更好的自我 2020-12-09 14:08

This is a followup to this question.

The accepted answer is generally sufficient, but requires user to supply personal information (e.g. name) for generating the key

相关标签:
2条回答
  • 2020-12-09 14:47

    You can use digital signature, generated by your key, and with public key inserted into your program.

    0 讨论(0)
  • 2020-12-09 15:00

    The problem with standard symmetric/asymetric/hash algorithms (MDx, SHAx, RSA, etc.) is they cannot operate on very short strings like a 20/30 characters-long product key strings. If you can give your users ~1000 characters-long strings, then I would go for these algorithms.

    If you can't, here is some C# code that is capable of building an "almost-totally-random" short product key, but with a hidden byte in it. You can put anything you want in that "hidden byte" and the key follows some algorithm that is not so easy to guess. You can validate a passed in string & reread the byte using the TryReadKey() like this:

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

    The algorithm uses Steganography principles, is certainly not bullet-proof and could be improved, but can be useful. Note key generation can take some time, it's normal. It produces keys like this (note the first word is 6 chars long, the other 5 chars long):

    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
    ....
    

    Here is the code:

        // 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)));
                }
            }
        }
    
    0 讨论(0)
提交回复
热议问题