本文针对对称加密,非对称加密,散列函数和数字签名做一个简单的概述
对称加密
一、介绍
对称加密是指加密和解密用同一个密钥对加密,加密就是通过密码和明文获取到密文,解密就是通过密文和密码,获取到明文
优点:计算速度快,适合数据量大的明文进行加密
缺点:不安全,容易被破解;密钥管理困难
二、常见的加密算法
算法 | 密钥长度 | 工作模式 | 填充模式 |
DES | 56/64 | ECB/CBC/PCBC/CTR/… | NoPadding/PKCS5Padding/… |
AES | 128/192/256 | ECB/CBC/PCBC/CTR/… | NoPadding/PKCS5Padding/PKCS7Padding/… |
IDEA | 128 | ECB | PKCS5Padding/PKCS7Padding/… |
三、AES加密
public static void main(String[] args) throws Exception {
// 原文:
String message = "Hello, world!";
System.out.println("明文: " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "HFUOIAYFUGEWGOYO".getBytes("UTF-8");
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = cbcEncrypt(key, data);
System.out.println("加密: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = cbcDecrypt(key, encrypted);
System.out.println("解密: " + new String(decrypted, "UTF-8"));
}
/**
* 加密
*
* @param key 密钥
* @param input 明文
* @return 密文
* @throws GeneralSecurityException
*/
public static byte[] ecbEncrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
/**
* 解密
*
* @param key 密钥
* @param input 密文
* @return 明文
* @throws GeneralSecurityException
*/
public static byte[] ecbDecrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
// ============ CBC 模式 ===========
/**
* CBC 模式加密
* @param key 密钥
* @param input 密文
* @return 明文
* @throws GeneralSecurityException
*/
public static byte[] cbcEncrypt(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);
}
/**
* CBC 模式解密
* @param key 密钥
* @param input 密文
* @return 明文
* @throws GeneralSecurityException
*/
public static byte[] cbcDecrypt(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;
}
非对称加密
一、介绍
非对称加密的加密和解密使用的密钥是不一样的,公钥是公开的,私钥是私密的,只有同一对密钥进行加解密。
公钥加密需要私钥解密,反之,私钥加密需要公钥解密
优点:更加安全,密钥也更加便于管理
缺点:运算速度慢
二、常用算法
RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)。其中RSA使用最为广泛。
三、应用场景
- 信息加密:用于一些重要数据传输过程中,对信息的加密,发送方将信息用公钥加密,接收方用私钥进行解密,能保证数据不被他人窃取
- 数字签名:类似于手写签名一样,保证数据是发送发发送的,具有不可抵赖性
- 数字信封:信息发送者首先利用随机产生的【对称密码】加密信息(因为非对称加密技术的速度比较慢),再利用接收方的【公钥】加密对称密码,被公钥加密后的对称密钥被称之为数字信封。在传递信息时,信息接收方要解密信息时,必须先用自己的私钥解密数字信封,得到对称密码,才能利用对称密码解密所得到的信息。
- 数字证书:CA 用自己的私钥对信息原文所有者发布的公钥和相关信息进行加密,得出的内容就是数字证书。
四、RSA加密
/**
* RSA公钥加密
*
* @param data 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
private static String encrypt(String data, String publicKey) throws Exception {
// base64 编码的公钥
byte[] decoded = Base64.getDecoder().decode(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
// RSA加密
Cipher cipher = Cipher.getInstance("RSA");
// 公钥加密
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes("UTF-8")));
}
/**
* RSA私钥解密
*
* @param data 加密字符串
* @param privateKey 私钥
* @return 铭文
* @throws Exception 解密过程中的异常信息
*/
private static String decrypt(String data, String privateKey) throws Exception {
byte[] inputByte = Base64.getDecoder().decode(data.getBytes("UTF-8"));
// base64 编码的私钥
byte[] decoded = Base64.getDecoder().decode(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
// RSA 解密
Cipher cipher = Cipher.getInstance("RSA");
// 私钥解密
cipher.init(Cipher.DECRYPT_MODE, priKey);
return new String(cipher.doFinal(inputByte));
}
散列函数
一、介绍
散列函数又称哈希函数、消息摘要函数、单向函数或者杂凑函数。散列函数主要用来验证数据完整性的重要技术,通过散列函数,可以为数据创造一个散列值。通常是一个随机字母和数字组成的字符串。
二、特征
- 固定的长度:散列函数能够接收任意长度的消息,将其转化成固定长度的散列值
- 确定性:如果两个散列值不相同,那么两个散列值的原始输入消息也不相同
- 单向性:散列函数的运算过程是不可逆的
- 抗弱碰转性:对于一个已知的消息和散列值,要找到另外一个消息并获取到相同的散列值是不可能的。这个特征常用于防伪造
- 抗强碰撞性:任意两个消息的散列值是一定不相同的
- 雪崩效应:即使消息只有一个字节的变化,得到的散列值也是天差地别的
常用的散列函数
MD(消息摘要算法)
SHA(安全散列算法)
Mac(消息认证码算法)
数字签名
一、介绍
数字签名是针对以数字形式存储的消息进行处理,产生一种带有操作者身份信息的编码。
签名者:执行数字签名的实体
签名算法:签名过程中所使用的算法
原理:发送者将消息用私钥签名,将消息和签名发送给接收者。接收者通过公钥对签名和消息进行验证,验证过程中使用的算法叫做验证算法。签名算法受私钥控制,私钥由签名者保存;验证算法受公钥控制,公钥可向外公开
二、特征
- 不可否认性:签名者任何时候都无法否认自己签发的签名
- 可认证性:信息接收者能够验证收到的数字签名,但是任何人无法伪造信息发送者的数字签名
- 当收发双方对数字签名的真伪产生争议时,可以通过第三方机构进行仲裁
三、常用的数字签名
数字签名算法主要有RSA、DSA、ECDSA三种
RSA
RSA签名算法是比较经典的签名算法,使用范围最为广泛。RSA不仅包含了数字签名算法,还兼并加解密算法。RSA数字签名算法主要包括MD和SHA两种算法。MD系列主要包含了MD2withRSA和MD5withRSA,SHA系列主要包括了SHA1withRSA、SHA224withRSA、SHA256withRSA、SHA384withRSA和SHA512withRSA。
DSA
DSA全称Digital Signature Algorithm,他是最为简单的数字签名算法。他不能用于加解密,也不能用于密钥交换,只可以用于签名。所以他的速度更快。DSA仅支持SHA系列的消息摘要算法。SHA1withDSA、SHA224withDSA、SHA256withDSA、SHA384withDSA和SHA512withDSA。
DSA的一个重要特点是两个素数公开,这样,当使用别人的p和q时,即使不知道私钥,你也能确认它们是否是随机产生的,还是作了手脚
ECDSA
ECDSA的安全性更高;计算量小,速度快;存储空间小;带宽要求低
四、RSA签名案例
public static void main(String[] args) throws Exception {
// 生成RSA公钥/私钥:
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
PrivateKey privateKey = kp.getPrivate();
PublicKey publicKey = kp.getPublic();
// 待签名的消息:
byte[] message = "Hello, World!".getBytes(StandardCharsets.UTF_8);
// 用私钥签名:
Signature s = Signature.getInstance("SHA1withRSA");
s.initSign(privateKey);
s.update(message);
byte[] signed = s.sign();
System.out.println(String.format("signature: %x", new BigInteger(1, signed)));
// 用公钥验证:
Signature v = Signature.getInstance("SHA1withRSA");
v.initVerify(publicKey);
v.update(message);
boolean valid = v.verify(signed);
System.out.println("valid? " + valid);
}