【公众号官网单页源码】【打水软件源码】【pt 站 源码】密码哈希函数c源码_密码哈希函数c源码是多少

时间:2024-11-15 06:28:37 分类:轻社区 源码 来源:微信公众平台 源码

1.如何安全地存储密码
2.C语言实现HashMap
3.密码学HASH函数的密码码密码哈安全性要求是有哪些?
4.计算机管理员密码
5.哈希排序 c表示
6.C语言也能使用的哈希表·uthash

密码哈希函数c源码_密码哈希函数c源码是多少

如何安全地存储密码

       ã€€ä¿æŠ¤å¯†ç æœ€å¥½çš„的方式就是使用带盐的密码hash(salted password hashing).对密码进行hash操作是一件很简单的事情,但是很多人都犯了错。接下来我希望可以详细的阐述如何恰当的对密码进行hash,以及为什么要这样做。

       ã€€ã€€é‡è¦æé†’

       ã€€ã€€å¦‚果你打算自己写一段代码来进行密码hash,那么赶紧停下吧。这样太容易犯错了。这个提醒适用于每一个人,不要自己写密码的hash算法 !关于保存密码的问题已经有了成熟的方案,那就是使用phpass或者本文提供的源码。

       ã€€ã€€ä»€ä¹ˆæ˜¯hash

       ã€€ã€€hash("hello") = 2cfdba5fb0aeeb2ac5b9ee1be5c1faeb

       hash("hbllo") = ccdfacfad6affaafe7ddf

       hash("waltz") = c0efcbc6bd9ecfbfda8ef

       ã€€ã€€Hash算法是一种单向的函数。它可以把任意数量的数据转换成固定长度的“指纹”,这个过程是不可逆的。而且只要输入发生改变,哪怕只有一个bit,输出的hash值也会有很大不同。这种特性恰好合适用来用来保存密码。因为我们希望使用一种不可逆的算法来加密保存的密码,同时又需要在用户登陆的时候验证密码是否正确。

       ã€€ã€€åœ¨ä¸€ä¸ªä½¿ç”¨hash的账号系统中,用户注册和认证的大致流程如下:

       ã€€ã€€1, 用户创建自己的账号

       2, 用户密码经过hash操作之后存储在数据库中。没有任何明文的密码存储在服务器的硬盘上。

       3, 用户登陆的时候,将用户输入的密码进行hash操作后与数据库里保存的密码hash值进行对比。

       4, 如果hash值完全一样,则认为用户输入的密码是正确的。否则就认为用户输入了无效的密码。

       5, 每次用户尝试登陆的时候就重复步骤3和步骤4。

       ã€€ã€€åœ¨æ­¥éª¤4的时候不要告诉用户是账号还是密码错了。只需要显示一个通用的提示,比如账号或密码不正确就可以了。这样可以防止攻击者枚举有效的用户名。

       ã€€ã€€è¿˜éœ€è¦æ³¨æ„çš„是用来保护密码的hash函数跟数据结构课上见过的hash函数不完全一样。比如实现hash表的hash函数设计的目的是快速,但是不够安全。只有加密hash函数(cryptographic hash functions)可以用来进行密码的hash。这样的函数有SHA, SHA, RipeMD, WHIRLPOOL等。

       ã€€ã€€ä¸€ä¸ªå¸¸è§çš„观念就是密码经过hash之后存储就安全了。这显然是不正确的。有很多方式可以快速的从hash恢复明文的密码。还记得那些md5破解网站吧,只需要提交一个hash,不到一秒钟就能知道结果。显然,单纯的对密码进行hash还是远远达不到我们的安全需求。下一部分先讨论一下破解密码hash,获取明文常见的手段。

       ã€€ã€€å¦‚何破解hash

       ã€€ã€€å­—典和暴力破解攻击(Dictionary and Brute Force Attacks)

       ã€€ã€€æœ€å¸¸è§çš„破解hash手段就是猜测密码。然后对每一个可能的密码进行hash,对比需要破解的hash和猜测的密码hash值,如果两个值一样,那么之前猜测的密码就是正确的密码明文。猜测密码攻击常用的方式就是字典攻击和暴力攻击。

       ã€€ã€€Dictionary Attack

       Trying apple : failed

       Trying blueberry : failed

       Trying justinbeiber : failed

       ...

       Trying letmein : failed

       Trying s3cr3t : success!

       ã€€ã€€å­—典攻击是将常用的密码,单词,短语和其他可能用来做密码的字符串放到一个文件中,然后对文件中的每一个词进行hash,将这些hash与需要破解的密码hash比较。这种方式的成功率取决于密码字典的大小以及字典的是否合适。

       ã€€ã€€Brute Force Attack

       Trying aaaa : failed

       Trying aaab : failed

       Trying aaac : failed

       ...

       Trying acdb : failed

       Trying acdc : success!

       ã€€ã€€æš´åŠ›æ”»å‡»å°±æ˜¯å¯¹äºŽç»™å®šçš„密码长度,尝试每一种可能的字符组合。这种方式需要花费大量的计算机时间。但是理论上只要时间足够,最后密码一定能够破解出来。只是如果密码太长,破解花费的时间就会大到无法承受。

       ã€€ã€€ç›®å‰æ²¡æœ‰æ–¹å¼å¯ä»¥é˜»æ­¢å­—典攻击和暴力攻击。只能想办法让它们变的低效。如果你的密码hash系统设计的是安全的,那么破解hash唯一的方式就是进行字典或者暴力攻击了。

       ã€€ã€€æŸ¥è¡¨ç ´è§£(Lookup Tables)

       ã€€ã€€å¯¹äºŽç‰¹å®šçš„hash类型,如果需要破解大量hash的话,查表是一种非常有效而且快速的方式。它的理念就是预先计算(pre-compute)出密码字典中每一个密码的hash。然后把hash和对应的密码保存在一个表里。一个设计良好的查询表结构,即使存储了数十亿个hash,每秒钟仍然可以查询成百上千个hash。

       ã€€ã€€å¦‚果你想感受下查表破解hash的话可以尝试一下在CraskStation上破解下下面的sha hash。

       ã€€ã€€cb4b0aafcddfee9fbb8bcf3a7f0dbaadfc

       eacbadcdc7d8fbeb7c7bd3a2cbdbfcbbbae7

       e4ba5cbdce6cd1cfa3bd8dabcb3ef9f

       b8b8acfcbcac7bfba9fefeebbdcbd

       ã€€ã€€åå‘查表破解(Reverse Lookup Tables)

       ã€€ã€€Searching for hash(apple) in users' hash list... : Matches [alice3, 0bob0, charles8]

       Searching for hash(blueberry) in users' hash list... : Matches [usr, timmy, john]

       Searching for hash(letmein) in users' hash list... : Matches [wilson, dragonslayerX, joe]

       Searching for hash(s3cr3t) in users' hash list... : Matches [bruce, knuth, john]

       Searching for hash(z@hjja) in users' hash list... : No users used this password

       ã€€ã€€è¿™ç§æ–¹å¼å¯ä»¥è®©æ”»å‡»è€…不预先计算一个查询表的情况下同时对大量hash进行字典和暴力破解攻击。

       ã€€ã€€é¦–先,攻击者会根据获取到的数据库数据制作一个用户名和对应的hash表。然后将常见的字典密码进行hash之后,跟这个表的hash进行对比,就可以知道用哪些用户使用了这个密码。这种攻击方式很有效果,因为通常情况下很多用户都会有使用相同的密码。

       ã€€ã€€å½©è™¹è¡¨ (Rainbow Tables)

       ã€€ã€€å½©è™¹è¡¨æ˜¯ä¸€ç§ä½¿ç”¨ç©ºé—´æ¢å–时间的技术。跟查表破解很相似。只是它牺牲了一些破解时间来达到更小的存储空间的目的。因为彩虹表使用的存储空间更小,所以单位空间就可以存储更多的hash。彩虹表已经能够破解8位长度的任意md5hash。彩虹表具体的原理可以参考/

       ã€€ã€€ä¸‹ä¸€ç« èŠ‚我们会讨论一种叫做“盐”(salting)的技术。通过这种技术可以让查表和彩虹表的方式无法破解hash。

       ã€€ã€€åŠ ç›(Adding Salt)

       ã€€ã€€hash("hello") = 2cfdba5fb0aeeb2ac5b9ee1be5c1faeb

       hash("hello" + "QxLUF1bgIAdeQX") = 9ecfaebfe5ed3bacffed1

       hash("hello" + "bv5PehSMfVCd") = d1d3ec2e6ffddedab8eac9eaaefab

       hash("hello" + "YYLmfY6IehjZMQ") = ac3cb9eb9cfaffdc8aedb2c4adf1bf

       ã€€ã€€æŸ¥è¡¨å’Œå½©è™¹è¡¨çš„方式之所以有效是因为每一个密码的都是通过同样的方式来进行hash的。如果两个用户使用了同样的密码,那么一定他们的密码hash也一定相同。我们可以通过让每一个hash随机化,同一个密码hash两次,得到的不同的hash来避免这种攻击。

       ã€€ã€€å…·ä½“的操作就是给密码加一个随即的前缀或者后缀,然后再进行hash。这个随即的后缀或者前缀成为“盐”。正如上面给出的例子一样,通过加盐,相同的密码每次hash都是完全不一样的字符串了。检查用户输入的密码是否正确的时候,我们也还需要这个盐,所以盐一般都是跟hash一起保存在数据库里,或者作为hash字符串的一部分。

       ã€€ã€€ç›ä¸éœ€è¦ä¿å¯†ï¼Œåªè¦ç›æ˜¯éšæœºçš„话,查表,彩虹表都会失效。因为攻击者无法事先知道盐是什么,也就没有办法预先计算出查询表和彩虹表。如果每个用户都是使用了不同的盐,那么反向查表攻击也没法成功。

       ã€€ã€€ä¸‹ä¸€èŠ‚,我们会介绍一些盐的常见的错误实现。

       ã€€ã€€é”™è¯¯çš„方式:短的盐和盐的复用

       ã€€ã€€æœ€å¸¸è§çš„错误实现就是一个盐在多个hash中使用或者使用的盐很短。

       ã€€ã€€ç›çš„复用(Salt Reuse)

       ã€€ã€€ä¸ç®¡æ˜¯å°†ç›ç¡¬ç¼–码在程序里还是随机一次生成的,在每一个密码hash里使用相同的盐会使这种防御方法失效。因为相同的密码hash两次得到的结果还是相同的。攻击者就可以使用反向查表的方式进行字典和暴力攻击。只要在对字典中每一个密码进行hash之前加上这个固定的盐就可以了。如果是流行的程序的使用了硬编码的盐,那么也可能出现针对这种程序的这个盐的查询表和彩虹表,从而实现快速破解hash。

       ã€€ã€€ç”¨æˆ·æ¯æ¬¡åˆ›å»ºæˆ–者修改密码一定要使用一个新的随机的盐

       ã€€ã€€çŸ­çš„盐

       ã€€ã€€å¦‚果盐的位数太短的话,攻击者也可以预先制作针对所有可能的盐的查询表。比如,3位ASCII字符的盐,一共有xx = ,种可能性。看起来好像很多。假如每一个盐制作一个1MB的包含常见密码的查询表,,个盐才是GB。现在买个1TB的硬盘都只要几百块而已。

       ã€€ã€€åŸºäºŽåŒæ ·çš„理由,千万不要用用户名做为盐。虽然对于每一个用户来说用户名可能是不同的,但是用户名是可预测的,并不是完全随机的。攻击者完全可以用常见的用户名作为盐来制作查询表和彩虹表破解hash。

       ã€€ã€€æ ¹æ®ä¸€äº›ç»éªŒå¾—出来的规则就是盐的大小要跟hash函数的输出一致。比如,SHA的输出是bits(bytes),盐的长度也应该是个字节的随机数据。

       ã€€ã€€é”™è¯¯çš„方式:双重hash和古怪的hash函数

       ã€€ã€€è¿™ä¸€èŠ‚讨论另外一个常见的hash密码的误解:古怪的hash算法组合。人们可能解决的将不同的hash函数组合在一起用可以让数据更安全。但实际上,这种方式带来的效果很微小。反而可能带来一些互通性的问题,甚至有时候会让hash更加的不安全。本文一开始就提到过,永远不要尝试自己写hash算法,要使用专家们设计的标准算法。有些人会觉得通过使用多个hash函数可以降低计算hash的速度,从而增加破解的难度。通过减慢hash计算速度来防御攻击有更好的方法,这个下文会详细介绍。

       ã€€ã€€ä¸‹é¢æ˜¯ä¸€äº›ç½‘上找到的古怪的hash函数组合的样例。

       ã€€ã€€md5(sha1(password))

       md5(md5(salt) + md5(password))

       sha1(sha1(password))

       sha1(str_rot(password + salt))

       md5(sha1(md5(md5(password) + sha1(password)) + md5(password)))

       ã€€ã€€ä¸è¦ä½¿ç”¨ä»–们!

       ã€€ã€€æ³¨æ„ï¼šè¿™éƒ¨åˆ†çš„内容其实是存在争议的!我收到过大量邮件说组合hash函数是有意义的。因为如果攻击者不知道我们用了哪个函数,就不可能事先计算出彩虹表,并且组合hash函数需要更多的计算时间。

       ã€€ã€€æ”»å‡»è€…如果不知道hash算法的话自然是无法破解hash的。但是考虑到Kerckhoffs’s principle,攻击者通常都是能够接触到源码的(尤其是免费软件和开源软件)。通过一些目标系统的密码–hash对应关系来逆向出算法也不是非常困难。

       ã€€ã€€å¦‚果你想使用一个标准的”古怪”的hash函数,比如HMAC,是可以的。但是如果你的目的是想减慢hash的计算速度,那么可以读一下后面讨论的慢速hash函数部分。基于上面讨论的因素,最好的做法是使用标准的经过严格测试的hash算法。

       ã€€ã€€hash碰撞(Hash Collisions)

       ã€€ã€€å› ä¸ºhash函数是将任意数量的数据映射成一个固定长度的字符串,所以一定存在不同的输入经过hash之后变成相同的字符串的情况。加密hash函数(Cryptographic hash function)在设计的时候希望使这种碰撞攻击实现起来成本难以置信的高。但时不时的就有密码学家发现快速实现hash碰撞的方法。最近的一个例子就是MD5,它的碰撞攻击已经实现了。

       ã€€ã€€ç¢°æ’žæ”»å‡»æ˜¯æ‰¾åˆ°å¦å¤–一个跟原密码不一样,但是具有相同hash的字符串。但是,即使在相对弱的hash算法,比如MD5,要实现碰撞攻击也需要大量的算力(computing power),所以在实际使用中偶然出现hash碰撞的情况几乎不太可能。一个使用加盐MD5的密码hash在实际使用中跟使用其他算法比如SHA一样安全。不过如果可以的话,使用更安全的hash函数,比如SHA, SHA, RipeMD, WHIRLPOOL等是更好的选择。

       ã€€ã€€æ­£ç¡®çš„方式:如何恰当的进行hash

       ã€€ã€€è¿™éƒ¨åˆ†ä¼šè¯¦ç»†è®¨è®ºå¦‚何恰当的进行密码hash。第一个章节是最基础的,这章节的内容是必须的。后面一个章节是阐述如何继续增强安全性,让hash破解变得异常困难。

       ã€€ã€€åŸºç¡€ï¼šä½¿ç”¨åŠ ç›hash

       ã€€ã€€æˆ‘们已经知道恶意黑客可以通过查表和彩虹表的方式快速的获得hash对应的明文密码,我们也知道了通过使用随机的盐可以解决这个问题。但是我们怎么生成盐,怎么在hash的过程中使用盐呢?

       ã€€ã€€ç›è¦ä½¿ç”¨å¯†ç å­¦ä¸Šå¯é å®‰å…¨çš„伪随机数生成器(Cryptographically Secure Pseudo-Random Number Generator (CSPRNG))来产生。CSPRNG跟普通的伪随机数生成器比如C语言中的rand(),有很大不同。正如它的名字说明的那样,CSPRNG提供一个高标准的随机数,是完全无法预测的。我们不希望我们的盐能够被预测到,所以一定要使用CSPRNG。

C语言实现HashMap

       本文将阐述哈希表的基本原理,并以C语言为例,哈希函数展示如何实现一个完整的源源码HashMap。哈希表是希函一种高效的数据结构,支持快速查找、多少插入和删除操作,密码码密码哈公众号官网单页源码其核心在于通过哈希函数将键值对映射至数组中的哈希函数特定位置。

       哈希表通常包含线性存储和链式存储两种存储方式。源源码线性存储,希函如数组,多少通过索引直接定位数据;链式存储,密码码密码哈如链表或二叉树,哈希函数通过指针链接数据。源源码哈希表采用线性存储,希函利用哈希函数建立键值对与存储位置之间的多少关联,实现双向可逆过程。

       键值对结构的定义是哈希表实现的基础。为解决哈希冲突,文章提出两种策略:再散列法和链地址法。再散列法通过多次计算哈希函数避免冲突,但可能导致数据堆积。链地址法则在发生冲突时,打水软件源码将元素存储在冲突位置的链表中,增加查找时的额外操作。

       在C语言中实现HashMap时,通常采用动态数组作为存储空间,数组每一项存储冲突链表的头节点。HashMap包含关键属性和方法,如存储数量(size)、数组大小(listSize)、键值对结构、哈希函数和判等函数等。这些功能通过静态成员实现,便于封装和调用。

       哈希函数是HashMap性能的关键,它将键值映射至特定索引。文章提供一个通用的哈希函数,适用于字符串类型键值。put方法用于插入键值对,通过计算哈希码判断存储位置。当size超过listSize时,HashMap将自动扩容,以减少冲突,pt 站 源码提高查找效率。

       HashMap还包含遍历、迭代等功能,通过Iterator接口实现。运行测试表明,实现的HashMap支持高效操作。

       接下来,我们将探讨C语言中的异常处理机制,进一步丰富C语言编程的实践。

密码学HASH函数的安全性要求是有哪些?

       1、已知哈希函数的输出,要求它的输入是困难的,即已知c=Hash(m),求m是困难的。这表明函数应该具有单向性。

       2、已知m,计算Hash(m)是容易的。这表明函数应该具有快速性。

       3、已知,似水年源码构造m2使Hash(m2)=c1是困难的。这表明函数应该具有抗碰撞性。

       4、c=Hash(m),c的每一比特都与m的每一比特有关,并有高度敏感性。即每改变m的一比特,都将对c产生明显影响。这表明函数应该具有雪崩性。

       5、作为一种数字签名,还要求哈希函数除了信息m自身之外,应该基于发信方的秘密信息对信息m进行确认。

       6、接受的输入m数据没有长度限制;对输入任何长度的m数据能够生成该输入报文固定长度的输出。

计算机管理员密码

       计算机中的密码是以哈希算法存储的,无法逆向计算。

       1. Windows系统下的hash密码格式

        Windows系统下的hash密码格式为:用户名称:RID:LM-HASH值:NT-HASH值,例如:Administrator::CDBFEAAAD3BBEE:C5DCAACE6CC::: 表示用户名称为:Administrator,RID为:,LM-HASH值为:CDBFEAAAD3BBEE,网站源码lnwfymNT-HASH值为:C5DCAACE6CC。

       2. Windows下LM Hash值生成原理

        假设明文口令是“Welcome”,首先将其转换为大写“WELCOME”,然后将口令字符串大写后的字符串变换成二进制串:“WELCOME” -> CF4D。如果明文口令经过大写变换后的二进制字符串不足字节,则需要在其后添加0x补足字节。然后将数据切割成两组7字节的数据,分别经 str_to_key()函数处理得到两组8字节数据:CF4D -> str_to_key() -> AAA -> str_to_key() -> 。这两组8字节数据将作为DESKEY对魔术字符串“KGS!@#$%”进行标准DES加密。最后将加密后的这两组数据简单拼接,就得到了最后的LM Hash。

       3. Windows下NTLM Hash生成原理

        由于IBM设计的LM Hash算法存在几个弱点,微软在保持向后兼容性的同时提出了自己的挑战响应机制,NTLM Hash应运而生。假设明文口令是“”,首先将其转换为Unicode字符串,与LM Hash算法不同,这次不需要添加0x补足字节。"" -> 。从ASCII串转换成Unicode串时,使用little-endian序,微软在设计整个SMB协议时就没考虑过big-endian序,ntoh*()、hton*()函数不宜用在SMB报文解码中。0x之前的标准ASCII码转换成Unicode码,就是简单地从0x?变成0x?。此类标准ASCII串按little-endian序转换成Unicode串,就是简单地在原有每个字符字节之后添加0x。对所获取的Unicode串进行标准MD4单向哈希,无论数据源有多少字节,MD4固定产生-bit的哈希值,字节 -> 进行标准MD4单向哈希 -> EDBDB5FDC5E9CBAD4。就得到了最后的NTLM Hash。

       与LM Hash算法相比,明文口令大小写敏感,无法根据NTLM Hash判断原始明文口令是否小于8字节,摆脱了魔术字符串"KGS!@#$%"。 MD4是真正的单向哈希函数,穷举作为数据源出现的明文,难度较大。

       附录1 str_to_key()函数

        str_to_key()函数的C语言描述程序如下:

       ```c

       #include

       #include

       #include

       /** 读取形如"AABBCCDDEEFF"这样的进制数字串,主坦培调者自己进行形参的边界检查 */

       static void readhexstring(const unsigned char *src, unsigned char *dst, unsigned int len) {

        unsigned int i;

        unsigned char str[3];

        str[2] = '\0';

        for (i = 0; i < len; i++) {

        str[0] = src[i * 2];

        str[1] = src[i * 2 + 1];

        dst[i] = (unsigned char)strtoul(str, NULL, );

        }

       }

       /* from The Samba Team's source/libsmb/smbdes.c */

       static void str_to_key(const unsigned char *str, unsigned char *key) {

        unsigned int i;

        key[0] = str[0] >> 1;

        key[1] = (str[0] & 0x) < 6 | (str[1] >> 2);

        key[2] = (str[1] & 0x) < 5 | (str[2] >> 3);

        key[3] = (str[2] & 0x) < 4 | (str[3] >> 4);

        key[4] = (str[3] & 0x0F) < 3 | (str[4] >> 5);

        key[5] = (str[4] & 0x1F) < 2 | (str[5] >> 6);

        key[6] = (str[5] & 0x3F) < 1 | (str[6] >> 7);

        key[7] = str[6] & 0x7F;

        for (i = 0; i < 8; i++) {

        key[i] = (key[i] << 1);

        }

       }

       int main(int argc, char *argv[]) {

        unsigned int i;

        unsigned char buf_0[];

        unsigned char buf_1[];

        if (argc != 2) {

        fprintf(stderr, "Usage: %s \n", argv[0]);

        return (EXIT_FAILURE);

        }

        memset(buf_0, 0, sizeof(buf_0));

        memset(buf_1, 0, sizeof(buf_1));

        i = strlen(argv[1]) / 2;

        readhexstring(argv[1], buf_0, i);

        for (i = 0; i < sizeof(buf_0); i++) {

        fprintf(stderr, "%X", buf_0[i]);

        }

        fprintf(stderr, "\n");

        str_to_key(buf_0, buf_1);

        str_to_key(buf_0 + 7, buf_1 + 8);

        str_to_key(buf_0 + , buf_1 + );

        for (i = 0; i < sizeof(buf_1); i++) {

        fprintf(stderr, "%X", buf_1[i]);

        }

        fprintf(stderr, "\n");

        return (EXIT_SUCCESS);

       }

       ```

哈希排序 c表示

       #include<stdlib.h>

       #include<stdio.h>

       #define NULL 0

       typedef int KeyType;

       typedef struct

       {

        KeyType key;

       } ElemType;

       int haxi(KeyType key,int m) /*根据哈希表长m,构造除留余数法的哈希函数haxi*/

       {

        int i,p,flag;

        for(p=m;p>=2;p--) /*p为不超过m的最大素数*/

        {

        for(i=2,flag=1;i<=p/2&&flag;i++)

        if(p%i==0)

        flag=0;

        if(flag==1)

        break;

        }

        return key%p; /*哈希函数*/

       }

       void inputdata(ElemType **ST,int n) /*从键盘输入n个数据,存入数据表ST(采用动态分配的数组空间)*/

       {

        KeyType key;

        int i;

        (*ST)=(ElemType*)malloc(n*sizeof(ElemType));

        printf("\nPlease input %d data:",n);

        for(i=1;i<=n;i++)

        scanf("%d",&((*ST)[i].key));

       }

       void createhaxi(ElemType **HASH,ElemType *ST,int n,int m) /*由数据表ST构造哈希表HASH,n、m分别为数据集合ST和哈希表的长度*/

       {

        int i,j;

        (*HASH)=(ElemType *)malloc(m*sizeof(ElemType));

        for(i=0;i<m;i++)

        (*HASH)[i].key=NULL; /*初始化哈希为空表(以0表示空)*/

        for(i=0;i<n;i++)

        {

        j=haxi(ST[i].key,m); /*获得直接哈希地址*/

        while((*HASH)[j].key!=NULL)

        j=(j+1)%m; /*用线性探测再散列技术确定存放位置*/

        (*HASH)[j].key=ST[i].key; /*将元素存入哈希表的相应位置*/

        }

       }

       int search(ElemType *HASH,KeyType key,int m,int *time) /*在表长为m的哈希表中查找关键字等于key的元素,并用time记录比较次数,若查找成功,函数返回值为其在哈希表中的位置,否则返回-1*/

       {

        int i;

        *time=1;

        i=haxi(key,m);

        while(HASH[i].key!=0&&HASH[i].key!=key)

        {

        i++;

        ++*time;

        }

        if(HASH[i].key==0)

        return -1;

        else

        return i;

       }

       main()

       {

        ElemType *ST,*HASH;

        KeyType key;

        int i,n,m,stime,time;

        char ch;

        printf("\nPlease input n&&m(n<=m)"); /*输入关键字集合元素个数和哈希表长*/

        scanf("%d%d",&n,&m);

        inputdata(&ST,n); /*输入n个数据*/

        createhaxi(&HASH,ST,n,m); /*创建哈希表*/

        printf("\nThe haxi Table is\n"); /*输出已建立的哈希表*/

        for(i=0;i<m;i++)

        printf("%5d",i);

        printf("\n");

        for(i=0;i<m;i++)

        printf("%5d",HASH[i].key);

        do /*哈希表的查找,可进行多次查找*/

        {

        printf("\nInput the key you want to search:");

        scanf("%d",&key);

        i=search(HASH,key,m,&time);

        if(i!=-1) /*查找成功*/

        {

        printf("\nSuccess,the position is %d",i);

        printf("\nThe time of compare is %d",time);

        }

        else /*查找失败*/

        {

        printf("\nUnsuccess");

        printf("\nThe time of compare is %d",time);

        }

        printf("\nContiue(y/n):\n"); /*是否继续查找yes or no*/

        ch=getch();

        }

        while(ch=='y'||ch=='Y'); /*计算表在等概率情况下的平均查找长度,并输出*/

        for(stime=0,i=0;i<n;i++)

        {

        search(HASH,ST[i].key,m,&time);

        stime+=time;

        }

        printf("\nThe Average Search Length is %5.2f",(float)stime/n);

        ch=getch();

       }

C语言也能使用的哈希表·uthash

       哈希表在数据结构领域中扮演着重要角色,因其高效查找的特性被广泛应用于算法和项目中。C语言虽然原生没有内置哈希表,但开发者们可以借助uthash这个开源库来实现。uthash是一个专为C语言设计的哈希表,官网地址为troydhanson.github.io。

       要使用uthash,首先需要下载uthash.h文件并在代码中包含。关键步骤是定义自定义的哈希节点结构,并利用uthash提供的宏函数进行增删改查操作。增删操作会直接修改hashtable结构,所以必须传入原对象。删除操作仅标记删除,源对象内存需手动释放。哈希表仅标记节点存在,不管理实体对象,修改操作则直接在节点中进行。

       查找操作需要指定键值类型的地址,成功时返回节点地址,失败则返回NULL。uthash支持多种键类型,如指针和字符串,操作方式与整数键类似。在实际应用中,例如LeetCode的“两数之和”问题,uthash可以简化存储和查找过程。

       尽管uthash的使用简单,依赖宏函数,可能导致调试困难,但其轻量级的特性使其在实际项目中仍然值得学习。总的来说,uthash是一个推荐学习的C语言哈希表实现工具。作者天赐细莲和编辑平平提醒,如需转载请尊重版权,联系“力扣”获取许可。

C语言实例_获取文件MD5值

       MD5(Message Digest Algorithm 5)是一种常用的哈希函数算法,它将任意长度的数据转换为一个固定长度(通常为位)的唯一哈希值,即MD5值。MD5算法因其高度可靠性和广泛应用而受到重视。其特点包括不可逆性、唯一性和高效性。MD5值的应用场景丰富,包括数据完整性验证、密码存储、安全认证及数据指纹等。

       获取数据或文件的MD5值,可以通过使用第三方库,如OpenSSL。以下示例展示了如何在C语言中使用OpenSSL计算数据或文件的MD5值。

       使用OpenSSL计算数据MD5值,首先需要包含相应的头文件,并创建一个子函数来计算数据的MD5值。此子函数接收三个参数:待计算的数据指针、数据长度以及存储MD5值的数组。完整的程序包含调用此子函数并打印MD5值,程序将输出数据的MD5值。

       同样,使用OpenSSL计算文件的MD5值,需要包含相关头文件,并创建一个子函数来计算文件的MD5值。此子函数接收两个参数:待计算的文件名以及存储MD5值的数组。完整的程序展示如何打开指定文件并计算其MD5值,程序将输出文件的MD5值。

       若自行实现MD5算法,需要理解其复杂性,涉及位操作、逻辑运算、位移等。以下是一个简化版本的纯C语言实现,可用于计算给定字符串的MD5值。程序接收待计算的数据存储在字符串中,并根据需要调整数据长度。