最近,内部聊天中出现了生成随机优惠码和其他字符串的话题。我的首选一直是基于Feistel网络的解决方案,我认为它并不是非常晦涩难懂。但当其他人似乎没有认出它时,我很惊讶,所以可能是这样的。无论如何,这里有一个小插图来说明这件事在起作用。
Feistel网络是DES和其他加密算法背后的密码的数学基础。我不会详述细节(因为这会表明我完全理解它,有些地方我不太清楚),但最终它是一种有点简单且非常快速的机制,对于我们在这里的使用相当有效。
对于字符串生成,我们有两个部分。对于第一部分,我们获取一个整数,比如数据库中按顺序生成的id主键字段,然后通过一个函数将其运行,该函数会将其转换为其他一些随机的整数。我们的函数实现有一个有趣的属性:如果您将看起来随机的整数通过相同的函数运行回来,我们就会得到原来的整数。换句话说,…。
…。对于n的任何整数值,这种一对一映射实质上保证了随机外观的输出在整个整数空间中实际上是唯一的。换句话说,我们可以肯定,一旦我们到了做线的部分,就不会有碰撞。
最初的函数基于PostgreSQL wiki上的代码,为清晰起见只做了几处改动,应该适用于任何现代(或古老)版本的Postgres。
CREATE OR REPLACE Function PUBLIC.feistel_crypt(值整数)返回整数语言plpgsql不可变STRICTAS$Function$DECLARE KEY NUMERIC;L1INT;L2INT;R1INT:=0;BEGIN L1:=(VALUE&>;&>16)&;65535;R1:=VALUE&;65535;而I<;3LOOP--KEY可以是返回0到1之间数值的任何函数:=(。R2:=L1#(KEY*32767)::INT;L1:=L2;R1:=R2;I:=i+1;结束循环;返回((R1<;<;16)+L1);结束;$Function$;
交换一下分配给那个关键变量的值,这样就可以得到与我所说明的不同的输出。毕竟,如果有人可以一字不差地使用这个示例并生成您的优惠券代码,那就不好了。此外,一旦开始以某种方式使用生成的数字,您可能不想更改该键函数,因为这可能会引入与前一个键生成的现有值冲突的可能性。
无论如何,有了这些,您就可以开始生成一些随机整数,并确保它们映射回来:
TOTESRANDOM=#SELECT feistel_crypt(1),feistel_crypt(2),feistel_crypt(3),feistel_crypt(4);FESTEL_CRYPT|feistel_crypt-+-561465857|436885871|576481439|483424269(1行)总计RANDOM=#SELECT FESTEL_CRYPT(561465857),FESTEL_CRYPT(436885871),FESTEL_CRYPT(576481439),FESTEL_CRYPT(483424269);FESTEL_CRYPT|feistel_crypt-+-1|2|3|4(1行)。
TOTESRANDOM=#SELECT COUNT(*)FROM GENERATE_SELECTION(110000000)WHERE FESTEL_CRYPT(FEISTEL_CRYPT(GENERATE_RESERENCE))!=GENERATE_STERENCE;COUNT-0(1行)时间:185151.416毫秒(03:05.151)。
一旦我们有了新的价值,第二部分就更容易了。我们获取新的整数并将其映射到字符串,实质上是创建数字的基数N表示。
CREATE OR REPLACE Function PUBLIC.INT_TO_STRING(NINT)返回文本语言plpgsql不可变STRICTAS$Function$DECLARE字母text:=';abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';;BASE INT:=LENGTH(字母表);输出文本:=';';;BEGIN LOOP OUTPUT:=OUTPUT||SUBSTR(字母表,1+(n%BASE)::INT,1);n:=n/BASE;当n=0时退出;结束循环;返回输出;结束$Function$;
瞧,看起来像随机的短字符串,你可以用来作为优惠码、电子邮件确认令牌,任何你需要的东西:
Talesdom=#select int_to_string(feistel_crypt(1)),int_to_string(feistel_crypt(2)),int_to_string(feistel_crypt(3)),int_to_string(feistel_crypt(4));INT_TO_STRING|int_to_string-+-5409L|t8hJD|Tj1aN|NTySG(1行)时间:0.473毫秒。
当然,您可以根据需要调整该字符集。如果你对有人对它进行反向工程非常偏执,也许会把它弄得乱七八糟。您的字符集可以是您想要的任何内容。一个纯粹的表情符号集可能会很有趣,或者可能会将它设置为一组单词,以便连接在一起,而不是单个字母。或者,如果有人可能正在大声朗读其中的一个,例如,在电话中,你可能会想要一个单独的案例:
';ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';totesrandom=#_to_string(FESTEL_CRYPT(1)),INT_TO_STRING(FESTEL_CRYPT(2)),INT_TO_STRING(FESTEL_CRYPT(3)),INT_TO_STRING(FESTEL_CRYPT(4));INT_TO_STRING|int_to_string-+-33FKKJ|XK9DIH|L79HTJ|7TQ39H(1行)时间:0.681毫秒
当然也可以颠倒这一部分,并将字符串读回原始整数。但我建议将结果字符串存储到数据库字段中,并直接对其进行查找。
在某种程度上,我最终将其移植到了Python。它仍然是超级简单的,而且工作起来也是一样的。但是,也许以另一种形式查看它会帮助您将其移植到您可能需要它的任何其他语言。
Def Simple_Feistel(Value):#用于ID混淆的简单自逆Feistel密码L1=(Value>;>;16)&;65535 R1=值&;65535,范围(3):KEY=(1366*R1+150889)%714025)/714025.0)L2=R1=R2=L1^INT(KEY*32767)L1=L2 R1=R2 RETURN(R1<;<;16)+l1def stringify_INTEGER(VALUE):#获取整数并将其编码为基(LEN(字母))字符串字母表=';abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';;BASE=LEN(字母表)输出=';';而VALUE>;0:OUTPUT+=字母表[VALUE%BASE]VALUE//=BASE返回输出