前⾔:数字签名、信息加密 是前后端开发都经常需要使⽤到的技术,应⽤场景包括了⽤户登⼊、交易、信息通讯、oauth 等等,不同的应⽤场景也会需要使⽤到不同的签名加密算法,或者需要搭配不⼀样的 签名加密算法 来达到业务⽬标。这⾥简单的给⼤家介绍⼏种常见的签名加密算法和⼀些典型场景下的应⽤。 感谢⼤佬的⽂章:
⼀、数字签名
数字签名,简单来说就是通过提供 可鉴别 的 数字信息 验证 ⾃⾝⾝份 的⼀种⽅式。⼀套 数字签名 通常定义两种 互补 的运算,⼀个⽤
于 签名,另⼀个⽤于 验证。分别由 发送者 持有能够 代表⾃⼰⾝份 的 私钥 (私钥不可泄露),由 接受者 持有与私钥对应的 公钥 ,能够在 接受 到来⾃发送者信息时⽤于 验证 其⾝份。
注意:图中 加密过程 有别于 公钥加密,更多 。签名 最根本的⽤途是要能够唯⼀ 证明发送⽅的⾝份,防⽌ 中间⼈攻击、CSRF 跨域⾝份伪造。基于这⼀点在诸如 设备认证、⽤户认证、第三⽅认证 等认证体系中都会使⽤到 签名算法 (彼此的实现⽅式可能会有差异)。
⼆、加密和解密
2.1. 加密
数据加密 的基本过程,就是对原来为 明⽂ 的⽂件或数据按 某种算法 进⾏处理,使其成为 不可读 的⼀段代码,通常称为 “密⽂”。通过这样的途径,来达到 保护数据 不被 ⾮法⼈窃取、阅读的⽬的。 2.2. 解密
加密 的 逆过程 为 解密,即将该 编码信息 转化为其 原来数据 的过程。
加密算法分 对称加密 和 ⾮对称加密,其中对称加密算法的加密与解密 密钥相同,⾮对称加密算法的加密密钥与解密 密钥不同,此外,还有⼀类 不需要密钥 的 散列算法。
常见的 对称加密 算法主要有 DES、3DES、AES 等,常见的 ⾮对称加密算法 主要有 RSA、DSA 等,散列算法 主要有 SHA-
1、MD5 等。
3.1. 对称加密
对称加密算法 是应⽤较早的加密算法,⼜称为 共享密钥加密算法。在 对称加密算法 中,使⽤的密钥只有⼀个,发送 和 接收 双⽅都使⽤这个密钥对数据进⾏ 加密 和 解密。这就要求加密和解密⽅事先都必须知道加密的密钥。
1. 数据加密过程:在对称加密算法中,数据发送⽅ 将 明⽂ (原始数据) 和 加密密钥 ⼀起经过特殊 加密处理,⽣成复杂的 加密密⽂ 进⾏
发送。
2. 数据解密过程:数据接收⽅ 收到密⽂后,若想读取原数据,则需要使⽤ 加密使⽤的密钥 及相同算法的 逆算法 对加密的密⽂进⾏解
密,才能使其恢复成 可读明⽂。
3.2. ⾮对称加密
⾮对称加密算法,⼜称为 公开密钥加密算法。它需要两个密钥,⼀个称为 公开密钥 (public key),即 公钥,另⼀个称为 私有密钥 (private key),即 私钥。
因为 加密 和 解密 使⽤的是两个不同的密钥,所以这种算法称为 ⾮对称加密算法。
1. 如果使⽤ 公钥 对数据 进⾏加密,只有⽤对应的 私钥 才能 进⾏解密。
2. 如果使⽤ 私钥 对数据 进⾏加密,只有⽤对应的 公钥 才能 进⾏解密。
例⼦:甲⽅⽣成 ⼀对密钥 并将其中的⼀把作为 公钥 向其它⼈公开,得到该公钥的 ⼄⽅ 使⽤该密钥对机密信息 进⾏加密 后再发送给甲⽅,甲⽅再使⽤⾃⼰保存的另⼀把 专⽤密钥 (私钥),对 加密 后的信息 进⾏解密。
四. 常见的摘要和加密算法
4.1. MD5摘要算法
MD5 ⽤的是 哈希函数,它的典型应⽤是对⼀段信息产⽣ 信息摘要,以 防⽌被篡改。严格来说,MD5 不是⼀种 加密算法 ⽽是 摘要算法。⽆论是多长的输⼊,MD5 都会输出长度为 128bits 的⼀个串 (通常⽤ 16 进制 表⽰为 32 个字符)。
public static final byte[] computeMD5(byte[] content) {
try {
MessageDigest md5 = Instance("MD5");
return md5.digest(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
4.2. SHA1摘要算法
SHA1 是和 MD5 ⼀样流⾏的 消息摘要算法,然⽽ SHA1 ⽐ MD5 的 安全性更强。对于长度⼩于 2 ^ 64 位的消息,SHA1 会产⽣⼀
个 160 位的 消息摘要。基于 MD5、SHA1 的信息摘要特性以及 不可逆 (⼀般⽽⾔),可以被应⽤在检查 ⽂件完整性 以及 数字签名 等场景。
public static byte[] computeSHA1(byte[] content) {
try {
MessageDigest sha1 = Instance("SHA1");
return sha1.digest(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
4.3. HMAC摘要算法
HMAC 是密钥相关的 哈希运算消息认证码(Hash-based Message Authentication Code),HMAC 运算利⽤ 哈希算
法 (MD5、SHA1 等),以 ⼀个密钥 和 ⼀个消息 为输⼊,⽣成⼀个 消息摘要 作为 输出。
HMAC 发送⽅ 和 接收⽅ 都有的 key 进⾏计算,⽽没有这把 key 的第三⽅,则是 ⽆法计算 出正确的 散列值的,这样就可以 防⽌数据被篡改。
package net.pocrd.util;
import net.pocrd.annotation.NotThreadSafe;
import net.pocrd.define.ConstField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
pto.Mac;
pto.SecretKey;
pto.spec.SecretKeySpec;
import java.util.Arrays;
@NotThreadSafe
public class HMacHelper {
private static final Logger logger = Logger(HMacHelper.class);
private Mac mac;
/**
* MAC算法可选以下多种算法
* HmacMD5/HmacSHA1/HmacSHA256/HmacSHA384/HmacSHA512
*/
private static final String KEY_MAC = "HmacMD5";
public HMacHelper(String key) {
try {
SecretKey secretKey = new Bytes(ConstField.UTF8), KEY_MAC);
mac = Algorithm());
mac.init(secretKey);
} catch (Exception e) {
<("create hmac helper failed.", e);
}
}
public byte[] sign(byte[] content) {
return mac.doFinal(content);
}
public boolean verify(byte[] signature, byte[] content) {
try {
byte[] result = mac.doFinal(content);
return Arrays.equals(signature, result);
} catch (Exception e) {
<("verify sig failed.", e);
}
return false;
}
}
测试结论:HMAC 算法实例在 多线程环境 下是 不安全的。但是需要在 多线程访问 时,进⾏同步的辅助类,使⽤ ThreadLocal 为 每个线程缓存 ⼀个实例可以避免进⾏锁操作。
4.4. AES/DES/3DES对称加密算法
AES、DES、3DES 都是 对称 的 块加密算法,加解密 的过程是 可逆的。常⽤的有 AES128、AES192、AES256 (默认安装的 JDK 尚不⽀持 AES256,需要安装对应的 jce 补丁进⾏升级 jce1.7,jce1.8)。
4.4.1. DES算法
DES 加密算法是⼀种 分组密码,以 64 位为 分组对数据 加密,它的 密钥长度 是 56 位,加密解密 ⽤ 同⼀算法。
DES 加密算法是对 密钥 进⾏保密,⽽ 公开算法,包括加密和解密算法。这样,只有掌握了和发送⽅ 相同密钥 的⼈才能解读由 DES加密算法加密的密⽂数据。因此,破译 DES 加密算法实际上就是 搜索密钥的编码。对于 56 位长度的 密钥 来说,如果⽤ 穷举法 来进⾏搜索的话,其运算次数为 2 ^ 56 次。
4.4.2. 3DES算法
是基于 DES 的 对称算法,对 ⼀块数据 ⽤ 三个不同的密钥 进⾏ 三次加密,强度更⾼。
4.4.3. AES算法
AES 加密算法是密码学中的 ⾼级加密标准,该加密算法采⽤ 对称分组密码体制,密钥长度的最少⽀持为 128 位、 192 位、256 位,分组长度 128 位,算法应易于各种硬件和软件实现。这种加密算法是美国联邦政府采⽤的 区块加密标准。
AES 本⾝就是为了取代 DES 的,AES 具有更好的 安全性、效率 和 灵活性。
import net.pocrd.annotation.NotThreadSafe;
pto.Cipher;
pto.KeyGenerator;
pto.spec.IvParameterSpec;
pto.spec.SecretKeySpec;
import java.security.SecureRandom;
@NotThreadSafe
public class AesHelper {
private SecretKeySpec keySpec;
private IvParameterSpec iv;
public AesHelper(byte[] aesKey, byte[] iv) {
if (aesKey == null || aesKey.length < 16 || (iv != null && iv.length < 16)) {
throw new RuntimeException("错误的初始密钥");
}
if (iv == null) {
iv = pute(aesKey);
}
keySpec = new SecretKeySpec(aesKey, "AES");
this.iv = new IvParameterSpec(iv);
}
public AesHelper(byte[] aesKey) {
if (aesKey == null || aesKey.length < 16) {
throw new RuntimeException("错误的初始密钥");
}
keySpec = new SecretKeySpec(aesKey, "AES");
this.iv = new pute(aesKey));
}
public byte[] encrypt(byte[] data) {
byte[] result = null;
Cipher cipher = null;
try {
cipher = Instance("AES/CFB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
result = cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
public byte[] decrypt(byte[] secret) {
byte[] result = null;
Cipher cipher = null;
try {
cipher = Instance("AES/CFB/NoPadding");