再次更新:发现尽然有现成的Base32方案,无奈已经写好了,还是继续分享出来吧。本方案比Base32相比还是有优势的。
1、自带校验功能
2、解码后可以和数据库中的生成项目对比进行二次验证
3、生成的邀请码是随机,相比Base32规律更难找
本类所在的项目,使用 ThinkPHP 搭建,把 IntConvert.php 放到 app\services 目录下,就可以直接使用app\services\IntConvert 自动调用。如果存放目录后,注意修改下 对应的明明空间。
—-分割线—–
最近 的APP上需要增加一个 邀请码功能,通过邀请码注册。因为内部使用,需要防止 外部用户 随便填写一个 邀请码注册成功。因为主要通过图片的方式进行传播,还不恩那个设计的太长。
初期考虑了两种方案: 最笨的方法是一次生成好,用时随机获取一个。另一个是通过Base64对用户ID进行转换下。分析之后,两种方案都不太理想。
一次生成好方案的坏处:1、占用空间 2、有限制,比如生成1万个,超过1万个用户则无法邀请
使用Base64方案的坏处:1、太容易解密 2、长度太长 3、生成的邀请码太有规律 4、大小写问题(MySQL数据库比选择 *_bin 方式建表,会有重复)
没有合适的方案,准备自己造一个轮子。
设计之初考虑了几个方面:
1、为了减轻服务器的负担,能自带校验功能,校验通过后,再去数据库匹配
2、生成时,不能存在 重复的 值,能通过 系统中某项 唯一的key(比如:user_id)去生成,这样就不用考虑取数据库查询是否已经有重复的记录
3、因为最终还要取数据库匹配和方便用户输入,所以 要做到 大小写可以混用。
4、生成的邀请码不能太有规律
实现的过程如下:
之前看过 Base64 的原理,我们可以仿照他,写个 山寨版 的。使用 A-Z0-9 共36个字符,去掉 4个,正好32个。最重要的是这样不存在大小写的问题了。
对数字进行 二进制处理,Base64 是6个字节一段,我的以5个字节一段,如果不是5的倍数,前面自动补充0
对选择的 32个字符,随机打乱,就可以生成 没有太有规律的 邀请码了。
自带校验功能,对输入的数字md5后,随机选取一位当作验证就可以了,验证通过后,再去数据库匹配。范围 0-F,破解率为 1/16。
按上面的方法写好后,发现 邀请码还是太有规律,比如:10000(AA1),100001(AAZ)。想到前面实现的 校验功能,存在16种值,如果生成 16 个 KeyMap,效果会好很多。最终运行效果如下:
当然,不能完全相信 自带校验功能,还需要结合数据库中验证。本方案另外一个好处是本方法 是可逆转的, 用户ID和 邀请码不匹配,也可以判断出是恶意猜出来的邀请莫。
直接上代码,演示地址:http://www.miaoqiyuan.cn/products/php-int-convert/git/app/test/
<?php namespace app\services; /** * 类名:IntConvert * 作者:mqycn * 博客:http://www.miaoqiyuan.cn * 源码:https://gitee.com/mqycn/IntConvert * 说明:数字字符串互换类,常见的应用场景比如邀请码 */ class IntConvert { /** * KeyMap 在初始时,建议重新生成一下 */ static private $keyMap = [ 'LOXB3V64IGUEYCQF8A72WKSHN915RDZM', 'DZ7O3VMWHB85CELGI6XQYSRA9N4K1FU2', 'BZNS6WAG83IDK5M2OCUEF4RLQV917HYX', 'LXOZQKEI7F49US5N1M36ADRV2YW8CGHB', 'XRNOYZDGIS45UM3LV7E6QW8FC91BH2KA', 'UQW7V4YSRD1B6KML32ZANF8E9O5GXHCI', 'MNQ9Y3I65K4VOGE2L7DHRSXAW1UZBC8F', 'G1QDUKIM4OX67LNRYEB8V3S9ACF5WZ2H', '65I13C7XM9ZFKWNR2DEUHQLYB48AGSVO', '53ZK4CG86LOMQRAVNEFI1XU7HS2YB9DW', 'Q4FC3ZV8GYNRH1297DKXUBE5ALIO6WSM', 'CSI2ON7MBYRVWH63UX8LQ9FG5DEAKZ14', '3D1GWSEN7L9IQ8Y5UMVXACORKBHZ42F6', '285V46AGWB9LI3CKM1ONURYSF7QHDXZE', 'OX7NB2SU8IMKQ1Z9YEWVD6H53C4AFLGR', 'W9RL6NS74HG15ZID2OCAYFBEM83UQXVK', ]; /** * 生成随机Key */ static public function randomKey() { header('content-type: text/text; charset=utf-8'); echo " #请复制到 IntConvert 头部\n"; echo " static private $" . "keyMap = [\n"; for ($i = 0; $i < 16; $i++) { $keys = self::$keyMap[0]; $keys_new = ''; $word = ''; $len = strlen($keys); while ($len > 0) { $word = substr($keys, rand(0, $len - 1), 1); $keys = str_replace($word, '', $keys); $keys_new .= $word; $len = strlen($keys); } echo " '$keys_new',\n"; } echo " ];\n"; die(); } /** * 将数字编码为字符串 */ static public function toString($num = 0) { // 对传入的数字,取hash的第一位 $hash = self::getHash($num); // 根据Hash,选择不同的字典 $keymap = self::getKeyMap($hash); // 数字转二进制 $map = self::fixBin(decbin($num)); // 如果不足10位,前面自动补全对应个数的0 $len = strlen($map); if ($len < 10) { $map = substr('00000000000000000000', 0, (10 - $len)) . $map; } // 按5个一组,编码成数字,根据KeyMap加密 $keys = ''; $len = strlen($map); for ($index = 0; $index < strlen($map); $index += 5) { $keys .= substr($keymap, bindec(substr($map, $index, 5)), 1); } return $hash . $keys; } /** * 将字符串编码为数字 */ static public function toInt($str = '') { //根据生成规则,最小长度为3 if (strlen($str) < 3) { return false; } $hash = substr($str, 0, 1); $keys = substr($str, 1); // 根据Hash,选择不同的字典 $keymap = self::getKeyMap($hash); $bin = ''; // 根据字典,依次 index,并转换为二进制 for ($i = 0; $i < strlen($keys); $i++) { for ($index = 0; $index < strlen($keymap); $index++) { if (strtoupper(substr($keys, $i, 1)) === substr($keymap, $index, 1)) { $bin .= self::fixBin(decbin($index)); } } } // 二进制转换为数字 $num = bindec($bin); if (self::getHash($num) === $hash) { return $num; } else { return false; } } /** * 根据Hash取字典 */ static private function getKeyMap($hash = 'A') { return self::$keyMap[hexdec($hash)]; } /** * 不足5位的二进制,自动补全二进制位数 */ static private function fixBin($bin = '110') { $len = strlen($bin); if ($len % 5 != 0) { $bin = substr('00000', 0, (5 - $len % 5)) . $bin; } return $bin; } /** * 对数字进行Hash */ static private function getHash($num = 0) { return strtoupper(substr(md5(self::getKeyMap(0) . $num), 1, 1)); } } ?>
考虑到不同的项目,根据用户ID生成,可能仍然会存在被猜出来的结果,我在上边的类中专门编写了randomKey方法,直接调用即可。
演示地址:http://www.miaoqiyuan.cn/products/php-int-convert/git/app/test/get_keys.php
所有代码已经上传到 gitee.com,如果对您有所帮助,欢迎 给我点亮 star。项目地址:https://gitee.com/mqycn/IntConvert