Skip to content

Commit b80a874

Browse files
committed
CA证书
1 parent b3497bb commit b80a874

12 files changed

Lines changed: 671 additions & 0 deletions

File tree

Demo/dos.txt

36 Bytes
Binary file not shown.

bin/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/Father.class
2+
/SubClass.class
3+
/Test.class
4+
/com/
5+
/conf/
6+
/dom4j.xml
7+
/dom4j2.xml
8+
/xPath.xml

file.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
你好你好你好你好你好

src/com/yale/test/io/file/ClassPathInput.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* 如果我们把默认的配置放到jar包中,再从外部文件系统读取一个可选的配置文件,就可以做到既有默认的配置文件,又可以让用户自己修改配置:
1313
* 这样读取配置文件,应用程序启动就更加灵活。
1414
* Class对象的getResourceAsStream()可以从classpath中读取指定资源;
15+
* https://www.liaoxuefeng.com/wiki/1252599548343744/1298366384308257
1516
* @author dell
1617
*/
1718
public class ClassPathInput {

src/com/yale/test/ps/AESDemo.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import javax.crypto.spec.SecretKeySpec;
1717

1818
/*
19+
* 对称加密算法
1920
* 对称加密算法就是传统的用一个密码进行加密和解密。例如,我们常用的WinZIP和WinRAR对压缩包的加密和解密,就是使用对称加密算法:
2021
* 从程序的角度看,所谓加密,就是这样一个函数,它接收密码和明文,然后输出密文:
2122
* secret = encrypt(key, message);
@@ -35,6 +36,11 @@ public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorit
3536
/*
3637
* 使用AES加密:AES算法是目前应用最广泛的加密算法。我们先用ECB模式加密并解密:
3738
* AES加密有多种工作模式:ECB模式,CBC模式,PCBC模式,CTR模式
39+
* 注意AES加密相当于是WinZIP和WinRAR对你要打包的文件进行加密,跟你在用WinZIP和WinRAR时输入的密码没有任何关系.
40+
* 下面的message = "Hello,world!"这个就相当于文件,AES要对这些文件进行加密
41+
* "1234567890abcdef".getBytes("UTF-8") 注意这行代码,这行代码是AES自己指定的16bytes的密钥,不是你输入的那个密码
42+
* 你在用WinZIP和WinRAR时输入的密码在AES加密这里都没有用到,懂了吧,你这个密码只是为了打开压缩包时验证一下是你本人打开的
43+
* 跟加密文件没有任何关系
3844
*/
3945
String message = "Hello,world!";
4046
System.out.println("Message明文:" + message);
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.yale.test.ps;
2+
3+
import java.io.UnsupportedEncodingException;
4+
import java.math.BigInteger;
5+
import java.security.InvalidAlgorithmParameterException;
6+
import java.security.InvalidKeyException;
7+
import java.security.NoSuchAlgorithmException;
8+
import java.security.SecureRandom;
9+
import java.security.Security;
10+
import java.security.spec.InvalidKeySpecException;
11+
import java.util.Base64;
12+
13+
import javax.crypto.BadPaddingException;
14+
import javax.crypto.Cipher;
15+
import javax.crypto.IllegalBlockSizeException;
16+
import javax.crypto.NoSuchPaddingException;
17+
import javax.crypto.SecretKey;
18+
import javax.crypto.SecretKeyFactory;
19+
import javax.crypto.spec.PBEKeySpec;
20+
import javax.crypto.spec.PBEParameterSpec;
21+
22+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
23+
24+
/*
25+
* 口令加密算法
26+
* 上一节我们讲的AES加密,细心的童鞋可能会发现,密钥长度是固定的128/192/256位,而不是我们用WinZip/WinRAR那样,随便输入几位都可以。
27+
* 这是因为对称加密算法决定了口令必须是固定长度,然后对明文进行分块加密。又因为安全需求,口令长度往往都是128位以上,即至少16个字符。
28+
* 但是我们平时使用的加密软件,输入6位、8位都可以,难道加密方式不一样?
29+
* 实际上用户输入的口令并不能直接作为AES的密钥进行加密(除非长度恰好是128/192/256位),并且用户输入的口令一般都有规律,安全性远远不如安全随机数产生的随机口令。
30+
* 因此,用户输入的口令,通常还需要使用PBE算法,采用随机数杂凑计算出真正的密钥,再进行加密。
31+
* PBE就是Password Based Encryption的缩写,它的作用如下:
32+
* key = generate(userPassword, secureRandomPassword);
33+
* PBE的作用就是把用户输入的口令和一个安全随机的口令采用杂凑后计算出真正的密钥。以AES密钥为例,我们让用户输入一个口令,然后生成一个随机数,通过PBE算法计算出真正的AES口令,再进行加密,代码如下:
34+
* 理解原理,PBE假定用户的口令很简单不安全,所以需要一个高强度密码和用户口令混在一起加密
35+
*/
36+
public class AesPbeDemo {
37+
public static void main(String[] args) {
38+
//PBE的作用就是把用户输入的口令和一个安全随机的口令采用杂凑后计算出真正的密钥。以AES密钥为例,我们让用户输入一个口令,然后生成一个随机数,通过PBE算法计算出真正的AES口令,再进行加密,代码如下:
39+
Security.addProvider(new BouncyCastleProvider());//把BouncyCastle作为Provider添加到java.security
40+
String message = "Hello,world!";//明文相当于文件
41+
String password = "hello12345";//加密口令,相当于你输入的密码
42+
43+
try {
44+
byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16);//16byte随机Salt,密钥
45+
System.out.printf("salt:%032x\n", new BigInteger(1, salt));
46+
47+
byte[] data = message.getBytes("UTF-8");
48+
byte[] encrypted = encrypt(password, salt, data);//加密
49+
System.out.println("加密encrypted:" + Base64.getEncoder().encodeToString(encrypted));
50+
51+
//解密
52+
byte[] decrypted = decrypt(password, salt, encrypted);
53+
System.out.println("解密decrypted:" + new String(decrypted, "UTF-8"));
54+
} catch (NoSuchAlgorithmException e) {
55+
e.printStackTrace();
56+
} catch (UnsupportedEncodingException e) {
57+
e.printStackTrace();
58+
} catch (InvalidKeyException e) {
59+
e.printStackTrace();
60+
} catch (InvalidKeySpecException e) {
61+
e.printStackTrace();
62+
} catch (NoSuchPaddingException e) {
63+
e.printStackTrace();
64+
} catch (InvalidAlgorithmParameterException e) {
65+
e.printStackTrace();
66+
} catch (IllegalBlockSizeException e) {
67+
e.printStackTrace();
68+
} catch (BadPaddingException e) {
69+
e.printStackTrace();
70+
}
71+
}
72+
73+
/*
74+
* PBE加密
75+
* 使用PBE时,我们还需要引入BouncyCastle,并指定算法是PBEwithSHA1and128bitAES-CBC-BC。观察代码,实际上真正的AES密钥是调用Cipher的init()方法时同时传入SecretKey和PBEParameterSpec实现的。
76+
* 在创建PBEParameterSpec的时候,我们还指定了循环次数1000,循环次数越多,暴力破解需要的计算量就越大。
77+
* 如果我们把salt和循环次数固定,就得到了一个通用的“口令”加密软件。如果我们把随机生成的salt存储在U盘,就得到了一个“口令”加USB Key的加密软件,它的好处在于,即使用户使用了一个非常弱的口令,
78+
* 没有USB Key仍然无法解密,因为USB Key存储的随机数密钥安全性非常高。
79+
* 这个其实主要用于加密文件和解密文件,比如你把一个文件加密了,但是这个加密的文件被黑客拿走了,并且黑客知道你的密码(口令),但是黑客不知道你的salt,黑客仍然打不开这个加密的文件
80+
* 我自己用的时候把salt拿出来就行了。
81+
* 小结
82+
* PBE算法通过用户口令和安全的随机salt计算出Key,然后再进行加密;
83+
* Key通过口令和安全的随机salt计算得出,大大提高了安全性;
84+
* PBE算法内部使用的仍然是标准对称加密算法(例如AES)。
85+
* 理解原理,PBE假定用户的口令很简单不安全,所以需要一个高强度密码和用户口令混在一起加密
86+
*/
87+
public static byte[] encrypt(String password, byte[] salt, byte[] input)
88+
throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{
89+
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());//口令
90+
SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
91+
SecretKey skey = skeyFactory.generateSecret(keySpec);
92+
PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
93+
Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
94+
cipher.init(Cipher.ENCRYPT_MODE, skey, pbeps);
95+
return cipher.doFinal(input);//加密明文
96+
}
97+
98+
/**
99+
* 解密
100+
* @param password 口令
101+
* @param salt
102+
* @param input 加密后的明文
103+
* @return
104+
* @throws NoSuchAlgorithmException
105+
* @throws InvalidKeySpecException
106+
* @throws NoSuchPaddingException
107+
* @throws InvalidKeyException
108+
* @throws InvalidAlgorithmParameterException
109+
* @throws IllegalBlockSizeException
110+
* @throws BadPaddingException
111+
*/
112+
public static byte[] decrypt(String password, byte[] salt, byte[] input)
113+
throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
114+
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
115+
SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
116+
SecretKey skey = skeyFactory.generateSecret(keySpec);
117+
PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
118+
Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
119+
cipher.init(Cipher.DECRYPT_MODE, skey, pbeps);
120+
return cipher.doFinal(input);//解密
121+
}
122+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package com.yale.test.ps;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.UnsupportedEncodingException;
6+
import java.math.BigInteger;
7+
import java.security.InvalidKeyException;
8+
import java.security.KeyStore;
9+
import java.security.KeyStoreException;
10+
import java.security.NoSuchAlgorithmException;
11+
import java.security.PrivateKey;
12+
import java.security.Signature;
13+
import java.security.SignatureException;
14+
import java.security.UnrecoverableKeyException;
15+
import java.security.cert.CertificateException;
16+
import java.security.cert.X509Certificate;
17+
18+
import javax.crypto.BadPaddingException;
19+
import javax.crypto.Cipher;
20+
import javax.crypto.IllegalBlockSizeException;
21+
import javax.crypto.NoSuchPaddingException;
22+
23+
24+
/*
25+
* 数字证书
26+
* 我们知道,摘要算法用来确保数据没有被篡改,非对称加密算法可以对数据进行加解密,签名算法可以确保数据完整性和抗否认性,把这些算法集合到一起,并搞一套完善的标准,这就是数字证书。
27+
* 因此,数字证书就是集合了多种密码学算法,用于实现数据加解密、身份认证、签名等多种功能的一种安全标准。
28+
* 数字证书可以防止中间人攻击,因为它采用链式签名认证,即通过根证书(Root CA)去签名下一级证书,这样层层签名,直到最终的用户证书。而Root CA证书内置于操作系统中,所以,任何经过CA认证的数字证书都可以对其本身进行校验,确保证书本身不是伪造的。
29+
* 我们在上网时常用的HTTPS协议就是数字证书的应用。浏览器会自动验证证书的有效性:
30+
* 要使用数字证书,首先需要创建证书。正常情况下,一个合法的数字证书需要经过CA签名,这需要认证域名并支付一定的费用。开发的时候,我们可以使用自签名的证书,
31+
* 这种证书可以正常开发调试,但不能对外作为服务使用,因为其他客户端并不认可未经CA签名的证书。
32+
* 在Java程序中,数字证书存储在一种Java专用的key store文件中,JDK提供了一系列命令来创建和管理key store。我们用下面的命令创建一个key store,并设定口令123456:
33+
* keytool -storepass 123456 -genkeypair -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 3650 -alias mycert -keystore my.keystore -dname "CN=www.sample.com, OU=sample, O=sample, L=BJ, ST=BJ, C=CN"
34+
* 几个主要的参数是:
35+
keyalg:指定RSA加密算法;
36+
sigalg:指定SHA1withRSA签名算法;
37+
validity:指定证书有效期3650天;
38+
alias:指定证书在程序中引用的名称;
39+
dname:最重要的CN=www.sample.com指定了Common Name,如果证书用在HTTPS中,这个名称必须与域名完全一致。
40+
* 执行上述命令,JDK会在当前目录创建一个my.keystore文件,并存储创建成功的一个私钥和一个证书,它的别名是mycert。
41+
* 有了key store存储的证书,我们就可以通过数字证书进行加解密和签名:
42+
* 在上述代码中,我们从key store直接读取了私钥-公钥对,私钥以PrivateKey实例表示,公钥以X509Certificate表示,实际上数字证书只包含公钥,因此,读取证书并不需要口令,只有读取私钥才需要。如果部署到Web服务器上,例如Nginx,需要把私钥导出为Private Key格式,把证书导出为X509Certificate格式。
43+
* 以HTTPS协议为例,浏览器和服务器建立安全连接的步骤如下:
44+
* 1.浏览器向服务器发起请求,服务器向浏览器发送自己的数字证书;
45+
* 2.浏览器用操作系统内置的Root CA来验证服务器的证书是否有效,如果有效,就使用该证书加密一个随机的AES口令并发送给服务器;
46+
* 3.服务器用自己的私钥解密获得AES口令,并在后续通讯中使用AES加密。
47+
* 上述流程只是一种最常见的单向验证。如果服务器还要验证客户端,那么客户端也需要把自己的证书发送给服务器验证,这种场景常见于网银等。
48+
* 注意:数字证书存储的是公钥,以及相关的证书链和算法信息。私钥必须严格保密,如果数字证书对应的私钥泄漏,就会造成严重的安全威胁。如果CA证书的私钥泄漏,那么该CA证书签发的所有证书将不可信。
49+
* 数字证书服务商DigiNotar(https://en.wikipedia.org/wiki/DigiNotar)就发生过私钥泄漏导致公司破产的事故。
50+
*/
51+
public class CASignDemo {
52+
public static void main(String[] args)
53+
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, SignatureException {
54+
byte[] message = "Hello, use X:509 cert!".getBytes("UTF-8");
55+
//读取KeyStore
56+
KeyStore ks = loadKeyStore("/my.keystore", "123456");
57+
58+
//读取私钥:
59+
PrivateKey privateKey = (PrivateKey)ks.getKey("mycert", "123456".toCharArray());
60+
61+
//读取证书:
62+
X509Certificate certificate = (X509Certificate)ks.getCertificate("mycert");
63+
64+
//加密:
65+
byte[] encrypted = encrypt(certificate, message);
66+
System.out.println(String.format("加密后的字符串,encrypted:%x", new BigInteger(1, encrypted)));
67+
68+
//解密
69+
byte[] decrypted = decrypt(privateKey, encrypted);
70+
System.out.println("解密,decrypted:" + new String(decrypted, "UTF-8"));
71+
72+
//签名:
73+
byte[] sign = sign(privateKey, certificate, message);
74+
System.out.println(String.format("signature:%x", new BigInteger(1, sign)));
75+
76+
//验证签名
77+
boolean verified = verify(certificate, message, sign);
78+
System.out.println("验证签名是否通过:" + verified);
79+
}
80+
81+
static KeyStore loadKeyStore(String keyStoreFile, String password)
82+
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
83+
try(InputStream input = CASignDemo.class.getResourceAsStream(keyStoreFile)) {
84+
if (input == null) {
85+
throw new RuntimeException("file not found in classpath:" + keyStoreFile);
86+
}
87+
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
88+
ks.load(input, password.toCharArray());
89+
return ks;
90+
} catch (KeyStoreException e) {
91+
e.printStackTrace();
92+
throw e;
93+
} catch (NoSuchAlgorithmException e) {
94+
e.printStackTrace();
95+
throw e;
96+
} catch (CertificateException e) {
97+
e.printStackTrace();
98+
throw e;
99+
} catch (IOException e) {
100+
e.printStackTrace();
101+
throw e;
102+
}
103+
}
104+
105+
static byte[] encrypt(X509Certificate certificate, byte[] message)
106+
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
107+
Cipher cipher = Cipher.getInstance(certificate.getPublicKey().getAlgorithm());
108+
cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
109+
return cipher.doFinal(message);
110+
}
111+
112+
static byte[] decrypt(PrivateKey privateKey, byte[] data)
113+
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
114+
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
115+
cipher.init(Cipher.DECRYPT_MODE, privateKey);
116+
return cipher.doFinal(data);
117+
}
118+
119+
static byte[] sign(PrivateKey privateKey, X509Certificate certificate, byte[] message)
120+
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
121+
Signature signature = Signature.getInstance(certificate.getSigAlgName());
122+
signature.initSign(privateKey);
123+
signature.update(message);
124+
return signature.sign();
125+
}
126+
127+
static boolean verify(X509Certificate certificate, byte[] message, byte[] sig)
128+
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
129+
Signature signature = Signature.getInstance(certificate.getSigAlgName());
130+
signature.initVerify(certificate);
131+
signature.update(message);
132+
return signature.verify(sig);
133+
}
134+
}

0 commit comments

Comments
 (0)