PHP 数字转字符串互换类

2018-02-03 10:29:57来源:http://www.miaoqiyuan.cn/p/php-int-convert作者:苗启源的部落格人点击

分享

再次更新:发现尽然有现成的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


最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台