加密与安全
加密与安全
数据安全:防窃听、防篡改、防伪造
编码算法
URL编码
URL编码的目的是把任意文本数据编码为%前缀表示文本
编码规则:
1)A-Z,a-z,0-9以及- _ . *保持不变
2)其他字符以%XX表示
<:%3C
中:%E4%B8%AD(UTF-8:0xe4b8ad)
String orginal = "URL 参数";
String encoded = URLEncoder.encode(orginal, "UTF-8");
System.out.println(encoded); // URL+%E5%8F%82%E6%95%B0
String ori = new String(URLDecoder.decode(encoded, "UTF-8"));
System.out.println(ori); // URL 参数
Base64编码
Base64编码的目的是把任意二进制数据编码为文本(长度增加1/3)
String base64Encode(byte[] data)
byte[]{
0xe4, 0xb8, 0xad} -> "5Lit"
Base64编码是一种文本(a-z,A-Z,0-9,+/=)表示二进制内容的方式,适用于文本协议,但是效率会下降,Base64应用于:电子邮件协议
Base64编码长度如果不是3的整数倍,会末尾补0x00或0x00 0x00,编码后加一个等号(=)表示补充了1个字节,编码后加两个等号(==)表示补充了2个字节。
public class Main {
public static void main(String[] args) throws Exception {
String original = "Hello\u00ff编码测试";
String b64 = Base64.getEncoder().encodeToString(original.getBytes());
System.out.println(b64);
String ori = new String(Base64.getDecoder().decode(b64), "UTF-8");
System.out.println(ori);
}
}
// SGVsbG/Dv+e8lueggea1i+ivlQ==
// Helloÿ编码测试
// 还可以利用withoutPadding()去掉末尾的等号
String b64 = Base64.getEncoder().withoutPadding().encodeToString(original.getBytes());
// SGVsbG/Dv+e8lueggea1i+ivlQ
// 使用URL的Base64编码是 加号(+)变减号(-),反斜线(/)变下划线(_)
String original = "Hello\u00ff编码测试";
String b64 = Base64.getUrlEncoder().withoutPadding().encodeToString(original.getBytes());
System.out.println(b64);
String ori = new String(Base64.getUrlDecoder().decode(b64), "UTF-8");
System.out.println(ori);
// SGVsbG_Dv-e8lueggea1i-ivlQ
// Helloÿ编码测试
其他类似Base64的编码有:Base32、Base48、Base58
摘要算法
摘要算法(哈希算法 / Hash / Digest / 数字指纹)
计算任意长度数据的摘要,输出固定长度,相同的输入始终得到相同的输出,不同的输入尽量得到不同的输出
Java的Object.hashCode()方法就是一个摘要算法
输入:任意数据
输出:固定长度数据(int,byte[4])
相同的输入得到相同的输出:equals、hashCode
碰撞:两个不同的输入得到了相同的输出
Hash算法的安全性:
1)碰撞率低
2)不能猜测输出
3)输入的任意一个bit的变化会造成输出完全不同
4)很难从输出反推输入(只能穷举)
常用的摘要算法
算法 | 输出长度 | |
---|---|---|
MD5 | 128bits | 16bytes |
SHA-1 | 160bits | 20bytes |
SHA-256 | 256bits | 32bytes |
RipeMD-160 | 160bits | 20bytes |
MD5
1)验证原始数据是否被篡改
2)存储用户口令
3)需要防止彩虹表攻击:对每个口令额外添加随机数salt
用途:验证文件的完整性、存储用户口令
md5(inputPassword)
md5(salt + inputPassword)
import java.math.BigInteger;
import java.security.MessageDigest;
public class Main {
public static byte[] toMD5(byte[] input) {
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (Exception e) {
throw new RuntimeException(e);
}
md.update(input);
return md.digest();
}
public static void main(String[] args) throws Exception {
String s = "Md5摘要算法测试";
byte[] r = toMD5(s.getBytes("UTF-8"));
System.out.println(String.format("%032x", new BigInteger(1, r)));
}
}
String password = "Hello, world";
String salt = "Random salt";
byte[] r = toMD5((salt + password).getBytes("UTF-8"));
System.out.println(String.format("%032x", new BigInteger(1, r)));
SHA1
是由美国国家安全局开发的一种哈希算法,输出160bits或20bytes,主要有SHA-0 / SHA-1 / SHA-256 / SHA-512,比MD5更加安全
算法 | 输出长度 | |
---|---|---|
SHA-1 | 160bits | 20bytes |
SHA-256 | 256bits | 32bytes |
SHA-512 | 512bits | 64bytes |
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(input);
...
byte[] result = md.digest(); // 20bytes
BouncyCastle
是第三方算法提供的一组加密/哈希算法,官网:http://www.bouncycastle.org/
如何使用第三方提供的算法
添加第三方jar至classpath
注册第三方算法提供方 Security.addProvider(new BouncyCastleProvider());//注册
正常使用JDK提供的接口
Security.addProvider(new BouncyCastleProvider());//注册
MessageDigest md = MessageDigest.getInstance("RipeMD160");
md.update(input);
...
byte[] result = md.digest(); // 20bytes
Hmac
Hash-based Message Authentication Code,是基于秘钥的消息认证码算法,是一种更安全的消息摘要算法
HmacMd5 ≈ md5(secure_ksy, data);
Hmac是把Key混入摘要的算法
byte data = ...
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
SecretKey skey = keyGen.generateKey();
Mac mac = Mac.getInstance("HmacMD5");
mac.init(skey);
mac.update(data);
byte[] result = mac.doFinal();
可以配合MD5、SHA-1等摘要算法
摘要长度和原摘要算法长度相同
加密算法
对称加密算法
使用同一个密钥进行加密和解密
常用算法:DES、AES、IDEA等
算法 | 密钥长度 | 工作模式 | 填充模式 |
---|---|---|---|
DES | 56/64 | ECB/CBC/PCBC/CTR/… | NoPadding/PKCS5Padding… |
AES | 128/192/256 | ECB/CBC/PCBC/CTR/… | NoPadding/PKCS5Padding/PKCS7Padding… |
IDEA | 128 | ECB | PKCS5Padding/PKCS5Padding… |
密钥长度由算法设计决定,AES的密钥长度是128 / 192 / 256 | |||
使用对称加密算法需要指定:算法名称 / 工作模式(ECB、CBC、PCBC…)/填充模式(NoPadding、PKCS5Padding、PKCS7Padding…) | |||
ECB模式 |
public class AES_ECB_Cipher {
static final String CIPHER_NAME = "AES/ECB/PKCS5Padding";
/**
* 加密
*/
public static byte[] encrypt(byte[] key, byte[] input) throws Exception{
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
/**
* 解密
*/
public static byte[] decrpty(byte[] key, byte[] input) throws Exception{
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
public static void main(String[] args) throws Exception{
// 原文:
String message = "Hello, world! encrypted using AES!";
System.out.println("Message: " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "1234567890abcdef".getBytes("UTF-8");
// 加密:
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] encrypted = encrypt(key, data);
System.out.println("Enctypted data: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrpty(key, encrypted);
System.out.println("Decrypted data: " + new String(decrypted, "UTF-8"));
}
}
// Message: Hello, world! encrypted using AES!
// Enctypted data: 6ofAje3dbEse