无需用户数据即可生成序列号

发布于 2024-10-10 13:04:17 字数 316 浏览 0 评论 0原文

这是此问题的后续问题。

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

我想知道是否可以基于公共种子生成不同的密钥,程序能够验证这些密钥是否属于特定产品,但不会使最终用户明显看到此过程。

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

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.

I'm wondering if it's possible to generate different keys based on a common seed, in a way that program would be able to validate if those keys belong to particular product, but without making this process obvious to the end user.

I mean it could be a hash of product ID plus some random sequence of characters, but that would allow user to guess potential new keys. There should be some sort of algorithm difficult to guess.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

丢了幸福的猪 2024-10-17 13:04:17

标准对称/非对称/哈希算法(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)));
            }
        }
    }

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)));
            }
        }
    }
东北女汉子 2024-10-17 13:04:17

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文