SHA算法(及示例)演变历史

安全散列算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族,是FIPS所认证的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。

家族成员

       SHA家族的五个算法,分别是SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512,由美国国家安全局(NSA)所设计,并由美国国家标准与技术研究院(NIST)发布;是美国的政府标准。后四者有时并称为SHA-2。SHA-1在许多安全协定中广为使用,包括TLSSSLPGPSSH、S/MIME和IPsec,曾被视为是MD5(更早之前被广为使用的杂凑函数)的后继者。但SHA-1的安全性如今被密码学家严重质疑;虽然至今尚未出现对SHA-2有效的攻击,它的算法跟SHA-1基本上仍然相似;因此有些人开始发展其他替代的杂凑算法。 [1]

SHA-0和1

       最初载明的算法于1993年发布,称做安全杂凑标准(Secure Hash Standard),FIPS PUB 180。这个版本常被称为SHA-0。它在发布之后很快就被NSA撤回,并且由1995年发布的修订版本FIPS PUB 180-1(通常称为SHA-1)取代。SHA-1和SHA-0的算法只在压缩函数的讯息转换部分差了一个位元的循环位移。根据NSA的说法,它修正了一个在原始算法中会降低杂凑安全性的弱点。然而NSA并没有提供任何进一步的解释或证明该弱点已被修正。而后SHA-0和SHA-1的弱点相继被攻破,SHA-1似乎是显得比SHA-0有抵抗性,这多少证实了NSA当初修正算法以增进安全性的声明。
SHA-0和SHA-1可将一个最大2的64次方位元的讯息,转换成一串160位元的讯息摘要;其设计原理相似于MIT教授Ronald L. Rivest所设计的密码学杂凑算法MD4MD5

SHA-0破解

        在CRYPTO 98上,两位法国研究者提出一种对SHA-0的攻击方式:在261的计算复杂度之内,就可以发现一次碰撞(即两个不同的讯息对应到相同的讯息摘要);这个数字小于生日攻击法所需的2的80次方,也就是说,存在一种算法,使其安全性不到一个理想的杂凑函数抵抗攻击所应具备的计算复杂度。
2004年时,Biham和 Chen也发现了SHA-0的近似碰撞,也就是两个讯息可以杂凑出几乎相同的数值;其中162位元中有142位元相同。他们也发现了SHA-0的完整碰撞(相对于近似碰撞),将本来需要80次方的复杂度降低到62次方。
2004年8月12日,Joux, Carribault, Lemuet和Jalby宣布找到SHA-0算法的完整碰撞的方法,这是归纳Chabaud和Joux的攻击所完成的结果。发现一个完整碰撞只需要251的计算复杂度。他们使用的是一台有256颗Itanium2处理器的超级电脑,约耗80,000 CPU工时。
2004年8月17日,在CRYPTO 2004的Rump会议上,王小云,冯登国、来学嘉,和于红波宣布了攻击MD5、SHA-0 和其他杂凑函数的初步结果。他们攻击SHA-0的计算复杂度是2的40次方,这意味着他们的攻击成果比Joux还有其他人所做的更好。请参见MD5 安全性。2005年二月,王小云和殷益群、于红波再度发表了对SHA-0破密的算法,可在2的39次方的计算复杂度内就找到碰撞。

SHA-1破解

       鉴于SHA-0的破密成果,专家们建议那些计划利用SHA-1实作密码系统的人们也应重新考虑。在2004年CRYPTO会议结果公布之后,NIST即宣布他们将逐渐减少使用SHA-1,改以SHA-2取而代之。
2005年,Rijmen和Oswald发表了对SHA-1较弱版本(53次的加密循环而非80次)的攻击:在2的80次方的计算复杂度之内找到碰撞。
2005年二月,王小云、殷益群及于红波发表了对完整版SHA-1的攻击,只需少于2的69次方的计算复杂度,就能找到一组碰撞。(利用生日攻击法找到碰撞需要2的80次方的计算复杂度。)
这篇论文的作者们写道;“我们的破密分析是以对付SHA-0的差分攻击、近似碰撞、多区块碰撞技术、以及从MD5算法中寻找碰撞的讯息更改技术为基础。没有这些强力的分析工具,SHA-1就无法破解。”此外,作者还展示了一次对58次加密循环SHA-1的破密,在2的33次方个单位操作内就找到一组碰撞。完整攻击方法的论文发表在2005年八月的CRYPTO会议中。
殷益群在一次面谈中如此陈述:“大致上来说,我们找到了两个弱点:其一是前置处理不够复杂;其二是前20个循环中的某些数学运算会造成不可预期的安全性问题。”
2005年8月17日的CRYPTO会议尾声中王小云、姚期智、姚储枫再度发表更有效率的SHA-1攻击法,能在2的63次方个计算复杂度内找到碰撞。
2006年的CRYPTO会议上,Christian Rechberger和Christophe De Cannière宣布他们能在容许攻击者决定部分原讯息的条件之下,找到SHA-1的一个碰撞。
在密码学的学术理论中,任何攻击方式,其计算复杂度若少于暴力搜寻法所需要的计算复杂度,就能被视为针对该密码系统的一种破密法;但这并不表示该破密法已经可以进入实际应用的阶段。
就应用层面的考量而言,一种新的破密法出现,暗示着将来可能会出现更有效率、足以实用的改良版本。虽然这些实用的破密法版本根本还没诞生,但确有必要发展更强的杂凑算法来取代旧的算法。在“碰撞”攻击法之外,另有一种反译攻击法(Pre-image attack),就是由杂凑出的字串反推原本的讯息;反译攻击的严重性更在碰撞攻击之上,但也更困难。在许多会应用到密码杂凑的情境(如用户密码的存放、文件的数位签章等)中,碰撞攻击的影响并不是很大。举例来说,一个攻击者可能不会只想要伪造一份一模一样的文件,而会想改造原来的文件,再附上合法的签章,来愚弄持有私密金钥的验证者。另一方面,如果可以从密文中反推未加密前的使用者密码,攻击者就能利用得到的密码登入其他使用者的帐户,而这种事在密码系统中是不能被允许的。但若存在反译攻击,只要能得到指定使用者密码杂凑过后的字串(通常存在影档中,而且可能不会透露原密码资讯),就有可能得到该使用者的密码。 [1] 

SHA-1算法

以下是 SHA-1 算法的伪代码:
Initialize variables:
h0 := 0x67452301
h1 := 0xEFCDAB89
h2 := 0x98BADCFE
h3 := 0x10325476
h4 := 0xC3D2E1F0
Pre-processing:
append the bit '1' to the message
append k bits '0', where k is the minimum number >= 0 such that the resulting message
length (in bits) is congruent to 448 (mod 512)
append length of message (before pre-processing), in bits, as 64-bit big-endian integer
Process the message in successive 512-bit chunks:
break message into 512-bit chunks
for each chunk
break chunk into sixteen 32-bit big-endian words w[i], 0 ≤ i ≤ 15
Extend the sixteen 32-bit words into eighty 32-bit words:
for i from 16 to 79
w[i] := (w[i-3] xor w[i-8] xor w[i-14] xor w[i-16]) leftrotate 1
Initialize hash value for this chunk:
a := h0
b := h1
c := h2
d := h3
e := h4
Main loop:
for i from 0 to 79
if 0 ≤ i ≤ 19 then
f := (b and c) or ((not b) and d)
k := 0x5A827999
else if 20 ≤ i ≤ 39
f := b xor c xor d
k := 0x6ED9EBA1
else if 40 ≤ i ≤ 59
f := (b and c) or (b and d) or (c and d)
k := 0x8F1BBCDC
else if 60 ≤ i ≤ 79
f := b xor c xor d
k := 0xCA62C1D6
temp := (a leftrotate 5) + f + e + k + w[i]
e := d
d := c
c := b leftrotate 30
b := a
a := temp
Add this chunk's hash to result so far:
h0 := h0 + a
h1 := h1 + b
h2 := h2 + c
h3 := h3 + d
h4 := h4 + e
Produce the final hash value (big-endian):
digest = hash = h0 append h1 append h2 append h3 append h4

SHA-2

         NIST发布了三个额外的SHA变体,这三个函数都将讯息对应到更长的讯息摘要。以它们的摘要长度(以位元计算)加在原名后面来命名:SHA-256,SHA-384和SHA-512。它们发布于2001年的FIPS PUB 180-2草稿中,随即通过审查和评论。包含SHA-1的FIPS PUB 180-2,于2002年以官方标准发布。2004年2月,发布了一次FIPS PUB 180-2的变更通知,加入了一个额外的变种SHA-224",这是为了符合双金钥3DES所需的金钥长度而定义。
SHA-256和SHA-512是很新的杂凑函数,前者以定义一个word为32位元,后者则定义一个word为64位元。它们分别使用了不同的偏移量,或用不同的常数,然而,实际上二者结构是相同的,只在循环执行的次数上有所差异。SHA-224以及SHA-384则是前述二种杂凑函数的截短版,利用不同的初始值做计算。
这些新的杂凑函数并没有接受像SHA-1一样的公众密码社群做详细的检验,所以它们的密码安全性还不被大家广泛的信任。Gilbert和Handschuh在2003年曾对这些新变种作过一些研究,声称他们没有找到弱点。 [2] 

应用

       SHA-1, SHA-224, SHA-256, SHA-384 和 SHA-512 都被需要安全杂凑算法的美国联邦政府所应用,他们也使用其他的密码算法和协定来保护敏感的未保密资料。FIPS PUB 180-1也鼓励私人或商业组织使用 SHA-1 加密。Fritz-chip 将很可能使用 SHA-1 杂凑函数来实现个人电脑上的数位版权管理。
首先推动安全杂凑算法出版的是已合并的数位签章标准。
SHA 杂凑函数已被做为 SHACAL 分组密码算法的基础。 [3]
 

哈希表介绍

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
15 111 % 15

 

问题

有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址..),当输入该员工的id时,要求查找到该员工的 所有信息.

要求:
不使用数据库,,速度越快越好=>哈希表(散列)
添加时,保证按照id从低到高插入 [课后思考:如果id不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?]
使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
思路分析并画出示意图
代码实现[增删改查(显示所有员工,按id查询)]


 


代码实现

1 package cn.smallmartial.hashTable;
  2 
  3 import java.util.HashMap;
  4 import java.util.Scanner;
  5 
  6 /**
  7  * @Author smallmartial
  8  * @Date 2019/6/13
  9  * @Email smallmarital@qq.com
 10  */
 11 public class HashTableDemo {
 12 
 13     public static void main(String[] args) {
 14         //创建hash表
 15         HashTab hashTab = new HashTab(7);
 16 
 17         //写一个简单的菜单
 18         String key = "";
 19         Scanner scanner = new Scanner(System.in);
 20         while(true) {
 21             System.out.println("add:  添加雇员");
 22             System.out.println("list: 显示雇员");
 23             System.out.println("find: 查找雇员");
 24             System.out.println("exit: 退出系统");
 25 
 26             key = scanner.next();
 27             switch (key) {
 28                 case "add":
 29                     System.out.println("输入id");
 30                     int id = scanner.nextInt();
 31                     System.out.println("输入名字");
 32                     String name = scanner.next();
 33                     //创建 雇员
 34                     Emp emp = new Emp(id, name);
 35                     hashTab.add(emp);
 36                     break;
 37                 case "list":
 38                     hashTab.list();
 39                     break;
 40                 case "find":
 41                     System.out.println("请输入要查找的id");
 42                     id = scanner.nextInt();
 43                     hashTab.findEmpById(id);
 44                     break;
 45                 case "exit":
 46                     scanner.close();
 47                     System.exit(0);
 48                 default:
 49                     break;
 50             }
 51         }
 52     }
 53 }
 54 //创建hashTab
 55 class HashTab{
 56     private  int size;
 57     private EmpLinkedList[] empLinkedListArray;
 58 
 59     public HashTab(int size) {
 60         this.size = size;
 61         empLinkedListArray = new EmpLinkedList[size];
 62 
 63         for (int i = 0; i < size; i++) {
 64              empLinkedListArray[i] = new EmpLinkedList();
 65         }
 66 
 67     }
 68 
 69     //添加雇员
 70     public void add(Emp emp){
 71         //根据员工ID 添加到哪条联
 72         int hashFun = hashFun(emp.id);
 73         empLinkedListArray[hashFun].add(emp);
 74 
 75     }
 76     
 77     //遍历链表
 78     public void list(){
 79         for (int i = 0; i < size; i++) {
 80             empLinkedListArray[i].list(i);
 81         }
 82     }
 83 
 84     //根据输入的id,查找雇员
 85     public void findEmpById(int id) {
 86         //使用散列函数确定到哪条链表查找
 87         int empLinkedListNO = hashFun(id);
 88         Emp emp = empLinkedListArray[empLinkedListNO].findEmpById(id);
 89         if(emp != null) {//找到
 90             System.out.printf("在第%d条链表中找到 雇员 id = %d\n", (empLinkedListNO + 1), id);
 91         }else{
 92             System.out.println("在哈希表中,没有找到该雇员~");
 93         }
 94     }
 95     public int hashFun(int id){
 96         return id % size;
 97     }
 98 }
 99 //表示雇员
100 class Emp {
101     public int id;
102     public String name;
103     public Emp next;
104 
105     public Emp(int id, String name) {
106         this.id = id;
107         this.name = name;
108     }
109 }
110 //表示链表
111 class EmpLinkedList{
112     private Emp head;
113 
114     public void add(Emp emp){
115         if (head == null){
116             head = emp;
117             return;
118         }
119 
120         Emp curEmp = head;
121         while (true){
122             if (curEmp.next == null){
123                 break;
124             }
125             curEmp = curEmp.next;
126         }
127         curEmp.next = emp;
128     }
129 
130     //遍历
131     public void list(int no){
132         if (head == null){
133             System.out.println(""+no+"前链表为空");
134             return;
135         }
136         System.out.print(""+no+"链表信息为:");
137         Emp curEmp = head;
138         while (true){
139             System.out.print(curEmp.id+"=>"+curEmp.name);
140             if (curEmp.next == null){
141                 break;
142             }
143             curEmp = curEmp.next;
144         }
145         System.out.println();
146     }
147 
148     //根据id查找雇员
149     //如果查找到,就返回Emp, 如果没有找到,就返回null
150     public Emp findEmpById(int id) {
151         //判断链表是否为空
152         if(head == null) {
153             System.out.println("链表为空");
154             return null;
155         }
156         //辅助指针
157         Emp curEmp = head;
158         while(true) {
159             if(curEmp.id == id) {//找到
160                 break;//这时curEmp就指向要查找的雇员
161             }
162             //退出
163             if(curEmp.next == null) {//说明遍历当前链表没有找到该雇员
164                 curEmp = null;
165                 break;
166             }
167             curEmp = curEmp.next;//以后
168         }
169 
170         return curEmp;
171     }
172 }

 

posted @ 2020-05-11 22:33  你的龙儿  阅读(1767)  评论(0编辑  收藏  举报