4

假设我编写了一个程序并希望将其分发给人们。我希望能够在安装过程中向用户请求一个序列号,这将验证他们是否拥有该软件的许可副本。此外,我希望序列号存储他们可以访问的软件版本以及许可证何时到期。

我至少可以想到两种概念方法来实现这一点:

  1. 在用户购买软件的服务器上生成一个序列,并通过电子邮件发送给他们。该软件在安装过程中连接到服务器并激活产品。服务器返回许可证权限和到期时间。序列号没有特定的格式/数学规则,它只是根据服务器上的数据库进行检查。

  2. 为客户生成序列并通过电子邮件发送给他们。该系列具有一些特殊的数学属性,允许程序检查它是否有效、它对应的许可证以及到期时间。

我最感兴趣的是第二种方法。在序列号中编码此信息时使用了哪些技术?如果你能给出一个简短的概述,那就太好了。否则,你能推荐一些讨论这个的好书或网站吗?

我觉得奇怪的是,在我编码的这些年里,我从未真正看到过这些技术的实现或描述。

4

2 回答 2

3

您正在考虑做的基本上是创建自己的许可模块。没有错,如果你自己推出它,它将是免费的(只是你编写和调试它的时间)。但是,破解将非常简单,因此如果您这样做是为了获得某种许可合规性(即复制保护),我不会打扰。

你打算以某种方式将它绑定到物理机器上吗?一种方法是获取 CPU 序列号、HD 序列号、MAC 地址等,或以上的某种组合,对它们进行哈希处理,并在启动时使用哈希值检查以确保许可证有效。当然,如果用户更改任何内容,则检查将失败,并且即使他们拥有有效的许可证,他们也可能会因为他们的软件将不再运行而感到恼火。

如果您这样做是为了防止非法复制或使用,那么您提出的架构通常很容易破解——破解者会寻找您检查正确许可证的位置,然后修补 asm 代码以围绕它进行分支或总是返回一个积极的。如果您的应用程序是 .NET,他们可以只反转源代码并删除许可证检查部分。

免责声明:我在一家许可证管理/复制保护工具公司 ([Wibu-Systems]) 工作1这就是我们所做的一切,所以我们知道击败大多数系统是多么容易。

我推荐以下三种方法之一:

  1. 让您的应用程序不受保护,并希望您的销售与盗版副本的比率是合理的。
  2. 推出一个简单的系统,但要认识到如果软件有价值,有人会快速轻松地破解它。为一些复杂的编码和维护做好准备,甚至可以做到一半体面。
  3. 购买商业质量的保护系统,如CodeMeterHASPKeyLok。有些比其他更好(我们认为我们的自然是最好的),但没有一个是免费的。
于 2011-04-28T22:15:02.767 回答
1

好的,所以两周没有答案。我将用我想出的一种非常简单的方法来回答,该方法使用基本方法生成相对安全且高度可扩展的序列号。

作为一名数学家,我非常肯定有一些先进的技术可以将各种信息存储在一个序列号中——但我主要对快速和肮脏的东西感兴趣。

这里有一个幼稚的、非数学的、蛮力的技术可供考虑:

创建一个byte[]包含要使用的字符的数组。您只能使用十六进制,但没有理由限制自己。为什么不使用整个字母数字范围减去“0”/“O”和“1”/“I”(原因很明显)。

接下来写一个函数如下(例子是C#):

byte[] genRandomSerial(int length, byte[] characters, Random r)
{
  var sn = new byte[length];

  for (int i = 0; i < length; i++)
    sn[i] = characters[r.Next(0, characters.Length)];

  return sn;
}

这会给你一个随机的序列号,我们不知道它是否有效。

下一个:

int sum(byte[] sn, MD5 md5)
{
  val = 0;

  foreach (byte b in md5.ComputeHash(sn))
    val += (int)b;

  return val;
}

进而

bool validate(byte[] sn, uint radix, uint expected, MD5 md5)
{
  return (sum(sn, md5) % radix == expected);
}

我们现在拥有的是一种将 MD5 散列函数的 16 字节输出相加的方法,并评估求和模n是否等于某个x

现在,决定要存在多少个序列号。存在的序列号越多,就越容易有人随机猜出一个有效的组合。

将您的随机序列分成块。假设 5 个 4 块,以下列形式给出 20 个字符:ABCD-EFGH-IJKL-MNOP-QRST

从您的序列号中创建 5 个阵列:

{A、B、C、D}、{E、F、G、H}、{I、J、K、L}、{M、N、O、P} 和 {Q、R、S、T} .

测试您的 5 个数组是否验证如下:

if (validate(block1, radix, expected, md5))
  // This block is valid.

如果将基数设置为 2,则该块有效的概率为 1/2。如果将基数设置为 10,则该块有效的概率为 1/10。如果您有 5 个块,并且将每个基数设置为 10,则整个序列号有效的概率为 0.1^5 = 0.00001。(换句话说,每 100000 个随机序列中有 1 个是有效的。这意味着如果您使用完整的字母数字范围减去“0”/“O”、“1”/“I”,那么您有 (8 + 24)^ n * 0.00001 = ~1.2 * 10^19 个有效密钥,序列长度为 20。数量很多 - 但请记住,无论如何您都不会找到它们。基数越高,序列越安全,但生成所需的时间更长)。

请注意,“预期”应该介于 0 和 radix-1 之间

所以现在我们有了一种验证特定序列号是否有效的方法,但是我们如何存储它是什么类型的序列号呢?事实上,我们已经有了这样做的方法。取整个随机(但经过验证)序列“sn”:

int licenseType = sum(sn, md5) % 4; // Where 4 is the number of licenses you want to have

if (licenseType == 0)
{
  // Evaluation
}
else if (licenseType == 1)
{
  // Standard
}
else if (licenseType == 2)
{
  // Full
}
else // licenseType == 3
{
  // Unrestricted
}

随着您生成越来越多的密钥,每种许可证的数量将逐渐趋于平稳。

如果您想在密钥中存储其他信息,例如到期日期,您可以使用类似的方法。例如,您可以将奇数字符的总和取模 12 来获得到期月份,并将偶数字符总和的模 31 得出到期日。

您应用的这些限制和细分越多,生成每种类型的密钥所需的时间就越长。

于 2011-04-18T05:48:28.153 回答