首先应当区分加密与编码并不是一回事。
加密方式主要有3种:
可逆
【对称加密】:symmetric,例如 AES、DES 等。
【非对称加密】:asymmetric,例如 RSA、DSA 等。
不可逆
加解密思想:将一种排序好的二进制,转变为另一种排序的二进制。
注意:在加密后得到的是二进制数据,一般来说我们需要将其转变为更加容易阅读的十六进制范式,两种方式
String en = Base64.getEncoder().encodeToString( endoce );
// 默认的2进制转16进制。【1】表示整数,不加可能得负值(结果出错)
String encodeHex = new BigInteger(1,msg).toString(16);
// 16进制数据转2进制
new BigInteger("1f6f", 16).toString(2)
16进制一般针对无法显示的一些二进制进行显示,常用于:
【英语翻译】:
BouncyCastle库:
一个提供了很多哈希算法和加密算法的第三方库。它包含了Java原生中所缺失的一些加密方式(如国产算法),需要时再导入。
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk15onartifactId>
<version>1.70version>
dependency>
在接下来的章节中,为了达到快速开发的目的,我会结合着Java辅助开发框架【hutool】来讲解。
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-cryptoartifactId>
<version>5.8.5version>
dependency>
简介
分组加密:也叫块加密(block cyphers),一次加密明文中的一个块。
序列加密:也叫流加密(stream cyphers),一次加密明文中的一个位。
加密方式:

工作模式(6种):维基百科
填充模式(9种):
FF FF FF FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07FF FF FF FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF 3F 7A B4 09 14 36 07FF FF FF FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF 80 00 00 00 00 00 00FF FF FF FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00FF FF FF FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07FF FF FF FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00FF FF FF FF FF FF FF FF F0FF FF FF FF FF FF FF FF F0 FF FF FF FF FF FF FF密匙长度:
以AES为例,密匙长度指的就是 key 的长度,有AES128、AES192、AES256之分,密匙长度越长、保护性就越强、所需要的计算量就越大。密钥长度根据指定密钥位数分别为16、24、32个字符,IV与密钥超过长度则截取,不足则在末尾填充’\0’补足,选择了对应长度的密匙加密方式,密匙本身长度也要跟上。

AES,ECB模式加密
简介:
步骤:
【Java原生-加密】
String message = "待加密内容";
byte[] data = message.getBytes("UTF-8");
// 128位 = 16 byte 的 key 值
byte[] key = "1234567890abcdef".getBytes("UTF-8");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec); // 初始化加密工具
byte[] encrypted = cipher.doFinal(data); // 加密
String en = Base64.getEncoder().encodeToString(encrypted);
byte[] key = "1234567890abcdef".getBytes("UTF-8");
byte[] decode = Base64.getDecoder()
.decode("AYpKX4YMIhmojH89B3Pd9Q==");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec); // 解密模式
byte[] bytes = cipher.doFinal(decode);
String msg = new String(bytes,"utf-8");
System.out.println(msg); // 输出(正确):“待加密内容”
String content = "中文";
//随机生成密钥,当然也可以自定义
byte[] key = SecureUtil
.generateKey(SymmetricAlgorithm.AES.getValue())
.getEncoded();
//构建【对称加密类(工具)】
AES aes = SecureUtil.aes(key);
// 1. 普通加密、解密
byte[] encrypt = aes.encrypt(content);
byte[] decrypt = aes.decrypt(encrypt);
// 2. 加密、解密为16进制字符。
String encryptHex = aes.encryptHex(content);
String decryptStr = aes.decryptStr(encryptHex);
AES,CBC模式加密
简介:
需要一个随机数作为IV(Initialization Vector)初始化因子,这样对于同一份明文,每次生成的密文都会不同。IV参数不需要保密,在解密的时候作入参传入。IOS等移动端对AES加密有要求,必须为PKCS7Padding模式。
【原生】简单复合案例
public class Main {
public static void main(String[] args) throws Exception {
// 原文:
String message = "Hello, world!";
System.out.println("Message: " + message);
// 256位密钥 = 32 bytes Key:
byte[] key = "1234567890abcdef1234567890abcdef".getBytes("UTF-8");
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
}
// 加密:
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// CBC模式需要生成一个16 bytes的initialization vector:
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] iv = sr.generateSeed(16);
IvParameterSpec ivps = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
byte[] data = cipher.doFinal(input);
// IV不需要保密,把IV和密文一起返回:
return join(iv, data);
}
// 解密:
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 把input分割成IV和密文:
byte[] iv = new byte[16];
byte[] data = new byte[input.length - 16];
System.arraycopy(input, 0, iv, 0, 16);
System.arraycopy(input, 16, data, 0, data.length);
// 解密:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivps = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
return cipher.doFinal(data);
}
public static byte[] join(byte[] bs1, byte[] bs2) {
byte[] r = new byte[bs1.length + bs2.length];
System.arraycopy(bs1, 0, r, 0, bs1.length);
System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
return r;
}
}
AES aes = new AES(Mode.CBC,Padding.PKCS5Padding,
"0123456789ABHAEQ".getBytes(), // 密匙key
"DYgjCEIMVrj2W9xN".getBytes()); // iv加盐
// 加密、解密,16进制表示
String encryptHex = aes.encryptHex(content);
String decryptStr = aes.decryptStr(encryptHex);
简介
【必要性说明】
非对称加密相较于对称加密速度慢、效率低,实际开发中,非对称加密总是和对称加密一起使用。
RAS加密
public class Main {
public static void main(String[] args) throws Exception {
// 明文:
byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
// 创建公钥/私钥对:
Person alice = new Person("Alice");
// 用Alice的公钥加密:
byte[] pk = alice.getPublicKey();
System.out.println(String.format("public key: %x", new BigInteger(1, pk)));
byte[] encrypted = alice.encrypt(plain);
System.out.println(String.format("encrypted: %x", new BigInteger(1, encrypted)));
// 用Alice的私钥解密:
byte[] sk = alice.getPrivateKey();
System.out.println(String.format("private key: %x", new BigInteger(1, sk)));
byte[] decrypted = alice.decrypt(encrypted);
System.out.println(new String(decrypted, "UTF-8"));
}
}
class Person {
String name;
// 私钥:
PrivateKey sk;
// 公钥:
PublicKey pk;
public Person(String name) throws GeneralSecurityException {
this.name = name;
// 生成公钥/私钥对:
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
this.sk = kp.getPrivate();
this.pk = kp.getPublic();
}
// 把私钥导出为字节
public byte[] getPrivateKey() {
return this.sk.getEncoded();
}
// 把公钥导出为字节
public byte[] getPublicKey() {
return this.pk.getEncoded();
}
// 用公钥加密:
public byte[] encrypt(byte[] message) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, this.pk);
return cipher.doFinal(message);
}
// 用私钥解密:
public byte[] decrypt(byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, this.sk);
return cipher.doFinal(input);
}
}
// 构建 RSA 加密工具,可传参【公匙】与【私匙】。
AsymmetricCrypto rsa = new AsymmetricCrypto("RSA");
// 获得私钥,可存储于其他文件中
PrivateKey sk = rsa.getPrivateKey();
String sk64 = rsa.getPrivateKeyBase64();
// 获得公钥,可存储于其他文件中
PublicKey pk = rsa.getPublicKey();
String pk64 = rsa.getPublicKeyBase64();
// 公钥加密,私钥解密
String encode = rsa.encryptBase64("中文", KeyType.PublicKey);
String decode = rsa.decryptStr(encode, KeyType.PrivateKey);
RAS签名

// 生成RSA公钥、私钥
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
PrivateKey sk = kp.getPrivate();
PublicKey pk = kp.getPublic();
// 待签名的消息
byte[] message = "中文".getBytes(StandardCharsets.UTF_8);
// 用私钥签名
Signature signSk = Signature.getInstance("SHA1withRSA");
signSk.initSign(sk);
signSk.update(message);
byte[] signed = signSk.sign();
System.out.println(String.format("signature: %x", new BigInteger(1, signed)));
// 用公钥验证,最后得到的是Boolean值,判断真假。
Signature signPk = Signature.getInstance("SHA1withRSA");
signPk.initVerify(pk);
signPk.update(message);
boolean valid = signPk.verify(signed);
System.out.println("valid? " + valid);
byte[] data = "中文".getBytes();
Sign sign = SecureUtil.sign(SignAlgorithm.MD5withRSA);
//签名
byte[] signed = sign.sign(data);
//验证签名
boolean verify = sign.verify(data, signed);
System.out.println(verify);
// 可以将本次生成的私匙与公匙保存到文件
PublicKey pk = sign.getPublicKey();
PrivateKey sk = sign.getPrivateKey();
简介
加盐思想:为什么加盐能够防止黑客通过彩虹表破解?
因为在没有加盐时,黑客拥有一张通过大量时间计算得出来的“彩虹表1”,可以轻易的碰撞到原始密码。当我们加盐之后,由于盐值可以是随机的(比如盐值为用户名),此时黑客就需要重新计算大量数据,这种计算是极其耗费时间的。
Java原生哈希算法:输入任意字符,输出固定4字节int型整数。
// 1. hash 后获得十进制数
Integer code = "Java".hashCode();
// 2. 将十进制数转为十六进制数
String hashString = Integer.toHexString(code);
// 3. 输出(0x)231e42,4字符 = 32位 = 长度为2+6的十六进制数。
System.out.println(hashString);
常见Hash算法

hutool提供了一些哈希算法实现

MD5加密:不加盐版
// 获取MessageDigest实例:
MessageDigest md = MessageDigest.getInstance("MD5");
// 反复调用update输入数据:
md.update("中".getBytes("UTF-8"));
md.update("文".getBytes("UTF-8"));
// a7bac2239fcdcb3a067903d8077c4a07
byte[] result = md.digest();
System.out.println(new BigInteger(1, result).toString(16));
String testStr = "中文";
Digester md5 = new Digester(DigestAlgorithm.MD5);
String digestHex = md5.digestHex(testStr);
// a7bac2239fcdcb3a067903d8077c4a07
System.out.println(digestHex);
MD5加密:加盐版。
加盐本质:拼接字符串再加密。
【原生】:直接拼接字符串,将 key 与 原文拼接一起再 encry。
【Hmac算法】:Hash-based Message Authentication Code,Java原生类,一种更安全的哈希算法,适用于任何可迭代的哈希算法,例如md5、SHA-1等。
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
SecretKey key = keyGen.generateKey();
// 打印随机生成的key:
byte[] skey = key.getEncoded();
System.out.println(new BigInteger(1, skey).toString(16));
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);
mac.update("HelloWorld".getBytes("UTF-8"));
byte[] result = mac.doFinal();
System.out.println(new BigInteger(1, result).toString(16));
byte[] salt = "password".getBytes();
HMac hmac = new HMac(HmacAlgorithm.HmacMD5, salt);
// b977f4b13f93f549e06140971bded384
String hmacHex = hmac.digestHex("中文".getBytes("UTF-8"));
System.out.println(macHex);
重要章节
简介:
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk15onartifactId>
<version>1.70version>
dependency>
国密加密主要分为:
SM1算法不公开,仅以IP核的方式存在于芯片中,例如智能门锁、智能IC卡等,成本高。
非对称加密SM2
String text = "中文";
SM2 sm2 = SmUtil.sm2();
// 随机公钥加密、私钥解密,自定义直接构造时传入或set__()即可
String enStr = sm2.encryptBcd(text, KeyType.PublicKey);
byte[] de = sm2.decryptFromBcd(encryptStr, KeyType.PrivateKey);
String deStr = new String(decode);
【签名】
HexUtil是将字符串或byte数组与16进制表示转换的工具类,效果与使用BigInteger一样,其两者底层实现一样,可以互用。
String content = "中文";
final SM2 sm2 = SmUtil.sm2();
// 先转为十六进制数据Hex,然后再签名
String sign = sm2.signHex(HexUtil.encodeHexStr(content));
// true
boolean v = sm2.verifyHex(HexUtil.encodeHexStr(content), sign);
摘要加密SM3
String digestHex = SmUtil.sm3("中文");
对称加密SM4
String content = "test中文";
SymmetricCrypto sm4 = SmUtil.sm4();
String encryptHex = sm4.encryptHex(content);
String decryptStr = sm4.decryptStr(encryptHex);