加密与安全
加密与安全
数据安全:防窃听、防篡改、防伪造
编码算法
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: 6ofAje3dbEseeIBkwKEonQIUi09dPO9fVx4OgZ7ozsE7BWtJJdcJs1+N58l1mWqh
// Decrypted data: Hello, world! encrypted using AES!
CBC模式
public class AES_CBC_Cipher {
static final String CIPHER_NAME = "AES/CBC/PKCS5Padding";
/**
* 加密
*/
public static byte[] encrypt(byte[] key, byte[] input) throws Exception {
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// CBC模式需要生成一个16位的initialization vector
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] iv = sr.generateSeed(16);
IvParameterSpec ipvs = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ipvs);
byte[] data = cipher.doFinal(input);
// IV不需要保密,把IV和密文一起返回
return join(iv, data);
}
/**
* 解密
*/
public static byte[] decrpty(byte[] key, byte[] input) throws Exception {
// 把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(CIPHER_NAME);
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;
}
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: 0snryGJc4joIzXM2gq/qIY2Vzh/unufhvXfO4k7sk79whZ7nTf30YvO64SihyO2LaA0Ab7KIO9pDzLW8Z/c9Xg==
// Decrypted data: Hello, world! encrypted using AES!
AES256
使用256位加密需要修改JDK的policy文件
https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html下载,切换到目录D:\Program Files\Java\jdk1.8.0_131\jre\lib\security下替换调 local_policy.jar 和 US_export_policy.jar
public class AES256_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);
// 256位密钥 = 32 bytes Key:
byte[] key = "1234567890abcdef1234567890abcdef".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: zXgDofZMm8UHUqJJdyt4SBQToTtvTWjGUeh/XFlTzJr515ZUmGEgTWOL08g++rfL
// Decrypted data: Hello, world! encrypted using AES!
口令加密算法
PBE(Password Based Encryption)算法:
1)由用户输入口令,采用随机数杂凑计算出密钥进行加密
2)Password:用户口令,例如“hello 123”
3)Salt:随机生成byte[]
4)Key:generate(byte[] salt, String password)
Key通过口令和随机salt计算得出,提高安全性
PBE算法内部使用的仍然是标准对称加密算法(例如AES)
public class PBECipher {
static final String CIPHER_NAME = "PBEwithSHA1and128bitAES-CBC-BC";
/**
* 加密
*/
public static byte[] encrypt(String password, byte[] salt, byte[] input) throws Exception {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory sKeyFactory = SecretKeyFactory
.getInstance(CIPHER_NAME);
SecretKey skey = sKeyFactory.generateSecret(keySpec);
PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
cipher.init(Cipher.ENCRYPT_MODE, skey, pbeps);
return cipher.doFinal(input);
}
/**
* 解密
*/
public static byte[] decrypt(String password, byte[] salt, byte[] input) throws Exception {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory sKeyFactory = SecretKeyFactory
.getInstance(CIPHER_NAME);
SecretKey skey = sKeyFactory.generateSecret(keySpec);
PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
cipher.init(Cipher.DECRYPT_MODE, skey, pbeps);
return cipher.doFinal(input);
}
public static void main(String[] args) throws Exception {
// 把BouncyCastle作为Provider添加到java.security:
Security.addProvider(new BouncyCastleProvider());
// 原文:
String message = "Hello, world! encrypted using PBE!";
// 加密口令
String password = "hello12345";
// 16 bytes随机Salt:
byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16);
System.out.printf("salt: %032x\n", new BigInteger(1, salt));
// 加密:
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] encrypted = encrypt(password, salt, data);
System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(password, salt, encrypted);
System.out.println(new String(decrypted, "UTF-8"));
}
}
密钥交换算法
Diffe-Hellman(DH)算法:是一种密钥交换协议,通信双方通过不安全的信道协商密钥,然后进行对称加密传输,DH算法没有解决中间人攻击
public class DH {
public static void main(String[] args) {
// Bob 和 Alice
Person bob = new Person("Bob");
Person alice = new Person("Alice");
// 各自生成KeyPair:
bob.generateKeyPair();
alice.generateKeyPair();
// 双方交换各自的PublicKey:
// Bob根据Alice的PublicKey生成自己的本地密钥:
bob.generateSecretKey(alice.publicKey.getEncoded());
// Alice根据Bob的PublicKey生成自己的本地密钥:
alice.generateSecretKey(bob.publicKey.getEncoded());
// 检查双方本地密钥是否相同
bob.printKeys();
alice.printKeys();
// 双方的SecretKey相同,后续通信将使用SecretKey作为密钥进行AES加密
String msgBobToAlice = bob.sendMessage("Hello, Alice!");
System.out.println("Bob -> Alice: " + msgBobToAlice);
String aliceDecrypted = alice.receiveMessage(msgBobToAlice);
System.out.println("Alice decrypted: " + aliceDecrypted);
}
}
class Person {
public final String name;
public PublicKey publicKey;
private PrivateKey privateKey;
private SecretKey secretKey;
public Person(String name) {
this.name = name;
}
/**
* 生成本地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);
}
}
/**
* 生成密钥
*
* @param receivedPubKeyBytes
*/
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
// 生成AES密钥:
this.secretKey = keyAgreement.generateSecret("AES");
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
/**
* 打印密钥
*/
public void printKeys() {
System.out.printf("Name: %s\n", this.name);
System.out.printf("Private key: %x\n", new BigInteger(1,
this.privateKey.getEncoded()));
System.out.printf("Public key: %x\n",
new BigInteger(1, this.publicKey.getEncoded()));
System.out.printf("Secret key: %x\n",
new BigInteger(1, this.secretKey.getEncoded()));
}
/**
* 发送加密消息
*
* @param message
* @return
*/
public String sendMessage(String message) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, this.secretKey);
byte[] data = cipher.doFinal(message.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(data);
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
/**
* 接收加密消息并解密
*
* @param message
* @return
*/
public String receiveMessage(String message) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, this.secretKey);
byte[] data = cipher.doFinal(Base64.getDecoder().decode(message));
return new String(data, "UTF-8");
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
}
非对称加密算法
RSA(Ron Rivest,Adi Shamir,Leonard Adleman)
非对称加密就是加密和解密使用的不是相同的密钥
只有同一个公钥 / 私钥对才能正常加密 / 解密
加密:用自己的私钥加密,然后发送给对方
encrypt(privateKeyA, message) ==> encrypted
解密:对方用自己的公钥解密
decrypted(publicKeyA, encrypted) ==> message
公钥公开,私钥保密
只是用非对称加密算法不能防止中间人攻击
public class RSAKeyPair {
/**
* 私钥
*/
PrivateKey sk;
/**
* 公钥
*/
PublicKey pk;
/**
* 生成公钥/私钥对
* @throws GeneralSecurityException
*/
public RSAKeyPair() throws GeneralSecurityException{
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
this.sk = kp.getPrivate();
this.pk = kp.getPublic();
}
/**
* 从以保存的字节中(例如:读取文件)恢复公钥/私钥
* @param pk
* @param sk
* @throws GeneralSecurityException
*/
public RSAKeyPair(byte[] pk, byte[] sk) throws GeneralSecurityException{
KeyFactory kf = KeyFactory.getInstance("RSA");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pk);
this.pk = kf.generatePublic(pkSpec);
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(sk);
this.sk = kf.generatePrivate(skSpec);
}
/**
* 把私钥导出为字节
* @return
*/
public byte[] getProvateKey(){
return this.sk.getEncoded();
}
/**
* 把公钥导出为字节
* @return
*/
public byte[] getPublicKey(){
return this.pk.getEncoded();
}
/**
* 用公钥加密
* @param message
* @return
* @throws GeneralSecurityException
*/
public byte[] encrypt(byte[] message) throws GeneralSecurityException{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, this.pk);
return cipher.doFinal(message);
}
/**
* 用私钥解密
* @param input
* @return
* @throws GeneralSecurityException
*/
public byte[] decrypt(byte[] input) throws GeneralSecurityException{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, this.sk);
return cipher.doFinal(input);
}
public static void main(String[] args) throws Exception{
// 明文:
byte[] plain = "Hello,使用RSA非对称加密算法对数据进行加密!".getBytes();
// 创建公钥/私钥对:
RSAKeyPair rsa = new RSAKeyPair();
// 加密:
byte[] encrypted = rsa.encrypt(plain);
System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = rsa.decrypt(encrypted);
System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
// 保存公钥/私钥:
byte[] pk = rsa.getPublicKey();
byte[] sk = rsa.getProvateKey();
System.out.println("pk: " + Base64.getEncoder().encodeToString(pk));
System.out.println("sk: " + Base64.getEncoder().encodeToString(sk));
// 重新恢复公钥/私钥:
RSAKeyPair rsa2 = new RSAKeyPair(pk, sk);
// 加密:
byte[] encrypted2 = rsa2.encrypt(plain);
System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted2));
// 解密:
byte[] decrypted2 = rsa2.decrypt(encrypted2);
System.out.println("decrypted: " + new String(decrypted2, "UTF-8"));
}
}
// encrypted: dfED6ShwTYfQnYTG+1xqqZtn4jzhtciwdqe2hd0/oZqdH4JosyhfekZvGQlehpDAeAYKrPJWzCBbD8k/lt66HSlZutPddR/9L+DF1/JCghgyoLHtWf2L4XU2n0DNw5LSdp2gvoh/xK6ryauxyx9cr/fAnYPvFT4P0PYJwRzKJYQ=
// decrypted: Hello,使用RSA非对称加密算法对数据进行加密!
// pk: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6boCz2BXq/6ZhrVC2WUf6IY64gSBghsi9d4iDlvAzA+M5lYuswv77ppmO+1tJhiQq6rb+SAcjiYBMyOfB5YIPwvnD0zHfKJysDtbgmxGJGfnVMEcQOZrom8BlJ6HbVpPhMJrBNeTnl83VmPN6YSXCyRn5d7fBj1o4OYjKQVhyBQIDAQAB
// sk: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALpugLPYFer/pmGtULZZR/ohjriBIGCGyL13iIOW8DMD4zmVi6zC/vummY77W0mGJCrqtv5IByOJgEzI58Hlgg/C+cPTMd8onKwO1uCbEYkZ+dUwRxA5muibwGUnodtWk+EwmsE15OeXzdWY83phJcLJGfl3t8GPWjg5iMpBWHIFAgMBAAECgYB5DuBL5/jzi+ZCbwSGJBt09O10KYUYqO0Y+tGOgSigN9oIvm7gVA624ku8aMOyToSVyaviGmtoTBUfxyWW5q046Qw9b/eLGmUjhIjhFsCmUhmjwZFGWBIJjcgwhFNk216ElHWGxmhBHzsYUcsMi8vM5EnKy6IxXKE4qOc5GqX70QJBAPbyBK5RhOdPIZo6iWhLbTSbhLGr0V571wnbSP1wvQDeEjDl54bHwVBIzGWck9it4XRBArlqvIP6XKa1j68Eh68CQQDBRHcZldATuivO16UK3jBhbzgZnDuNIf6V83YhjdLoFeoG78J8/2lFsIsh2ZHEpXNE+WfN1tfcwADbhLN5VRqLAkApPbf8aM7aVoPVHwuNqHMfgw2BIqG/tszt73pcITTfbTb6hZrKDphBUTcZjTG/0SJC6QyMbL/5BpnsmYvafdYvAkArvsV+gwxwCqmzzdfLl/M3eqcmFNjd3x3y7wKEIcflRGCOZWKOTHmVjppPdSGrcw+eQx2d181QaB1JksXU5tB1AkEAnvjnG96xhZfkzUOZeS205FwwDcawjnyTTGAIIwwMLnBAmVzKfurGHN1vPjKyBevrO5hoVf0O0IG54tOgUH6r/g==
// encrypted: WqweDrqJ0nSjm/HOg1tJpm8JNnjDppvWmCw0NmBpIxQR3mbP/7wU0yff3tv01iWXOYh9pRC4vVSATUV+AS2IgsgGYH9RCAVpwbgF1gig5bcNYfi3RzrlBfP9vap3lpE46CWm8w8PusVgIw7/1K4ISyokCcfnxItuHiRKBLqAs0Q=
// decrypted: Hello,使用RSA非对称加密算法对数据进行加密!
非对称加密的优点:
1)对称加密需要协商密钥,而非对称加密可以安全地公开各自的公钥
2)N个人之间通信:
使用非对称加密只需要N个密钥对,每个人只管理自己的密钥对
使用对称加密需要N*(N-1)/2个密钥,每个人需要管理N-1个密钥
非对称加密的缺点:
运算速度慢
签名算法
RSA签名算法
用发送方的私钥对原始数据进行签名:sig = signature(privateKey, “message”);
只有用发送方公钥才能通过签名验证:boolean valid = verify(publicKey, sig, “message”)
数字签名 ≈ 混入了私钥/公钥的摘要
数字签名的目的:确认信息是某个发送方发送的,发送方不能抵赖发送过的信息,数据在传输过程中没有被修改
常用算法:MD5withRSA / SHA1withRSA / SHA256withRSA
public class SecRSASignature {
PrivateKey sk;
PublicKey pk;
/**
* 生成公钥/私钥对
* @throws GeneralSecurityException
*/
public SecRSASignature() throws GeneralSecurityException{
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
this.sk = kp.getPrivate();
this.pk = kp.getPublic();
}
/**
* 从以保存的字节中(例如:读取文件)恢复公钥/私钥
* @param pk
* @param sk
* @throws GeneralSecurityException
*/
public SecRSASignature(byte[] pk, byte[] sk) throws GeneralSecurityException{
KeyFactory kf = KeyFactory.getInstance("RSA");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pk);
this.pk = kf.generatePublic(pkSpec);
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(sk);
this.sk = kf.generatePrivate(skSpec);
}
/**
* 把私钥导出为字节
* @return
*/
public byte[] getProvateKey(){
return this.sk.getEncoded();
}
/**
* 把公钥导出为字节
* @return
*/
public byte[] getPublicKey(){
return this.pk.getEncoded();
}
/**
* sign by sk
* @param message
* @return
* @throws GeneralSecurityException
*/
public byte[] sign(byte[] message) throws GeneralSecurityException{
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(this.sk);
signature.update(message);
return signature.sign();
}
/**
* sign by pk
* @param message
* @param sign
* @return
* @throws GeneralSecurityException
*/
public boolean verify(byte[] message, byte[] sign) throws GeneralSecurityException{
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(this.pk);
signature.update(message);
return signature.verify(sign);
}
public static void main(String[] args) throws Exception{
byte[] message = "Hello, 使用SHA1withRSA算法进行数字签名!".getBytes();
SecRSASignature rsas = new SecRSASignature();
byte[] sign = rsas.sign(message);
System.out.println("sign: " + Base64.getEncoder().encodeToString(sign));
boolean verified = rsas.verify(message, sign);
System.out.println("verify: " + verified);
// 用另一个公钥验证:
boolean verified2 = new SecRSASignature().verify(message, sign);
System.out.println("verify with another public key: " + verified2);
// 修改原始信息:
message[0] = 100;
boolean verified3 = rsas.verify(message, sign);
System.out.println("verify changed message: " + verified3);
}
}
// sign: HlqRdx162ZG1p52VfXF9TVwPLSsaZup6VlXp2xi5VkwM5v8KIAunKcFNE7WhOuzdZjySxNz2NMJGd4LHdRq0D280nTOgBvGvWXX8m3R6hVMIk5pdDLDfoIsZ+cALz1Q8nTgV83gEl0ugeuJvBLPLd3arNKQ4ERgzYpTgh6SNGaM=
// verify: true
// verify with another public key: false
// verify changed message: false
DSA签名算法
DSA:Digital Signature Algorithm
使用EIGamal数字签名算法,和RSA数字签名相比,速度更快
算法包括:SHA1withDSA / SHA256withDSA / SHA512withDSA
其他数字签名算法:ECDSA(Elliptic Curve Digital Signature Algorithm(Bouncy Castle))
public class SecDSASignature {
PrivateKey sk;
PublicKey pk;
/**
* 生成公钥/私钥对
* @throws GeneralSecurityException
*/
public SecDSASignature() throws GeneralSecurityException{
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
this.sk = kp.getPrivate();
this.pk = kp.getPublic();
}
/**
* 从以保存的字节中(例如:读取文件)恢复公钥/私钥
* @param pk
* @param sk
* @throws GeneralSecurityException
*/
public SecDSASignature(byte[] pk, byte[] sk) throws GeneralSecurityException{
KeyFactory kf = KeyFactory.getInstance("DSA");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pk);
this.pk = kf.generatePublic(pkSpec);
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(sk);
this.sk = kf.generatePrivate(skSpec);
}
/**
* 把私钥导出为字节
* @return
*/
public byte[] getProvateKey(){
return this.sk.getEncoded();
}
/**
* 把公钥导出为字节
* @return
*/
public byte[] getPublicKey(){
return this.pk.getEncoded();
}
/**
* sign by sk
* @param message
* @return
* @throws GeneralSecurityException
*/
public byte[] sign(byte[] message) throws GeneralSecurityException{
Signature signature = Signature.getInstance("SHA1withDSA");
signature.initSign(this.sk);
signature.update(message);
return signature.sign();
}
/**
* sign by pk
* @param message
* @param sign
* @return
* @throws GeneralSecurityException
*/
public boolean verify(byte[] message, byte[] sign) throws GeneralSecurityException{
Signature signature = Signature.getInstance("SHA1withDSA");
signature.initVerify(this.pk);
signature.update(message);
return signature.verify(sign);
}
public static void main(String[] args) throws Exception{
byte[] message = "Hello, 使用SHA1withDSA算法进行数字签名!".getBytes();
SecDSASignature rsas = new SecDSASignature();
byte[] sign = rsas.sign(message);
System.out.println("sign: " + Base64.getEncoder().encodeToString(sign));
boolean verified = rsas.verify(message, sign);
System.out.println("verify: " + verified);
// 用另一个公钥验证:
boolean verified2 = new SecDSASignature().verify(message, sign);
System.out.println("verify with another public key: " + verified2);
// 修改原始信息:
message[0] = 100;
boolean verified3 = rsas.verify(message, sign);
System.out.println("verify changed message: " + verified3);
}
}
// sign: MCwCFFHhAUZW49W+m+YX7eHc1geBOMymAhQk3j/hi1SdbxzcjO4DQGRjUlMfvw==
// verify: true
// verify with another public key: false
// verify changed message: false
数字证书
摘要算法:确保数据没有被篡改
非对称加密算法:对数据进行加密、解密
签名算法:确保数据完整性和抗否认性
数字证书就是集合了多种密码学算法,用于实现数据加解密、身份认证、签名等多种功能的一种网络安全标准。
数字证书采用链式签名管理,顶级CA证书已内置在操作系统中。
Root CA证书(根证书)
下一级证书
用户证书
常用算法:MD5、SHA1、SHA256、RSA、DSA、…
应用:https等
先用jdk自带的keytool命令生成keystore
keytool -genkeypair -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 36500 -alias mycert -keystore my.keystore -dname “CN=test07, OU=sample, O=sample, L=BJ, ST=BJ, C=CN” -keypass 123456 -storepass 456789
查看keystore 信息
keytool -list -keystore my.keystore -storepass 456789
public class X509 {
private final PrivateKey privateKey;
private final X509Certificate certificate;
public X509(KeyStore ks, String certName, String password) throws GeneralSecurityException{
this.privateKey = (PrivateKey) ks.getKey(certName, password.toCharArray());
this.certificate = (X509Certificate) ks.getCertificate(certName);
}
public byte[] encrypt(byte[] message) throws GeneralSecurityException{
Cipher cipher = Cipher.getInstance(this.privateKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, this.privateKey);
return cipher.doFinal(message);
}
public byte[] decrypt(byte[] data) throws GeneralSecurityException{
PublicKey publicKey = this.certificate.getPublicKey();
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
public byte[] sign(byte[] message) throws GeneralSecurityException{
Signature signature = Signature.getInstance(this.certificate.getSigAlgName());
signature.initSign(this.privateKey);
signature.update(message);
return signature.sign();
}
public boolean verify(byte[] message, byte[] sign) throws GeneralSecurityException{
Signature signature = Signature.getInstance(this.certificate.getSigAlgName());
signature.initVerify(this.certificate);
signature.update(message);
return signature.verify(sign);
}
static KeyStore loadKeyStore(String keyStoreFile, String password) throws GeneralSecurityException, IOException{
try(InputStream input = new BufferedInputStream(new FileInputStream(keyStoreFile))){
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(input, password.toCharArray());
return ks;
}
}
public static void main(String[] args) throws GeneralSecurityException, IOException {
byte[] message = "Hello, 使用X.509证书进行加密和签名!".getBytes();
// 读取KeyStore:
KeyStore ks = loadKeyStore("my.keystore", "456789");
// 读取证书:
X509 x509 = new X509(ks, "mycert", "123456");
// 加密:
byte[] encrypted = x509.encrypt(message);
System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = x509.decrypt(encrypted);
System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
// 签名:
byte[] sign = x509.sign(message);
System.out.println("sign: " + Base64.getEncoder().encodeToString(sign));
// 验证签名:
boolean verified = x509.verify(message, sign);
System.out.println("verify: " + verified);
}
}
// encrypted: FWIANXZEVSvRCv6HlMPUEDxyYb6iAAJs+hAugy1CJuFMT3d7SxlhANZAizZhMuL40aF/iANOh2M6XZaPU7Lx+1qI87fScPaQHN5uOaGgsIFaLk7XO1NXqvT6JvHx0lXouU+hz3Sg612QcRqZYqsvnVh5JfXuMAWnZnN47B1WzCY=
// decrypted: Hello, 使用X.509证书进行加密和签名!
// sign: T8lgW6ZLtl/oWgV/M4m/6flU34Jq2VNWdvC7vFFYXnu3Q/JgjHkaV4SUTt2cbNzAenuFsjWrZn5girapOn2t4Nn0Y9brbLQDTG6SavrupqJYbTLt2Q4XUzG/hg54F9vlq/JkDhY1jwctJf4p578RVSostMmv6Cf/aksc/TQWv8E=
// verify: true