当前位置: 首页 > 后端技术 > PHP

PHP使用32进制实现id的加解密

时间:2023-03-29 22:56:21 PHP

前言最近在项目中遇到了一个问题。当前用户与朋友分享邀请码。好友根据邀请码注册为新用户后,成为当前用户的下属。具体在一定条件下,可以获得下级用户的一系列返利。这里需要实现的是根据当前用户的id生成一个加密的字符串,并且可以逆向解密。经过不断的测试和调整,终于得出了最终的结果。例如:id=12code=85U43DM首先先实现代码,如下:/***加密解密用户邀请码,*@paramunknown$string*@paramstring$actionencode|decode*@returnstring*/functionendecodeUserId($string,$action='encode'){$startLen=13;$endLen=8;$coderes='';#TOD暂时将uid字符的最大长度设置为9if($action=='encode'){$uidlen=strlen($string);$salt='你自己的代码';$codestr=$string.$salt;$encodestr=hash('md4',$codestr);$coderes=$uidlen.substr($encodestr,5,$startLen-$uidlen).$string.substr($encodestr,-12,$endLen);$coderes=strtoupper($coderes);}elseif($action=='decode'){$strlen=strlen($string);$uidlen=$string[0];$coderes=substr($string,$startLen-$uidlen+1,$uidlen);}return$coderes;}思路介绍:设置一个salt值,将$salt和ids拼接成一个新的字符串,salt值可以用于后期邀请码的安全校验。用md4加密字符串(考虑到md4比md5快,安全性也不弱),得到$encodestr,将字符串拆分成两部分,第一部分$startLen,13个字符串;第二部分$endLen,8个字符串。混合$string,这里指的是传入的id,和$uidlen放入字符串的前半部分。所以目前只支持最大长度为9的id,所以$uidlen的长度为1,所以我们最终得到的是一个长度为22的字符串。在加密过程中,我们实际上是将id的值和将id放入加密字符串中。加密的时候,我们根据存储的信息找到对应的位置,就可以得到id了。在这里,我们对安全性的要求不高。为了让程序跑得更快,解密的时候没有校验。测试,加密id:echoendecodeUserId(12);输出结果:23471DC2352712F34D6780测试,解密邀请码echoendecodeUserId('23471DC2352712F34D6780','decode');outputresult:12得到的结果貌似没什么问题,但是实际测试我发现有这样的问题,对于普通用户来说可能存在。朋友用手机发微信邀请码,然后想用电脑注册,但是不知道怎么从手机传邀请码。到电脑前可能会很麻烦。这个时候,他就会开始在电脑上手动输入邀请码。我的天,22位,还是大写字母和数字的混合。我猜他会放弃注册。因此,我们调整更改为7位邀请码。写文章之前再探讨一下方法是封装好了,还是直接把代码放在前面'0123456789ABCDEFGHJKMNPQRSTVWXYZ',//withoutILOU);公共函数__construct($type='32'){$this->type=$type;$this->baseChar=self::$convertList[$type];}/***公共方法,数字转换*@param$num*@returnstring*/privatefunction_idToString($num){$str='';while($num!=0){$tmp=$num%$this->type;$str.=$this->baseChar[$tmp];$num=intval($num/$this->type);}返回$str;}/***@descim:十进制数转三十进制数*@param(string)$char三十进制数*return返回:十进制数*/publicfunctionidToString($id){//10-digitidreturns7-digitalphanumeric//数组添加备用值$id+=self::INIT_NUM;//左边加0补全10位$str=str_pad($id,10,'0',STR_PAD_LEFT);//逐位拆分46位(32基46位除法)$num1=intval($str[0].$str[2].$str[6].$str[9]);$num2=intval($str[1].$str[3].$str[4].$str[5].$str[7].$str[8]);$str1=$str2='';$str1=$this->_idToString($num1);$str1=strrev($str1);$str2=$this->_idToString($num2);$str2=strrev($str2);//4补34位ULreturnstr_pad($str1,3,'U',STR_PAD_RIGHT).str_pad($str2,4,'L',STR_PAD_RIGHT);}/***@descim:三十个二进制数转换为十个机制Number*@param(string)$char三十个二进制数*returnreturn:decimalnumber*/publicfunctionstringToId($str){//1清除3and4个补位$str1=trim(substr($str,0,3),'U');$str2=trim(substr($str,3,4),'L');$num1=$this->_stringToId($str1);$num2=$this->_stringToId($str2);//补拼接$str1=str_pad($num1,4,'0',STR_PAD_LEFT);$str2=str_pad($num2,6,'0',STR_PAD_LEFT);$id=ltrim($str1[0].$str2[0].$str1[1].$str2[1].$str2[2].$str2[3].$str1[2].$str2[4].$str2[5].$str1[3],'0');//减去备用值$id-=self::INIT_NUM;返回$id;}/***publicmethodstringtonumber*@param$str*@returnfloat|int|string*/privatefunction_stringToId($str){//转换为数组$charArr=array_flip(str_split($this->baseChar));$数=0;对于($i=0;$i<=strlen($str)-1;$i++){$linshi=substr($str,$i,1);if(!isset($charArr[$linshi])){返回'';}$num+=$charArr[$linshi]*pow($this->type,strlen($str)-$i-1);}返回$num;}}思路介绍在工作多年的高手指导下,该方法用于将id转为定长的32位字符串,并加入自己的算法。为什么这里使用32基系统而不是其他基系统?32进制可以包含足够多的英文字符,生成的加密字符串看起来会更规范。另一方面,一些不易识别的英文字符(这里不包括ILOU),所以使用32进制而不是36进制。加密过程,方法idToString(),考虑到一开始id比较小的时候,转成32位会多0,显得很不规则,所以设置一个初始值INIT_NUM,可以自定义。根据传入的id,加上初始值得到一个长度为10位的值,将这个值拆分为长度为4位的$num1和长度为6位的$num2,将两个值进行转换分别是32进制,$num1转换得到长度为3的字符串,不够用U补,$num2得到长度为4的字符串,不够用L补。解密就是逆运算,就是逆运算。测试:生成$obj=newconvert(32);$res1=$obj->idToString(12);结果:85U43DM解密:$obj=newconvert(32);$res1=$obj->stringToId('85U43DM');结果:12小结当然,即使是最后一种方法也有不足之处,比如在将加密值拆分为2个num值时,使用的方法非常不灵活,一旦修改解密的地方就相应更改。这里只是分享一个想法,欢迎批评指正。