概述
此库实现了一个可扩展的框架,用于实现可以支持用户定义类型的哈希算法。其结构主要基于 Howard Hinnant、Vinnie Falco 和 John Bytheway 的论文 “Types don’t know #”。
设计的关键特性是 哈希算法 和 hash_append
函数之间的清晰分离。哈希算法 接受非类型化的字节流(消息)并生成哈希值(消息摘要),而 hash_append
函数接受类型,并负责将此类型的值转换为字节序列并将其馈送到 哈希算法。
这允许用户定义类型只需编写一次哈希支持,然后就可以自动与任何哈希算法一起使用,即使是在定义类型时还不可用的算法也是如此。
提供了以下流行的哈希算法
但用户也可以编写自己的算法;只要哈希算法符合概念,hash_append
就可以与它一起工作,所有支持 hash_append
的用户定义类型也可以。
哈希字节序列
此库解决了两个主要用例:哈希非类型化的字节序列和哈希 C++ 对象。
非类型化的字节序列(也称为 消息)通过将它们传递给 哈希算法 进行哈希,然后生成哈希值(或 消息摘要)。
同一个 哈希算法 在传递相同的 消息 时,将始终产生相同的 摘要。(发布的算法提供 消息 和相应的 摘要 对,称为 测试向量,以实现对独立实现的验证。)
(要哈希 C++ 对象,首先将其转换(序列化)为字节序列,然后传递给 哈希算法。)
哈希算法要求
哈希算法 必须具有以下结构,并满足以下最低要求
struct HashAlgorithm
{
using result_type = /*integral or array-like*/;
static constexpr std::size_t block_size = /*...*/; // optional
HashAlgorithm();
explicit HashAlgorithm( std::uint64_t seed );
HashAlgorithm( void const* seed, std::size_t n );
HashAlgorithm( HashAlgorithm const& r );
HashAlgorithm& operator=( HashAlgorithm const& r );
void update( void const* data, std::size_t n );
result_type result();
};
result_type
嵌套类型 result_type
是生成的哈希值的类型。它可以是无符号整数类型(不是 bool
),通常是 std::uint32_t
或 std::uint64_t
,或者是值类型为 unsigned char
的 std::array
类型的类型。
通常,非加密哈希函数具有整数 result_type
,而加密哈希函数具有类似数组的 result_type
,但这并非必需。
提供的实用工具函数 get_integral_result
可用于从任何有效的 result_type
获取整数哈希值。
block_size
加密哈希函数提供一个 block_size
值,这是它们的块大小(例如,MD5 为 64,SHA2-512 为 128),并且是实现相应的 HMAC 所必需的。
block_size
是一个可选要求。
接受整数种子的构造函数
所有 哈希算法 都必须可以从 std::uint64_t
类型的值构造,该值用作种子。
使用种子值 0 等同于默认构造。
不同的种子值会导致内部状态以不同的方式初始化,因此,当传递相同的消息时,由不同种子初始化的哈希算法实例会产生不同的哈希值。
使用随机(从外部不可观察)值进行播种对于防止 哈希洪泛攻击 非常有用。
接受字节序列种子的构造函数
所有 哈希算法 都可以从 unsigned char
值的种子序列构造(这使得所有哈希算法都成为 密钥哈希函数。)
空序列(长度为 0 的序列)会生成默认构造的实例。
不同的种子序列会产生不同的初始化实例。
update
函数 update
是输入消息提供给哈希算法的机制。
多次调用 update
等同于使用来自各个调用的串联字节序列调用一次。也就是说,输入消息可以分部分提供,并且将其拆分为部分的方式无关紧要,并且不影响最终的哈希值。
给定
Hash hash; // some hash algorithm
unsigned char message[6] = { /*...*/ }; // some input message
以下 update
调用
hash.update( message, 6 );
等同于
hash.update( message, 4 );
hash.update( message + 4, 2 );
和
for( int i = 0; i < 6; ++i ) hash.update( &message[i], 1 );
result
在通过调用 update
提供整个输入消息后,可以通过调用 result
获得最终的哈希值。
对 result
的调用会根据具体算法规范填充消息,可选地将消息的长度并入状态,并对状态执行最终确定操作,同样按照具体算法的规定,从而最终确定内部状态。
然后,通过转换内部状态获得最终的哈希值并返回。
请注意,result
是非 const 的,因为它会更改内部状态。允许多次调用 result
;后续调用会再次执行状态最终确定,并因此产生 result_type
值的伪随机序列。这可以用于有效地扩展哈希函数的输出。例如,可以通过调用 result
四次从 result_type
为 64 位的哈希算法获得 256 位的结果。
作为一个玩具示例,不用于生产用途,这就是如何基于库提供的 FNV-1a 实现编写随机数生成器
std::uint64_t random()
{
static boost::hash2::fnv1a_64 hash;
return hash.result();
}
编译时哈希
在 C++14 下,可以在编译时调用某些哈希算法。这些算法提供以下接口
struct HashAlgorithm
{
using result_type = /*integral or array-like*/;
static constexpr std::size_t block_size = /*...*/; // optional
constexpr HashAlgorithm();
explicit constexpr HashAlgorithm( std::uint64_t seed );
HashAlgorithm( void const* seed, std::size_t n );
constexpr HashAlgorithm( unsigned char const* seed, std::size_t n );
constexpr HashAlgorithm( HashAlgorithm const& r );
constexpr HashAlgorithm& operator=( HashAlgorithm const& r );
void update( void const* data, std::size_t n );
constexpr void update( unsigned char const* data, std::size_t n );
constexpr result_type result();
};
除了添加的 constexpr
限定符外,唯一的区别是字节种子构造函数和 update
具有第二个重载,它接受 unsigned char const*
而不是 void const*
。(指向 void
的指针在 C++26 之前不能在 constexpr
函数中使用。)
提供的哈希算法
FNV-1a
Fowler-Noll-Vo 哈希函数 作为一次处理一个字节的哈希函数类的代表提供。对于每个输入字符 ch
,使用操作 state = (state ^ ch) * fnv_prime
更新 32 位或 64 位状态。
FNV-1a 是非加密的,与最先进的哈希函数相比相对较弱(尽管对于其类别来说很好),但当输入字符串很短时速度很快。
xxHash
xxHash 是 Yann Collet 开发的快速非加密哈希算法。
它的速度(在 Xeon E5-2683 v4 @ 2.10GHz 上,xxhash_32
约为 5GB/s,xxhash_64
约为 10GB/s)使其非常适合快速生成文件或数据完整性校验和。
SipHash
Jean-Philippe Aumasson 和 Daniel J. Bernstein 的 SipHash (论文) 旨在阻止针对接收外部不受信任的输入(例如 HTTP 消息头或 JSON 对象)的哈希表的 哈希洪泛攻击。
它不是加密哈希函数(即使其设计与加密哈希函数相似),因为它在初始种子已知时不提供抗冲突性。
然而,它是一个密码学上强大的密钥哈希函数(或伪随机函数,PRF)。如果攻击者不知道初始种子,则在计算上难以构造冲突,或者通过观察输出来恢复种子。
SipHash 已被采用为哈希表的实际标准哈希函数,这些哈希表可能会暴露于外部输入,并在 Python、Perl、Ruby、Rust 和其他语言中使用。
SipHash 是推荐用于暴露于外部输入的哈希表的哈希函数。作为最佳实践,它应该使用每个连接都不同的随机值进行播种,而不是每个进程固定的值。
MD5
当需要加密强度时,除非实现调用其使用的现有规范或协议,否则 MD5 不应再在新代码中使用。
请改用 SHA2-512/256(或 32 位代码中的 SHA2-256)。
如果您需要正好 128 位的摘要,请改用 RIPEMD-128。请注意,128 位摘要不再被认为是加密的,因为复杂度为 264 的攻击在资金充足的攻击者的能力范围内。
SHA-1
SHA-1 现在被认为对于资金充足的攻击者来说是不安全的,并且不应再在新代码中使用。请改用 SHA2-512/256、32 位代码中的 SHA2-256,或者如果您需要正好 160 位的摘要,请改用 RIPEMD-160。
SHA-2
SHA-2 是一系列加密哈希函数,也是由 NSA 设计的,最初由 NIST 于 2002 年 首次发布,并于 2015 年 更新。它包括 SHA2-224、SHA2-256、SHA2-384、SHA2-512、SHA2-512/224 和 SHA2-512/256,每个都产生具有相应位长度的摘要。
其中,SHA2-256 和 SHA2-512 是基础函数,其余的是摘要截断的变体。
SHA-2 函数尚未被破解,并且被广泛使用,尽管存在更新的标准 (SHA-3)。
SHA2-256 及其截断变体 SHA2-224 使用 32 位运算,因此在 32 位平台上不会损失性能。
SHA2-512 及其截断变体 SHA2-384、SHA2-512/224 和 SHA2-512/256 使用 64 位运算,在 64 位平台上大约比 SHA2-256 快 1.5 倍,但在 32 位代码中慢两倍。
在 64 位平台上,SHA2-512/256 和 SHA2-512/224 应优先于 SHA2-256 和 SHA2-224,不仅因为速度,还因为它们可以抵抗长度扩展攻击,因为它们不会在最终摘要中暴露其内部状态的所有位。
SHA-3
SHA-3 包括 6 种算法:SHA3-224、SHA3-256、SHA3-384、SHA3-512、SHAKE128 和 SHAKE256。
SHA3 函数输出固定大小的摘要,而 SHAKE128 和 SHAKE256 是批准的可扩展输出函数,也称为 XOF。XOF 允许以定义的方式生成任意长的消息摘要。
与 SHA-2 不同,SHA3-224 不是 SHA3-256 算法的截断版本。相反,所有 SHA-3 算法都通过截断由 Keccak 算法置换的 1600 位状态的较低部分来创建其摘要。XOF 以固定大小的块增量返回其摘要,使用 FIPS 202 的算法 8 的步骤 8 中指定的截断。
SHA-3 对系列中的所有算法都使用 64 位运算。
RIPEMD-160, RIPEMD-128
RIPEMD-160 于 1996 年设计,是一种加密哈希函数,不如 MD5 和 SHA-1 广为人知,但由于其在比特币和其他加密货币中的使用,最近变得流行起来。
即使它尚未被破解,也没有理由在新代码中优先使用它而不是 SHA-2。
RIPEMD-128 是 RIPEMD-160 的截断变体。(请注意,128 位摘要不再被认为是加密的,因为复杂度为 264 的攻击在资金充足的攻击者的能力范围内。)
HMAC
消息认证码与摘要的不同之处在于,它既取决于消息的内容,又取决于秘密 密钥;相比之下,消息摘要仅取决于消息的内容。
即使库提供的所有哈希算法都可以通过最初使用字节序列构造函数以密钥播种哈希算法的方式来生成消息认证码,但哈希算法通常并非设计为以这种方式使用,并且此类使用尚未经过密码学分析和审查。(SipHash 是一个例外;它专门设计为 MAC。)
HMAC 算法以类模板 hmac<H>
的形式提供,该模板适配加密哈希算法 H
。hmac<H>
满足加密哈希算法的要求。
提供了常见 HMAC 实例化的便捷别名。例如,定义 md5_128
的 md5.hpp
标头也将 hmac_md5_128
定义为 hmac<md5_128>
的别名。
选择哈希算法
如果您的用例需要加密强度,请使用 SHA2-512/256(或 32 位代码中的 SHA2-256)作为摘要,并使用相应的 HMAC 作为消息认证码。
注意
|
当需要或期望加密安全性时,不再推荐长度小于 256 位的摘要。 |
对于计算文件或内容校验和,当速度至关重要且外部引起的冲突不是问题时,请使用 xxHash-64。
注意
|
xxHash-32 在 32 位代码中会更快,但由于它仅产生 32 位结果,因此当项目数量达到数万时,冲突将成为问题,这通常是不可接受的。 |
对于大量项目(数百万),64 位可能不足以满足需求;在这种情况下,请使用 MD5 或 xxHash-64,扩展到 128 位。
注意
|
即使 MD5 不再具有密码学安全性,但当密码学强度不是必需品时,它仍然可以使用。 |
对于哈希表,默认情况下使用 SipHash,使用每个连接或每个容器都不同的随机(从外部不可预测的)种子。避免使用固定的进程范围种子。切勿在没有种子的情况下使用 SipHash。
对于具有非常短的键(3-4-5 字节),未暴露于外部输入的哈希表,您可以使用 FNV-1a,尽管例如 boost::unordered_flat_map
的默认哈希函数通常会表现更好。
哈希 C++ 对象
哈希 C++ 对象的传统方法是使它们负责提供哈希值。例如,标准遵循此方法,使每个类型 T
负责实现 std::hash<T>
的特化,当使用值调用时,该特化返回其 size_t
哈希。
当然,这意味着特定的哈希算法因类型而异,并且在一般情况下是完全不透明的。
此库采用不同的方法;哈希算法是已知的,并且由用户选择。通过首先将 C++ 对象转换为表示其值的字节序列(消息),然后将其传递给哈希算法来哈希 C++ 对象。
转换必须遵守以下要求
-
相等的对象必须产生相同的消息;
-
不同的对象应产生不同的消息;
-
对象应始终产生非空消息。
前两个要求直接来自哈希值要求,而第三个要求则更为微妙,旨在防止诸如不同的序列 [[1], [], []]
和 [[], [1], []]
产生相同的消息之类的事件。(这类似于所有 C++ 对象都具有非零 sizeof
的要求,包括空对象。)
在此库中,转换由函数 hash_append
执行。它声明如下
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
调用 hash_append(h, f, v)
的效果是多次(但绝非零次)调用 h.update(p, n)
。这些调用的组合结果构成了与 v
对应的消息。
hash_append
本机处理以下类型 T
-
整型(有符号和无符号整数、字符类型、
bool
); -
浮点型(
float
和double
); -
枚举类型;
-
指针类型(对象和函数,但不包括成员指针类型);
-
C 数组;
-
容器和范围(提供
begin()
和end()
的类型); -
无序容器和范围;
-
恒定大小容器(
std::array
,boost::array
); -
类似元组的类型(
std::pair
,std::tuple
); -
描述的类(使用 Boost.Describe)。
不属于上述类别的用户定义类型可以通过声明具有适当参数的 tag_invoke
函数的重载来提供对 hash_append
的支持。
hash_append
的第二个参数,flavor,用于在可能且期望有多种行为的情况下控制序列化过程。它目前包含以下成员
-
static constexpr endian byte_order; // native, little, or big
-
using size_type = std::uint32_t; // or std::uint64_t
flavor 的 byte_order
成员影响标量 C++ 对象如何序列化为字节。例如,当 byte_order
为 endian::big
时,uint32_t
整数 0x01020304
可以序列化为 { 0x01, 0x02, 0x03, 0x04 }
,当 byte_order
为 endian::little
时,可以序列化为 { 0x04, 0x03, 0x02, 0x01 }
。
值 endian::native
表示使用当前平台的字节顺序。这通常会带来更高的性能,因为它允许 hash_append
将底层对象字节直接传递给哈希算法,而无需任何处理。
flavor 的 size_type
成员类型影响容器和范围大小(通常为 size_t
类型)的序列化方式。由于 size_t
的字节大小可能会有所不同,因此直接序列化类型会导致为 64 位或 32 位编译代码时产生不同的哈希值。使用固定宽度类型可以避免这种情况。
在 boost/hash2/flavor.hpp
中定义了三种预定义的 flavor
struct default_flavor
{
using size_type = std::uint32_t;
static constexpr auto byte_order = endian::native;
};
struct little_endian_flavor
{
using size_type = std::uint32_t;
static constexpr auto byte_order = endian::little;
};
struct big_endian_flavor
{
using size_type = std::uint32_t;
static constexpr auto byte_order = endian::big;
};
当在不传递 flavor 的情况下调用 hash_append
时,使用默认的 flavor:hash_append(h, {}, v);
。它会带来更高的性能,但哈希值依赖于字节序。
可连续哈希的类型
hash_append(h, f, v)
所做的第一件事是通过测试 is_contiguously_hashable<T, Flavor::byte_order>::value
来检查类型在请求的字节顺序下是否为可连续哈希的。当结果为 true
时,它会调用 h.update(&v, sizeof(v))
。
整型
当 T
是整型(bool
、有符号或无符号整型,如 int
或 unsigned long
,或字符类型,如 char8_t
或 char32_t
)时,v
将在请求的字节顺序下转换为其字节表示形式(unsigned char
数组和大小为 sizeof(T)
)。
然后 hash_append
调用 h.update(p, n)
,其中 p
是此表示形式的地址,n
是其大小。
例如,当 Flavor::byte_order
为 endian::little
时,类型为 std::uint32_t
的值 0x01020304
将转换为数组 { 0x04, 0x03, 0x02, 0x01 }
。
int main()
{
boost::hash2::fnv1a_32 h1;
std::uint32_t v1 = 0x01020304;
boost::hash2::hash_append( h1, boost::hash2::little_endian_flavor(), v1 );
boost::hash2::fnv1a_32 h2;
unsigned char v2[] = { 0x04, 0x03, 0x02, 0x01 };
h2.update( v2, sizeof(v2) );
assert( h1.result() == h2.result() );
}
浮点型
当 T
是浮点类型(目前仅支持 float
和 double
)时,v
将使用等效于 std::bit_cast
的方法转换为相同大小的无符号整型,然后使用转换后的值调用 hash_append
。
int main()
{
boost::hash2::fnv1a_32 h1;
float v1 = 3.14f;
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
std::uint32_t v2 = 0x4048f5c3;
boost::hash2::hash_append( h2, {}, v2 );
assert( h1.result() == h2.result() );
}
但是,这里有一个微妙之处。哈希函数 H
的要求指出,如果 x == y
,则 H(x) == H(y)
。但是 +0.0 == -0.0
,即使这两个值的位表示形式不同。
因此,为了满足要求,如果 v
是负零,则在 bit_cast
为整数之前,首先将其替换为相同类型的正零。
int main()
{
boost::hash2::fnv1a_32 h1;
boost::hash2::hash_append( h1, {}, +0.0 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append( h2, {}, -0.0 );
assert( h1.result() == h2.result() );
}
枚举类型
当 T
是枚举类型时,v
将转换为 T
的底层类型,然后将转换后的值传递给 hash_append
。
enum E: int
{
v1 = 123
};
int main()
{
boost::hash2::fnv1a_32 h1;
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
int v2 = 123;
boost::hash2::hash_append( h2, {}, v2 );
assert( h1.result() == h2.result() );
}
指针
当 T
是指针类型时,它使用 reinterpret_cast
转换为 std::uintptr_t
,并将转换后的值传递给 hash_append
。
int x1 = 0;
int main()
{
boost::hash2::fnv1a_32 h1;
boost::hash2::hash_append( h1, {}, &x1 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append( h2, {}, reinterpret_cast<std::uintptr_t>(&x1) );
assert( h1.result() == h2.result() );
}
数组
当 T
是数组类型 U[N]
时,v
的元素按顺序传递给 hash_append
。
这是通过调用 hash_append_range(h, f, v + 0, v + N)
完成的。
int main()
{
boost::hash2::fnv1a_32 h1;
int v1[4] = { 1, 2, 3, 4 };
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append_range( h2, {}, v1 + 0, v1 + 4 );
assert( h1.result() == h2.result() );
boost::hash2::fnv1a_32 h3;
boost::hash2::hash_append( h3, {}, v1[0] );
boost::hash2::hash_append( h3, {}, v1[1] );
boost::hash2::hash_append( h3, {}, v1[2] );
boost::hash2::hash_append( h3, {}, v1[3] );
assert( h1.result() == h3.result() );
}
范围
当 T
是 范围(boost::container_hash::is_range<T>::value
为 true
)时,其元素将按如下方式传递给 hash_append
-
当
T
是 无序范围(boost::container_hash::is_unordered_range<T>::value
为true
)时,hash_append
调用hash_append_unordered_range(h, f, v.begin(), v.end())
。hash_append_unordered_range
从范围元素派生哈希值,使得它们的顺序不影响哈希值。 -
当
T
是 连续范围(boost::container_hash::is_contiguous_range<T>::value
为true
)时,hash_append
首先调用hash_append_range(h, f, v.data(), v.data() + v.size())
,然后,如果has_constant_size<T>::value
为false
,则调用hash_append_size(h, f, v.size())
。 -
否则,
hash_append
首先调用hash_append_range(h, f, v.begin(), v.end())
,然后,如果has_constant_size<T>::value
为false
,则调用hash_append_size(h, f, m)
,其中m
是std::distance(v.begin(), v.end())
。
作为特殊情况,为了满足对 hash_append
的调用必须始终导致至少一次调用 Hash::update
的要求,对于恒定大小为 0 的范围,将调用 hash_append(h, f, '\x00')
。
int main()
{
boost::hash2::fnv1a_32 h1;
std::vector<int> v1 = { 1, 2, 3, 4 };
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
std::list<int> v2 = { 1, 2, 3, 4 };
boost::hash2::hash_append( h2, {}, v2 );
assert( h1.result() == h2.result() );
boost::hash2::fnv1a_32 h3;
boost::hash2::hash_append_range( h3, {}, v1.data(), v1.data() + v1.size() );
boost::hash2::hash_append_size( h3, {}, v1.size() );
assert( h1.result() == h3.result() );
boost::hash2::fnv1a_32 h4;
boost::hash2::hash_append_range( h4, {}, v2.begin(), v2.end() );
boost::hash2::hash_append_size( h4, {}, std::distance(v2.begin(), v2.end()) );
assert( h2.result() == h4.result() );
}
元组
当 T
是元组(boost::container_hash::is_tuple_like<T>::value
为 true
)时,通过 get<I>(v)
获取的元素(对于 I
在 [0, std::tuple_size<T>::value)
中)按顺序传递给 hash_append
。
作为特殊情况,为了满足对 hash_append
的调用必须始终导致至少一次调用 Hash::update
的要求,对于大小为 0 的元组,将调用 hash_append(h, f, '\x00')
。
int main()
{
boost::hash2::fnv1a_32 h1;
std::tuple<int, int, int> v1 = { 1, 2, 3 };
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append( h2, {}, get<0>(v1) );
boost::hash2::hash_append( h2, {}, get<1>(v1) );
boost::hash2::hash_append( h2, {}, get<2>(v1) );
assert( h1.result() == h2.result() );
}
描述的类
当 T
是一个已描述的类 (boost::container_hash::is_described_class<T>::value
为 true
) 时,Boost.Describe 原语用于枚举它的基类和成员,然后,对于 v
的每个基类子对象 b
,调用 hash_append(h, f, b)
,然后对于 v
的每个成员子对象 m
,调用 hash_append(h, f, m)
。
struct X
{
int a;
};
BOOST_DESCRIBE_STRUCT(X, (), (a))
struct Y: public X
{
int b;
};
BOOST_DESCRIBE_STRUCT(Y, (X), (b))
int main()
{
boost::hash2::fnv1a_32 h1;
X v1 = { { 1 }, 2 };
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append( h2, {}, v1.a );
boost::hash2::hash_append( h2, {}, v1.b );
assert( h1.result() == h2.result() );
}
作为一种特殊情况,为了满足对 hash_append
的调用必须始终至少导致一次对 Hash::update
的调用的要求,对于没有任何基类或成员的类,将调用 hash_append(h, f, '\x00')
。
用户自定义类型
当 T
是不属于上述任何类别的用户自定义类型时,它需要通过定义适当的 tag_invoke
重载来提供自己的哈希支持。
此 tag_invoke
重载需要具有以下形式
template<class Provider, class Hash, class Flavor>
void tag_invoke( boost::hash2::hash_append_tag const&, Provider const& pr, Hash& h, Flavor const& f, X const* v );
其中 X
是用户自定义类型。
它可以定义为 X
命名空间中的一个单独的自由函数,但推荐的方法是在 X
的定义中将其定义为内联 friend
。
#include <boost/hash2/hash_append_fwd.hpp>
#include <string>
class X
{
private:
std::string a;
int b;
// not part of the salient state
void const* c;
public:
friend bool operator==( X const& x1, X const& x2 )
{
return x1.a == x2.a && x1.b == x2.b;
}
template<class Provider, class Hash, class Flavor>
friend void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const&,
Hash& h, Flavor const& f, X const* v )
{
boost::hash2::hash_append(h, f, v->a);
boost::hash2::hash_append(h, f, v->b);
}
};
此重载需要满足哈希函数的三个要求。在实践中,这意味着相等性 (operator==
) 和哈希 (tag_invoke
) 的定义必须在需要包含哪些成员上达成一致。
在上面的示例中,成员 c
不是对象状态的一部分,因此它既不与 operator==
进行比较,也不包含在 tag_invoke
中的对象消息中。
tag_invoke
的具体实现是类型特定的。一般来说,它需要包含对象值的所有显著部分到结果消息中,但确切的做法取决于类型。
作为另一个示例,以下是如何为一个“内联字符串”类型(一种将其字符存储在类型本身中的字符串,直到某个最大计数)实现 tag_invoke
的方法
#include <boost/hash2/hash_append_fwd.hpp>
#include <algorithm>
#include <cstdint>
class Str
{
private:
static constexpr std::size_t N = 32;
std::uint8_t size_ = 0;
char data_[ N ] = {};
public:
friend constexpr bool operator==( Str const& x1, Str const& x2 )
{
return x1.size_ == x2.size_ && std::equal( x1.data_, x1.data_ + x1.size_, x2.data_ );
}
template<class Provider, class Hash, class Flavor>
friend constexpr void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const&,
Hash& h, Flavor const& f, Str const* v )
{
boost::hash2::hash_append_range_and_size( h, f, v->data_, v->data_ + v->size_ );
}
};
注意
|
此示例是说明性的;在实践中,上述类型很可能提供 begin() 、end() 、data() 和 size() 成员函数,这将使其成为连续范围,并且内置支持将执行正确的操作。 |
我们忽略的 tag_invoke
的 Provider
参数接收类型为 hash_append_provider
的对象。此对象包含静态成员函数,这些函数是 <boost/hash2/hash_append_fwd.hpp>
中声明的函数的副本。这允许编写 tag_invoke
重载,而无需包含 <boost/hash2/hash_append_fwd.hpp>
,从而使头文件不依赖于 Hash2 中的任何内容。
例如,以下是我们如何替代性地声明用户自定义类型 X
的方法
#include <string>
class X
{
private:
std::string a;
int b;
// not part of the salient state
void const* c;
public:
friend bool operator==( X const& x1, X const& x2 )
{
return x1.a == x2.a && x1.b == x2.b;
}
template<class Provider, class Hash, class Flavor>
friend void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const& pr,
Hash& h, Flavor const& f, X const* v )
{
pr.hash_append(h, f, v->a);
pr.hash_append(h, f, v->b);
}
};
使用示例
与无序容器一起使用
要将我们的一个哈希算法(例如 fnv1a_64
)与无序容器(例如 boost::unordered_flat_map
)一起使用,我们需要创建一个适配器类,该类公开与 std::hash<T>
兼容的接口。
为此,在我们的适配器的 operator()(T const& v)
成员函数中,我们需要创建一个哈希算法的实例 h
,使用 hash_append(h, {}, v)
将 v
发送给它,然后使用 h.result()
提取结果并将其作为 std::size_t
返回。
下面的最小工作示例说明了这种方法。
#include <boost/hash2/fnv1a.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <string>
template<class T, class H> class hash
{
public:
std::size_t operator()( T const& v ) const
{
H h;
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
using hasher = hash<std::string, boost::hash2::fnv1a_64>;
boost::unordered_flat_map<std::string, int, hasher> map;
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
由于 hash<T, H>
不仅在键类型 T
上进行模板化,而且在哈希算法类型 H
上也进行模板化,因此我们可以轻松地从 fnv1a_64
切换到另一个哈希算法,例如 siphash_64
,只需更改行
using hasher = hash<std::string, boost::hash2::fnv1a_64>;
为
using hasher = hash<std::string, boost::hash2::siphash_64>;
这将起作用,但是 SipHash 不打算在没有初始随机种子的情况下使用,并且我们没有传递任何种子。为了纠正这一点,让我们修改 hash<T, H>
,使其具有一个接受 uint64_t
类型种子的构造函数
#include <boost/hash2/siphash.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <string>
template<class T, class H> class hash
{
private:
std::uint64_t seed_;
public:
explicit hash( std::uint64_t seed ): seed_( seed )
{
}
std::size_t operator()( T const& v ) const
{
H h( seed_ );
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
std::uint64_t seed = 0x0102030405060708ull;
using hasher = hash<std::string, boost::hash2::siphash_64>;
boost::unordered_flat_map<std::string, int, hasher> map( 0, hasher( seed ) );
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
注意
|
在实际代码中,种子不会是硬编码的常量;理想情况下,每个无序容器实例都将具有自己的随机且不可预测的种子。 |
由于所有符合我们库要求的哈希算法都可以使用 uint64_t
类型的初始种子构造,因此以上方法适用于任何哈希算法。
这对于任何实际目的都足够好,但原则上,SipHash64 按照规范采用 16 字节的种子,而我们只(有效地)传递 8 字节。我们可以再次修改我们的 hash
,这次使用一个接受字节序列作为种子的构造函数
#include <boost/hash2/siphash.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <string>
template<class T, class H> class hash
{
private:
H h_;
public:
hash( unsigned char const* p, std::size_t n ): h_( p, n )
{
}
std::size_t operator()( T const& v ) const
{
H h( h_ );
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
unsigned char const seed[ 16 ] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
};
using hasher = hash<std::string, boost::hash2::siphash_64>;
boost::unordered_flat_map<std::string, int, hasher> map( 0, hasher( seed, sizeof(seed) ) );
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
与之前一样,从字节序列构造是哈希算法接口的必需部分,因此以上方法适用于任何哈希算法。
为了避免像我们在 uint64_t
情况下那样存储初始种子——这将需要分配内存,因为 n
可以是任意的,需要使用 std::vector<unsigned char>
——我们构造哈希算法的实例 h_
,传递种子以捕获初始种子状态,然后在 operator()
中复制此种子实例。
但是一旦我们完成此操作,我们可能会注意到我们可以使用任何三个受支持的构造函数来构造此初始实例 h_
,而不仅仅是采用字节序列的构造函数
#include <boost/hash2/siphash.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <string>
template<class T, class H> class hash
{
private:
H h_;
public:
hash(): h_()
{
}
explicit hash( std::uint64_t seed ): h_( seed )
{
}
hash( unsigned char const* p, std::size_t n ): h_( p, n )
{
}
std::size_t operator()( T const& v ) const
{
H h( h_ );
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
using hasher = hash<std::string, boost::hash2::siphash_64>;
{
boost::unordered_flat_map<std::string, int, hasher> map;
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
{
std::uint64_t seed = 0x0102030405060708ull;
boost::unordered_flat_map<std::string, int, hasher> map( 0, hasher( seed ) );
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
{
unsigned char const seed[ 16 ] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
};
boost::unordered_flat_map<std::string, int, hasher> map( 0, hasher(seed, sizeof(seed)) );
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
}
hash<T, H>
的这种变体是通用的;它可以不使用种子、使用无符号整数种子和使用字节序列种子。
注意
|
在实际代码中,您可能希望省略默认构造函数,以避免意外使用未播种的哈希算法的可能性。 |
我们可以对 hash
进行最后一次修改。在上面的示例中,我们无条件地使用 SipHash 的 64 位变体,即使我们只需要 std::size_t
类型的结果,因为这是 std::hash
要求的。
如果 std::size_t
是 32 位,则最好使用 siphash_32
,如果它是 64 位,则最好使用 siphash_64
,这样可以提高性能。
为此,我们可以使 hash
采用两个哈希算法,一个 32 位和一个 64 位,并让它自动选择合适的哈希算法
#include <boost/hash2/siphash.hpp>
#include <boost/hash2/md5.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/core/type_name.hpp>
#include <type_traits>
#include <string>
#include <iostream>
template<class T, class H1, class H2 = H1> class hash
{
public:
using hash_type = typename std::conditional<
sizeof(typename H1::result_type) == sizeof(std::size_t), H1, H2
>::type;
private:
hash_type h_;
public:
hash(): h_()
{
}
explicit hash( std::uint64_t seed ): h_( seed )
{
}
hash( unsigned char const* p, std::size_t n ): h_( p, n )
{
}
std::size_t operator()( T const& v ) const
{
hash_type h( h_ );
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
{
using hasher = hash<std::string, boost::hash2::siphash_32, boost::hash2::siphash_64>;
std::cout << boost::core::type_name<hasher>() << " uses "
<< boost::core::type_name<hasher::hash_type>() << std::endl;
boost::unordered_flat_map<std::string, int, hasher> map;
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
{
using hasher = hash<std::string, boost::hash2::md5_128>;
std::cout << boost::core::type_name<hasher>() << " uses "
<< boost::core::type_name<hasher::hash_type>() << std::endl;
boost::unordered_flat_map<std::string, int, hasher> map;
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
}
为了保持我们只传递一个哈希算法的情况能够工作,我们将第二个模板参数默认为第一个模板参数,这样,如果只传递一个哈希算法,它将始终被使用。
md5sum
一个命令行实用程序,用于打印作为参数传递的文件列表的 MD5 摘要。
#include <boost/hash2/md5.hpp>
#include <array>
#include <string>
#include <cerrno>
#include <cstdio>
static void md5sum( std::FILE* f, char const* fn )
{
boost::hash2::md5_128 hash;
int const N = 4096;
unsigned char buffer[ N ];
for( ;; )
{
std::size_t n = std::fread( buffer, 1, N, f );
if( std::ferror( f ) )
{
std::fprintf( stderr, "'%s': read error: %s\n", fn, std::strerror( errno ) );
return;
}
if( n == 0 ) break;
hash.update( buffer, n );
}
std::string digest = to_string( hash.result() );
std::printf( "%s *%s\n", digest.c_str(), fn );
}
int main( int argc, char const* argv[] )
{
for( int i = 1; i < argc; ++i )
{
std::FILE* f = std::fopen( argv[i], "rb" );
if( f == 0 )
{
std::fprintf( stderr, "'%s': open error: %s\n", argv[i], std::strerror( errno ) );
continue;
}
md5sum( f, argv[i] );
std::fclose( f );
}
}
示例命令
md5sum apache_builds.json canada.json citm_catalog.json twitter.json
示例输出
7dc25b5fd9eb2217ed648dad23b311da *apache_builds.json 8767d618bff99552b4946078d3a90c0c *canada.json b4391581160654374bee934a3b91255e *citm_catalog.json bf7d37451840af4e8873b65763315cbf *twitter.json
hash2sum
一个命令行实用程序,用于打印文件列表的摘要,使用指定的哈希算法。
哈希算法作为第一个命令行参数传递。
此示例需要 C++14。
#include <boost/hash2/md5.hpp>
#include <boost/hash2/sha1.hpp>
#include <boost/hash2/sha2.hpp>
#include <boost/hash2/ripemd.hpp>
#include <boost/mp11.hpp>
#include <array>
#include <string>
#include <cerrno>
#include <cstdio>
template<class Hash> void hash2sum( std::FILE* f, char const* fn )
{
Hash hash;
int const N = 4096;
unsigned char buffer[ N ];
for( ;; )
{
std::size_t n = std::fread( buffer, 1, N, f );
if( std::ferror( f ) )
{
std::fprintf( stderr, "'%s': read error: %s\n", fn, std::strerror( errno ) );
return;
}
if( n == 0 ) break;
hash.update( buffer, n );
}
std::string digest = to_string( hash.result() );
std::printf( "%s *%s\n", digest.c_str(), fn );
}
template<class Hash> void hash2sum( char const* fn )
{
std::FILE* f = std::fopen( fn, "rb" );
if( f == 0 )
{
std::fprintf( stderr, "'%s': open error: %s\n", fn, std::strerror( errno ) );
}
else
{
hash2sum<Hash>( f, fn );
std::fclose( f );
}
}
using namespace boost::mp11;
using namespace boost::hash2;
using hashes = mp_list<
md5_128,
sha1_160,
sha2_256,
sha2_224,
sha2_512,
sha2_384,
sha2_512_256,
sha2_512_224,
ripemd_160,
ripemd_128
>;
constexpr char const* names[] = {
"md5_128",
"sha1_160",
"sha2_256",
"sha2_224",
"sha2_512",
"sha2_384",
"sha2_512_256",
"sha2_512_224",
"ripemd_160",
"ripemd_128"
};
int main( int argc, char const* argv[] )
{
if( argc < 2 )
{
std::fputs( "usage: hash2sum <hash> <files...>\n", stderr );
return 2;
}
std::string hash( argv[1] );
bool found = false;
mp_for_each< mp_iota<mp_size<hashes>> >([&](auto I){
if( hash == names[I] )
{
using Hash = mp_at_c<hashes, I>;
for( int i = 2; i < argc; ++i )
{
hash2sum<Hash>( argv[i] );
}
found = true;
}
});
if( !found )
{
std::fprintf( stderr, "hash2sum: unknown hash algorithm name '%s'; use one of the following:\n\n", hash.c_str() );
for( char const* name: names )
{
std::fprintf( stderr, " %s\n", name );
}
return 1;
}
}
示例命令
hash2sum sha2_512_224 apache_builds.json canada.json citm_catalog.json twitter.json
示例输出
a95d7fde785fe24f9507fd1709014567bbc595867f1abaad96f50dbc *apache_builds.json b07e42587d10ec323a25fd8fc3eef2213fb0997beb7950350f4e8a4b *canada.json 4ceee5a83ad320fedb0dfddfb6f80af50b99677e87158e2d039aa168 *citm_catalog.json 854ebe0da98cadd426ea0fa3218d60bb52cf6494e435d2f385a37d48 *twitter.json
编译时哈希
此示例演示了在编译时计算嵌入在程序源代码中的数据数组的 MD5 摘要。它需要 C++14。
#include <boost/hash2/md5.hpp>
#include <iostream>
// xxd -i resource
constexpr unsigned char resource[] = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x6d, 0x90,
0xcf, 0x6e, 0x83, 0x30, 0x0c, 0xc6, 0xcf, 0x45, 0xea, 0x3b, 0x78, 0x9c,
0x4b, 0x02, 0x3d, 0x6e, 0xd0, 0x43, 0xff, 0x1c, 0x26, 0x55, 0x3b, 0x14,
0x6d, 0xd7, 0x2a, 0x04, 0x43, 0x22, 0x95, 0x84, 0x25, 0x66, 0x8c, 0x47,
0xda, 0x5b, 0x2e, 0x91, 0xd6, 0xcb, 0xd4, 0x93, 0x2d, 0xdb, 0xdf, 0xef,
0xb3, 0x5d, 0x2a, 0x1a, 0x6e, 0xbb, 0x75, 0x52, 0x2a, 0x14, 0x6d, 0x8c,
0x03, 0x92, 0x00, 0x45, 0x34, 0x66, 0xf8, 0x39, 0xe9, 0xaf, 0x2a, 0x75,
0xd8, 0x39, 0xf4, 0x2a, 0x05, 0x69, 0x0d, 0xa1, 0xa1, 0x2a, 0xcd, 0x5f,
0xe0, 0xfd, 0x72, 0xae, 0x5a, 0x2b, 0x79, 0x54, 0x73, 0x25, 0xbc, 0xda,
0xb2, 0x98, 0xa6, 0x91, 0xc0, 0xef, 0xa8, 0xc6, 0xb6, 0x4b, 0x88, 0x17,
0x6c, 0xb5, 0x43, 0x49, 0xda, 0xf4, 0x40, 0x16, 0xca, 0x80, 0x0f, 0xcc,
0x2a, 0x7d, 0xa8, 0x7f, 0x50, 0x2c, 0xb9, 0xd8, 0x31, 0xc6, 0x22, 0xf9,
0x8f, 0x58, 0xf2, 0xfb, 0xd6, 0x4f, 0x59, 0xb6, 0x4e, 0x56, 0x3f, 0x70,
0xb0, 0xe3, 0xe2, 0x74, 0xaf, 0x08, 0xf6, 0x38, 0x08, 0x03, 0x47, 0x31,
0xa3, 0xdf, 0xc0, 0x36, 0xcf, 0x8b, 0xd0, 0x3f, 0x6a, 0x4f, 0x4e, 0x37,
0x13, 0x61, 0x0b, 0x93, 0x69, 0xd1, 0x01, 0x29, 0x84, 0xbd, 0xb5, 0x9e,
0xa0, 0xb6, 0x1d, 0xcd, 0xc2, 0x21, 0x9c, 0xb5, 0x44, 0xe3, 0x71, 0x03,
0x1f, 0xe8, 0xbc, 0xb6, 0x06, 0x0a, 0x96, 0x07, 0xd3, 0x55, 0x8d, 0x08,
0x42, 0x4a, 0x3b, 0x8c, 0xc2, 0x2c, 0xf1, 0x86, 0x4e, 0xdf, 0xc2, 0xf4,
0xeb, 0xe1, 0xf4, 0x56, 0x9f, 0xae, 0xc5, 0x35, 0x67, 0xf4, 0x4d, 0x60,
0x5d, 0xf8, 0xcf, 0xb8, 0x80, 0xa0, 0x20, 0x89, 0xef, 0x7b, 0xe6, 0x7c,
0x9e, 0x67, 0xd6, 0x44, 0x13, 0x66, 0x5d, 0xcf, 0xff, 0x29, 0xd6, 0x49,
0x96, 0x85, 0x0b, 0x7e, 0x01, 0x36, 0x66, 0x95, 0x6b, 0x80, 0x01, 0x00,
0x00
};
template<std::size_t N> constexpr auto md5( unsigned char const(&a)[ N ] )
{
boost::hash2::md5_128 hash;
hash.update( a, N );
return hash.result();
}
constexpr auto resource_digest = md5( resource );
int main()
{
std::cout << "Resource digest: " << resource_digest << std::endl;
}
由于 constexpr
的 update
重载采用 unsigned char const*
(void const*
在 constexpr
函数中是不允许的),如果要哈希的数据是 char const[]
类型的字符数组,则直接将其传递给 update
将无法编译。在这种情况下,我们可以使用 hash_append_range
而不是调用 update
,如下例所示。
#include <boost/hash2/sha2.hpp>
#include <boost/hash2/hash_append.hpp>
#include <iostream>
extern constexpr char const license[] =
"Boost Software License - Version 1.0 - August 17th, 2003\n"
"\n"
"Permission is hereby granted, free of charge, to any person or organization\n"
"obtaining a copy of the software and accompanying documentation covered by\n"
"this license (the \"Software\") to use, reproduce, display, distribute,\n"
"execute, and transmit the Software, and to prepare derivative works of the\n"
"Software, and to permit third-parties to whom the Software is furnished to\n"
"do so, all subject to the following:\n"
"\n"
"The copyright notices in the Software and this entire statement, including\n"
"the above license grant, this restriction and the following disclaimer,\n"
"must be included in all copies of the Software, in whole or in part, and\n"
"all derivative works of the Software, unless such copies or derivative\n"
"works are solely in the form of machine-executable object code generated by\n"
"a source language processor.\n"
"\n"
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
"FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\n"
"SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\n"
"FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\n"
"ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n"
"DEALINGS IN THE SOFTWARE.\n"
;
constexpr unsigned char secret[] = {
0xA4, 0x80, 0x0E, 0xE8, 0x20, 0x0B, 0x7C, 0x9A,
0xF1, 0x3E, 0x3D, 0xEC, 0x64, 0x4F, 0x64, 0xCA,
0x33, 0xCC, 0x84, 0xC8, 0x34, 0xE3, 0x08, 0xAE,
0x92, 0x89, 0xEB, 0xD0, 0x47, 0x39, 0x87, 0xD8,
};
template<std::size_t N> constexpr auto hmac_sha2_256( char const(&s)[ N ] )
{
boost::hash2::hmac_sha2_256 hmac( secret, sizeof(secret) );
// N-1, in order to not include the null terminator
boost::hash2::hash_append_range( hmac, {}, s, s + N - 1 );
return hmac.result();
}
constexpr auto license_mac = hmac_sha2_256( license );
int main()
{
std::cout << "License authentication code: " << license_mac << std::endl;
}
结果扩展
我们的一些哈希算法,例如 xxhash_64
和 siphash_64
,具有超过 64 位的内部状态,但仅产生 64 位的结果。
如果我们使用这些算法之一来生成文件或内容校验和,不容忍冲突,并且操作大量文件或项目(数百万个),则最好使用 128 位摘要。
由于这些算法维护超过 64 位的状态,我们可以调用 result()
两次并获得有意义的 128 位结果。
以下示例演示了如何实现。它定义了一个算法 xxhash_128
,该算法通过包装 xxhash_64
并适当地重新定义其 result_type
和 result
成员来实现
#include <boost/hash2/xxhash.hpp>
#include <boost/hash2/digest.hpp>
#include <boost/endian/conversion.hpp>
class xxhash_128: private boost::hash2::xxhash_64
{
public:
using result_type = boost::hash2::digest<16>;
using xxhash_64::xxhash_64;
using xxhash_64::update;
result_type result()
{
std::uint64_t r1 = xxhash_64::result();
std::uint64_t r2 = xxhash_64::result();
result_type r = {};
boost::endian::store_little_u64( r.data() + 0, r1 );
boost::endian::store_little_u64( r.data() + 8, r2 );
return r;
}
};
#include <string>
#include <iostream>
int main()
{
std::string tv( "The quick brown fox jumps over the lazy dog" );
xxhash_128 hash( 43 );
hash.update( tv.data(), tv.size() );
std::cout << hash.result() << std::endl;
}
实现特性
支持的编译器
该库需要 C++11。以下编译器
-
g++ 4.8 或更高版本
-
clang++ 3.9 或更高版本
-
Visual Studio 2015 及以上版本
正在 Github Actions 和 Appveyor 上进行测试。
参考
哈希算法
<boost/hash2/fnv1a.hpp>
namespace boost {
namespace hash2 {
class fnv1a_32;
class fnv1a_64;
} // namespace hash2
} // namespace boost
此头文件实现了 FNV-1a 算法,具有 32 位和 64 位变体。
fnv1a_32
class fnv1a_32
{
private:
std::uint32_t state_; // exposition only
public:
using result_type = std::uint32_t;
constexpr fnv1a_32();
explicit constexpr fnv1a_32( std::uint64_t seed );
fnv1a_32( void const* p, std::size_t n );
constexpr fnv1a_32( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr fnv1a_32();
默认构造函数。
- 效果
-
将
state_
初始化为0x811c9dc5
。
explicit constexpr fnv1a_32( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
像默认构造一样初始化状态,然后如果
seed
不为零,则执行update(p, 8)
,其中p
指向seed
值的 小端表示。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
fnv1a_32( void const* p, std::size_t n );
constexpr fnv1a_32( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n)
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
fnv1a_64
class fnv1a_64
{
private:
std::uint64_t state_; // exposition only
public:
using result_type = std::uint64_t;
constexpr fnv1a_64();
explicit constexpr fnv1a_64( std::uint64_t seed );
fnv1a_64( void const* p, std::size_t n );
constexpr fnv1a_64( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr fnv1a_64();
默认构造函数。
- 效果
-
将
state_
初始化为0xcbf29ce484222325
。
explicit constexpr fnv1a_64( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
像默认构造一样初始化状态,然后如果
seed
不为零,则执行update(p, 8)
,其中p
指向seed
值的 小端表示。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
fnv1a_64( void const* p, std::size_t n );
constexpr fnv1a_64( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n)
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
<boost/hash2/xxhash.hpp>
namespace boost {
namespace hash2 {
class xxhash_32;
class xxhash_64;
} // namespace hash2
} // namespace boost
此头文件实现了 XXH32 和 XXH64 算法。
xxhash_32
class xxhash_32
{
public:
using result_type = std::uint32_t;
constexpr xxhash_32();
explicit constexpr xxhash_32( std::uint64_t seed );
xxhash_32( void const* p, std::size_t n );
constexpr xxhash_32( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr xxhash_32();
默认构造函数。
- 效果
-
将 XXH32 算法的内部状态初始化为其初始值。
explicit constexpr xxhash_32( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用
seed
的低 32 位作为种子初始化 XXH32 算法的内部状态,然后,如果seed
的高 32 位不为零,则将其混合到状态中。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
xxhash_32( void const* p, std::size_t n );
constexpr xxhash_32( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
xxhash_64
class xxhash_64
{
public:
using result_type = std::uint64_t;
constexpr xxhash_64();
explicit constexpr xxhash_64( std::uint64_t seed );
xxhash_64( void const* p, std::size_t n );
constexpr xxhash_64( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr xxhash_64();
默认构造函数。
- 效果
-
将 XXH64 算法的内部状态初始化为其初始值。
explicit constexpr xxhash_64( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用
seed
作为种子初始化 XXH64 算法的内部状态。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
xxhash_64( void const* p, std::size_t n );
constexpr xxhash_64( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
<boost/hash2/siphash.hpp>
namespace boost {
namespace hash2 {
class siphash_32;
class siphash_64;
} // namespace hash2
} // namespace boost
此头文件实现了 SipHash 和 HalfSipHash 算法。
siphash_32
class siphash_32
{
public:
using result_type = std::uint32_t;
constexpr siphash_32();
explicit constexpr siphash_32( std::uint64_t seed );
siphash_32( void const* p, std::size_t n );
constexpr siphash_32( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr siphash_32();
默认构造函数。
- 效果
-
将 HalfSipHash 算法的内部状态初始化为好像使用 8 个零字节的序列作为密钥。
explicit constexpr siphash_32( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用
seed
作为密钥初始化 HalfSipHash 算法的内部状态,就像它是其 8 个组成字节的序列(以小端顺序)。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
siphash_32( void const* p, std::size_t n );
constexpr siphash_32( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
如果
n
为 8,则按照算法指定的初始化状态;否则,像默认构造一样初始化状态,然后,如果n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
siphash_64
class siphash_64
{
public:
using result_type = std::uint64_t;
constexpr siphash_64();
explicit constexpr siphash_64( std::uint64_t seed );
siphash_64( void const* p, std::size_t n );
constexpr siphash_64( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr siphash_64();
默认构造函数。
- 效果
-
将 SipHash 算法的内部状态初始化为好像使用 16 个零字节的序列作为密钥。
explicit constexpr siphash_64( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用
seed
作为密钥初始化 SipHash 算法的内部状态,就像它是其 8 个组成字节的序列(以小端顺序),后跟 8 个零字节。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
siphash_64( void const* p, std::size_t n );
constexpr siphash_64( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
如果
n
为 16,则按照算法指定的初始化状态;否则,像默认构造一样初始化状态,然后,如果n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
<boost/hash2/hmac.hpp>
namespace boost {
namespace hash2 {
template<class H> class hmac;
} // namespace hash2
} // namespace boost
此头文件实现了 HMAC 算法。
hmac
template<class H> class hmac
{
public:
using result_type = typename H::result_type;
static constexpr std::size_t block_size = H::block_size;
constexpr hmac();
explicit constexpr hmac( std::uint64_t seed );
hmac( void const* p, std::size_t n );
constexpr hmac( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
类模板 hmac
将加密哈希算法 H
作为参数,并实现相应的基于哈希的消息身份验证代码 (HMAC) 算法。
例如,HMAC-SHA2-256 由 hmac<sha2_256>
实现。
构造函数
constexpr hmac();
默认构造函数。
- 效果
-
使用空字节序列作为密钥初始化内部状态。
explicit constexpr hmac( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
如果
seed
为零,则像默认构造一样初始化状态,否则,使用seed
的小端表示的 8 个字节作为密钥初始化状态。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
hmac( void const* p, std::size_t n );
hmac( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
使用
[p, p+n)
作为密钥,按照 HMAC 算法指定的初始化状态。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
<boost/hash2/md5.hpp>
#include <boost/hash2/hmac.hpp>
#include <boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
class md5_128;
using hmac_md5_128 = hmac<md5_128>;
} // namespace hash2
} // namespace boost
此头文件实现了 MD5 算法。
md5_128
class md5_128
{
public:
using result_type = digest<16>;
static constexpr std::size_t block_size = 64;
constexpr md5_128();
explicit constexpr md5_128( std::uint64_t seed );
md5_128( void const* p, std::size_t n );
constexpr md5_128( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr md5_128();
默认构造函数。
- 效果
-
将 MD5 算法的内部状态初始化为其初始值。
explicit constexpr md5_128( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
seed
不为零,则执行update(p, 8); result();
,其中p
指向seed
值的 小端表示。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
md5_128( void const* p, std::size_t n );
constexpr md5_128( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
<boost/hash2/sha1.hpp>
#include <boost/hash2/hmac.hpp>
#include <boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
class sha1_160;
using hmac_sha1_160 = hmac<sha1_160>;
} // namespace hash2
} // namespace boost
此头文件实现了 SHA-1 算法。
sha1_160
class sha1_160
{
public:
using result_type = digest<20>;
static constexpr std::size_t block_size = 64;
constexpr sha1_160();
explicit constexpr sha1_160( std::uint64_t seed );
sha1_160( void const* p, std::size_t n );
constexpr sha1_160( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr sha1_160();
默认构造函数。
- 效果
-
将 SHA-1 算法的内部状态初始化为其初始值。
explicit constexpr sha1_160( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
seed
不为零,则执行update(p, 8); result();
,其中p
指向seed
值的 小端表示。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
sha1_160( void const* p, std::size_t n );
constexpr sha1_160( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
<boost/hash2/sha2.hpp>
#include <boost/hash2/hmac.hpp>
#include <boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
class sha2_256;
class sha2_224;
class sha2_512;
class sha2_384;
class sha2_512_256;
class sha2_512_224;
using hmac_sha2_256 = hmac<sha2_256>;
using hmac_sha2_224 = hmac<sha2_224>;
using hmac_sha2_512 = hmac<sha2_512>;
using hmac_sha2_384 = hmac<sha2_384>;
using hmac_sha2_512_256 = hmac<sha2_512_256>;
using hmac_sha2_512_224 = hmac<sha2_512_224>;
} // namespace hash2
} // namespace boost
此头文件实现了 SHA-2 系列函数。
sha2_256
class sha2_256
{
using result_type = digest<32>;
static constexpr std::size_t block_size = 64;
constexpr sha2_256();
constexpr explicit sha2_256( std::uint64_t seed );
sha2_256( void const* p, std::size_t n );
constexpr sha2_256( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr sha2_256();
默认构造函数。
- 效果
-
将 SHA-256 算法的内部状态初始化为其初始值。
constexpr explicit sha2_256( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
seed
不为零,则执行update(p, 8); result();
,其中p
指向seed
值的 小端表示。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
sha2_256( void const* p, std::size_t n );
constexpr sha2_256( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
sha2_224
SHA-224 算法与上面描述的 SHA-256 算法相同。
唯一的区别是内部状态的初始值和消息摘要的大小,即
using result_type = digest<28>;
否则,所有其他操作和常量都相同。
消息摘要是通过将 SHA-256 算法的最终结果截断为其最左边的 224 位而获得的。
sha2_512
class sha2_512
{
using result_type = digest<64>;
static constexpr std::size_t block_size = 128;
constexpr sha2_512();
constexpr explicit sha2_512( std::uint64_t seed );
sha2_512( void const* p, std::size_t n );
constexpr sha2_512( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr sha2_512();
默认构造函数。
- 效果
-
将 SHA-512 算法的内部状态初始化为其初始值。
constexpr explicit sha2_512( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
seed
不为零,则执行update(p, 8); result();
,其中p
指向seed
值的 小端表示。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
sha2_512( void const* p, std::size_t n );
constexpr sha2_512( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
sha2_384
SHA-384 算法与上面描述的 SHA-512 算法相同。
唯一的区别是内部状态的初始值和消息摘要的大小,即
using result_type = digest<48>;
否则,所有其他操作和常量都相同。
消息摘要是通过将 SHA-512 算法的最终结果截断为其最左边的 384 位而获得的。
<boost/hash2/sha3.hpp>
#include <boost/hash2/digest.hpp>
#include <boost/hash2/hmac.hpp>
namespace boost {
namespace hash2 {
class sha3_256;
class sha3_224;
class sha3_512;
class sha3_384;
class shake_128;
class shake_256;
using hmac_sha3_256 = hmac<sha3_256>;
using hmac_sha3_224 = hmac<sha3_224>;
using hmac_sha3_512 = hmac<sha3_512>;
using hmac_sha3_384 = hmac<sha3_384>;
} // namespace hash2
} // namespace boost
此头文件实现了 SHA-3 系列函数。
固定摘要哈希函数(sha3_256
、sha3_224
、sha3_512
、sha3_384
)在内部都使用相同的常量,并且以相同的方式初始化。它们之间唯一的区别是消息摘要的大小和输入块的大小。否则,摘要都以相同的方式收集,即通过截断 1600 位状态的底部部分。
shake_128
和 shake_256
函数类似于固定摘要函数,不同之处在于它们使用不同的分隔符来表示摘要的最终确定,并且扩展输出具有定义的行为,这与库中其他地方使用的默认伪随机行为不同。但它们的工作方式类似,即 1600 位状态的截断部分作为摘要返回。
sha3_256
class sha3_256
{
using result_type = digest<32>;
static constexpr std::size_t block_size = 136; // ( 1600 - 2 * 256 ) / 8 => 136
constexpr sha3_256();
constexpr explicit sha3_256( std::uint64_t seed );
sha3_256( void const* p, std::size_t n );
constexpr sha3_256( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr sha3_256();
默认构造函数。
- 效果
-
将 SHA3-256 算法的内部状态初始化为其初始值。
constexpr explicit sha3_256( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
seed
不为零,则执行update(p, 8); result();
,其中p
指向seed
值的 小端表示。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
sha3_256( void const* p, std::size_t n );
constexpr sha3_256( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
sha3_224
SHA3-224 算法与上面描述的 SHA3-256 算法相同。
唯一的区别是输入块的大小和消息摘要的大小,即
using result_type = digest<28>;
static constexpr std::size_t block_size = 144; // ( 1600 - 2 * 224 ) / 8 => 144
否则,所有其他操作和常量都相同。
消息摘要是通过将 Keccak-1600 算法的最终结果截断为其最左边的 224 位而获得的。
sha3_512
SHA3-512 算法与上面描述的 SHA3-256 算法相同。
唯一的区别是输入块的大小和消息摘要的大小,即
using result_type = digest<64>;
static constexpr std::size_t block_size = 72; // ( 1600 - 2 * 512 ) / 8 => 72
否则,所有其他操作和常量都相同。
消息摘要是通过将 Keccak-1600 算法的最终结果截断为其最左边的 512 位而获得的。
sha3_384
SHA3-384 算法与上面描述的 SHA3-256 算法相同。
唯一的区别是输入块的大小和消息摘要的大小,即
using result_type = digest<48>;
static constexpr std::size_t block_size = 104; // ( 1600 - 2 * 384 ) / 8 => 104
否则,所有其他操作和常量都相同。
消息摘要是通过将 Keccak-1600 算法的最终结果截断为其最左边的 384 位而获得的。
shake_128
class shake_128
{
using result_type = digest<168>;
static constexpr std::size_t block_size = 168; // ( 1600 - 2 * 128 ) / 8 => 168
constexpr shake_128();
constexpr explicit shake_128( std::uint64_t seed );
shake_128( void const* p, std::size_t n );
constexpr shake_128( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr shake_128();
默认构造函数。
- 效果
-
将 SHAKE128 算法的内部状态初始化为其初始值。
constexpr explicit shake_128( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
seed
不为零,则执行update(p, 8); result();
,其中p
指向seed
值的 小端表示。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
shake_128( void const* p, std::size_t n );
constexpr shake_128( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
update
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
- 效果
-
从字节序列
[p, p+n)
更新 SHAKE128 算法的内部状态。 - 备注
-
连续调用
update
等效于对各个调用的串联字节序列进行单次调用。
result
constexpr result_type result();
- 效果
-
填充累积的消息并完成摘要。
- 返回
-
1600 位状态的截断部分,直到指定的速率
r
,即block_size
,如 FIPS 202 中算法 8 的步骤 8 中指定的那样。 - 备注
-
重复调用
result()
以定义的 manner 扩展哈希函数的输出。调用update()
将重置当前的最终确定过程。
<boost/hash2/ripemd.hpp>
#include <boost/hash2/hmac.hpp>
#include <boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
class ripemd_160;
class ripemd_128;
using hmac_ripemd_160 = hmac<ripemd_160>;
using hmac_ripemd_128 = hmac<ripemd_128>;
} // namespace hash2
} // namespace boost
此头文件实现了 RIPEMD-160 和 RIPEMD-128 算法。
ripemd_160
class ripemd_160
{
using result_type = digest<20>;
static constexpr std::size_t block_size = 64;
constexpr ripemd_160();
explicit constexpr ripemd_160( std::uint64_t seed );
ripemd_160( void const* p, std::size_t n );
constexpr ripemd_160( unsigned char const* p, std::size_t n );
void update( void const * pv, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr ripemd_160();
默认构造函数。
- 效果
-
将 RIPEMD-160 算法的内部状态初始化为其初始值。
explicit constexpr ripemd_160( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
seed
不为零,则执行update(p, 8); result();
,其中p
指向seed
值的 小端表示。 - 备注
-
按照惯例,如果
seed
为零,则此构造函数的效果与默认构造相同。
ripemd_160( void const* p, std::size_t n );
constexpr ripemd_160( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
像默认构造一样初始化状态,然后,如果
n
不为零,则执行update(p, n); result()
。 - 备注
-
按照惯例,如果
n
为零,则此构造函数的效果与默认构造相同。
实用程序和特性
<boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
template<std::size_t N> class digest
{
private: // exposition only
unsigned char data_[ N ] = {};
public:
using value_type = unsigned char;
using reference = unsigned char&;
using const_reference = unsigned char const&;
using iterator = unsigned char*;
using const_iterator = unsigned char const*;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
// constructors
constexpr digest() = default;
constexpr digest( unsigned char const (&v)[ N ] ) noexcept;
// copy
constexpr digest( digest const& ) = default;
constexpr digest& operator=( digest const& ) = default;
// iteration
constexpr iterator begin() noexcept;
constexpr const_iterator begin() const noexcept;
constexpr iterator end() noexcept;
constexpr const_iterator end() const noexcept;
// data, size
constexpr unsigned char* data() noexcept;
constexpr unsigned char const* data() const noexcept;
constexpr size_type size() const noexcept;
constexpr size_type max_size() const noexcept;
// element access
constexpr reference operator[]( std::size_t i );
constexpr const_reference operator[]( std::size_t i ) const;
constexpr reference front() noexcept;
constexpr const_reference front() const noexcept;
constexpr reference back() noexcept;
constexpr const_reference back() const noexcept;
};
// comparisons
template<std::size_t N>
constexpr bool operator==( digest<N> const& a, digest<N> const& b ) noexcept;
template<std::size_t N>
constexpr bool operator!=( digest<N> const& a, digest<N> const& b ) noexcept;
// to_chars
template<std::size_t N>
constexpr char* to_chars( digest<N> const& v, char* first, char* last ) noexcept;
template<std::size_t N, std::size_t M>
constexpr void to_chars( digest<N> const& v, char (&w)[ M ] ) noexcept;
// operator<<
template<std::size_t N>
std::ostream& operator<<( std::ostream& os, digest<N> const& v );
// to_string
template<std::size_t N>
std::string to_string( digest<N> const& v );
} // namespace hash2
} // namespace boost
digest
digest<N>
是一个类似于 std::array<unsigned char, N>
的 constexpr
友好的类模板。它用于存储哈希算法(例如 SHA2-256 或 RIPEMD-160)的结果消息摘要。
构造函数
constexpr digest() = default;
- 效果
-
零初始化
data_
。
constexpr digest( unsigned char const (&v)[ N ] ) noexcept;
- 效果
-
从
v
的相应元素初始化data_
的元素。
迭代
constexpr iterator begin() noexcept;
constexpr const_iterator begin() const noexcept;
- 返回
-
data_
.
constexpr iterator end() noexcept;
constexpr const_iterator end() const noexcept;
- 返回
-
data_ + N
.
访问器
constexpr unsigned char* data() noexcept;
constexpr unsigned char const* data() const noexcept;
- 返回
-
data_
.
constexpr size_type size() const noexcept;
constexpr size_type max_size() const noexcept;
- 返回
-
N
.
元素访问
constexpr reference operator[]( std::size_t i );
constexpr const_reference operator[]( std::size_t i ) const;
- 要求
-
i < size()
. - 返回
-
data_[ i ]
.
constexpr reference front() noexcept;
constexpr const_reference front() const noexcept;
- 返回
-
data_[ 0 ]
.
constexpr reference back() noexcept;
constexpr const_reference back() const noexcept;
- 返回
-
data_[ N-1 ]
.
比较
template<std::size_t N>
constexpr bool operator==( digest<N> const& a, digest<N> const& b ) noexcept;
- 返回
-
当
a.data_
的元素等于b.data_
的对应元素时为true
,否则为false
。
template<std::size_t N>
constexpr bool operator!=( digest<N> const& a, digest<N> const& b ) noexcept;
- 返回
-
!(a == b)
.
格式化
template<std::size_t N>
constexpr char* to_chars( digest<N> const& v, char* first, char* last ) noexcept;
- 效果
-
将
data_
的内容作为十六进制字符串写入到提供的输出范围[first, last)
。 - 返回
-
生成的输出末尾之后的一个指针,如果
[first, last)
不够大,则为nullptr
。
template<std::size_t N, std::size_t M>
constexpr void to_chars( digest<N> const& v, char (&w)[ M ] ) noexcept;
- 要求
-
M >= N*2 + 1
. - 效果
-
将
data_
的内容作为十六进制字符串,然后是一个空终止符,写入到提供的输出缓冲区w
。
template<std::size_t N>
std::ostream& operator<<( std::ostream& os, digest<N> const& v );
- 效果
-
将
data_
的内容作为十六进制字符串写入到os
。 - 返回
-
os
.
template<std::size_t N>
std::string to_string( digest<N> const& v );
- 返回
-
一个字符串,其中包含十六进制格式的
data_
内容。
<boost/hash2/endian.hpp>
namespace boost {
namespace hash2 {
enum class endian;
} // namespace hash2
} // namespace boost
<boost/hash2/flavor.hpp>
#include <boost/hash2/endian.hpp>
namespace boost {
namespace hash2 {
struct default_flavor;
struct little_endian_flavor;
struct big_endian_flavor;
} // namespace hash2
} // namespace boost
头文件 boost/hash2/flavor.hpp
包含预定义的 flavor 类型。
(flavor 作为第二个参数传递给 hash_append
,以便影响其行为。)
Flavor 类型有两个成员,类型为 size_type
的类型 size_type
和类型为 boost::hash2::endian
的值 byte_order
。
size_type
控制如何处理 hash_append_size
的参数(在哈希之前将其转换为 size_type
。)
byte_order
控制用于哈希标量类型的字节序。
default_flavor
struct default_flavor
{
using size_type = std::uint32_t;
static constexpr auto byte_order = endian::native;
};
default_flavor
请求标量类型的原生、依赖于字节序的哈希。这使得哈希值依赖于当前平台的字节序,但如果将标量类型的大型数组传递给 hash_append
,则有可能显着提高速度。
很少需要显式使用 default_flavor
,因为它在没有给 hash_append
flavor 时是默认值,例如:hash_append( h, {}, v );
<boost/hash2/get_integral_result.hpp>
namespace boost {
namespace hash2 {
template<class T, class Hash> constexpr T get_integral_result( Hash& h );
} // namespace hash2
} // namespace boost
get_integral_result
template<class T, class Hash> constexpr T get_integral_result( Hash& h );
- 要求
-
T
必须是一个不是bool
的整数类型。Hash
必须是一个哈希算法。 - 效果
-
调用
h.result()
一次或多次。 - 返回
-
一个值,该值从
h.result()
调用的返回值中派生出来,并且以近似均匀分布的方式分布在T
的可能值上。 - 备注
-
当
Hash::result_type
是类似数组的类型时,get_integral_result
允许假设Hash
是高质量的哈希算法,因此h.result()
的值在Hash::result_type
的整个域上均匀分布。 - 示例
-
template<class T, class Hash> struct my_hash { std::size_t operator()( std::string const& st ) const noexcept { Hash hash; boost::hash2::hash_append( hash, {}, st ); return boost::hash2::get_integral_result<std::size_t>( hash ); } };
<boost/hash2/is_trivially_equality_comparable.hpp>
namespace boost {
namespace hash2 {
template<class T> struct is_trivially_equality_comparable;
} // namespace hash2
} // namespace boost
is_trivially_equality_comparable
template<class T> struct is_trivially_equality_comparable:
std::integral_constant< bool,
std::is_integral<T>::value || std::is_enum<T>::value || std::is_pointer<T>::value >
{
};
template<class T> struct is_trivially_equality_comparable<T const>:
is_trivially_equality_comparable<T>
{
};
特性 is_trivially_equality_comparable
由库用于检测可平凡相等比较的类型。
如果对于该类型的两个值 x
和 y
,x == y
等效于 std::memcmp( &x, &y, sizeof(x) ) == 0
,则该类型是可平凡相等比较的。
也就是说,对于可平凡相等比较的类型,比较它们的值与比较它们的存储字节表示形式相同。
这允许 hash_append
假设标识对象值的消息与对象的存储字节相同。
如果默认实现未给出正确的结果,则可以为用户定义的类型专门化 is_trivially_equality_comparable
。
例如,对于以下类型
struct X
{
int v;
};
bool operator==( X const& x1, X const& x2 )
{
return x1.v == x2.v;
}
(在假设它没有填充字节的情况下,即 sizeof(X) == sizeof(int)
)is_trivially_equality_comparable<X>::value
默认将为 false
,但该类型满足可平凡相等比较的要求,因此可以添加专门化
template<> struct boost::hash2::is_trivially_equality_comparable<X>: std::true_type {};
或者,如果您想安全起见,
template<> struct boost::hash2::is_trivially_equality_comparable<X>:
std::integral_constant<bool, sizeof(X) == sizeof(int)> {};
另一方面,以下类型
enum E: unsigned {};
bool operator==( E e1, E e2 )
{
return e1 % 256 == e2 % 256;
}
不是可平凡相等比较的(因为 (E)0x204 == (E)0x704
,但 memcmp
将给出非零结果),但 is_trivially_equality_comparable<E>::value
默认将为 true
。
在此(相当罕见的)情况下,可以添加特化来报告 false
template<> struct boost::hash2::is_trivially_equality_comparable<E>: std::false_type {};
<boost/hash2/is_endian_independent.hpp>
namespace boost {
namespace hash2 {
template<class T> struct is_endian_independent;
} // namespace hash2
} // namespace boost
is_endian_independent
template<class T> struct is_endian_independent:
std::integral_constant< bool, sizeof(T) == 1 >
{
};
template<class T> struct is_endian_independent<T const>:
is_endian_independent<T>
{
};
库使用特性 is_endian_independent
来检测字节序无关类型。
如果一个类型的内存表示在小端和大端平台上相同,则该类型是字节序无关的。
这包括所有单字节类型(sizeof
为 1)以及所有组成成员也是字节序无关的类型。
特性的默认实现仅对单字节类型报告 true
。它可以为字节序无关的用户定义类型进行特化。
例如,以下类型
struct X
{
unsigned char a;
unsigned char b;
unsigned char c;
};
是字节序无关的,并且可以为其适当地特化 is_endian_independent
template<> struct boost::hash2::is_endian_independent<X>: std::true_type {};
<boost/hash2/is_contiguously_hashable.hpp>
#include <boost/hash2/endian.hpp>
#include <boost/hash2/is_trivially_equality_comparable.hpp>
#include <boost/hash2/is_endian_independent.hpp>
namespace boost {
namespace hash2 {
template<class T, endian E> struct is_contiguously_hashable;
} // namespace hash2
} // namespace boost
is_contiguously_hashable
template<class T, endian E> struct is_contiguously_hashable:
std::integral_constant<bool,
is_trivially_equality_comparable<T>::value &&
(E == endian::native || is_endian_independent<T>::value)>
{
};
template<class T, std::size_t N, endian E> struct is_contiguously_hashable<T[N], E>:
is_contiguously_hashable<T, E>
{
};
库使用特性 is_contiguously_hashable
来检测连续可哈希类型。
如果对于类型,在特定的字节顺序 E
下,如果它不被认为是连续可哈希的,则会产生的消息与其底层存储字节表示相同,那么该类型在该字节顺序 E
下是连续可哈希的。
当 value
的类型在 flavor
请求的字节顺序(decltype(flavor)::byte_order
)下是连续可哈希的时,hash_append(hash, flavor, value)
会发出对 hash.update(&value, sizeof(value))
的单次调用作为优化。
is_contiguously_hashable
不旨在为用户定义的类型进行特化。它的实现依赖于 is_trivially_equality_comparable
和 is_endian_independent
,并且只要这些底层特性是正确的,它就是正确的。
<boost/hash2/has_constant_size.hpp>
namespace boost {
namespace hash2 {
template<class T> struct has_constant_size;
} // namespace hash2
} // namespace boost
has_constant_size
template<class T> struct has_constant_size<T>: std::integral_constant<bool, /*see below*/>
{
};
template<class T> struct has_constant_size<T const>: has_constant_size<T>
{
};
库使用特性 has_constant_size
来检测具有常量大小的容器和范围类型。这允许 hash_append
不在消息中包含大小,因为它不对对象状态做出贡献。
如果对于该类型的所有值 v
,v.size()
具有相同的值,则容器或范围类型具有常量大小。
默认实现对类似元组的类型(那些为 std::tuple_size
特化的类型)报告 true
,例如 std::array
、boost::array
和 digest
。
has_constant_size
可以为具有常量大小的用户定义容器和范围类型进行特化。
例如,boost::uuids::uuid
具有 16 的常量大小,因此可以适当地添加特化
template<> struct boost::hash2::has_constant_size<boost::uuids::uuid>: std::true_type {};
哈希 C++ 对象
<boost/hash2/hash_append_fwd.hpp>
概要
namespace boost {
namespace hash2 {
template<class Hash, class Flavor, class T>
constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor, class It>
constexpr void hash_append_range( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor, class T>
constexpr void hash_append_size( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor, class It>
constexpr void hash_append_range_and_size( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor, class It>
constexpr void hash_append_unordered_range( Hash& h, Flavor const& f, It first, It last );
struct hash_append_tag;
} // namespace hash2
} // namespace boost
头文件 boost/hash2/hash_append_fwd.hpp
声明了在 boost/hash2/hash_append.hpp
中实现的函数。
当代码希望为用户定义的类型实现 hash_append
支持,而无需物理依赖于 hash_append
的实现时,可以使用它。
示例
#include <boost/hash2/hash_append_fwd.hpp>
#include <vector>
class X
{
private:
int a = -1;
std::vector<int> b{ 1, 2, 3 };
template<class Provider, class Hash, class Flavor>
friend void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const&,
Hash& h, Flavor const& f, X const* v )
{
boost::hash2::hash_append( h, f, v->a );
boost::hash2::hash_append( h, f, v->b );
}
public:
X() = default;
};
#include "X.hpp"
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/md5.hpp>
#include <iostream>
int main()
{
X x;
boost::hash2::md5_128 hash;
boost::hash2::hash_append( hash, {}, x );
std::cout << "MD5 digest of x: " << hash.result() << std::endl;
}
但是请注意,即使不包含 <boost/hash2/hash_append_fwd.hpp>
,也可以通过使用 tag_invoke
的 Provider
参数来达到相同的效果
#include <vector>
class X
{
private:
int a = -1;
std::vector<int> b{ 1, 2, 3 };
template<class Provider, class Hash, class Flavor>
friend void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const& pr,
Hash& h, Flavor const& f, X const* v )
{
pr.hash_append( h, f, v->a );
pr.hash_append( h, f, v->b );
}
public:
X() = default;
};
<boost/hash2/hash_append.hpp>
#include <boost/hash2/flavor.hpp>
namespace boost {
namespace hash2 {
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_range( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append_size( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_range_and_size( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_unordered_range( Hash& h, Flavor const& f, It first, It last );
struct hash_append_tag;
struct hash_append_provider;
} // namespace hash2
} // namespace boost
hash_append
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
将 v
的表示追加到存储在 h
中的消息。
- 效果
-
-
如果
is_contiguously_hashable<T, Flavor::byte_order>::value
为true
,则调用h.update(&v, sizeof(v))
; -
如果
std::is_integral<T>::value
为true
,则以Flavor::byte_order
请求的字节顺序获取v
的字节表示,然后调用h.update(p, n)
,其中p
是该表示的地址,n
是sizeof(v)
; -
如果
std::is_floating_point<T>::value
为 true,则首先将v
替换为正零(如果它是负零),然后调用hash_append(h, f, std::bit_cast<U>(v))
,其中U
是与T
大小相同的无符号整数类型; -
如果
std::is_pointer<T>::value
为true
,则调用hash_append(h, f, reinterpret_cast<std::uintptr_t>(v))
; -
如果
T
是std::nullptr_t
,则调用hash_append(h, f, static_cast<void*>(v))
; -
如果
T
是数组类型U[N]
,则调用hash_append_range(h, f, v + 0, v + N)
; -
如果存在适用于
T
的tag_invoke
的合适重载,则调用(非限定)tag_invoke(hash_append_tag(), hash_append_provider(), h, f, v)
; -
如果
std::is_enum<T>::value
为true
,则调用hash_append(h, f, w)
,其中w
是v
转换为T
的底层类型; -
如果
boost::container_hash::is_unordered_range<T>::value
为true
,则调用hash_append_unordered_range(h, f, v.begin(), v.end())
; -
如果
boost::container_hash::is_contiguous_range<T>::value
为true
并且-
has_constant_size<T>::value
为true
,则调用hash_append_range(h, f, v.data(), v.data() + v.size())
; -
has_constant_size<T>::value
为false
,则调用hash_append_range_and_size(h, f, v.data(), v.data() + v.size())
;
-
-
如果
boost::container_hash::is_range<T>::value
为true
并且-
has_constant_size<T>::value
为true
,则调用hash_append_range(h, f, v.begin(), v.end())
; -
has_constant_size<T>::value
为false
,则调用hash_append_range_and_size(h, f, v.begin(), v.end())
;
-
-
如果
boost::container_hash::is_tuple_like<T>::value
为true
,则为每个元组元素w
调用hash_append(h, f, w)
; -
如果
boost::container_hash::is_described_class<T>::value
为true
,则为v
的每个基类子对象b
调用hash_append(h, f, b)
,然后为v
的每个成员子对象m
调用hash_append(h, f, m)
; -
否则,结果是编译时错误。
-
- 备注
-
如果上述描述导致没有进行任何调用(例如,对于常量大小为零的范围,或没有基类和成员的描述
struct
),则会调用hash_append(h, f, '\x00')
,以满足hash_append
始终至少导致一次对Hash::update
的调用的要求。
hash_append_range
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_range( Hash& h, Flavor const& f, It first, It last );
将范围 [first, last)
的元素的表示追加到存储在 h
中的消息。
- 要求
-
It
必须是迭代器类型。[first, last)
必须是有效的迭代器范围。 - 效果
-
-
如果
It
是T*
并且is_contiguously_hashable<T, Flavor::byte_order>::value
为true
,则调用h.update(first, (last - first) * sizeof(T));
。 -
否则,对于
[first, last)
表示的范围中的每个元素v
,调用hash_append(h, f, v);
。
-
- 备注
-
如果在常量表达式中调用
hash_append_range
,则仅对unsigned char*
和unsigned char const*
应用连续可哈希优化。
hash_append_size
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append_size( Hash& h, Flavor const& f, T const& v );
将 v
的表示(转换为 Flavor::size_type
)追加到存储在 h
中的消息。
- 要求
-
T
必须是整型。 - 效果
-
等价于
hash_append(h, f, static_cast<typename Flavor::size_type>(v));
hash_append_range_and_size
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_range_and_size( Hash& h, Flavor const& f, It first, It last );
将范围 [first, last)
的元素的表示,后跟范围的大小,追加到存储在 h
中的消息。
- 要求
-
It
必须是迭代器类型。[first, last)
必须是有效的迭代器范围。 - 效果
-
等价于
hash_append_range(h, f, first, last); hash_append(h, f, m);
,其中m
是std::distance(first, last)
。
hash_append_unordered_range
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_unordered_range( Hash& h, Flavor const& f, It first, It last );
从范围 [first, last)
的元素的表示构建一个值,以某种方式使它们的顺序不影响结果,然后将该值,后跟范围的大小,追加到存储在 h
中的消息。
- 要求
-
It
必须是迭代器类型。[first, last)
必须是有效的迭代器范围。 - 效果
-
对于
[first, last)
表示的范围中的每个元素v
,通过执行以下操作获得哈希值r
Hash h2(h); hash_append(h2, f, v); auto r = h2.result();
然后以对它们的顺序不敏感的方式组合如此获得的
r
值,产生一个组合值q
。调用hash_append(h, f, q)
,然后调用hash_append(h, f, m)
,其中m
是std::distance(first, last)
。
hash_append_tag
struct hash_append_tag
{
};
hash_append_tag
是一种标签类型,用作 tag_invoke
重载的第一个参数,以标识 hash_append
操作。
hash_append_provider
struct hash_append_provider
{
template<class Hash, class Flavor, class T>
static constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor, class It>
static constexpr void hash_append_range( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor, class T>
static constexpr void hash_append_size( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor, class It>
static constexpr void hash_append_range_and_size( Hash& h, Flavor const& f,
It first, It last );
template<class Hash, class Flavor, class It>
static constexpr void hash_append_unordered_range( Hash& h, Flavor const& f,
It first, It last );
};
类型为 hash_append_provider
的对象作为 tag_invoke
重载的第二个参数传递。
它的所有成员函数都调用 boost::hash2
中具有相同名称的相应函数。
版权和许可证
本文档的版权归 2020, 2024 Peter Dimov 和 Christian Mazakas 所有,并根据 Boost Software License, Version 1.0 分发。