如何自定义 Postgres 伪加密函数的输出?

How to customize the output of the Postgres Pseudo Encrypt function?

我想使用 Whosebug 上多次提到的 pseudo_encrypt 函数让我的 ID 看起来更随机:https://wiki.postgresql.org/wiki/Pseudo_encrypt

我如何自定义它以只为我输出唯一的 "random" 号码。我在某处读到,您可以只更改 1366.0 常量,但我不想对我的 ID 冒险,因为任何潜在的 ID 重复都会导致重大问题。

我真的不知道每个常量的实际作用,所以我不想弄乱它,除非我得到一些指导。有谁知道我可以安全地更改哪些常量?

这里是:

CREATE OR REPLACE FUNCTION "pseudo_encrypt"("VALUE" int) RETURNS int     IMMUTABLE STRICT AS $function_pseudo_encrypt$
DECLARE
l1 int;
l2 int;
r1 int;
r2 int;
i int:=0;
BEGIN
    l1:= ("VALUE" >> 16) & 65535;
    r1:= "VALUE" & 65535;
    WHILE i < 3 LOOP
        l2 := r1;
        r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767)::int;
        r1 := l2;
        l1 := r2;
        i := i + 1;
END LOOP;
RETURN ((l1::int << 16) + r1);
END;
$function_pseudo_encrypt$ LANGUAGE plpgsql;

对于 bigint 的

CREATE OR REPLACE FUNCTION "pseudo_encrypt"("VALUE" bigint) RETURNS bigint IMMUTABLE STRICT AS $function_pseudo_encrypt$
DECLARE
l1 bigint;
l2 bigint;
r1 bigint;
r2 bigint;
i int:=0;
BEGIN
    l1:= ("VALUE" >> 32) & 4294967295::bigint;
    r1:= "VALUE" & 4294967295;
    WHILE i < 3 LOOP
        l2 := r1;
        r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767*32767)::bigint;
        r1 := l2;
        l1 := r2;
        i := i + 1;
    END LOOP;
RETURN ((l1::bigint << 32) + r1);
END;
$function_pseudo_encrypt$ LANGUAGE plpgsql;

这个函数看起来像一个基于 Feistel network 的块密码 - 但它缺少密钥。

Feistel 构造是双射的,即它保证没有碰撞。有趣的部分是:r2 := l1 # f(r1)。只要 f(r1) 仅依赖于 r1,无论函数做什么,pseudo_encrypt 都是双射的。

缺少密钥意味着任何知道源代码的人都可以恢复顺序ID。因此,您依赖于隐蔽的安全性。

备选方案是使用采用密钥的分组密码。对于 32 位块,选择相对较少,我知道 Skip32 和 ipcrypt。对于 64 位块,有许多密码可供选择,包括 3DES、Blowfish 和 XTEA.

备选方案:使用不同的密码

其他密码函数现在可以在 postgres wiki 上使用。它们的速度会明显变慢,但除此之外,它们更适合生成自定义的随机外观系列唯一数字。

对于 32 位输出,Skip32 in plpgsql 将使用 10 字节宽的密钥对其输入进行加密,因此您只需选择自己的密钥来进行特定的排列(2 的特定顺序) ^32个唯一值会出来)。

对于 64 位输出,XTEA in plpgsql 将执行类似的操作,但使用 16 字节宽的密钥。

否则,只需自定义 pseudo_encrypt,见下文:

关于pseudo_encrypt实现的解释:

这个函数有 3 个属性

  • 输出值的全局唯一性
  • 可逆性
  • 伪随机效应

第一个和第二个 属性 来自 Feistel 网络,正如@CodesInChaos 的回答中已经解释的那样,它们不依赖于这些常量的选择:1366 以及 150889714025.

确保在更改 f(r1) 时它仍然是数学意义上的函数,即 x=y 意味着 f(x)=f(y),或者换句话说,相同的输入必须始终产生相同的结果输出。打破这一点将打破唯一性。

这些常量的目的和 f(r1) 的这个公式是为了产生相当好的伪随机效果。使用 postgres 内置 random() 或类似方法是不可能的,因为它不是上述的数学函数。

为什么是这些任意常数?在这部分函数中:

r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767)::int;

公式和值 1366150889714025 来自 Numerical recipes in C (1992, by William H.Press,第 2 版),第 7 章:随机数,特别是第 284 和 285 页。 这本书不能直接在网络上索引,但可以通过此处的界面阅读:http://apps.nrbook.com/c/index.html。它也被引用为各种实现 PRNG 的源代码的参考。

在本章讨论的算法中,上面用到的算法非常简单,也比较有效。从前一个随机数 (jran) 中获取新随机数的公式是:

jran = (jran * ia + ic) % im;
ran = (float) jran / (float) im;  /* normalize into the 0..1 range */

其中 jran 是当前的随机整数。

此生成器在一定数量的值("period")后必然会循环自身,因此常量 iaicim 必须是仔细选择那个时期尽可能大。该书提供了一个 table 第 285 页,其中建议了各种长度的周期的常量。

ia=1366ic=150889im=714025 是一段时间内的条目之一 229 位,远远超过需要。

最后乘以 32767 或 215-1 不是 PRNG 的一部分,但意味着从 0..1 产生正半整数伪随机浮点值。不要更改那部分,除非要扩大算法的块大小。