序
上一篇文章中介绍了基本的单向加密算法 — — MD5,也大致的说了说它实现的原理。这篇文章继续之前提到的单向加密,主要讲的是 SHA,同 MD5 一样,SHA 同样也是一个系列,它包括 SHA-1,SHA-224,SHA-256,SHA-384,和 SHA-512 等几种算法。其中,SHA-1,SHA-224 和 SHA-256 适用于长度不超过 2^64 二进制位的消息。SHA-384 和 SHA-512 适用于长度不超过 2^128 二进制位的消息。
背景
开始正文之前,简单的说一下背景。乍一说 SHA 你可能不知道,但说到散列和散列算法,你一定会知道,也就是平常所指的 Hash。那么,先了解一下什么是散列。散列,是信息的提炼,通常其长度要比信息小得多,且为一个固定长度。加密性强的散列一定是不可逆的,这就意味着通过散列结果,无法推出任何部分的原始信息。说的很明确,散列的结果是不可逆的,根据散列结果,无法推出原始信息。
正文
了解了背景之后,我们就开始介绍 SHA 了。
SHA,全称为“Secure Hash Algorithm”,中文名“安全哈希算法”,主要适用于数字签名标准(Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于 2^64 位的消息,SHA1 会产生一个 160 位的消息摘要。
该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值的过程。
上边也提到了,SHA 规定了很多种算法,包括了 SHA-1,SHA-224,SHA-256,等很多种。这里我以 SHA-1 为例,讲一下 SHA-1 是如何工作的。
SHA-1 有两个特点:
- 不可以从消息摘要中复原信息
- 两个不同的消息,不会产生同样的消息摘要
SHA-1 是一种数据加密算法,主要是接收一段明文,然后以一种不可逆的方式将它转换成一段密文,也可以简单的理解为取一串输入码,并把它们转化为长度较短、位数固定的输出序列即散列值的过程。
单向散列函数的安全性在于其产生散列值的操作过程具有较强的单向性。如果在输入序列中嵌入密码,那么任何人在不知道密码的情况下都不能产生正确的散列值,从而保证了其安全性。SHA 将输入流按照每块 512 位(64 个字节)进行分块,并产生 20 个字节的被称为信息认证代码或信息摘要的输出。
该算法输入报文的长度不限,产生的输出是一个 160 位的报文摘要。输入是按 512 位的分组进行处理的。SHA-1 是不可逆的、防冲突,并具有良好的雪崩效应。
通过散列算法可实现数字签名实现,数字签名的原理是将要传送的明文通过一种函数运算(Hash)转换成报文摘要(不同的明文对应不同的报文摘要),报文摘要加密后与明文一起传送给接受方,接受方将接受的明文产生新的报文摘要与发送方的发来报文摘要解密比较,比较结果一致表示明文未被改动,如果不一致表示明文已被篡改。
SHA-1 与 MD5 的比较
因为二者均由 MD4 导出,SHA-1 和 MD5 彼此很相似。相应的,他们的强度和其他特性也是相似,但还有以下几点不同:
- 对强行攻击的安全性
- 对密码分析的安全性
由于 MD5 的设计,易受密码分析的攻击,SHA-1 显得不易受这样的攻击。
- 速度
在相同的硬件上,SHA-1 的运行速度比 MD5 慢。
代码实现
- <span style="font-family:Comic Sans MS;font-size:12px;">package com.sica.sha;
- import com.google.common.base.Strings;
- import java.security.MessageDigest;
- /**
- * Created by xiang.li on 2015/2/11.
- */
- public class SHA {
- /**
- * 定义加密方式
- */
- private final static String KEY_SHA = "SHA";
- private final static String KEY_SHA1 = "SHA-1";
- /**
- * 全局数组
- */
- private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5",
- "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
- /**
- * 构造函数
- */
- public SHA() {
- }
- /**
- * SHA 加密
- * @param data 需要加密的字节数组
- * @return 加密之后的字节数组
- * @throws Exception
- */
- public static byte[] encryptSHA(byte[] data) throws Exception {
- // 创建具有指定算法名称的信息摘要
- // MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
- MessageDigest sha = MessageDigest.getInstance(KEY_SHA1);
- // 使用指定的字节数组对摘要进行最后更新
- sha.update(data);
- // 完成摘要计算并返回
- return sha.digest();
- }
- /**
- * SHA 加密
- * @param data 需要加密的字符串
- * @return 加密之后的字符串
- * @throws Exception
- */
- public static String encryptSHA(String data) throws Exception {
- // 验证传入的字符串
- if (Strings.isNullOrEmpty(data)) {
- return "";
- }
- // 创建具有指定算法名称的信息摘要
- MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
- // 使用指定的字节数组对摘要进行最后更新
- sha.update(data.getBytes());
- // 完成摘要计算
- byte[] bytes = sha.digest();
- // 将得到的字节数组变成字符串返回
- return byteArrayToHexString(bytes);
- }
- /**
- * 将一个字节转化成十六进制形式的字符串
- * @param b 字节数组
- * @return 字符串
- */
- private static String byteToHexString(byte b) {
- int ret = b;
- //System.out.println("ret = " + ret);
- if (ret < 0) {
- ret += 256;
- }
- int m = ret / 16;
- int n = ret % 16;
- return hexDigits[m] + hexDigits[n];
- }
- /**
- * 转换字节数组为十六进制字符串
- * @param bytes 字节数组
- * @return 十六进制字符串
- */
- private static String byteArrayToHexString(byte[] bytes) {
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < bytes.length; i++) {
- sb.append(byteToHexString(bytes[i]));
- }
- return sb.toString();
- }
- /**
- * 测试方法
- * @param args
- */
- public static void main(String[] args) throws Exception {
- String key = "123";
- System.out.println(encryptSHA(key));
- }
- }</span>
结束语
看到这,我想 SHA-1 的简单原理你应该是了解了,而且,对于应用来说也并不难,可以参考上述的 Java 代码。回过头来在想想 MD5,通过上面的文章,你就能知道,其实,SHA-1 与 MD5 是同出一辙的,只是他们各自的实现方式不同,SHA-1 在位操作数量级上也要比 MD5 更加的复杂,因此,对于安全的考虑,SHA-1 相对来说还是很可靠的。
至于什么时候会用到它,那么就要考虑 SHA-1 的特点了。很明确的,不可逆性,以及唯一性。那么,我想,适用于 MD5 的加密的,也同样适用于 SHA-1。而且,在安全性上来说,SHA-1 更胜于 MD5,如果是对速度有严格要求的话,那么,还是优先考虑 MD5 吧。