文章目录
加密与安全
编码算法(URL编码/Base64编码)
URL编码和Base64编码都是编码算法,它们不是加密算法;
URL编码的目的是把任意文本数据编码为%前缀表示的文本,便于浏览器和服务器处理;
Base64编码的目的是把任意二进制数据编码为文本,但编码后数据量会增加1/3。
哈希算法(Hmac算法)
哈希算法最重要的特点就是:
- 相同的输入一定得到相同的输出;
- 不同的输入大概率得到不同的输出。
哈希算法的目的就是为了验证原始数据是否被篡改。
哈希碰撞
哈希碰撞是指,两个不同的输入得到了相同的输出
一个安全的哈希算法必须满足:
- 碰撞概率低;
- 不能猜测输出。
常用的哈希算法有:
| 算法 | 输出长度(位) | 输出长度(字节) |
|---|---|---|
| MD5 | 128 bits | 16 bytes |
| SHA-1 | 160 bits | 20 bytes |
| RipeMD-160 | 160 bits | 20 bytes |
| SHA-256 | 256 bits | 32 bytes |
| SHA-512 | 512 bits | 64 bytes |
根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
小结
哈希算法可用于验证数据完整性,具有防篡改检测的功能;
常用的哈希算法有MD5、SHA-1等;
用哈希存储口令时要考虑彩虹表攻击。
对称加密算法(AES算法)
对称加密算法就是传统的用一个密码进行加密和解密
- 一个密钥
- 需要协商密钥
原文+密钥 = 密文
密文+密钥 = yuan’we
| 算法 | 密钥长度 | 工作模式 | 填充模式 |
|---|---|---|---|
| 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加密
AES算法是目前应用最广泛的加密算法。我们先用ECB模式加密并解密:
原文+密钥 = 加密后的数据
import java.security.*;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;
public class Main {
public static void main(String[] args) throws Exception {
// 原文:
String message = "Hello, world!";
System.out.println("Message: " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "1234567890abcdef".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/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
// 解密:
public static byte[] decrypt(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);
}
}
Java标准库提供的对称加密接口非常简单,使用时按以下步骤编写代码:
- 根据算法名称/工作模式/填充模式获取
Cipher实例; - 根据算法名称初始化一个
SecretKey实例,密钥必须是指定长度; - 使用
SecretKey初始化Cipher实例,并设置加密或解密模式; - 传入明文或密文,获得密文或明文。
小结
对称加密算法使用同一个密钥进行加密和解密,常用算法有DES、AES和IDEA等;
密钥长度由算法设计决定,AES的密钥长度是128/192/256位;
使用对称加密算法需要指定算法名称、工作模式和填充模式。
口令加密算法(PBE算法)
PBE(PBE就是Password Based Encryption的缩写)
- 密钥 = PBE(口令+salt)
- 作用就是把用户输入的口令和一个安全随机的口令采用杂凑后计算出真正的密钥。
举例
以AES密钥为例,我们让用户输入一个口令,然后生成一个随机数,通过PBE算法计算出真正的AES口令,再进行加密
原文:“Hello, world!“
String message = "Hello, world!";
加密的口令:”hello12345“
String password = "hello12345";
随机生成的Salt:byte[] salt
// 16 bytes随机Salt:
byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16);
System.out.println(HexFormat.of().formatHex(salt));
加密:传入口令+salt+文本 = 加密后的数据
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = encrypt(password, salt, data);
System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 实现:
public static byte[] encrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
SecretKey skey = skeyFactory.generateSecret(keySpec);
PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
cipher.init(Cipher.ENCRYPT_MODE, skey, pbeps);
return cipher.doFinal(input);
}
解密:传入口令+salt+加密后的数据 = 文本
// 解密:
byte[] decrypted = decrypt(password, salt, encrypted);
System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
// 实现:
public static byte[] decrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
SecretKey skey = skeyFactory.generateSecret(keySpec);
PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
cipher.init(Cipher.DECRYPT_MODE, skey, pbeps);
return cipher.doFinal(input);
}
小结
PBE算法通过用户口令和安全的随机salt计算出Key,然后再进行加密;
Key通过口令和安全的随机salt计算得出,大大提高了安全性;
PBE算法内部使用的仍然是标准对称加密算法(例如AES)。
密钥交换算法(DH算法)
DH算法:Diffie-Hellman算法 (主要解决密钥交换加密问题)
- 公钥、密钥、交换计算后的共享密钥
密钥交换的数学原理
Diffie-Hellman 基于离散对数问题的困难性:
设:
p: 大素数(模数)
g: 原根(生成元)
Alice:
私钥: a (随机数)
公钥: A = g^a mod p
Bob:
私钥: b (随机数)
公钥: B = g^b mod p
共享密钥:
Alice计算: s = B^a mod p = (g^b)^a mod p = g^(ab) mod p
Bob计算: s = A^b mod p = (g^a)^b mod p = g^(ab) mod p
关键特性:
- 双方无需预先共享秘密
- 即使攻击者截获了 A 和 B,也无法计算出 s(离散对数问题)
- 最终双方得到相同的共享密钥
使用Java实现DH算法
对象Bob和Alice
// Bob和Alice:
Person bob = new Person("Bob");
Person alice = new Person("Alice");
// 实现:
public final String name;
public PublicKey publicKey;
private PrivateKey privateKey;
private byte[] secretKey;
public Person(String name) {
this.name = name;
}
生成各自的Key对
Bob = publicKey + privateKey
Alice = publicKey + privateKey
// 各自生成KeyPair:
bob.generateKeyPair();
alice.generateKeyPair();
//实现:
// 生成本地KeyPair:
public void generateKeyPair() {
try {
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH");
kpGen.initialize(512);
KeyPair kp = kpGen.generateKeyPair();
this.privateKey = kp.getPrivate();
this.publicKey = kp.getPublic();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
交换双方各自的publicKey,计算出SecretKey
Bob.SecretKey = Alice.publicKey + Bob.privateKey
Alice.SecretKey = Bob.publicKey + Alice.privateKey
// Bob根据Alice的PublicKey生成自己的本地密钥:
bob.generateSecretKey(alice.publicKey.getEncoded());
// Alice根据Bob的PublicKey生成自己的本地密钥:
alice.generateSecretKey(bob.publicKey.getEncoded());
// 实现:
public void generateSecretKey(byte[] receivedPubKeyBytes) {
try {
// 从byte[]恢复PublicKey:
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(receivedPubKeyBytes);
KeyFactory kf = KeyFactory.getInstance("DH");
PublicKey receivedPublicKey = kf.generatePublic(keySpec);
// 生成本地密钥:
KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
keyAgreement.init(this.privateKey); // 自己的PrivateKey
keyAgreement.doPhase(receivedPublicKey, true); // 对方的PublicKey
// 生成SecretKey密钥:
this.secretKey = keyAgreement.generateSecret();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
双方的SecretKey相同,后续通信将使用SecretKey作为密钥进行AES加解密…
实际应用场景:
- SSL/TLS 握手过程中的密钥交换
- VPN 连接的建立
- 安全消息应用的端到端加密
- 物联网设备的安全配对
非对称加密算法(RSA算法)
非对称加密的典型算法就是RSA算法
-
非对称加密就是加密和解密使用的不是相同的密钥:只有同一个公钥-私钥对才能正常加解密
-
公钥、私钥
-
公钥加密信息
-
私钥能解密信息
-
非对称加密算法不能防止中间人攻击
Alice = publicKey +privateKey
other.原文 + Alice.publicKey = other.密文
Alice.privateKey + other.密文 = 原文
Java标准库提供了RSA算法的实现
原文
// 明文:
byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
创建密钥对
// 创建公钥/私钥对:
Person alice = new Person("Alice");
// 实现:
//对象名
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();
}
其他人可以用Alice的公钥加密信息
// 用Alice的公钥加密:
byte[] pk = alice.getPublicKey();
System.out.println("public key: " + HexFormat.of().formatHex(pk));
byte[] encrypted = alice.encrypt(plain);
System.out.println("encrypted: " + HexFormat.of().formatHex(encrypted));
// 实现:
// 用公钥加密: 返回信息加密后的字节数据
public byte[] encrypt(byte[] message) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, this.pk);
return cipher.doFinal(message);
}
只有Alice的密钥能解密信息
// 用Alice的私钥解密:
byte[] sk = alice.getPrivateKey();
System.out.println("private key: " + HexFormat.of().formatHex(sk));
byte[] decrypted = alice.decrypt(encrypted);
//将解密后的原文字节数据转换成UTF-8编码的字符串
System.out.println(new String(decrypted, "UTF-8"));
// 实现:
// 用私钥解密: 返回解密后原文信息(字节数据形式)
public byte[] decrypt(byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, this.sk);
return cipher.doFinal(input);
}
RSA的公钥和私钥都可以通过getEncoded()方法获得以byte[]表示的二进制数据,并根据需要保存到文件中。
要从byte[]数组恢复公钥或私钥
byte[] pkData = ...
byte[] skData = ...
KeyFactory kf = KeyFactory.getInstance("RSA");
// 恢复公钥:
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pkData);
PublicKey pk = kf.generatePublic(pkSpec);
// 恢复私钥:
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(skData);
PrivateKey sk = kf.generatePrivate(skSpec);
小结
非对称加密就是加密和解密使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密;
只使用非对称加密算法不能防止中间人攻击。
签名算法(SHA256withRSA)
- 密钥、公钥
- 用密钥加密信息
- 用公钥解密信息
收到的信息的哈希对比签名解密后哈希是否一致,来验证收到的信息是否正确
常用的数字签名算法
MD5withRSASHA1withRSASHA256withRSA
ECDSA签名
椭圆曲线签名算法ECDSA:Elliptic Curve Digital Signature Algorithm也是一种常用的签名算法,它的特点是可以从私钥推出公钥。比特币的签名算法就采用了ECDSA算法,使用标准椭圆曲线secp256k1。BouncyCastle提供了ECDSA的完整实现。
小结
数字签名就是用发送方的私钥对原始数据进行签名,只有用发送方公钥才能通过签名验证。
数字签名用于:
- 防止伪造;
- 防止抵赖;
- 检测篡改。
常用的数字签名算法包括:MD5withRSA/SHA1withRSA/SHA256withRSA/SHA1withDSA/SHA256withDSA/SHA512withDSA/ECDSA等。
数字证书(CA)
数字证书是摘要算法、非对称加密算法和签名算法的完美结合,形成了一个完整的安全体系。
- CA 、对象Alice、对象Bob
证书 cert
签名 Signature
Alice --> Bob
证书 = 公钥 + 信息 + 数字签名
Bob.cert = Bob.publicKey + message+ Signature( hash(Bob.publicKey + message) + CA.privateKey )
Alice.cert = Alice.publicKey + message+ Signature( hash(Alice.publicKey + message) + CA.privateKey )
Alice验证:Bob数字证书
H1 = hash ( Bob..publicKey + message )
H2 = hash ( Signature )
H1 == H2 ? true : false
小结
数字证书就是集合了多种密码学算法,用于实现数据加解密、身份认证、签名等多种功能的一种安全标准;
数字证书采用链式签名管理,顶级的Root CA证书已内置在操作系统中;
数字证书存储的是公钥,可以安全公开,而私钥必须严格保密
2045

被折叠的 条评论
为什么被折叠?



