Генерация серийных номеров без пользовательских данных

Это продолжение этого вопроса.

Принятого ответа обычно достаточно, но он требует, чтобы пользователь предоставил личную информацию (например, имя) для создания ключа.

Мне интересно, возможно ли генерировать разные ключи на основе общего начального числа таким образом, чтобы программа могла проверить, принадлежат ли эти ключи конкретному продукту, но не делая этот процесс очевидным для конечного пользователя.

Я имею в виду, что это может быть хэш идентификатора продукта плюс какая-то случайная последовательность символов, но это позволит пользователю угадать потенциальные новые ключи. Должен быть какой-то алгоритм, который трудно угадать.


person SharpAffair    schedule 03.01.2011    source источник


Ответы (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);
}

Алгоритм использует принципы стеганографии, конечно, не является пуленепробиваемым и может быть улучшен, но может быть полезен. . Обратите внимание, что генерация ключа может занять некоторое время, это нормально. Он создает такие ключи (обратите внимание, что первое слово имеет длину 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)));
            }
        }
    }
person Simon Mourier    schedule 05.01.2011
comment
Это похоже на решение, которое я искал. Спасибо Саймон! - person SharpAffair; 05.01.2011

Вы можете использовать цифровую подпись, сгенерированную вашим ключом, и с открытым ключом, вставленным в вашу программу.

person Nickolay Olshevsky    schedule 03.01.2011