持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情
前言:由于近期与三方系统对接openapi接口,以及经常涉及到接口开发,出于接口数据安全考虑,需对请求做签名加密和请求报文加密解密处理。通过对常用的几种加密解密方式的了解和总结,在项目中选用标准安全的加密方式:SHA-256。
一、BASE64加密与解密
1. Base64 编码定义
Base64 编码本质上是一种将二进制数据转成文本数据的方案,它用 64 个可打印字符来表示二进制数据。
这 64 个字符是:小写字母 a-z、大写字母 A-Z、数字 0-9、符号"+"、"/"(再加上作为垫字的"=",实际上是 65 个字符),其他所有符号都转换成这个字符集中的字符。Base64 编码通常用作存储、传输一些二进制数据编码方法,例如电子邮件。
2. Base64 编码步骤:
(1) 将每三个字节作为一组,一共是24个二进制位
(2) 将这24个二进制位分为四组,每个组有6个二进制位 (因为 6 位 2 进制最大数为 63)
(3) 在每组前面加两个00,扩展成32个二进制位,即四个字节
(4) 根据序号表(0-63),得到扩展后的每个字节的对应符号就是Base64的编码值
3. sun 包下的 BASE64Encoder
缺点:编码和解码的效率不高
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class Base64Util {
// BASE64加密
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encode(key);
}
// BASE64解密
public static byte[] decryBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}
}
4. apache 包下的 Base64
优点:比 sun 包更精简,实际执行效率高不少;
缺点:需要引用 Apache Commons Codec, 但 tomcat 容器下开发, 一般都自动引入可直接使用。
import org.apache.tomcat.util.codec.binary.Base64;
public class Base64Util {
private static Base64 base64 = new Base64();
// BASE64加密
public static String encryptBASE64(byte[] key) throws Exception {
return base64.encodeToString(key);
}
// BASE64解密
public static byte[] decryBASE64(String key) throws Exception {
return base64.decode(key);
}
5. util 包下的 Base64 (jdk8)
优点:Java 8 提供的 Base64 效率最高. 比 sun包下的要快大约 11 倍,比 Apache 的快大约 3 倍。
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
public class Base64Util {
private static Decoder decoder = Base64.getDecoder();
private static Encoder encoder = Base64.getEncoder();
// BASE64加密
public static String encryptBASE64(byte[] key) throws Exception {
return encoder.encodeToString(key);
}
// BASE64解密
public static byte[] decryBASE64(String key) throws Exception {
return decoder.decode(key);
}
}
二、MD5(Message Digest Algorithm)加密
1. MD5 定义
MD5是一种单向加密算法,只能加密不能解密,它是将任意长度的数据字符串转化成短小的固定长度的值的单向操作,任意两个字符串不应有相同的散列值。
2. 应用
MD5 主要用做数据一致性验证、数字签名和安全访问认证,而不是用作加密。比如说用户在某个网站注册账户时,输入的密码一般经过 MD5 编码,更安全的做法还会加一层盐(salt),这样密码就具有不可逆性。然后把编码后的密码存入数据库,下次登录的时候把密码 MD5 编码,然后和数据库中的作对比,这样就提升了用户账户的安全性。
3. 代码示例
import java.security.MessageDigest;
public class MD5Util {
public static final String KEY_MD5 = "MD5";
// MD5加密(生成唯一的MD5值)
public static byte[] encryMD5(byte[] data) throws Exception {
MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
md5.update(data);
return md5.digest();
}
}
三、DES(Data Encryption Standard)对称加密与解密
1. DES 定义
DES 是一种对称加密算法,所谓对称加密算法就是:加密和解密使用相同密钥的算法。 数据加密标准算法,和BASE64最明显的区别就是有一个工作密钥,该密钥既用于加密、也用于解密,并且要求密钥是一个长度至少大于8位的字符串。
2. 注意事项
- 因为 DES 使用 56 位密钥,容易破解,安全性差;
- 实现 DES 加密解密,注意密码长度要是 8 的倍数;
- 加密和解密的 Cipher 构造参数一定要相同,不然会报错。
3. 代码示例
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class DesUtil {
private static Key key;
private static String KEY_STR = "myKey";
private static String CHARSETNAME = "UTF-8";
private static String ALGORITHM = "DES";
static {
try {
// 生成DES算法对象
KeyGenerator generator = KeyGenerator.getInstance(ALGORITHM);
// 运用SHA1安全策略
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
// 设置上密钥种子
secureRandom.setSeed(KEY_STR.getBytes());
// 初始化基于SHA1的算法对象
generator.init(secureRandom);
// 生成密钥对象
key = generator.generateKey();
generator = null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 获取加密的信息
public static String getEncryptString(String str) {
// 基于BASE64编码,接收byte[]并转换成String
BASE64Encoder encoder = new BASE64Encoder();
try {
// 按utf8编码
byte[] bytes = str.getBytes(CHARSETNAME);
// 获取加密对象
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化密码信息
cipher.init(Cipher.ENCRYPT_MODE, key);
// 加密
byte[] doFinal = cipher.doFinal(bytes);
// byte[]to encode好的String 并返回
return encoder.encode(doFinal);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 获取解密之后的信息
public static String getDecryptString(String str) {
BASE64Decoder decoder = new BASE64Decoder();
try {
// 将字符串decode成byte[]
byte[] bytes = decoder.decodeBuffer(str);
// 获取解密对象
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化解密信息
cipher.init(Cipher.DECRYPT_MODE, key);
// 解密
byte[] doFial = cipher.doFinal(bytes);
return new String(doFial, CHARSETNAME);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
四、AES(Advanced Encryption Standard)加密与解密
1. AES 定义
AES 是高级加密标准,是 DES 的增强版,比 DES 的加密强度更高。它由四个不同阶段组成:字节代替,行移位,列混淆,轮密钥加,每个阶段是可逆的,将所有的四个阶段求逆,很容易证明解密函数可以恢复明文。
2. 代码示例
public class AESUtil {
public static final String algorithm = "AES";
// AES/CBC/NOPaddin
// AES 默认模式
// 使用CBC模式, 在初始化Cipher对象时, 需要增加参数, 初始化向量IV : IvParameterSpec iv = new
// IvParameterSpec(key.getBytes());
// NOPadding: 使用NOPadding模式时, 原文长度必须是8byte的整数倍
public static final String transformation = "AES/CBC/NOPadding";
public static final String key = "1234567812345678";
// 加密
// original 需要加密的参数(注意必须是16位)
public static String encryptByAES(String original) throws Exception {
// 获取Cipher
Cipher cipher = Cipher.getInstance(transformation);
// 生成密钥
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), algorithm);
// 指定模式(加密)和密钥
// 创建初始化向量
IvParameterSpec iv = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
// cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// 加密
byte[] bytes = cipher.doFinal(original.getBytes());
return Base64Util.encryptBASE64(bytes);
}
// 解密
// encrypted 需要解密的参数
public static String decryptByAES(String encrypted) throws Exception {
// 获取Cipher
Cipher cipher = Cipher.getInstance(transformation);
// 生成密钥
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), algorithm);
// 指定模式(解密)和密钥
// 创建初始化向量
IvParameterSpec iv = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
// 解密
byte[] bytes = cipher.doFinal(Base64Util.decryBASE64(encrypted));
return new String(bytes);
}
}
五、HMAC(Hash Message Authentication Code)散列消息鉴别码
1. HMAC 定义
HMAC是一种使用单向散列函数来构造消息认证码的方法,其中HMAC中的H就是Hash的意思。使用一个密钥生成一个固定大小的小数据块,即MAC,并将其加入到消息中,然后传输,接收方利用与发送方共享的密钥进行鉴别认证。
2. 代码示例
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class HMACUtil {
public static final String KEY_MAC = "HmacMD5";
// 初始化HMAC密钥
public static String initMacKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
SecretKey secreKey = keyGenerator.generateKey();
return Base64Util.encryptBASE64(secreKey.getEncoded());
}
// HMAC加密
public static byte[] encryHMAC(byte[] data, String key) throws Exception {
SecretKey secreKey = new SecretKeySpec(Base64Util.decryBASE64(key),
KEY_MAC);
Mac mac = Mac.getInstance(secreKey.getAlgorithm());
mac.init(secreKey);
return mac.doFinal();
}
}
六、SHA(Secure Hash Algorithm)安全散列算法
1. SHA 定义
SHA 算法能计算出一个数位讯息所对应到的,长度固定的字串(又称讯息摘要)。且若输入的讯息不同,它们对应到不同字串的机率很高。
2. SHA是FIPS所认证的安全散列算法的原因
(1) 由讯息摘要反推原输入讯息,从计算理论上来说是很困难的。
(2) 想要找到两组不同的讯息对应到相同的讯息摘要,从计算理论上来说也是很困难的。任何对输入讯息的变动,都有很高的机率导致其产生的讯息摘要迥异。
3. 应用
- 数字签名等密码学应用中重要的工具;
- 广泛地应用于电子商务等信息安全领域。
4. 代码示例
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHAUtil {
public static final String KEY_SHA = "SHA";
public static final String ALGORITHM = "SHA-256";
// SHA加密(比MD5更安全)
public static byte[] encryptSHA(byte[] data) throws Exception {
MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
sha.update(data);
return sha.digest();
}
public static String SHAEncrypt(final String content) {
try {
MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
byte[] sha_byte = sha.digest(content.getBytes());
StringBuffer hexValue = new StringBuffer();
for (byte b : sha_byte) {
// 将其中的每个字节转成十六进制字符串:byte类型的数据最高位是符号位,通过和0xff进行与操作,转换为int类型的正整数。
String toHexString = Integer.toHexString(b & 0xff);
hexValue.append(toHexString.length() == 1 ? "0" + toHexString
: toHexString);
}
return hexValue.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
// SHA-256加密
public static String SHA256Encrypt(String sourceStr) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
if (null != md) {
md.update(sourceStr.getBytes());
String digestStr = getDigestStr(md.digest());
return digestStr;
}
return null;
}
private static String getDigestStr(byte[] origBytes) {
String tempStr = null;
StringBuilder stb = new StringBuilder();
for (int i = 0; i < origBytes.length; i++) {
tempStr = Integer.toHexString(origBytes[i] & 0xff);
if (tempStr.length() == 1) {
stb.append("0");
}
stb.append(tempStr);
}
return stb.toString();
}
}
七、RSA 加密与解密
1. RSA 定义
RSA加密算法是一种非对称加密算法,所谓非对称,就是指该算法加密和解密使用不同的密钥,即使用加密密钥进行加密、解密密钥进行解密。在RAS算法中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。
2. 应用
- 客户端将其公钥发送到服务器并请求一些数据
- 服务器使用客户端的公钥加密数据并发送加密数据
- 客户端接收此数据并对其进行解密
3. RSA 加密步骤
- 生成密钥对
- 公开公钥
- 公钥加密私钥解密
- 私钥加密公钥解密
4. 代码示例
import com.sun.org.apache.xml.internal.security.utils.Base64;
import javax.crypto.Cipher;
import org.apache.commons.io.FileUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RsaUtil {
// 生成密钥对并保存在本地文件中
// algorithm: 算法; pubPath: 公钥保存路径 ; priPath: 私钥保存路径
private static void generateKeyToFile(String algorithm, String pubPath,
String priPath) throws Exception {
// 获取密钥对生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator
.getInstance(algorithm);
// 获取密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 获取公钥
PublicKey publicKey = keyPair.getPublic();
// 获取私钥
PrivateKey privateKey = keyPair.getPrivate();
// 获取byte数组
byte[] publicKeyEncoded = publicKey.getEncoded();
byte[] privateKeyEncoded = privateKey.getEncoded();
// 进行Base64编码
String publicKeyString = Base64.encode(publicKeyEncoded);
String privateKeyString = Base64.encode(privateKeyEncoded);
// 保存文件
FileUtils.writeStringToFile(new File(pubPath), publicKeyString,
Charset.forName("UTF-8"));
FileUtils.writeStringToFile(new File(priPath), privateKeyString,
Charset.forName("UTF-8"));
}
// 从文件中加载公钥
//algorithm: 算法; filePath: 文件路径: 公钥
private static PublicKey loadPublicKeyFromFile(String algorithm,
String filePath) throws Exception {
// 将文件内容转为字符串
String keyString = FileUtils.readFileToString(new File(filePath),
Charset.forName("UTF-8"));
return loadPublicKeyFromString(algorithm, keyString);
}
// 从字符串中加载公钥
// algorithm: 算法; keyString: 公钥字符串
private static PublicKey loadPublicKeyFromString(String algorithm,
String keyString) throws Exception {
// 进行Base64解码
byte[] decode = Base64.decode(keyString);
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 构建密钥规范
X509EncodedKeySpec keyspec = new X509EncodedKeySpec(decode);
// 获取公钥
return keyFactory.generatePublic(keyspec);
}
// 从文件中加载私钥
// algorithm: 算法; filePath : 文件路径
private static PrivateKey loadPrivateKeyFromFile(String algorithm,
String filePath) throws Exception {
// 将文件内容转为字符串
String keyString = FileUtils.readFileToString(new File(filePath),
Charset.forName("UTF-8"));
// 获取私钥
return loadPrivateKeyFromString(algorithm, keyString);
}
// 从字符串中加载私钥
// algorithm : 算法;keyString: 私钥字符串
private static PrivateKey loadPrivateKeyFromString(String algorithm,
String keyString) throws Exception {
// 进行Base64解码
byte[] decode = Base64.decode(keyString);
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 构建密钥规范
PKCS8EncodedKeySpec keyspec = new PKCS8EncodedKeySpec(decode);
// 生成私钥
return keyFactory.generatePrivate(keyspec);
}
// 使用密钥加密数据
//algorithm: 算法;input: 原文;key: 密钥;maxEncryptSize: 最大加密长度(需要根据实际情况进行调整)
private static String encrypt(String algorithm, String input, Key key,
int maxEncryptSize) throws Exception {
// 获取Cipher对象
Cipher cipher = Cipher.getInstance(algorithm);
// 初始化模式(加密)和密钥
cipher.init(Cipher.ENCRYPT_MODE, key);
// 将原文转为byte数组
byte[] data = input.getBytes();
// 总数据长度
int total = data.length;
// 输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
decodeByte(maxEncryptSize, cipher, data, total, baos);
// 对密文进行Base64编码
return Base64.encode(baos.toByteArray());
}
// 解密数据
//algorithm: 算法;encrypted: 密文;key: 密钥;maxDecryptSize: 最大解密长度(需要根据实际情况进行调整)
private static String decrypt(String algorithm, String encrypted, Key key,
int maxDecryptSize) throws Exception {
// 获取Cipher对象
Cipher cipher = Cipher.getInstance(algorithm);
// 初始化模式(解密)和密钥
cipher.init(Cipher.DECRYPT_MODE, key);
// 由于密文进行了Base64编码, 在这里需要进行解码
byte[] data = Base64.decode(encrypted);
// 总数据长度
int total = data.length;
// 输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
decodeByte(maxDecryptSize, cipher, data, total, baos);
// 输出原文
return baos.toString();
}
// 分段处理数据
// maxSize: 最大处理能力;cipher: Cipher对象;data: 要处理的byte数组;total: 总数据长度;baos: 输出流
private static void decodeByte(int maxSize, Cipher cipher, byte[] data,
int total, ByteArrayOutputStream baos) throws Exception {
// 偏移量
int offset = 0;
// 缓冲区
byte[] buffer;
// 如果数据没有处理完, 就一直继续
while (total - offset > 0) {
// 如果剩余的数据 >= 最大处理能力, 就按照最大处理能力来加密数据
if (total - offset >= maxSize) {
// 加密数据
buffer = cipher.doFinal(data, offset, maxSize);
// 偏移量向右侧偏移最大数据能力个
offset += maxSize;
} else {
// 如果剩余的数据 < 最大处理能力, 就按照剩余的个数来加密数据
buffer = cipher.doFinal(data, offset, total - offset);
// 偏移量设置为总数据长度, 这样可以跳出循环
offset = total;
}
// 向输出流写入数据
baos.write(buffer);
}
}
}
八、PBE 加密与解密
1. PBE 定义
PBE是一种基于口令的加密算法,使用口令代替其他对称加密算法中的密钥。其特点在于口令由用户自己掌管,不借助任何物理媒体,采用随机数(这里我们叫做盐)杂凑多重加密等方法保证数据的安全性。
2. 密钥长度和默认密钥长度
| 算法 | 密钥长度 | 默认密钥长度 |
|---|---|---|
| PBEWithMD5AndDES | 56 | 56 |
| PBEWithMD5AndTripleDES | 112,168 | 168 |
| PBEWithSHA1AndDESede | 112,168 | 168 |
| PBEWithSHA1AndRC2_40 | 40-1024 | 128 |
3. 代码示例
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class PBEUtil {
public static final String ALGORITHM = "PBEWITHMD5andDES";
public static final int ITERATION_COUNT = 100;
public static byte[] initSalt() throws Exception {
// 实例化安全随机数
SecureRandom random = new SecureRandom();
return random.generateSeed(8);
}
// 转换密钥
// password:密码
private static Key toKey(String password) throws Exception {
// 密钥材料
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
// 实例化
SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
// 生成密钥
return factory.generateSecret(keySpec);
}
// 加密
// data:待加密数据;password:密钥;salt:盐值
public static byte[] encrypt(byte[] data, String password, byte[] salt)
throws Exception {
// 转换密钥
Key key = toKey(password);
// 实例化PBE参数材料
PBEParameterSpec spec = new PBEParameterSpec(salt, ITERATION_COUNT);
// 实例化
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
return cipher.doFinal(data);
}
// 解密
// data:待解密数据; password:密钥 ;salt:盐值
public static byte[] decrypt(byte[] data, String password, byte[] salt)
throws Exception {
// 转换密钥
Key key = toKey(password);
// 实例化PBE参数材料
PBEParameterSpec spec = new PBEParameterSpec(salt, ITERATION_COUNT);
// 实例化
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化
cipher.init(Cipher.DECRYPT_MODE, key, spec);
// 执行操作
return cipher.doFinal(data);
}
private static String showByteArray(byte[] data) {
if (null == data) {
return null;
}
StringBuilder sb = new StringBuilder();
for (byte b : data) {
sb.append(b).append(",");
}
sb.deleteCharAt(sb.length() - 1);
sb.append("");
return sb.toString();
}
public static void main(String[] args) throws Exception {
byte[] salt = initSalt();
System.out.println("salt:" + showByteArray(salt));
String password = "1111";
System.out.println("口令:" + password);
String data = "PBE数据";
System.out.println("加密前数据:String:" + data);
System.out.println("加密前数据:byte[]:" + showByteArray(data.getBytes()));
byte[] encryptData = encrypt(data.getBytes(), password, salt);
System.out.println("加密后数据:byte[]:" + showByteArray(encryptData));
byte[] decryptData = decrypt(encryptData, password, salt);
System.out.println("解密后数据: byte[]:" + showByteArray(decryptData));
System.out.println("解密后数据: string:" + new String(decryptData));
}
}
九、恺撒加密
1. 定义
恺撒加密是一种替换加密的技术,明文中的所欲字母都在字母表上向后(或向前)按照一个固定的数目进行偏移后被替换成密文。例如:当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。
2. 代码示例
public class KaisaUtil {
// 凯撒加密方式加密数据
// orignal:原文;key:密钥;
private static String encryptKaisa(String orignal, int key) {
// 将字符串转换为数组
char[] chars = orignal.toCharArray();
StringBuffer buffer = new StringBuffer();
// 遍历数组
for (char aChar : chars) {
// 获取字符的ASCII编码
int asciiCode = aChar;
// 偏移数据
asciiCode += key;
// 将偏移后的数据转为字符
char result = (char) asciiCode;
// 拼接数据
buffer.append(result);
}
return buffer.toString();
}
// 使用凯撒加密方式解密数据
// encryptedData:密文;key:密钥
private static String decryptKaiser(String encryptedData, int key) {
// 将字符串转为字符数组
char[] chars = encryptedData.toCharArray();
StringBuilder sb = new StringBuilder();
// 遍历数组
for (char aChar : chars) {
// 获取字符的ASCII编码
int asciiCode = aChar;
// 偏移数据
asciiCode -= key;
// 将偏移后的数据转为字符
char result = (char) asciiCode;
// 拼接数据
sb.append(result);
}
return sb.toString();
}
public static void main(String[] args) {
String str = "open fire";
String encode = encryptKaisa(str, 3);
System.out.println("加密后:" + encode);
String decode = decryptKaiser(encode, 3);
System.out.println("解密后:" + decode);
}
}
十、加密算法的安全级别
DES, MD5(薄弱) < RC4, SHA-1(传统) < 3DES(基准) < AES-128, SHA-256(标准) < AES-192, SHA-384(较高) < AES-256, SHA-512(超高)