内容加密
消息摘要
它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生,输入长度不定,输出长度一定,单向不可逆。使用数字摘要生成的值是不可以篡改的,为了保证文件或者值的安全
特点:无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如:应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出
常见算法:MD5、SHA1、SHA256、SHA512
public static void main(String[] args) throws NoSuchAlgorithmException {
String s = "hello";
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] digest = md5.digest(s.getBytes());
// Base64 = XUFAKrxLKna5cZ2REBfFkg==
String result = Base64.encodeBase64String(digest);
// 16进制 = 5d41402abc4b2a76b9719d911017c592
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
// 转成 16进制
String st = Integer.toHexString(b & 0xff);
if (st.length() == 1) {
// 如果生成的字符只有一个,前面补0
st = "0" + st;
}
sb.append(st);
}
// 输出Base64
System.out.println(result);
// 输出16进制
System.out.println(sb);
}
出现的结果是不可逆的
对称加密
使用相同的密钥(称为对称密钥)来加密和解密数据。这意味着加密和解密过程都使用相同的密钥。对称加密的优点是加密和解密速度快,适合对大量数据进行加密,但缺点是在密钥管理方面存在挑战
对称加密有两种方式:流加密、块加密
加密:
public static void main(String[] args) throws
NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidKeyException,
IllegalBlockSizeException,
BadPaddingException {
// 加密的内容
String input = "hello world";
// 加密的密钥
String key = "mysecretkey12345";
// 加密的算法(AES、DES)
String string = "AES";
// 创建加密对象
Cipher des = Cipher.getInstance(string);
// 创建加密规则(第一个表示key的字节 第二个则是加密的类型)
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), string);
// 初始化(第一个写[加密|解密] 第二个写规则)
des.init(Cipher.ENCRYPT_MODE, secretKeySpec);
// [加密|解密]完成
byte[] bytes = des.doFinal(input.getBytes());
// 使用 Base64 转码(不然会乱码)
String s = Base64.encodeBase64String(bytes);
// 输出
System.out.println(s);
}
创建加密对象时候,内部参数由两种写法
Cipher.getInstance("算法");
Cipher.getInstance("算法/模式/填充");
解密:Cipher.DECRYPT_MODE
加密:Cipher.ENCRYPT_MODE
如果使用AES进行加密则key必须是16位,DES则必须是8位
解密:
// 被加密的内容
String input = "wK97HBWTSMjnyLsrSrpCLg==";
......
// 初始化(第一个写[加密|解密] 第二个写规则)
des.init(Cipher.DECRYPT_MODE, secretKeySpec);
// [加密|解密]完成
byte[] bytes = des.doFinal(Base64.decodeBase64(input));
Base64
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.16.1</version>
</dependency>
Base64不是加密算法,是可读性算法
Base64是网络上最常见的用于传输8bit字节码的可读性编码算法之一,可读性编码算法不是为了保护数据的安全性,而是为了可读性,可读性编码不改变信息内容,只改变信息内容的表现形式
Base64 算法原理
base64 是3个字节为一组,一个字节 8位,一共 就是24位 ,然后,把3个字节转成4组,每组6位,3×8=4×6
3 * 8 = 4 * 6 = 24 ,每组6位(然后扩展为8位1B),缺少的2位,会在高位进行补0 ,这样做的好处在于 ,base取的是后面6位,去掉高2位 ,那么base64的取值就可以控制在0-63位了,所以就叫base64,111 111 = 32 + 16 + 8 + 4 + 2 + 1 =
base64 构成原则
① 小写 a - z = 26个字母
② 大写 A - Z = 26个字母
③ 数字 0 - 9 = 10 个数字
④ + / = 2个符号
等号非常特殊,因为base64是三个字节一组 ,如果当位数不够的时候,会使用等号来补齐
加密模式
ECB:把一段文本进行分拆加密,使用同一个key,分别进行加密,然后组合到一起
CBC:在进行加密的时候,会取决于前面的iv向量,把前面的向量进行异或处理,后面的明文进行加密的时候,会一直依赖于前面的加密key
// 创建IV向量 (DES/CBC/PKCS5Padding)
IvParameterSpec iv = new IvParameterSpec("iv".getBytes());
des.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
填充模式
当需要按块处理的数据, 数据长度不符合块处理需求时, 按照一定的方法填充满块长的规则
- 但要注意DES和AES的限制,不能不俗8/16字节的整数倍
- iv字节也必须是8个字节
PKCS5Padding(默认):数据块的大小为8位, 不够就补足
NoPadding:
- 不填充.
- 在DES加密算法下, 要求原文长度必须是8byte的整数倍
- 在AES加密算法下, 要求原文长度必须是16byte的整数倍
Tips:
- 默认情况下, 加密模式和填充模式为 : ECB/PKCS5Padding
- 如果使用CBC模式, 在初始化Cipher对象时, 需要增加参数, 初始化向量IV : IvParameterSpec iv = new IvParameterSpec(key.getBytes());
加密模式和填充模式
AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
DES/CBC/NoPadding (56)
DES/CBC/PKCS5Padding (56)
DES/ECB/NoPadding (56)
DES/ECB/PKCS5Padding (56)
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
RSA/ECB/PKCS1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
非对称加密
非对称加密需要由两个密钥:公钥和私钥(密钥对);
如果使用公钥加密
,必须使用私钥解密
如果使用私钥加密
,必须使用公钥解密
常见算法:RSA、ECC
创建密钥对:
public static void main(String[] args) throws NoSuchAlgorithmException {
// 创建密钥对
KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
// 获取密钥
KeyPair keyPair = rsa.generateKeyPair();
// 公钥
byte[] publicKey = keyPair.getPublic().getEncoded();
System.out.println(Base64.encodeBase64String(publicKey));
// 私钥
byte[] privateKey = keyPair.getPrivate().getEncoded();
System.out.println(Base64.encodeBase64String(privateKey));
}
使用RSA加密解密:
public static void main(String[] args) throws
NoSuchAlgorithmException,
NoSuchPaddingException,
InvalidKeyException,
IllegalBlockSizeException,
BadPaddingException {
// 创建密钥对
String encrypt = "RSA";
KeyPairGenerator rsa = KeyPairGenerator.getInstance(encrypt);
// 获取密钥
KeyPair keyPair = rsa.generateKeyPair();
// 公钥
PublicKey publicKey = keyPair.getPublic();
String publicKeyString = Base64.encodeBase64String(publicKey.getEncoded());
// 私钥
PrivateKey privateKey = keyPair.getPrivate();
String privateKeyString = Base64.encodeBase64String(privateKey.getEncoded());
// 创建加密对象
Cipher instance = Cipher.getInstance(encrypt);
// 初始化(第一个参数:加密模式;第二个参数:私钥加密)
instance.init(Cipher.ENCRYPT_MODE, privateKey);
// 使用私钥加密并使用Base转换
String s = Base64.encodeBase64String(instance.doFinal("今天天气真不错".getBytes()));
System.out.println(s);
// 再次初始化(第一个参数:解密模式;第二个参数:公钥解密)
instance.init(Cipher.DECRYPT_MODE, publicKey);
byte[] bytes = instance.doFinal(Base64.decodeBase64(s));
System.out.println(new String(bytes));
}
数字签名
数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证
原理
张三有公钥和私钥,私钥自己留着公钥给朋友,在发送邮件的时候使用私钥加密对方使用公钥解密,对方向张三发送使用公钥加密,张三使用公钥解密

还是在上面的基础上,不过在发送邮件的时候对邮件内容进行摘要并使用hash进行计算得到hash码并附带到邮件消息一起发送到对方手里,当对方收到邮件有会先对邮件进行相同的摘要并进行hash计算,并将得到的hash码与跟着邮件一起过来的hash码进行对比,这样可以判断邮件内容是否被更改过

数字证书
对签名进行验证时,需要用到公钥。如果公钥是伪造的,那就无法验证数字签名,也就根本不可能从数字签名确定对方的合法性
这时候就需要证书了,如果不理解证书的作用,可以举一个例子,比如:我们的毕业证书,任何公司都会承认。因为那是国家发得,大家都信任国家。也就是说只要是国家的认证机构,我们都信任它是合法的
服务器有公钥A,私钥A。认证机构有私钥B,公钥B,服务器把公钥A给认证机构,为了让认证中心给自己开个证明说我这个公钥确实是我的,而认证中心自己系统里也知道我给谁鉴定过以及他的公钥,即使浏览器把公钥丢了也可以继续从证书里获取到
生成签名与检验签名:
public static void main(String[] args) throws
NoSuchAlgorithmException,
InvalidKeyException,
SignatureException {
// 创建密钥对
String encrypt = "RSA";
KeyPairGenerator rsa = KeyPairGenerator.getInstance(encrypt);
// 获取密钥
KeyPair keyPair = rsa.generateKeyPair();
// 公钥
PublicKey publicKey = keyPair.getPublic();
// 私钥
PrivateKey privateKey = keyPair.getPrivate();
// 原文
String input = "你好啊";
// 签名算法(输入哈希算法SHA256)
String sha256 = "SHA256withRSA";
// 获取签名数据
String signatureData = getSignature(input, sha256, privateKey);
// 校验签名
boolean b = verifySignature(input, sha256, publicKey, signatureData);
}
// 生成签名
public static String getSignature(String input, String sha256, PrivateKey privateKey) throws
NoSuchAlgorithmException,
InvalidKeyException,
SignatureException {
Signature instance = Signature.getInstance(sha256);
// 使用私钥初始化签名
instance.initSign(privateKey);
// 传入原文
instance.update(input.getBytes());
// 开始签名
byte[] sign = instance.sign();
// Base64转码
return Base64.encodeBase64String(sign);
}
// 校验签名
public static boolean verifySignature(String input, String sha256, PublicKey publicKey, String signatureData) throws
NoSuchAlgorithmException,
InvalidKeyException,
SignatureException {
Signature signature = Signature.getInstance(sha256);
// 初始化签名
signature.initVerify(publicKey);
// 传入原文
signature.update(input.getBytes());
// 校验数据,验证自己活得的签名和传入的签名是否匹配
return signature.verify(Base64.decodeBase64(signatureData));
}