对称加密算法
对称加密算法,加密和解密所用密钥一样。常用的对称加密算法有 DES,DESede(又称3DES),AES,RC2,RC4。其中DESede和RC2,RC4都是DES的替代算法。实际AES用的最多。所以这里暂时只总结AES的用法。
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class Demo {
@Test
public void test() throws Exception {
String value = "rawchen";
System.out.println("待加密值:" + value);
// 加密算法
String algorithm = "AES";
// 转换模式
String transformation = "AES";
// --- 生成秘钥 ---
// 实例化秘钥生成器
KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
// 初始化秘钥长度
keyGenerator.init(256);
// 生成秘钥
SecretKey secretKey = keyGenerator.generateKey();
// 生成秘钥材料
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm);
System.out.println("AES秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded()));
// 实例化密码对象
Cipher cipher = Cipher.getInstance(transformation);
// 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
// 加密
byte[] encrypt = cipher.doFinal(value.getBytes());
System.out.println("AES加密结果:" + Base64.getEncoder().encodeToString(encrypt));
// 解密
// 设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decrypt = cipher.doFinal(encrypt);
System.out.println("AES解密结果:" + new String(decrypt));
}
}
结果
待加密值:rawchen
AES秘钥:kSiEi1NwlB+dRdzNtI+ACzcZVk5jX5C6jASByxTHBqg=
AES加密结果:l6+wvdlNmasW2PpVjAhWhw==
AES解密结果:rawchen
生成密钥时也可以生成加密密钥,例如
/**
* 生成加密秘钥
*
* @return
*/
private static SecretKeySpec getSecretKey(String key) {
//返回生成指定算法密钥生成器的 KeyGenerator 对象
KeyGenerator kg = null;
try {
kg = KeyGenerator.getInstance(KEY_ALGORITHM);
//兼容linux和window的内核
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes(Charset.forName("UTF-8")));
//AES 要求密钥长度为 128
kg.init(128, secureRandom);
//生成一个密钥
SecretKey secretKey = kg.generateKey();
// 转换为AES专用密钥
return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(AESUtil.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
转换模式为AES/CBC/PKCS5Padding时,需要指定初始化向量,cipher.init()方法多了一个初始化向量参数,如
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class Demo {
@Test
public void test() throws Exception {
String value = "rawchen";
System.out.println("待加密值:" + value);
// 加密算法
String algorithm = "AES";
// 转换模式
String transformation = "AES/CBC/PKCS5Padding";
// --- 生成秘钥 ---
// 实例化秘钥生成器
KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
// 初始化秘钥长度
keyGenerator.init(256);
// 生成秘钥
SecretKey secretKey = keyGenerator.generateKey();
// 生成秘钥材料
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm);
System.out.println("AES秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded()));
// 初始化向量,123456789abcdefg初始化向量秘钥,16字节
IvParameterSpec iv = new IvParameterSpec("123456789abcdefg".getBytes());
// 实例化密码对象
Cipher cipher = Cipher.getInstance(transformation);
// 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
// 加密
byte[] encrypt = cipher.doFinal(value.getBytes());
System.out.println("AES加密结果:" + Base64.getEncoder().encodeToString(encrypt));
// 解密
// 设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
byte[] decrypt = cipher.doFinal(encrypt);
System.out.println("AES解密结果:" + new String(decrypt));
}
}
输出
待加密值:rawchen
AES秘钥:+hAO/51KV9FAgMHMEPwc+RpUlpEfBjyzJ699LRYFLf8=
AES加密结果:/pnOn0QjFc8QYjZTuuAKTg==
AES解密结果:rawchen
手动指定密钥
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class Demo {
@Test
public void test() throws Exception {
String algorithm = "AES";
String transformation = "AES/CTR/PKCS5Padding";
String key = "xaBsoZIBs1Dz6veBfwfzpPwzrmMsu8mKqu4Lljk+zZo=";
String encrypt = "VKOtjAOQJQ==";
String ivKey = "123456789abcdefg";
Cipher cipher = Cipher.getInstance(transformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), algorithm);
IvParameterSpec iv = new IvParameterSpec(ivKey.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
byte[] decrypt = cipher.doFinal(Base64.getDecoder().decode(encrypt));
System.out.println("AES解密结果:" + new String(decrypt));
}
}
AES解密结果:rawchen
非对称加密算法
非对称加密算法有两个密钥,一个公钥,一个私钥。公钥加密,私钥解密。非对称加密算法也用来签名和验证签名。私钥用来签名,公钥用来验证签名。(比如微信的支付通知,签名使用微信平台私钥,验证使用平台公钥)常用的算法有RSA,ECC。RSA用的比较多。这里只总结RSA的用法
- 从公钥和私钥字符串获取公私钥
- 生成加解密密码器,加解密
从公钥和私钥字符串获取公钥
private static final String SIGN_TYPE_RSA = "RSA";
private static final String SIGN_ALGORITHMS = "SHA1WithRSA";
private static final int MAX_ENCRYPT_BLOCK_1024 = 117;
private static final int MAX_DECRYPT_BLOCK_1024 = 128;
private static final int MAX_ENCRYPT_BLOCK_2048 = 245;
private static final int MAX_DECRYPT_BLOCK_2048 = 256;
private static final int MAX_ENCRYPT_LIMIT = 300;
private static final int MAX_DECRYPT_LIMIT = 1000;
private static PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
StringWriter writer = new StringWriter();
StreamUtils.io(new InputStreamReader(ins), writer);
byte[] encodedKey = writer.toString().getBytes();
encodedKey = Base64.getDecoder().decode(encodedKey);
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
}
从公钥和私钥字符串获取私钥
public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {
if (ins == null || algorithm == null) {
return null;
}
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
byte[] encodedKey = StreamUtils.readText(ins).getBytes();
encodedKey = Base64.getDecoder().decode(encodedKey);
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
}
生成加解密密码器,加密
/**
* 使用RSA公钥对业务数据加密
*
* @param content 业务数据json
* @param publicKey RSA公钥
* @return 加密后的字符串
* @throws Exception e
*/
public static String rsaEncrypt(String content, String publicKey) throws Exception {
PublicKey pubKey = getPublicKeyFromX509(SIGN_TYPE_RSA, new ByteArrayInputStream(publicKey.getBytes()));
Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] data = content.getBytes(StandardCharsets.UTF_8);
data = cipher.doFinal(data);
byte[] encryptedData = Base64.getEncoder().encode(data);
return new String(encryptedData, StandardCharsets.UTF_8);
}
生成加解密密码器,加密
/**
* RSA解密
* @param content 密文
* @param privateKey 私钥
* @return 解密后的明文
* @throws Exception e
*/
public static String rsaDecrypt(String content, String privateKey) throws Exception {
PrivateKey priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
cipher.init(Cipher.DECRYPT_MODE, priKey);
byte[] encryptedData = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));
byte[] decryptedData = cipher.doFinal(encryptedData)
return new String(decryptedData, StandardCharsets.UTF_8);
}
分段加解密
RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题。明文长度需要小于密钥长度,而密文长度则等于密钥长度。因此当加密内容长度大于密钥长度时,有效的RSA加解密就需要对内容进行分段。
/**
* 使用RSA公钥对业务数据加密
*
* @param content 业务数据json
* @param publicKey RSA公钥
* @return 加密后的字符串
* @throws Exception e
*/
public static String rsaEncrypt(String content, String publicKey) throws Exception {
PublicKey pubKey = getPublicKeyFromX509(SIGN_TYPE_RSA, new ByteArrayInputStream(publicKey.getBytes()));
Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] data = content.getBytes(StandardCharsets.UTF_8);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
int maxEncryptBlock = MAX_ENCRYPT_BLOCK_1024;
if (publicKey.length() > MAX_ENCRYPT_LIMIT) {
maxEncryptBlock = MAX_ENCRYPT_BLOCK_2048;
}
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > maxEncryptBlock) {
cache = cipher.doFinal(data, offSet, maxEncryptBlock);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * maxEncryptBlock;
}
byte[] encryptedData = Base64.getEncoder().encode(out.toByteArray());
out.close();
return new String(encryptedData, StandardCharsets.UTF_8);
}
/**
* RSA解密
* @param content 密文
* @param privateKey 私钥
* @return 解密后的明文
* @throws Exception e
*/
public static String rsaDecrypt(String content, String privateKey) throws Exception {
PrivateKey priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
cipher.init(Cipher.DECRYPT_MODE, priKey);
byte[] encryptedData = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
int maxDecryptBlock = MAX_DECRYPT_BLOCK_1024;
if (privateKey.length() > MAX_DECRYPT_LIMIT) {
maxDecryptBlock = MAX_DECRYPT_BLOCK_2048;
}
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > maxDecryptBlock) {
cache = cipher.doFinal(encryptedData, offSet, maxDecryptBlock);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * maxDecryptBlock;
}
byte[] decryptedData = out.toByteArray();
out.close();
return new String(decryptedData, StandardCharsets.UTF_8);
}
常见的签名算法
MD5算法
MD5 用的是 哈希函数,它的典型应用是对一段信息产生 信息摘要,以 防止被篡改。严格来说,MD5 不是一种 加密算法 而是 摘要算法。无论是多长的输入,MD5 都会输出长度为 128bits 的一个串 (通常用 16 进制 表示为 32 个字符)。
public static final byte[] computeMD5(byte[] content) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
return md5.digest(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
SHA1算法
SHA1 是和 MD5 一样流行的 消息摘要算法,然而 SHA1 比 MD5 的 安全性更强。对于长度小于 2 ^ 64 位的消息,SHA1 会产生一个 160 位的 消息摘要。基于 MD5、SHA1 的信息摘要特性以及 不可逆 (一般而言),可以被应用在检查 文件完整性 以及 数字签名 等场景。
public static byte[] computeSHA1(byte[] content) {
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
return sha1.digest(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
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;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
@NotThreadSafe
public class HMacHelper {
private static final Logger logger = LoggerFactory.getLogger(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 SecretKeySpec(key.getBytes(ConstField.UTF8), KEY_MAC);
mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
} catch (Exception e) {
logger.error("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) {
logger.error("verify sig failed.", e);
}
return false;
}
}
SHA256算法
SHA256是SHA2算法中的一种,如SHA2加密算法中有:SHA244、SHA256、SHA512等。SHA2属于SHA1的升级,SHA1是160位的哈希值,而SHA2是组合值,有不同的位数,其中最受欢迎的是256位(SHA256算法)。
SSL行业选择SHA作为数字签名的散列算法,从2011到2015,一直以SHA-1位主导算法。但随着互联网技术的提升,SHA-1的缺点越来越突显。从去年起,SHA-2成为了新的标准,所以现在签发的SSL证书,必须使用该算法签名。
public static byte[] getSHA256(String str) {
MessageDigest messageDigest;
String encodestr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
return messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
SHA1withRSA算法数据签名
/**
* SHA1withRSA算法数据加签
*
* @param content 要签名的字符串
* @param privateKey 私钥
* @return 签名串
* @throws Exception e
*/
public static String rsaSign(String content, String privateKey) throws Exception {
PrivateKey priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA,
new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8)));
Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
signature.initSign(priKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
return new String(Base64.getEncoder().encode(signed));
}
SHA1withRSA算法数据签名验签
/**
* SHA1withRSA算法数据验签
* @param content 待验签字符串
* @param sign 签文
* @param publicKey 公钥
* @return result
* @throws Exception e
*/
public static boolean rsaCheckSign(String content, String sign, String publicKey) throws Exception {
PublicKey pubKey = getPublicKeyFromX509(SIGN_TYPE_RSA, new ByteArrayInputStream(publicKey.getBytes()));
Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
signature.initVerify(pubKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
}
概括来说,加密步骤如下:
1、生成或获取密钥
2、生成指定算法的加解密密码器
3、使用密钥对加解密密码器初始化
4、传入加解密模式和明文或密文参数执行加解密方法
生成密钥,不同的算法略有区别:
对称加密算法可通过 javax.crypto.KeyGenerator 密钥生成器生成如:
KeyGenerator keyGenerator = KeyGenerator.getInstance("RSA");
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
获取密钥的初始编码格式的字节数组
byte[] key = secretKey.getEncoded();
从字节数组还原为密钥
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "算法字符串");
将密钥转为Base64编码的字符串
String keyString = Base64.getEncoder().encodeToString(key)
还有一种生成密钥的API:密钥工厂,不过部分算法才支持,如des
由密钥的字节数组获取密钥
DESKeySpec desKeySpec = new DESKeySpec(secretKey.getEncoded());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("算法字符串");
SecretKey desSecretKey = secretKeyFactory.generateSecret(desKeySpec);
非对称加密算法可通过java.security.KeyPairGenerator 密钥对生成器,生成公钥和私钥
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("算法签名如RSA");
// 初始化,秘钥长度512~16384位,64倍数
keyPairGenerator.initialize(512);
// 生成秘钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 公钥
PublicKey publicKey = keyPair.getPublic();
System.out.println("RSA公钥: " + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
// 私钥
PrivateKey privateKey = keyPair.getPrivate();
System.out.println("RSA私钥: " + Base64.getEncoder().encodeToString(privateKey.getEncoded()));