检查生成的许可证是不是有效
Posted
技术标签:
【中文标题】检查生成的许可证是不是有效【英文标题】:Check if a generated license is valid检查生成的许可证是否有效 【发布时间】:2013-04-19 06:39:54 【问题描述】:我有一个 php 脚本,它生成一些将用作许可证密钥的字符串:
function KeyGen()
$key = md5(microtime());
$new_key = '';
for($i=1; $i <= 25; $i ++ )
$new_key .= $key[$i];
if ( $i%5==0 && $i != 25) $new_key.='-';
return strtoupper($new_key);
$x = 0;
while($x <= 10)
echo KeyGen();
echo "<br />";
$x++;
运行脚本一次后,我得到了这些:
8B041-EC7D2-0B9E3-09846-E8C71
C8D82-514B9-068BC-8BF80-05061
A18A3-E05E5-7DED7-D09ED-298C4
FB1EC-C9844-B9B20-ADE2F-0858F
E9AED-945C8-4BAAA-6938D-713ED
4D284-C5A3B-734DF-09BD6-6A34C
EF534-3BAE4-860B5-D3260-1CEF8
D84DB-B8C72-5BDEE-1B4FE-24E90
93AF2-80813-CD66E-E7A5E-BF0AE
C3397-93AA3-6239C-28D9F-7A582
D83B8-697C6-58CD1-56F1F-58180
我现在要做的是更改它,以便我有另一个函数来检查密钥是否已使用我的脚本生成。目前,我正在考虑将$key
设置为一个特定字符串的MD5(例如test
),但当然,这会返回所有字符串相同。
谁能帮忙?
【问题讨论】:
您将这些密钥存储在哪里? 您正在散列一个“排序”随机值。如果不存储它并查看存储的密钥,您将无法知道它是否是由您的脚本生成的。另请注意,MD5 不会生成唯一值。你可能会得到 2 个相同的结果。并且使用微时间,在一个非常快的服务器上它可能会输出 10 个相同的键。 (或至少 2 个;)) @YogeshSuthar 这只是我在玩,试图学习 PHP。这些值不会永久存储在任何地方,只是作为$mykey = KeyGen()
存储一次。
@nvanesch 我也知道。谢谢你提到它:)
这很有趣..让我看看我能想出什么
【参考方案1】:
有三种基本的处理方法。你如何做到这一点取决于你生成了多少密钥,以及能够在以后使密钥失效的重要性。你选择哪一个取决于你。
选项 1:服务器数据库存储
当服务器生成密钥(例如使用您的算法)时,您将其存储在数据库中。然后,您需要做的就是检查密钥是否在数据库中。
注意,您的算法需要的熵比您提供的要多得多。当前时间戳不够。相反,使用强随机性:
$key = mcrypt_create_iv($length_needed, MCRYPT_DEV_URANDOM);
或者,如果您没有 mcrypt:
$key = openssl_random_pseudo_bytes($length_needed);
或者如果你没有 mcrypt 和 openssl,使用a library
请注意,md5
返回一个十六进制输出 (a-f0-9),其中所有以上都返回完整的随机二进制字符串(字符 0 - 255)。所以要么base64_encode()
它,要么bin2hex()
它。
优点:
实施简单 以后可以“停用”已发布的密钥 无法伪造新密钥缺点:
每个键都需要持久存储 可能无法很好地扩展 需要“密钥服务器”来验证密钥选项 2:签名密钥
基本上,您会生成一个强随机密钥(从这里开始称为私钥),并将其存储在您的服务器上。然后,在生成许可证密钥时,您会生成一个随机 blob,然后 HMAC 用私钥对其进行签名,并使许可证成为该块的一部分。这样,您就不需要存储每个单独的密钥。
function create_key($private_key)
$rand = mcrypt_create_iv(10, MCRYPT_DEV_URANDOM);
$signature = substr(hash_hmac('sha256', $rand, $private_key, true), 0, 10);
$license = base64_encode($rand . $signature);
return $license;
function check_key($license, $private_key)
$tmp = base64_decode($license);
$rand = substr($tmp, 0, 10);
$signature = substr($tmp, 10);
$test = substr(hash_hmac('sha256', $rand, $private_key, true), 0, 10);
return $test === $signature;
优点:
实施简单 不需要持久存储 微不足道缺点:
无法单独“停用”密钥 需要存储“私钥” 需要“密钥服务器”来验证密钥。选项 3:公钥加密
基本上,您会生成一个公钥/私钥对。您将公钥嵌入到您的应用程序中。然后,您生成一个密钥(类似于上面的“签名密钥”),但不是使用 HMAC 签名,而是使用私钥对其进行签名。
这样,应用程序(具有公钥)可以直接验证签名,而无需回调您的服务器。
function create_key($private_key)
$rand = mcrypt_create_iv(10, MCRYPT_DEV_URANDOM);
$pkeyid = openssl_get_privatekey($private_key);
openssl_sign($rand, $signature, $pkeyid);
openssl_free_key($pkeyid);
$license = base64_encode($rand . $signature);
return $license;
function check_key($license, $public_key)
$tmp = base64_decode($license);
$rand = substr($tmp, 0, 10);
$signature = substr($tmp, 10);
$pubkeyid = openssl_get_publickey($public_key);
$ok = openssl_verify($rand, $signature, $pubkeyid);
openssl_free_key($pubkeyid);
return $ok === 1;
优点:
实施简单 不需要持久存储 微不足道 不需要“密钥服务器”来验证密钥缺点:
无法单独“停用”密钥 需要存储“私钥”【讨论】:
非常感谢您的回答,遗憾的是我不能同时接受 Baba 和您的回答。 选项 3:公钥加密很棒 :D 我想投 10 票!!【参考方案2】:注意:
此解决方案假设您希望您的许可证密钥始终位于 fixed format
(见下文)并且仍然是 self authenticated
FORMAT : XXXXX-XXXXX-XXXXX-XXXXX-XXXX
如果不是这样,请参阅@ircmaxell
以获得更好的解决方案
简介
自我认证的串行是一个棘手的解决方案,因为:
序列大小有限 它需要在没有数据库或任何存储的情况下自行进行身份验证 如果私钥泄露......它很容易被逆转示例
$option = new CheckProfile();
$option->name = "My Application"; // Application Name
$option->version = 0.9; // Application Version
$option->username = "Benedict Lewis"; // you can limit the key to per user
$option->uniqid = null; // add if any
$checksum = new Checksum($option);
$key = $checksum->generate();
var_dump($key, $checksum->check($key));
输出
string '40B93-C7FD6-AB5E6-364E2-3B96F' (length=29)
boolean true
请注意,选项中的任何修改都会更改密钥并使其无效;
检查碰撞
我刚刚运行了这个简单的测试
set_time_limit(0);
$checksum = new Checksum($option);
$cache = array();
$collision = $error = 0;
for($i = 0; $i < 100000; $i ++)
$key = $checksum->generate();
isset($cache[$key]) and $collision ++;
$checksum->check($key) or $error ++;
$cache[$key] = true;
printf("Fond %d collision , %d Errors in 100000 entries", $collision, $error);
输出
Fond 0 collision , 0 Errors in 100000 entries
更好的安全性
默认情况下,脚本使用sha1
,但PHP
有很多更好的hash functions
,您可以使用以下代码获得它
print_r(hash_algos());
例子
$checksum = new Checksum($option, null, "sha512");
使用的类
class Checksum
// Used used binaray in Hex format
private $privateKey = "ec340029d65c7125783d8a8b27b77c8a0fcdc6ff23cf04b576063fd9d1273257"; // default
private $keySize = 32;
private $profile;
private $hash = "sha1";
function __construct($option, $key = null, $hash = "sha1")
$this->profile = $option;
$this->hash = $hash;
// Use Default Binary Key or generate yours
$this->privateKey = ($key === null) ? pack('H*', $this->privateKey) : $key;
$this->keySize = strlen($this->privateKey);
private function randString($length)
$r = 0;
switch (true)
case function_exists("openssl_random_pseudo_bytes") :
$r = bin2hex(openssl_random_pseudo_bytes($length));
break;
case function_exists("mcrypt_create_ivc") :
default :
$r = bin2hex(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM));
break;
return strtoupper(substr($r, 0, $length));
public function generate($keys = false)
// 10 ramdom char
$keys = $keys ? : $this->randString(10);
$keys = strrev($keys); // reverse string
// Add keys to options
$this->profile->keys = $keys;
// Serialise to convert to string
$data = json_encode($this->profile);
// Simple Random Chr authentication
$hash = hash_hmac($this->hash, $data, $this->privateKey);
$hash = str_split($hash);
$step = floor(count($hash) / 15);
$i = 0;
$key = array();
foreach ( array_chunk(str_split($keys), 2) as $v )
$i = $step + $i;
$key[] = sprintf("%s%s%s%s%s", $hash[$i ++], $v[1], $hash[$i ++], $v[0], $hash[$i ++]);
$i ++; // increment position
return strtoupper(implode("-", $key));
public function check($key)
$key = trim($key);
if (strlen($key) != 29)
return false;
// Exatact ramdom keys
$keys = implode(array_map(function ($v)
return $v[3] . $v[1];
, array_map("str_split", explode("-", $key))));
$keys = strrev($keys); // very important
return $key === $this->generate($keys);
【讨论】:
通过默默无闻的安全性处于最佳状态。此外,滥用名称(你所说的盐不是盐,而是私钥,因为它的有效性与它的保密性有关)。 非常感谢您的回答,正是我所需要的。 您能否展示该算法在公共安全中被使用或审查的任何示例?它看起来真的很奇怪而且是本土的(尤其是$hash1
和$hash2
部分。我的意思是,为什么是crc32()
?它似乎源自座右铭The more hash cycles, the better
。换句话说:SHA1 ALL事情!。它不会增加安全性......
1.
类似情况,一个离线的j2me
应用程序该应用程序是通过Bluetooth
分发但使用16
代码授权的(很难)我们将提出离线验证2.
crc32($keys)
正是我想要的... You did not expect it
都是关于预测 3.
SHA1 ALL THE THINGS!
我不是仅仅链接哈希,这是在保留 32
二进制密钥的熵的上下文中完成的。很好,您可以争辩 HMAC 的加密强度取决于底层哈希函数 (sha
) 的加密强度,但这可以更改。
4
是的,即使使用简单的sha
而不是HMAC
,它的增长方式和大量测试也已完成。 5
晚上不改变 hash function
我仍然认为它比你的 `$license = base64_encode($rand . $signature);` 好得多,顺便说一下不符合 OP
25 密钥许可证格式。 Finally
我已经在回答If that is not the case refer to @ircmaxell for a better solution
中说过,如果您有更好的解决方案来解决固定大小(20-25)的离线许可证,而不仅仅是通过更改(散列函数).. 做我喜欢学习的客人.. 谢谢
【参考方案3】:
您实际上正在寻找的是像 Partial Key Validation 这样的算法
查看这篇文章的工作原理并将其移植到 PHP
http://www.brandonstaggs.com/2007/07/26/implementing-a-partial-serial-number-verification-system-in-delphi/
【讨论】:
谢谢。看起来将 Delphi 转换为 PHP 需要做很多工作,所以我仍在寻找另一种选择,但如果涉及到它,我会这样做。【参考方案4】:在创建这些键时将它们存储在数据库中。稍后将它们与数据库行匹配,瞧..它将完成
【讨论】:
我知道最好的方法是存储在数据库中,但是我正在尝试找到一种不存储的方法,类似于如何激活 Adobe 产品,即使它们是离线。 【参考方案5】:请注意,使用此算法获得重复密钥并非不可能,但不太可能,但中奖也是如此。您必须将密钥存储在数据库或文件中以检查它是否已经存在。
【讨论】:
以上是关于检查生成的许可证是不是有效的主要内容,如果未能解决你的问题,请参考以下文章
NX 许可证错误:许可证服务器关机或未响应。检查UGS_LICENSE_SERVER设置是不是正确。[-96]这个问题怎么解决
注册许可证出现“输入的不是有效的 Base-64 字符串,因为它包含非 Base-64 字符两个以上的填充字符,或者填充字符间包含非空白字符”