Ruby的标准库和Rails的PostgreSQL适配器默认使用版本4的UUID。这可以在Rails的迁移中通过.参数来改变:
default: 'uuid_generate_v1()'
param,而Ruby的stdlib只支持版本4。
另一个有趣的区别是在实现方面。
在Ruby的stdlib中,UUID方法是在SecureRandom中找到的
# SecureRandom.uuid generates a random v4 UUID (Universally Unique IDentifier).
#
# The version 4 UUID is purely random (except the version).
# It doesn't contain meaningful information such as MAC addresses, timestamps, etc.
#
# See RFC 4122 for details of UUID.
#
def uuid
ary = random_bytes(16).unpack("NnnnnN")
ary[2] = (ary[2] & 0x0fff) | 0x4000
ary[3] = (ary[3] & 0x3fff) | 0x8000
"%08x-%04x-%04x-%04x-%04x%08x" % ary
end
Rails版本使用uuid-ossp postgres扩展
# By default, this will use the +uuid_generate_v4()+ function from the
# +uuid-ossp+ extension, which MUST be enabled on your database.
def primary_key(name, type = :primary_key, options = {})
return super unless type == :uuid
options[:default] = options.fetch(:default, 'uuid_generate_v4()')
options[:primary_key] = true
column name, type, options
end
唯一的区别是每个库中实际的数字是如何产生的--这一点很值得研究--让我们从Ruby标准库开始。
Ruby标准库的实现
module SecureRandom
if defined? OpenSSL::Random
def self.gen_random(n)
@pid = 0 unless defined?(@pid)
pid = $$
unless @pid == pid
now = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
ary = [now, @pid, pid]
OpenSSL::Random.random_add(ary.join("").to_s, 0.0)
@pid = pid
end
return OpenSSL::Random.random_bytes(n)
end
else
def self.gen_random(n)
ret = Random.raw_seed(n)
unless ret
raise NotImplementedError, "No random device"
end
unless ret.length == n
raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes"
end
ret
end
end
end
是以OpenSSL::Random 开始的,可以描述为:
OpenSSL不能直接生成真正的随机数。选择是使用一个具有良好随机种子的加密安全PRNG(即用操作系统从有效的随机硬件事件中收获的数据);或者使用一个真正的硬件RNG。[1]
如果OpenSSL不存在,它就会退回到Ruby的伪随机数生成器,该生成器在Random.raw_seed ,它使用一个修改过的Mersenne Twister,周期为2**19937-1。
uuid-ossp扩展实现
查看uuid-ossp 源代码,我们可以发现:
Datum
uuid_generate_v4(PG_FUNCTION_ARGS)
{
return uuid_generate_internal(UUID_MAKE_V4, NULL, NULL, 0);
}
和uuid_generate_internal 的相关部分的功能:
case 4: /* random uuid */
default:
{
#ifdef HAVE_UUID_E2FS
uuid_t uu;
uuid_generate_random(uu);
uuid_unparse(uu, strbuf);
#endif
break;
}
我们可以看到有一个对uuid_generate_random 的调用 - 这可能是什么?
uuid_generate_random(3) - Linux man page
它的描述:
uuid_generate_random函数强制使用全随机UUID格式,即使高质量的随机数发生器(即/dev/urandom)不可用,在这种情况下,将用伪随机数发生器代替。注意,使用伪随机生成器可能会损害以这种方式生成的UUID的唯一性。
有趣的是,如果/dev/urandom 不可用--它又回到了PRNG--这里似乎有一个细微的差别,甚至是一个矛盾,因为/dev/urandom 使用csPRNG(加密安全的伪随机数发生器)[3]
结论
这开始是一个简单的好奇心,我可以使用哪些版本的UUID--注意,这不是对真正的随机数与PRGN的批评,因为RFC明确指出。
版本4的UUID是用来从真正的随机数或伪随机数生成UUID的。[0]
这使得两种实现方式都是正确的:
UUID方法在Ruby的SecureRandom中是有意义的,因为它只是版本4的实现,而且默认情况下它使用OpenSSL::Random 。我对此的唯一小抱怨是,在Ruby中没有支持所有版本的通用UUID库 [2]。
关于他们的实现,应该注意到他们的不同,如果随机性的质量很重要,应该进一步调查(例如,由于缺少OpenSSL - Ruby将使用内部的PRNG,乍一看,它似乎相当可靠,因为它用于播种dev/urandom [6],但它可能不在同一级别的csPRNG中)。
对于UUID来说,两种实现都应该产生可用的随机UUID,即使库中有非csPRNG的算法。最后,有一个小问题:
在不同主机上生成UUID的分布式应用必须愿意依赖所有主机上的随机数源。如果这不可行,应该使用命名空间的变体。