1,摘要算法
1)特点
- 唯一性;
同一组数据哈希值一样,通常用于校验数据是否被篡改。
由于同一字符串每次hash一致,不安全。 - 碰撞性;
多个数据的md5计算可能是一样的。因此不适用于安全性认证,如SSL公开密钥认证或者数字签名等用途。 - 不可逆;
通过MD5无法还原原始数据。
Sqids算法:org.sqids
通过使用Sqids算法,将uid+时间戳然后散列,避免每次hash值不同。
将一系列整数散列成一个字符串,通过指定字符表的方式对散列多次打乱,确保不会碰撞,且保证密文非常简短。
2)MD算法(Message Digest,消息摘要、信息摘要算法)
MD算法包括MD2、MD4和MD5。
- 压缩性:任意长度的数据,算出的 MD值长度都是128 bit(16字节);都是单向加密,其中JDK自带MD2和MD5。
- 易计算:从原数据计算出 MD 值很容易。
- 抗修改:原数据只要有修改,MD值就会变;
- 解密:hash值无法逆转解密,但是可以通过彩虹表碰撞解密。通过加盐MD5,加大彩虹表构建的复杂度。
字典表
保存了常见的明文->md5映射。空间占用惊人。
彩虹表:
在字典法的基础上改进,以时间换空间。是现在破解哈希函数常用的办法。
1>场景
- 文档校验
如果文档被篡改则md5不同。
如:百度云上传检测机制。
2>MD5 (Message-Digest Algorithm 5,消息摘要算法第五版)
- 长度:256;
- 不可逆
3>加盐MD5
每个用户生成随机数salt,password + salt(自由组合)再做md5,就能生成不一样的hash值,然后将hash值和salt一起存入库里。
- 密码验证:重新加盐匹配hash值即可。
- 安全性:盐值泄露后不安全,此时加盐导致彩虹表的H函数无法普遍应用,由于盐值不同,需要给每个用户建立一个彩虹表,破解复杂度变高。
3)SHA(Secure Hash Algorithm,安全散列算法
1>SHA-1
- 场景:计算机浏览器的管理证书
- 长度:160;
2>SHA-2
SHA-2是SHA-224、SHA-256、SHA-384、SHA-512的统称。
- 长度:每个名称的后面标识数字。
- 安全性:碰撞率低,复杂度高,但不是绝对安全。
1>SHA-256算法
将原始消息(Message)拆解成512-bit大小的消息块。最后一个的消息块需要进行信息补全,并附上原消息的长度信息。消息被分为n块后,需要进行n次迭代,最后一次的结果就是最终的哈希值,即256bit的数字摘要。
2>SHA-3
- 核心算法:Keccak算法;
- 安全性:
2015年成为US建议使用的哈希函数。
4)HMAC(Hash-based Message Authentication Code,散列消息认证码)
HMAC是一种使用密码散列函数(基于不同的散列函数,分别为:HMAC-SHA1, HMAC-SHA256, HMAC-MD5),同时结合一个加密密钥,通过特别计算方式之后产生的消息认证码(MAC)。它要求通信双方共享密钥、约定算法、对报文进行Hash运算,形成固定长度的认证码。通信双方通过认证码的校验来确定报文的合法性。
1>场景
加密、数字签名、报文验证、检测内容是否被篡改、检测消息来源。
2>算法定义
HMAC算法的数学公式为:
HMAC(k,m)=H(k’⊕opad,H(k’⊕ipad,m))
其中:
H 为密码Hash函数(如MD5或SHA-2),能够对明文进行分组循环压缩;
k 为密钥(secret key);
m 为要认证的消息;
k’ 是从原始密钥 k 导出的另一个密钥(如果 k 短于散列函数的输入块大小,则向右填充零;如果比该块大小更长,则对 k 进行散列)
ipad 内部填充(0x5C5C5C…5C5C,一段十六进制常量);
opad 外部填充(0x363636…3636,一段十六进制常量)
5)摘要算法对比
MD5、SHA-1、sha256也都是计算 hash值的算法
算法 | MD5 | SHA1 | SHA256 |
---|---|---|---|
校验值的长度 | 16个字节(128位) | 20个字节(160位) | 32个字节(256位) |
运行速度得分 | 5 | 4 | 2 |
安全性得分 | 2(可能泄露 ) | 3 | 5(目前没有泄露过,主要是太长了) |
2,Base64
1)特点
- base64字符串长度必须是4的倍数;
- 如果 最后一组 字节不足3个字节,必须用“=”填充;
- 主要场景:网络传输、数据存储或数据加密。
- 不安全。
2)解码
import org.springframework.util.Base64Utils;
String code64 = new String( Base64Utils.decode(myStr.getBytes()));
3)转码
import org.springframework.util.Base64Utils;
String myStr = Base64Utils.encodeToString(code64 .getBytes());
3,国密算法
国家密码管理局发布。SM:商业密码,商密
1)SM1 (对称(分组)加密算法)
- 分组长度、钥长度均为128比特
1>应用领域
芯片
2)SM2 (非对称(基于椭圆曲线ECC)加密算法)
- ECC椭圆曲线密码机制256位,相比RSA处理速度快,消耗更少;
1>应用领域
数据加密
3)SM3 (散列(hash)函数算法 )
- 安全性及效率与SHA-256相当,压缩函数更复杂
1>应用领域
完整性校验;
4)SM4 (对称(分组)加密算法)
- 分组长度、密钥长度均为128比特,计算轮数多
1>应用领域
数据加密和局域网产品
5)SM7(对称(分组)加密算法)
- 分组长度、钥长度均为128比特
1>应用领域
非接触式IC卡
6)SM9(标识加密算法(IBE))
- 加密强度等同于3072位密钥的RSA加密算法
1>应用领域
端对端离线安全通讯
7)ZUC(祖冲之加密算法,对称(序列)加密算法)
- 流密码
1>应用领域
移动通信4G网络
8)java编码
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.56</version>
</dependency>
/**
* @Author zhoumengjun
* @Description sm4加密算法工具类
* @Date 2023/7/12 10:47
*/
public class SM4Util {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ENCODING = "UTF-8";
public static final String ALGORITHM_NAME = "SM4";
// 加密算法/分组加密模式/分组填充方式
// PKCS5Padding-以8个字节为一组进行分组加密
// 定义分组加密模式使用:PKCS5Padding
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
// 128-32位16进制;256-64位16进制
public static final int DEFAULT_KEY_SIZE = 128;
/**
* 生成ECB暗号
*
* @param algorithmName 算法名称
* @param mode 模式
* @param key
* @return
* @throws Exception
* @explain ECB模式(电子密码本模式:Electronic codebook)
*/
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
/**
* 自动生成密钥
*
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @explain
*/
public static String generateKey() throws Exception {
return new String(Hex.encode(generateKey(DEFAULT_KEY_SIZE)));
}
/**
* @param keySize
* @return
* @throws Exception
* @explain
*/
public static byte[] generateKey(int keySize) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(keySize, new SecureRandom());
return kg.generateKey().getEncoded();
}
/**
* sm4加密
*
* @param hexKey 16进制密钥(忽略大小写)
* @param paramStr 待加密字符串
* @return 返回16进制的加密字符串
* @throws Exception
* @explain 加密模式:ECB
* 密文长度不固定,会随着被加密字符串长度的变化而变化
*/
public static String encryptEcb(String hexKey, String paramStr) throws Exception {
String cipherText = "";
// 16进制字符串-->byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// String-->byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 加密后的数组
byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
// byte[]-->hexString
cipherText = ByteUtils.toHexString(cipherArray);
return cipherText;
}
/**
* 加密模式之Ecb
*
* @param key
* @param data
* @return
* @throws Exception
* @explain
*/
public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
/**
* sm4解密
*
* @param hexKey 16进制密钥
* @param cipherText 16进制的加密字符串(忽略大小写)
* @return 解密后的字符串
* @throws Exception
* @explain 解密模式:采用ECB
*/
public static String decryptEcb(String hexKey, String cipherText) throws Exception {
// 用于接收解密后的字符串
String decryptStr = "";
// hexString-->byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// hexString-->byte[]
byte[] cipherData = ByteUtils.fromHexString(cipherText);
// 解密
byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
// byte[]-->String
decryptStr = new String(srcData, ENCODING);
return decryptStr;
}
/**
* 解密
*
* @param key
* @param cipherText
* @return
* @throws Exception
*/
public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
/**
* 校验加密前后的字符串是否为同一数据
*
* @param hexKey 16进制密钥(忽略大小写)
* @param cipherText 16进制加密后的字符串
* @param paramStr 加密前的字符串
* @return 是否为同一数据
* @throws Exception
*/
public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception {
// 用于接收校验结果
boolean flag = false;
// hexString-->byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// 将16进制字符串转换成数组
byte[] cipherData = ByteUtils.fromHexString(cipherText);
// 解密
byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);
// 将原字符串转换成byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 判断2个数组是否一致
flag = Arrays.equals(decryptData, srcData);
return flag;
}
public static void main(String[] args) {
try {
String data = "2023-07-12,下午三点";
//生成key
String key = generateKey();
System.out.println("key:" + key);
//加密
String cipher = SM4Util.encryptEcb(key, data);
System.out.println("加密后:"+cipher);
//判断是否正确
System.out.println(SM4Util.verifyEcb(key, cipher, data));// true
//解密
String res = SM4Util.decryptEcb(key, cipher);
System.out.println("解密后:"+res);
} catch (Exception e) {
e.printStackTrace();
}
}
}
4,对称加密(私钥加密、传统密码算法、秘密密钥算法、单密钥算法)
- 加密和解密使用相同密钥;
- 算法公开、计算量小、加密速度快、加密效率高;
- 不安全。
1)加密模式
分组密码(块加密):
每次只能处理特定长度的一块数据的一类密码算法,这里的"一块"就称为分组。
一个分组的比特数就称为分组长度。
举例:ECB、CBC;DES、3DES、AES
流密码:
对数据流进行连续处理的一类密码算法。
流密码中一般以1bit、8bit、32bit等为单位进行加密存储。
举例:OFB、CFB、CTR;一次性密码
1>OFB(Output FeedBack,输出反馈)
优点:
- 隐藏了明文模式;
- 分组密码转化为流模式;
- 可以及时加密传送小于分组的数据;
缺点:
- 不利于并行计算;
- 误差传送:一个明文单元损坏影响多个单元;
- 对明文的主动攻击是可能的;
2>CFB(Cipher FeedBack Mode,加密反馈)
CFB与CBC的区别在于,CFB会把分组的密文再进行加密后,参与下一个分组的加密。
优点:
- 隐藏了明文模式;
- 分组密码转化为流模式;
- 可以及时加密传送小于分组的数据;
缺点:
- 不利于并行计算;
- 误差传送:一个明文单元损坏影响多个单元;
- 唯一的IV;
3>ECB(Electronic Code Book电子密码本)
a)算法
- 将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。
- 如果最后一个分组小于分组长度,padding(用特定数据填充)。中间密文修改,最后几位不变就不会影响解密。
b)缺点
- 不能隐藏明文的模式;
- 可能对明文进行主动攻击;
c)场景
加密小消息。
4>CBC(Cipher Block Chaining,加密块链)
相比ECB更安全。
a)算法
- IV(Initialization Vector,初始化向量):声明(或者随机产生)一个比特序列作为初始化向量。
- 第一个明文分组与IV进行XOR运算,得到加密分组。
解密时同理,IV XOR 加密分组=明文分组。 - 从第二个明文分组开始,每个分组与前一个密文分组进行XOR运算,得到加密分组。
解密时同理,前一个密文分组 XOR 加密分组=明文分组。 - 如果明文长度不为分组长度的整数倍时,需要在最后一个分组中填充一些数据,使其凑满一个分组长度。
- 密文返回16进制或者二进制。
b)缺点
- 不利于并行计算;
- 误差传递;
- 需要初始化向量IV
5>CTR(CounTeR mode, 计时器模式)
是一种通过将逐次累加的计算器进行加密来生成密钥流的流密码。每一个分组对应一个逐次累加的计数器,并通过计数器进行加密来生成密钥流。也就是说,最终的密文分组是通过将计数器加密得到的比特序列,与明文分组进行XOR而得到的。
2)补位算法
1>ZeroPadding
数据长度不对齐时使用0填充,否则不填充。
2>PKCS5Padding (Public Key Cryptography Standards #5,PKCS#5)
将数据填充到8的倍数,填充后数据长度的计算公式是 定于元数据长度为x, 填充后的长度是 x + (8 - (x % 8))
, 填充的数据是 8 - (x % 8)
,块大小固定为8字节。
3>PKCS7Padding (Public Key Cryptography Standards #7)
假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小;PKCS5只填充到8字节,而PKCS7可以在1-255之间任意填充。
4>Nopadding
不进行补位,数据长度必须为块大小的倍数。
3)DES加密(Data Encryption Standard, 数据加密标准)
DEA(Data Encryption Algorithm,数据加密算法)
1>加密参数
块加密。
- Key:秘钥;
7个字节共56位 + 8位校验位。
每个秘钥都有奇数个1。 - Data:要加解密的数据;
8个字节64位 - Mode:工作方式,有两种:加密或解密。
2>加密算法
import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
/**
* 使用DES加密和解密的方法
*/
public class Des {
//密钥
private final byte[] DESkey = "abcdefgh".getBytes();
private final byte[] DESIV = {0x12, 0x34, 0x56, 0x78, (byte) 0x90, (byte) 0xAB, (byte) 0xCD, (byte) 0xEF};// 设置向量
Key key = null;
AlgorithmParameterSpec iv = null;
public Des() throws Exception {
// 设置密钥参数
DESKeySpec keySpec = new DESKeySpec(DESkey);
// 设置向量
iv = new IvParameterSpec(DESIV);
// 获得密钥工厂
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
// 得到密钥对象
key = keyFactory.generateSecret(keySpec);
}
/**
* 加密
*/
public String encode(String data) throws Exception {
// 得到加密对象Cipher
Cipher enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
// 设置工作模式为加密模式,给出密钥和向量
enCipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] pasByte = enCipher.doFinal(data.getBytes("utf-8"));
BASE64Encoder base64Encoder = new BASE64Encoder();
return base64Encoder.encode(pasByte);
}
/**
* 解密
*/
public String decode(String data) throws Exception {
Cipher deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
deCipher.init(Cipher.DECRYPT_MODE, key, iv);
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] pasByte = deCipher.doFinal(base64Decoder.decodeBuffer(data));
return new String(pasByte, "UTF-8");
}
}
4)3DES(Triple DEA,三重数据加密算法)
DES向AES过渡的加密算法,它使用3条56位的密钥对数据进行三次加密,更加安全。
该方法使用两个密钥,执行三次DES算法,加密的过程是加密-解密-加密(加密-加密-加密这样的效果等同于DES),解密的过程是解密-加密-解密。
5)AES(Advanced Encryption Standard,高级加密标准)
5,非对称加密
常用算法:RSA、Elgamal、背包算法、Rabin、D-H、ECC (椭圆曲线加密算法)。
1)概念
- 需要2个秘钥:公钥、私钥配对使用:
公钥(public key, PK,加密秘钥)
公开,且互不相同。
私钥(private key,SK,解密秘钥)
保密,且互不相同,无法通过公钥计算出。
- 加密算法和解密算法公开。
- 性能差:对比对称加密,加解密甚至能慢到1000倍。
一般会在第一次身份验证(非对称加密)后,携带一个有时效的信任凭证进行后续的交互。
2)通信过程
A向B发送消息,A和B各自持有公私钥:P(A),S(A),P(B),S(B):
- 发送消息:A知道P(B)并用其加密;
- 接收消息:B收到密文,通过S(B)解密。其它人收到消息由于不知道私钥,无法解密。
3)场景
- 信息加密;
- 登录认证;
- 数字签名;
- 数字证书:SSL证书
4)RSA加密(常用)
原理:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难。
因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用。
1>攻击:大数因数分解
RSA-155 (512 bits) 在1999年时已经可以被分解;现存的量子计算机算量更不需多数。
2>公钥传递安全
通过中间人C,截留A和B之间通信的消息,然后把消息和自己的公钥分别发给A和B,即可以达到无感知的信息监听。一般通过数字证书来避免这种问题。
5)DSA (Digital Signature Algorithm)
基于椭圆曲线密码学的非对称加密算法(是基于整数有限域离散对数难题的),主要用于数字签名。
通信过程:
- 使用消息摘要算法将发送数据加密生成数字摘要。
- 发送方用自己的私钥对摘要再加密,形成数字签名。
- 将原文和加密的摘要同时传给对方。
- 接受方用发送方的公钥对摘要解密,同时对收到的数据用消息摘要算法产生同一摘要。
- 将解密后的摘要和收到的数据在接收方重新加密产生的摘要相互对比,如果两者一致,则说明在传送过程中信息没有破坏和篡改。否则,则说明信息已经失去安全性和保密性。
6)ECC(椭圆加密算法)
ECC 被广泛认为是在给定密钥长度的情况下,最强大的非对称算法,因此在对带宽要求十分紧的连接中会十分有用。
7)DH算法(Diffie-Hellman,密钥一致协议、密钥交换算法)
是一种确保共享 KEY 安全穿越不安全网络的方法。
- 场景:不信任网络的互信、加密问题;
- 过程:由甲方产出一对密钥(公钥、私钥),乙方依照甲方公钥产生乙方密钥对(公钥、私钥)。甲通过公钥和自己的私钥加密,乙方可通过公钥和自己的私钥解密。
默认密钥长度:1024
6,常见攻击
1)越权
1>水平越权
可以访问非当前用户的数据。
如:修改当前用户id+1去获取数据。
规避手段:
- 用uuid代替自增id,避免被猜出id数据;
- 限制ip、当前uid在单位时间内请求频次;
- 用户不同分权分域。
- 关键参数进行加密混淆。
如对称加密等,但是这样参数就会非常长,此处推荐Sqids
算法。
2)DDoS攻击
3)XSS攻击
4)CSRF攻击
5)DNS劫持
6)中间人攻击
7)SQL注入
引发拖库(导出db数据)、洗库(变卖数据)风险。
避免:
- sql的字符串参数做格式校验;
- sql预编译后执行。
8)XXE漏洞
XML外部实体注入攻击。
避免:
- 对
<!DOCTYPE
和<!ENTITY
,或者,SYSTEM
和PUBLIC
等做严格校验; - 使用简单的数据格式(如:JSON),避免对敏感数据进行序列化。xml可以转为json后再处理。