哪种哈希算法最适合唯一性和速度?


1387

哪种哈希算法最适合唯一性和速度?示例(良好)用法包括哈希字典。

我知道有SHA-256之类的东西,但是这些算法被设计安全的,这通常意味着它们比不那么独特的算法要慢。我希望哈希算法的设计速度要快,但要保持相当独特以避免冲突。


9
为了安全或其他目的?
2011年

19
@Orbling,用于实现哈希字典。因此,应将冲突减至最少,但根本没有安全目的。
Earlz 2011年

4
请注意,您将需要在哈希表中至少发生一些冲突,否则该表将需要足够大才能能够处理相对较少的键...
Dean Harding

19
很棒的帖子!您还能检查一下Yanmur Collet的xxHash(创建者或LZ4),它的速度是Murmur的两倍吗?主页:code.google.com/p/xxhash更多信息:fastcompression.blogspot.fr/2012/04/...

24
@zvrba取决于算法。bcrypt的设计速度很慢。
2013年

Answers:


2460

我测试了一些不同的算法,测量了速度和碰撞次数。

我使用了三种不同的键集:

对于每个语料库,记录冲突次数和散列所花费的平均时间。

我测试了:

结果

每个结果均包含平均哈希时间和冲突次数

Hash           Lowercase      Random UUID  Numbers
=============  =============  ===========  ==============
Murmur            145 ns      259 ns          92 ns
                    6 collis    5 collis       0 collis
FNV-1a            152 ns      504 ns          86 ns
                    4 collis    4 collis       0 collis
FNV-1             184 ns      730 ns          92 ns
                    1 collis    5 collis       0 collis▪
DBJ2a             158 ns      443 ns          91 ns
                    5 collis    6 collis       0 collis▪▪▪
DJB2              156 ns      437 ns          93 ns
                    7 collis    6 collis       0 collis▪▪▪
SDBM              148 ns      484 ns          90 ns
                    4 collis    6 collis       0 collis**
SuperFastHash     164 ns      344 ns         118 ns
                   85 collis    4 collis   18742 collis
CRC32             250 ns      946 ns         130 ns
                    2 collis    0 collis       0 collis
LoseLose          338 ns        -             -
               215178 collis

注意事项

  • 所述LoseLose算法(其中,散列=散列+字符)是真正可怕。一切都碰撞到相同的1,375个桶中
  • SuperFastHash速度很快,看起来很分散。我的天哪,数字冲突。我希望移植它的人出了点问题;这很糟糕
  • CRC32 很好。速度较慢,还有1k的查询表

碰撞真的发生了吗?

是。我开始编写测试程序以查看是否确实发生了哈希冲突-不仅仅是理论上的构造。他们确实确实发生了:

FNV-1碰撞

  • creamwove 与...碰撞 quists

FNV-1a碰撞

  • costarring 与...碰撞 liquid
  • declinate 与...碰撞 macallums
  • altarage 与...碰撞 zinke
  • altarages 与...碰撞 zinkes

Murmur2碰撞

  • cataract 与...碰撞 periti
  • roquette 与...碰撞 skivie
  • shawl 与...碰撞 stormbound
  • dowlases 与...碰撞 tramontane
  • cricketings 与...碰撞 twanger
  • longans 与...碰撞 whigs

DJB2碰撞

  • hetairas 与...碰撞 mentioner
  • heliotropes 与...碰撞 neurospora
  • depravement 与...碰撞 serafins
  • stylist 与...碰撞 subgenera
  • joyful 与...碰撞 synaphea
  • redescribed 与...碰撞 urites
  • dram 与...碰撞 vivency

DJB2a碰撞

  • haggadot 与...碰撞 loathsomenesses
  • adorablenesses 与...碰撞 rentability
  • playwright 与...碰撞 snush
  • playwrighting 与...碰撞 snushing
  • treponematoses 与...碰撞 waterbeds

CRC32冲突

  • codding 与...碰撞 gnu
  • exhibiters 与...碰撞 schlager

SuperFastHash冲突

  • dahabiah 与...碰撞 drapability
  • encharm 与...碰撞 enclave
  • grahams 与...碰撞 gramary
  • ...剪下79次碰撞...
  • night 与...碰撞 vigil
  • nights 与...碰撞 vigils
  • finks 与...碰撞 vinic

随机化

另一个主观度量是散列的随机分布。映射生成的HashTables将显示数据分配的均匀程度。线性映射表时,所有哈希函数均显示良好的分布:

在此处输入图片说明

或作为希尔伯特地图XKCD始终是相关的):

在此处输入图片说明

除了对数字字符串("1",,"2"...,"216553")进行哈希处理(例如邮政编码)时,大多数哈希算法中都开始出现模式:

SDBM

在此处输入图片说明

DJB2a

在此处输入图片说明

FNV-1

在此处输入图片说明

FNV-1a以外的所有东西对我来说仍然很随机:

在此处输入图片说明

事实上,Murmur2似乎有更好的随机性NumbersFNV-1a

在此处输入图片说明

当我看FNV-1a“数字”图时,我认为我看到了细微的垂直图案。有了Murmur,我根本看不到任何模式。你怎么看?


*表中的多余部分表示随机性有多糟。随着FNV-1a是最好的,DJB2x是最糟糕的:

      Murmur2: .
       FNV-1a: .
        FNV-1: ▪
         DJB2: ▪▪
        DJB2a: ▪▪
         SDBM: ▪▪▪
SuperFastHash: .
          CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
     Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
                                        ▪
                                 ▪▪▪▪▪▪▪▪▪▪▪▪▪
                        ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
          ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

我最初编写该程序是为了确定我是否还要担心碰撞:我愿意。

然后变成确保哈希函数足够随机。

FNV-1a算法

FNV1散列具有返回32、64、128、256、512和1024位散列的变体。

所述FNV-1A算法是:

hash = FNV_offset_basis
for each octetOfData to be hashed
    hash = hash xor octetOfData
    hash = hash * FNV_prime
return hash

其中常数FNV_offset_basisFNV_prime取决于你想要的回报散列大小:

Hash Size  
===========
32-bit
    prime: 2^24 + 2^8 + 0x93 = 16777619
    offset: 2166136261
64-bit
    prime: 2^40 + 2^8 + 0xb3 = 1099511628211
    offset: 14695981039346656037
128-bit
    prime: 2^88 + 2^8 + 0x3b = 309485009821345068724781371
    offset: 144066263297769815596495629667062367629
256-bit
    prime: 2^168 + 2^8 + 0x63 = 374144419156711147060143317175368453031918731002211
    offset: 100029257958052580907070968620625704837092796014241193945225284501741471925557
512-bit
    prime: 2^344 + 2^8 + 0x57 = 35835915874844867368919076489095108449946327955754392558399825615420669938882575126094039892345713852759
    offset: 9659303129496669498009435400716310466090418745672637896108374329434462657994582932197716438449813051892206539805784495328239340083876191928701583869517785
1024-bit
    prime: 2^680 + 2^8 + 0x8d = 5016456510113118655434598811035278955030765345404790744303017523831112055108147451509157692220295382716162651878526895249385292291816524375083746691371804094271873160484737966720260389217684476157468082573
    offset: 1419779506494762106872207064140321832088062279544193396087847491461758272325229673230371772250864096521202355549365628174669108571814760471015076148029755969804077320157692458563003215304957150157403644460363550505412711285966361610267868082893823963790439336411086884584107735010676915

有关详细信息,请参见FNV主页

我所有的结果都是32位版本。

FNV-1比FNV-1a好吗?

否。FNV-1a的状况更好。使用英文单词corpus时,与FNV-1a的冲突更多:

Hash    Word Collisions
======  ===============
FNV-1   1
FNV-1a  4

现在比较小写和大写:

Hash    lowercase word Collisions  UPPERCASE word collisions
======  =========================  =========================
FNV-1   1                          9
FNV-1a  4                          11

在这种情况下,FNV-1a不会比FN-1差“ 400%”,仅差20%。

我认为更重要的一点是,涉及碰撞时有两种算法:

  • 很少发生碰撞:FNV-1,FNV-1a,DJB2,DJB2a,SDBM
  • 常见的碰撞:SuperFastHash,Loselose

然后是哈希的分布方式:

  • 出色的发行版: Murmur2,FNV-1a,SuperFastHas
  • 出色的分配: FNV-1
  • 良好的分布: SDBM,DJB2,DJB2a
  • 可怕的分布: Loselose

更新资料

杂音?当然可以,为什么不


更新资料

@whatshisname想知道CRC32的性能如何,将数字添加到表中。

CRC32 很好。冲突很少,但速度较慢,并且有1k查找表的开销。

剪下有关CRC分发的所有错误信息-我的错


直到今天,我仍将FNV-1a用作我的事实上的哈希表哈希算法。但是现在我切换到Murmur2:

  • 快点
  • 所有输入类别的随机性更好

我真的非常希望SuperFastHash我发现算法有问题;太受欢迎了,真是太糟糕了。

更新:上谷歌的MurmurHash3主页

(1)-SuperFastHash具有非常差的碰撞属性,这在其他地方已有记录。

所以我想不仅仅是我。

更新:我意识到为什么Murmur比其他的更快。MurmurHash2一次操作四个字节。大多数算法都是逐字节的

for each octet in Key
   AddTheOctetToTheHash

这意味着,随着按键时间的延长,Murmur将有机会发光。


更新资料

GUID设计为唯一而非随机

雷蒙德·陈(Raymond Chen)在及时的帖子中重申了以下事实:“随机” GUID并非旨在用于其随机性。它们或它们的子集不适合作为哈希键:

即使第4版GUID算法也不保证是不可预测的,因为该算法未指定随机数生成器的质量。Wikipedia上有关GUID的文章包含初步研究,研究表明可以基于对随机数生成器状态的了解来预测将来和以前的GUID,因为生成器的密码学能力不强。

Randomess与避免碰撞不同;这就是为什么通过采用“随机” guid的某些子集来尝试发明自己的“哈希”算法将是一个错误的原因:

int HashKeyFromGuid(Guid type4uuid)
{
   //A "4" is put somewhere in the GUID.
   //I can't remember exactly where, but it doesn't matter for
   //the illustrative purposes of this pseudocode
   int guidVersion = ((type4uuid.D3 & 0x0f00) >> 8);
   Assert(guidVersion == 4);

   return (int)GetFirstFourBytesOfGuid(type4uuid);
}

注意:再次,我在引号中加上了“ random GUID”,因为它是GUID的“ random”变体。更准确的描述是Type 4 UUID。但是没人知道4型是什么,或者1、3和5型是什么。因此,将它们称为“随机” GUID会更容易。

所有英文单词的镜像


41
看到SHA如何进行比较将是非常有趣的,不是因为它是哈希算法的良好候选者,而是看到任何加密哈希与为速度算法而制成的哈希进行比较将是非常有趣的。
迈克尔

8
Yann Collet最近又进行了一次名为“ xxHash”的新哈希运算。我总是对新的哈希值感到怀疑。在您的比较中看到它会很有趣,(如果您不厌倦有人建议添加他们听说过的随机散列...)
th_in_gs 2012年

7
确实。xxHash项目页面上公布的性能数据令人印象深刻,也许太多了。好吧,至少这是一个开源项目:code.google.com/p/xxhash
ATTracker 2012年

9
嗨,伊恩,我的SuperFastHash的Delphi实现是正确的。实施时,我在C和Delphi中创建了一个测试集,以比较实施和参考实施的结果。没有区别。因此,您看到的是哈希的实际缺陷 ……(这就是为什么我还发布了MurmurHash实现:landman-code.blogspot.nl/2009/02/…
Davy Landman

19
张贴者知道这不仅是一个了不起的答案-这是世界上有关该主题的事实参考资源?每当我需要处理哈希值时,它都能如此迅速,权威地解决我的问题,以至于我不需要任何其他东西。
MaiaVictor 2014年

59

如果您希望通过不变的字典创建哈希映射,则可以考虑使用完美的哈希https://en.wikipedia.org/wiki/Perfect_hash_function-在构造哈希函数和哈希表期间,您可以保证,对于给定的数据集,不会有冲突。


2
这里有更多关于(最小的)完美散列burtleburtle.net/bob/hash/perfect.html包括性能数据,尽管它不使用最新的处理器等等
埃利后来的Kesselman

4
这很明显,但值得指出的是,为了保证不发生冲突,除非算法可以利用这些值,否则这些键的大小必须与这些值相同。
devios1 2013年

1
@ devios1您的陈述毫无意义。首先,哈希表中的值(完美与否)与键无关。其次,一个完美的哈希表只是一个线性值数组,由精心设计的函数结果索引,以使所有索引都是唯一的。
Jim Balter

1
@MarcusJ完美哈希通常使用少于100个键,但是看看cmph.sourceforge.net ...仍然远远超出您的范围。
Jim Balter

1
@DavidCary您的链接上没有任何内容支持您的主张。可能您已经将O(1)与“没有碰撞”混淆了,但是它们根本不是一回事。当然,完美的哈希不能保证没有冲突,但是它要求所有密钥都是事先知道的,并且密钥相对较少。(但请参阅上面的cmph链接。)
Jim Balter

34

是哈希函数的列表,但简短的版本是:

如果您只是想拥有一个好的哈希函数,并且迫不及待,那djb2是我所知道的最好的字符串哈希函数之一。它在许多不同的键和表大小集上具有出色的分布和速度

unsigned long
hash(unsigned char *str)
{
    unsigned long hash = 5381;
    int c;

    while (c = *str++)
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

6
实际上djb2是零敏感的,因为大多数此类简单的哈希函数都是如此,因此您可以轻松地破坏此类哈希。它有一个不好的偏见太多的碰撞和不好的分布,它打破了大多数smhasher质量测试:请参见github.com/rurban/smhasher/blob/master/doc/bernstein 他的CDB数据库使用它,但我不会用它具有公共访问权限。
rurban 2014年

2
从性能和发行的角度来看,DJB非常糟糕。我今天不会用。
康拉德·迈尔

@ConradMeyer我敢打赌,就像我的这个问题一样,DJB可以提高三倍,然后它可能会击败大多数可用的算法。关于分配,我同意。即使对于两个字母字符串也产生冲突的哈希值并不是很好。
maaartinus

28

Google的CityHash是您要寻找的算法。它对密码术不利,但对生成唯一的哈希值则有利。

阅读博客以获取更多详细信息,并在此处提供代码

CityHash用C ++编写。还有一个普通的C端口

关于32位支持:

所有CityHash功能都针对64位处理器进行了调整。也就是说,它们将以32位代码运行(使用SSE4.2的新版本除外)。他们不会很快。您可能要使用Murmur或其他32位代码。


11
CityHash的发音类似于“ City Sushi”吗?
埃里克

2
也看一下SipHash,它旨在替代MurmurHash / CityHash / etc。:131002.net/siphash
Török埃德温·

3
另请参阅FarmHash(CitHash的后继者)。code.google.com/p/farmhash
stevendaniels

7
xxHash声称比CityHash快5倍。
粘土桥

plain C port链接已断开
makerj

20

在对文件进行哈希处理时,我对不同的哈希算法进行了简短的速度比较。

由于所有文件都存储在tmpfs中,因此各个图在读取方法上仅稍有不同,在此处可以忽略。因此,如果您想知道基准测试不受IO限制。

算法包括:SpookyHash, CityHash, Murmur3, MD5, SHA{1,256,512}

结论:

  • 诸如Murmur3,Cityhash和Spooky之类的非加密哈希函数非常接近。应该注意的是,在具有SSE 4.2s CRC指令的CPU上,Cityhash可能更快,而我的CPU没有。就我而言,SpookyHash总是比CityHash靠前一点。
  • 尽管SHA256可能更安全地抵御MD5和SHA1 的冲突漏洞,但在使用加密哈希函数时,MD5似乎是一个不错的权衡。
  • 所有算法的复杂度都是线性的-这并不奇怪,因为它们是逐块工作的。(我想看看读取方法是否有所不同,所以您可以比较最右边的值)。
  • SHA256比SHA512慢。
  • 我没有研究哈希函数的随机性。但在这里它们是在缺少散列函数比较不错伊恩Boyds答案。这指出CityHash在极端情况下存在一些问题。

用于绘图的来源:


1
线性比例尺图会切断y轴标签,该标签会指出正在绘制的数量。我猜可能是“以秒为单位的时间”,与对数刻度相同。值得修复。
Craig McQueen 2015年

18

SHA算法(包括SHA-256)被设计快速的

实际上,有时它们的速度可能会成为问题。特别地,用于存储源自密码的令牌的常用技术是运行标准快速哈希算法10,000次(将哈希值的哈希值存储在...密码的哈希值中)。

#!/usr/bin/env ruby
require 'securerandom'
require 'digest'
require 'benchmark'

def run_random_digest(digest, count)
  v = SecureRandom.random_bytes(digest.block_length)
  count.times { v = digest.digest(v) }
  v
end

Benchmark.bmbm do |x|
  x.report { run_random_digest(Digest::SHA256.new, 1_000_000) }
end

输出:

Rehearsal ------------------------------------
   1.480000   0.000000   1.480000 (  1.391229)
--------------------------- total: 1.480000sec

       user     system      total        real
   1.400000   0.000000   1.400000 (  1.382016)

57
当然,对于加密哈希算法而言,这是相对较快的。但是OP只想将值存储在哈希表中,我不认为加密哈希函数真的适合于此。
Dean Harding

6
提出该问题(相切出现,现在看来)是加密哈希函数的主题。那就是我要回应的。
yfeldblum 2011年

15
只是为了使人们摆脱“特别是,一种存储密码派生令牌的通用技术是运行一次标准快速哈希算法10,000次”的想法-尽管很常见,但这只是愚蠢的。有针对这些情况设计的算法,例如bcrypt。使用正确的工具。
TC1

3
密码哈希被设计为具有高吞吐量,但是这通常意味着它们具有很高的设置,拆除.rodata和/或状态成本。当您需要用于哈希表的算法时,通常您会拥有非常短的密钥,并且其中有很多密钥,但是不需要额外的保证。我自己一次调整了詹金斯的作品。
mirabilos 2013年

1
@ChrisMorgan:通过使用哈希随机化可以更有效地解决HashTable DoS,而不是使用加密安全的哈希,这样程序的每次运行甚至每个哈希表都可以被使用,因此数据不会每次都被分组到同一个存储桶中。
Lie Ryan

14

我知道有SHA-256之类的东西,但是这些算法被设计安全的,这通常意味着它们比不那么独特的算法要慢。

密码散列函数更独特的假设是错误的,实际上,在实践中可以证明它经常倒退。事实上:

  1. 理想情况下,加密散列函数应该与随机函数没有区别
  2. 但是,对于非密码哈希函数,希望它们与可能的输入进行良好的交互

这意味着,与用于“良好”数据集(为它设计的数据集)相比,非加密哈希函数的冲突可能要得多。

我们实际上可以用Ian Boyd的答案中的数据和一些数学运算来证明这一点:Birthday问题。如果n从集合中随机选择整数,则期望的碰撞对数的公式[1, d]是(来自维基百科):

n - d + d * ((d - 1) / d)^n

堵塞n= 216,553和d= 2 ^ 32,我们得到约5.5个预期的碰撞。伊恩(Ian)的测试大部分显示了该邻域周围的结果,但有一个戏剧性的例外:大多数函数在连续数字测试中的碰撞次数为零。随机选择216,553个32位数字并获得零冲突的可能性约为0.43%。这仅是一个函数,这里我们有五个零碰撞的不同哈希函数系列

因此,我们在这里看到的是,Ian测试的哈希与连续数字数据集交互良好,即,与理想的密码散列函数相比,它们分散的最小差异输入更为广泛。(旁注:这意味着Ian可以从他自己的数据中驳斥Ian的图形评估,即FNV-1a和MurmurHash2在number数据集中对他“看起来是随机的”。对于该大小的数据集,对于两个哈希函数,零冲突,绝对是非随机的!)

这并不奇怪,因为对于哈希函数的许多使用,这是理想的行为。例如,哈希表键通常非常相似。伊恩(Ian)的答案提到了MSN曾经存在的邮政编码哈希表问题。这是在避免可能输入上的冲突胜过类似随机行为的情况下使用的。

这里的另一个有益的比较是CRC和加密散列函数在设计目标上的对比:

  • CRC旨在捕获由嘈杂的通信通道引起的错误,这些错误可能是少量的位翻转。
  • 加密散列旨在捕获恶意攻击者所做的修改,这些攻击者被分配了有限的计算资源,但随意得多。

因此,对于CRC来说,在最小限度的不同输入中具有比随机发生的冲突少的冲突也是一件好事。使用加密哈希,这是一个禁忌!


10

使用SipHash。它具有许多理想的属性:

  • 快速。 一个优化的实现每个字节大约需要1个周期。

  • 安全。 SipHash是强大的PRF(伪随机函数)。这意味着它与随机函数是没有区别的(除非您知道128位密钥)。因此:

    • 无需担心哈希表探针由于冲突而变成线性时间。使用SipHash,您知道无论输入多少,都将获得平均情况下的平均性能。

    • 不受基于散列的拒绝服务攻击的影响。

    • 您可以将SipHash(尤其是具有128位输出的版本)用作MAC(消息身份验证代码)。如果您收到一条消息和一个SipHash标记,并且该标记与使用您的秘密密钥运行SipHash的标记相同,则您知道创建哈希的任何人也都拥有您的秘密密钥,并且消息和密码都没有。此后,哈希值已更改。


1
除非您需要安全性,否则SipHash不会过度杀伤力吗?需要一个128位密钥,它只是一个荣耀的哈希种子。更不用说MurmurHash3具有128位输出,而SipHash仅具有64位输出。显然,较大的摘要具有较低的碰撞机会。
bryc '18

@bryc的区别在于,即使在恶意输入的情况下,SipHash仍将表现良好。基于SipHash的哈希表可用于来自潜在敌对来源的数据,并可使用对哈希函数的细节非常敏感的算法(例如线性探测)。
黛咪

9

这取决于您要散列的数据。一些散列可以更好地处理特定数据,例如文本。一些哈希算法经过专门设计,可用于特定数据。

保罗·谢(Paul Hsieh)曾经做过快速哈希运算。他列出了源代码和说明。但是它已经被击败了。:)


6

Java使用以下简单的乘加算法:

字符串对象的哈希码计算为

 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

使用INT算术,其中s[i]的字符串的第字符,n是字符串的长度,以及^表示取幂。(空字符串的哈希值为零。)

可能有更好的选择,但这是相当普遍的,并且似乎在速度和唯一性之间进行了很好的权衡。


12
我不会使用这里使用的完全相同的方法,因为与此产生冲突仍然相对容易。这肯定是不可怕,但也有很多更好的在那里。如果有要与Java兼容没有显著的原因,应该不会选择。
约阿希姆·绍尔

4
如果由于某种原因仍然选择这种哈希方式,则至少可以使用更好的质数(例如92821)作为乘法器。这样可以大大减少碰撞。stackoverflow.com/a/2816747/21499
汉斯·彼得·斯托尔2014年

1
您也可以改用FNV1a。它也是一个简单的基于乘法的哈希,但是使用较大的乘法器,可以更好地分散哈希。
bryc

4

首先,为什么需要实现自己的哈希?对于大多数任务,假设有可用的实现,您应该使用标准库中的数据结构获得良好的结果(除非您只是为了自己的学习而这样做)。

就实际的哈希算法而言,我个人最喜欢的是FNV。1个

这是C语言中32位版本的示例实现:

unsigned long int FNV_hash(void* dataToHash, unsigned long int length)
{
  unsigned char* p = (unsigned char *) dataToHash;
  unsigned long int h = 2166136261UL;
  unsigned long int i;

  for(i = 0; i < length; i++)
    h = (h * 16777619) ^ p[i] ;

  return h;
}

2
FNV-1a变体的随机性稍好一些。交换的顺序*^h = (h * 16777619) ^ p[i]==>h = (h ^ p[i]) * 16777619
伊恩·博伊德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.