如何自定义 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
以及 150889
和 714025
.
确保在更改 f(r1)
时它仍然是数学意义上的函数,即 x=y
意味着 f(x)=f(y)
,或者换句话说,相同的输入必须始终产生相同的结果输出。打破这一点将打破唯一性。
这些常量的目的和 f(r1)
的这个公式是为了产生相当好的伪随机效果。使用 postgres 内置 random()
或类似方法是不可能的,因为它不是上述的数学函数。
为什么是这些任意常数?在这部分函数中:
r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767)::int;
公式和值 1366
、150889
和 714025
来自 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")后必然会循环自身,因此常量 ia
、ic
和 im
必须是仔细选择那个时期尽可能大。该书提供了一个 table 第 285 页,其中建议了各种长度的周期的常量。
ia=1366
、ic=150889
和 im=714025
是一段时间内的条目之一
229 位,远远超过需要。
最后乘以 32767
或 215-1 不是 PRNG 的一部分,但意味着从 0..1 产生正半整数伪随机浮点值。不要更改那部分,除非要扩大算法的块大小。
我想使用 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
以及 150889
和 714025
.
确保在更改 f(r1)
时它仍然是数学意义上的函数,即 x=y
意味着 f(x)=f(y)
,或者换句话说,相同的输入必须始终产生相同的结果输出。打破这一点将打破唯一性。
这些常量的目的和 f(r1)
的这个公式是为了产生相当好的伪随机效果。使用 postgres 内置 random()
或类似方法是不可能的,因为它不是上述的数学函数。
为什么是这些任意常数?在这部分函数中:
r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767)::int;
公式和值 1366
、150889
和 714025
来自 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")后必然会循环自身,因此常量 ia
、ic
和 im
必须是仔细选择那个时期尽可能大。该书提供了一个 table 第 285 页,其中建议了各种长度的周期的常量。
ia=1366
、ic=150889
和 im=714025
是一段时间内的条目之一
229 位,远远超过需要。
最后乘以 32767
或 215-1 不是 PRNG 的一部分,但意味着从 0..1 产生正半整数伪随机浮点值。不要更改那部分,除非要扩大算法的块大小。