【密码学】一文读懂MurMurHash第一版

简介: 我来填坑了,之前说来讲一下MurMurHash算法,然后本文来简单描述一下这个算法的主要过程。MurMurHash这个哈希算法在2011年3月1日第一版被提出来,第一版呢最终输出的是一个32bit的哈希值,第一版已经不推荐使用了,本文先来讲一下第一版的算法过程,由于我只找到了这个第一版算法的代码,没有找到对应的参考文献,所以本文的算法流程是我根据Google提供的代码来说的,有哪里说的不对的地方,也欢迎读者指正。

【密码学】一文读懂MurMurHash第一版


YXDGY[6I7JLU}U7U~W[L)]A.jpgMurMurHash

我来填坑了,之前说来讲一下MurMurHash算法,然后本文来简单描述一下这个算法的主要过程。MurMurHash这个哈希算法在2011年3月1日第一版被提出来,第一版呢最终输出的是一个32bit的哈希值,第一版已经不推荐使用了,本文先来讲一下第一版的算法过程,由于我只找到了这个第一版算法的代码,没有找到对应的参考文献,所以本文的算法流程是我根据Google提供的代码来说的,有哪里说的不对的地方,也欢迎读者指正。


原始代码

Google原始的第一版的代码是用C写的,为了方便读者看到原汁原味的代码,这里贴一下(不是为了凑字数)。

//-----------------------------------------------------------------------------
// MurmurHash, by Austin Appleby
// Note - This code makes a few assumptions about how your machine behaves -
// 1. We can read a 4-byte value from any address without crashing
// 2. sizeof(int) == 4
// And it has a few limitations -
// 1. It will not work incrementally.
// 2. It will not produce the same results on little-endian and big-endian
//    machines.
// Changing the seed value will totally change the output of the hash.
// If you don't have a preference, use a seed of 0.
unsigned int MurmurHash ( const void * key, int len, unsigned int seed )
{
    // 'm' and 'r' are mixing constants generated offline.
    // They're not really 'magic', they just happen to work well.
    const unsigned int m = 0xc6a4a793;
    const int r = 16;
    // Initialize the hash to a 'random' value
    unsigned int h = seed ^ (len * m);
    // Mix 4 bytes at a time into the hash
    const unsigned char * data = (const unsigned char *)key;
    while(len >= 4)
    {
        h += *(unsigned int *)data;
        h *= m;
        h ^= h >> r;
        data += 4;
        len -= 4;
    }
    // Handle the last few bytes of the input array
    switch(len)
    {
    case 3:
        h += data[2] << 16;
    case 2:
        h += data[1] << 8;
    case 1:
        h += data[0];
        h *= m;
        h ^= h >> r;
    };
    // Do a few final mixes of the hash to ensure the last few
    // bytes are well-incorporated.
    h *= m;
    h ^= h >> 10;
    h *= m;
    h ^= h >> 17;
    return h;
}

可以发现,整个算法的代码还是比较少的,通过上面代码我们可以看出,这里输入是原始的字节流,输出是最终的哈希值是32bit的数字。


算法结构

根据代码里面的注释,里面说了m和r并不是魔数,只不过碰巧做得很好,所以里面也没有说为啥要取这两个数。

UH]}7NIO9~[576_[S`4U2(T.pngMurMurHash算法结构

这个算法整体来说并不算特别复杂,比起之前我们讲过的密码学安全的哈希函数,这个算法流程可真是太短了,最终生成的哈希值也只有32位,对于后面的版本实际上也有128位的,但是这个只有32位了,其他版本后面在讲,请叫我挖坑小王子。

根据上图简单描述一下算法的结构,三个初始值,然后初始化h, 之后就是针对每个消息的分块单独去处理,最后一个分块,如果剩余的不足32bit, 也就是4个字节,分别对于剩余的三个字节单独处理,最后输出得到最终的哈希值。


代码实现

虽然上面Google给出了c的代码,这里还是学习一下,我还是用rust来实现一下吧。

use std::convert::TryInto;
pub struct MurmurHashV1 {}
impl MurmurHashV1 {
    pub fn hash(key: &[u8], seed: u32) -> u32 {
        let m = 0xc6a4a793;
        let r = 16;
        let len = key.len();
        let mut h = seed ^ (len.wrapping_mul(m) as u32);
        let remain = len % 4;
        let mut offset = len;
        if remain > 0 {
            offset = len - remain;
        }
        let data = key[..offset].chunks(4).map(|it| u32::from_le_bytes(it.try_into().unwrap())).collect::<Vec<u32>>();
        for item in data {
            h = h.wrapping_add(item);
            h = h.wrapping_mul(m as u32);
            h ^= h >> r;
        }
        if remain >= 3 {
            h = h.wrapping_add((key[offset + 2] as u32).wrapping_shl(16));
        }
        if remain >= 2 {
            h = h.wrapping_add((key[offset + 1] as u32).wrapping_shl(8));
        }
        if remain >= 1 {
            h = h.wrapping_add(key[offset] as u32);
            h = h.wrapping_mul(m as u32);
            h ^= h >> r;
        }
        h = h.wrapping_mul(m as u32);
        h ^= h >> 10;
        h = h.wrapping_mul(m as u32);
        h ^= h >> 17;
        h
    }
}
#[cfg(test)]
mod tests {
    use crate::MurmurHashV1;
    #[test]
    fn it_works() {
        let result = MurmurHashV1::hash("1234567".as_bytes(), 0);
        println!("hash(1234567) = {}", result);
    }
}

因为最近go不是更新了支持泛型,感觉是时候了解一下go了,然后这个算法打算也用go来写一下(后面有可能,一些算法也会用go来实现一下,取决于我go的学习进度了,不保证一定有,rust版本后面的大概率都会有),虽然感觉这个我写起来一股c味,还是贴一下吧,原谅我这现学现卖的go的代码。

package main
import (
  "encoding/binary"
  "fmt"
)
func murmurhash(key []byte, seed uint32) uint32 {
  m := uint32(0xc6a4a793)
  r := 16
  keyLen := uint32(len(key))
  var h = seed ^ (keyLen * m)
  offset := 0
  e := binary.LittleEndian
  for keyLen >= 4 {
    h += e.Uint32(key[offset : offset+4])
    h *= m
    h ^= h >> r
    offset += 4
    keyLen -= 4
  }
  switch keyLen {
  case 3:
    h += uint32(key[offset+2]) << 16
    fallthrough
  case 2:
    h += uint32(key[offset+1]) << 8
    fallthrough
  case 1:
    h += uint32(key[offset])
    h *= m
    h ^= h >> r
  }
  h *= m
  h ^= h >> 10
  h *= m
  h ^= h >> 17
  return h
}
func main() {
  fmt.Println(murmurhash([]byte("1234567"), 0))
}


相关文章
|
7月前
|
机器学习/深度学习 算法 JavaScript
密码学系列之四:一文搞懂序列密码
密码学系列之四:一文搞懂序列密码
|
存储 Rust 并行计算
【密码学】一文读懂XTS模式
这篇文章的灵感来源于我偶然翻到的一个某U盘有关磁盘加密的一个介绍(这一篇不是广告蛤), 然后发现这个模式我之前还真没遇到过,因此呢,就学习了一下,就出来了这一篇文章。
5266 0
【密码学】一文读懂XTS模式
|
Web App开发 Rust 算法
【密码学】一文读懂ChaCha20
好久没写新的加密算法的原理了, 这次所选取的加密算法结构比较简单, 一起来看一下吧。
7809 0
【密码学】一文读懂ChaCha20
|
算法 数据安全/隐私保护
【密码学】一文读懂Whirlpool
首先呢,祝大家今晚节日快乐,Whirlpool是由Vincent Rijmen(高级加密标准的联合创始人)和Paulo S.L.M.Barreto设计的,后者于2000年首次提出了它。
1119 0
【密码学】一文读懂Whirlpool
|
Rust 算法 安全
【密码学】一文读懂MurMurHash2
上次我们聊过了一代的MurMurHash算法,是的,我又来水文章了,今天呢,接着来聊一下二代的MurMurHash算法,二代算法的整体结构实际上和一代算法差不太多,只是对于每一轮数据的处理过程当中的运算有一些差异,算法的来源依然是来自于Google官网给提供的源码,对着源码看的结构,对于这个算法呢,有两个版本,一个是32位的,一个是64位的,对于32位的算法和64位的算法,区别在于两个初始的魔数不同,整体运算过程还是十分相似的。
2224 0
【密码学】一文读懂MurMurHash2
|
Rust 算法 Go
【密码学】一文读懂MurMurHash3
本文应该是MurMurHash算法介绍的最后一篇,来一起看一下最新的MurMurHash算法的具体过程,对于最新的算法来说,整个流程和之前的其实也比较相似,这里从维基百科当中找到了伪代码,也就不贴出来Google官方给出的推荐代码了,先来看一下维基百科给出的伪代码,这里只有32位的伪代码。
2191 0
【密码学】一文读懂MurMurHash3
|
Rust 算法 网络安全
【密码学】一文读懂CMAC
介于上一篇文章比较水,然后这个和上一篇也比较相似,CMAC是为了解决DAA当中安全性不足的问题而出现的,这个算法一共有三个密钥,K, K1, K2, 其中K1和K2可以由K导出,接下来就来一起看一下CMAC的具体过程吧,这一篇文章其实也不长。
3786 0
【密码学】一文读懂CMAC
|
算法 安全 Go
【密码学】一文读懂HKDF
我这又来水一篇文章,来聊一下HKDF(基于HMAC的密钥导出函数)。密钥派生函数是密钥管理的组成部分,他的目标是通过一些初始的数据派生出来密码学安全的随机密钥。
3120 1
【密码学】一文读懂HKDF
|
存储 安全 算法
为什么人人都要懂点密码学
人类进入二十一世纪以来,随着计算机和移动设备的普及高速发展,我们的社会已经高度信息化,为了防止信息被窃取、修改,就需要对信息的存储、传递进行加密处理,而加密就需要使用到加密算法,解密需要使用密码才可以看到原文。
236 1
|
算法 索引
算法入门很简单:算法题的破解之道上篇
滑动窗口用于对给定数组和链表的特定窗口大小执行所需的操作 1.问题输入是线性数据结构。例如链表、数组或字符串 2.要求找到最长/最短的子字符串,子数组或所需的值