keystore truststore 本质是一样的,我们就用自建的keystore作为truststore.
1:在我的java工作目录/home/wzpad/java下运行,生成keystore文件:keystore.jks
keytool -genkey -alias mydomain -keyalg RSA -keystore keystore.jks -storepass 123456
2:打开chrome浏览器,导出想要的网页的证书,并存储,比如我导出https://cn.bing.com的证书取名为bing.crt 复制此文件到工作目录。
3:将 bing.crt 文件导入刚建的keystore.jks.
keytool -import -alias bing -keystore keystore.jks -file bing.crt
好了,现在可以用此 keystore.jks 来读取https://cn.bing.com网页了。经验证,这个证书可以打开所有微软bing搜索的网页。
命令行运行java 程序时,必须要指明此keystore.jks 文件的路径:
-Djavax.net.ssl.trustStore=/home/wzpad/java/keystore.jks -Djavax.net.ssl.trustStorePassword=123456注意最二个-D参数前要有空格
经验证,完美打开网页,如删掉keystore.jks中bing证书,则程序报错。或者用此证书打开除bing外的其他网页,如百度网页则报错。
删除命令:keytool -delete -alias bing -keystore keystore.jks
因为java 和eclipse 预置了cacerts 文件,它里面内置了很多著名网页的根证书,就包括www.bing.com 网页的。
特别要注意,在linux系统中使用命令行java 和使用eclipse程序两者使用不同的 JDK,所以它们使用的cacerts 也不同。如要验证,根据使用的程序的不同必须屏敝掉相应的cacerts. 千万不要删掉,可改名。这个问题困挠了我好久,否则对程序怎样加执行参数,它都会去找cacerts 来验证。只有把原版的 cacerts屏敝掉后,加参数才能执行我们的 keystore.jks .java命令行的cacerts 可用java -verbose 找 到,eclipse 中的在/opt/apps/ 下面,可以在网上查一下。补充一下, macbook 中java命令行和eclipse 使用的是同一个JDK,所以只要用java -verbose 查到cacerts 改名一下eclipse也不影响了。windows没有用,不清楚情况。
看JDK文档,SSLSocketFactory 中封装了握手协议和认证方法等。
以下是我的理解:认证https网页有两种方法,一种是用SSLSocketFactory类工厂化的认证,就是我们现在用的方法。如没加载自编的keystore.jks ,它就自动去加载预置的cacerts 库比对验证证书。
SocketFactory sf=SSLSocketFactory.getDefault();
第二种就是用户自定义认证:SSLContext引用各种接口。
SSLSocketFactory sslf=SSLContext.getSocketFactory(); 这里面有一个X509TrustManager接口,它有一重要的方法函数,checkServerTrusted() 如果不操作的话,程序对外来网页不作任何认证的。连最基本的证书都不检查,因为程序是把认证环节交给用户的。如用户不写检查代码则没有任何反馈,程序就认为证书合法通过放行了。
第一个程序是采用socket 和SSLSocketFactory 的工厂化认证方法读取https网页
import java.io.*;
import java.net.*;
import javax.net.*;
import javax.net.ssl.*;
public class Sslclient {
public static void main(String[] args) {
try {
SocketFactory ssf=SSLSocketFactory.getDefault();
Socket sc=ssf.createSocket("cn.bing.com",443);
//sc.startHandshake();
OutputStream os=sc.getOutputStream();
InputStream is=sc.getInputStream();
os.write("GET / HTTP/1.1\r\n".getBytes());
os.write("Host:cn.bing.com\r\n".getBytes());
os.write("Connection:close\r\n\r\n".getBytes());
os.flush();
InputStreamReader isr=new InputStreamReader(is,"UTF-8");
int k;
String s="";
do{
k=isr.read();
s=s+(char)k;
}while(k!=-1);
System.out.print(s);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
下面的程序是采用HttpsURLConnection类和 SSLSocketFactory的工厂化认证方法读取https网页
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
import javax.net.*;
public class Sslsocket {
public static void main(String[] args) {
try {
URL url=new URL("https://cn.bing.com");
HttpsURLConnection surl = (HttpsURLConnection)url.openConnection();
// surl.setDoInput(true);
SSLSocketFactory ssf=(SSLSocketFactory)SSLSocketFactory.getDefault();
surl.setSSLSocketFactory(ssf);
InputStream is1=surl.getInputStream();
InputStreamReader is=new InputStreamReader(is1,"UTF-8");
int k;
String s="";
do {
k=is.read();
s=s+(char)k;
}while(k!=-1);
System.out.print(s);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
eclipse run指令加上参数-Djavax.net.ssl.trustStore=/home/wzpad/java/keystore.jks -Djavax.net.ssl.trustStorePassword=123456 后,如果删除bing证书此二个程序都报错,加上读出网页。
学到这里突然发现,我们可以通过Socket SeverSocket 利用SSLSocketFactory的工厂化方法加密传送文件了。其实所有的网络通信编程都归到Socket上,只不过是加上了各种网络协议而已。都是Socket 和ServerSocket的应答交互模式。
RFC5280 中文翻译 中文RFC RFC文档 RFC翻译 RFC中文版
先试试水,用证书的数字签名(指纹)来判断。getSignature() 提取证书数字签名。
说一下我对接口这个概念的理解:接口就是java封装的类与编程者之间沟通的桥梁,接口的参数是封装程序已经得到的数据,这个数据是供编程者使用的,而类正好相反,是编程者向程序提供的方法,告诉程序怎样办。这样就好理解接口X509TrustManager 了。封装类SSLContext和 SSLSocketFactory 经过一系列交互取得证书,就用X509这接口把已取得的证书提供给编程者使用。
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password = {'1','2','3','4','5','6'};
FileInputStream fis = new FileInputStream("/home/wjs/java/mycert");
ks.load(fis, password);
// System.out.println(ks.size());
//上面四条是JDK文档标准加载密钥库代码
var cert=ks.getCertificate("business");
System.out.print(cert.toString());
//上面四条是JDK文档说明中加载密钥库的标准代码,这样就可以加载cacerts.
var is=new FileInputStream("证书文件名");
var cf=CertificateFactory.getInstance("X.509");
var cer=(X509Certificate)cf.generateCertificate(is);
这三句是加载证书的标准代码
证书签名,在JDK的文档中又叫公钥签名。在证书的第三版国际标准中的扩展部份才加入了域名,这说明早期的证书签名只是为了公钥这个目标的。
keytool -genkeypair -dname "cn=Mark Jones, ou=Java, o=Oracle, c=US" -alias business -keypass 123456 -keystore mycert -storepass 123456 -validity 180
这是JDK 文档中用keytool 生成自签名证书的命令行。最开始生成的是公钥私钥密码对。生成的mycert 是一个密码库,里面有一条business 。用keytool -list打开如下:
所有者和发布者相同,说明是自签名。
公钥私钥交互介绍 - 新手娃娃菜 - 博客园
推荐一好文,把证书的细节说清楚了
//-------------------------------------------------------------------------------------------------------------------------------
学到此,想搞懂ssl .https ,又要去学Base64 RSA编码解码,正好换一下思路。
先看看Base64:
import java.security.*;
import java.util.Base64;
public class Rsa {
public static void main(String[] args) {
String s="12wjs";
byte[] a=s.getBytes();
Base64.Encoder be=Base64.getEncoder();
byte[] b=be.encode(a);
for(byte k:b) {
System.out.print((char)k);
}
System.out.println();
Base64.Decoder bd=Base64.getDecoder();
byte[] c=bd.decode(b);
for(byte k1:c) {
System.out.print((char)k1);
}
这个Base64用处很大实用。用它可以简单加密一下你的文档。
因为Base64是针对字节加解密,下面程序我改进对中文操作
import java.security.*;
import java.util.Base64;
import java.io.*;
public class Rsa {
public static void main(String[] args) {
String s="我爱你老婆,哈哈哈";
byte[] a=s.getBytes();
Base64.Encoder be=Base64.getEncoder();
byte[] b=be.encode(a);
for(byte k:b) {
System.out.print((char)k);
}
System.out.println();
Base64.Decoder bd=Base64.getDecoder();
byte[] c=bd.decode(b);
var ou=new ByteArrayInputStream(c);
try {
var ou1=new InputStreamReader(ou,"UTF-8");
try {
for(int t=0;t
为什么要使用此编码,是因为生成的密钥很多是不可显示的字符,或者显示为乱码,所以要把它们转化为可见状态。比如生成的公钥数值很多就不可见,要让它可见就必须转换。
------------------------------------------
判断证书是否被信任从四方面入手:
1. 签名能不能被公钥解密。如能,则证明对方是服务器。
2. 证书的根证书是否在cacerts中,如在则被信任。
3.证书的内容的哈希值是否与本证书中的指纹相符,是被信住,否则内容被修改过。一般情况下一个网页至少要对应两张证书,那就要验两次,验根证书时可能是先调用cacerts中的证书比对一下接收的根证书?用equals()?
4 被访问主页域名,证书期限,是否被吊销
这几天正找验证证书内容哈希值的方法,突然想到传入的证书应该不只一张,经查baidu的chain长度为3,真有三张。最后第三张是根证书,应该在cacerts 中,是不是用根证书的公钥去解密第二张的加密指纹,验证第二张,再用第二张的公钥去解第一张的加密指纹,验证第一张。chain[0] 是baidu证书。现在的难点是不清楚证书内容怎样提取,摘要方法有了,没有准确的摘要内容也比对不了指纹。试了Certificate类和x509certificate 类中几个get函数方法得到的内容,摘要后去比对都不相符。还有用get提取的指纹要经过多少次解密。对于每一张证书又怎样解密。查遍了网络,没有一篇文章说清楚。怪不得著名的浏览器就只有3,4个。
我现在的理解:证书所有者申请证书的文件里至少有公钥,所有者名,申请者网站域名,不会有颁发者名字,证书串号,有效期。所以申请者提交的文件肯定没有指纹。此申请文件到了颁发者手里,再加入串号,有效期等内容后组成证书的明文内容,颁发者对明文内容进行摘要哈希计算得到指纹。此指纹是不可能被申请者私钥加密的。因为申请者只提供了公钥,好了,颁发者用自己的私钥给此证书的指纹加密,又叫签名。现在得到的签名又叫加密指纹是只加密了一次的指纹。所以chain[0]证书的指纹是没有被chain[0]证书所有者用私钥加密的?就不会被它的公钥解密了?现在正用这种思路验证根证书chain[0]。
再次发现,这个接收证书的数组是变化的,比如百度是三个,bing是两个。使用前要搜一下长度。
import java.io.*;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.net.ssl.*;
import java.net.*;
import java.security.*;
import java.security.cert.*;
import java.util.Arrays;
public class Hs {
public static void main(String[] args)throws Exception{
try {
SSLContext sslc = SSLContext.getInstance("TLSv1.2");
TrustManager[] tm = {new X509()};
sslc.init(null, tm, new SecureRandom());
SSLSocketFactory sc = sslc.getSocketFactory();
Socket in=sc.createSocket("www.baidu.com",443);
InputStream isr=in.getInputStream();
OutputStream os=in.getOutputStream();
os.write("GET / HTTP/1.1\r\n".getBytes());
os.write(("Host:www.baidu.com\r\n").getBytes());
os.write("Connection:close\r\n\r\n".getBytes());
os.flush();
InputStreamReader is=new InputStreamReader(isr,"UTF-8");
String s="";
int k;
k=is.read();
while(k!=-1) {
s=s+(char)k;
k=is.read();
}
System.out.print(s);
/* File fl=new File("/home/wzpad/java/1.html");
FileOutputStream os1=new FileOutputStream(fl);
os1.write(s.getBytes());
os1.close();*/
} catch (KeyManagementException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static class X509 implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// System.out.println(chain.length);
/* var is=new FileInputStream("/home/wzpad/java/bing.crt");
CertificateFactory cf=CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(is); //引入下载bing证书
byte[] a=cert.getSignature(); //读取bing证书数字签名
// PublicKey pub=cert.getPublicKey();
// System.out.println(cert.)*/
//-------------------------------------------------------------------
//* 1. 验证证书明文是否被修改,就是明文的哈希值是否与签名相互
X509Certificate cert1=chain[0];
X509Certificate cert2=chain[1];
// X509Certificate cert3=chain[2];
try {
MessageDigest md=MessageDigest.getInstance("SHA-256");
md.update(cert1.getTBSCertificate());
//MessageDigest tc1= md.clone();
byte[] tomd1=md.digest(); //证书明文取指纹,哈希算法
int z;
String sout="";
for(int n=0;n0
if(zk<0) {
System.out.println("certificate no ok");
throw new CertificateException(); //抛出异常
}
} catch (CertificateEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//1. 明文判断结束 现在只判断了二级证书,根证书还没有判断
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}
上面的程序提取的www.baidu.com网页。已加上异常报错功能.
细想,RSA解密部份没有报错,说明用公钥解密是正确的。那说明提取加密指纹的方法和思路是正确的。试验了一下用pub1解密,则报错。正好说明证书1的指纹是加密的,必须用证书二的公钥解密。而证书二是根证书。正好证明根证书验证环节是正确的。
解密指纹长度是51位,明文的摘要为32位. 输出发现明文摘要包含在解密签名指纹中.试了一下,cn.bing.com www.baidu.com 都成立.为了严谨,还是要搞清加密指纹还包含什么,但现在应该可以这样判断明文和签名的关系了.如果包含,就相符.
到现在,我们就清楚证书是什么玩意了。如果只收到一张,则有两种可能,一种是根证书,包含在cacerts中那种,另一种就是自签名的证书,自己用私钥加密明文的证书,此证书不包含在cacerts 中,所以浏览器不信任这种证书。如收到两张证书,如cn.bing.com这种网页,一张是微软的,另一张是根证书对微软签发认证的,最后是收到三张证书,如百度网页。 现在理一下认证的顺序。首先查收到几张证书,一张的简单,查一下证书发行方在不在cacerts 中,如在再检验一下明文与指纹相符不,如相等则通过,收到两张的,也是首先查根证书在不在cacerts 中,如在,首先检查收到的根证书明文与签名的哈希值加符不。如相等则检查第二张证书,正好是上面的程序,有第三张的检查和第二张相同。至此,证书明文部份验证就完成了。
我推测,同理,服务器认证客户端,首先是为客户端颁发一张服务器私钥认证签名的证书。就象网银给客户的U盾一样。∪盾里面我想就是一银行签名的数字证书。再在服务器端验证客发端发来的证书是否可靠。这种使用场景可以用在安全性要求高的公司服务器上。为每一个员工配一张数字证书。不通过则上不了内网。下一步计划就是在公网上配置好https服务器再折腾一下,把这个https搞明白。最后的理想是用原生Socket java 写一个https握手交互认证的程序。彻底理解这个交互握手过程。接下来先详细完善客户端对服务器认证的所有代码。
发现我现在明文的摘要和浏览器网页上的SHA256指纹值不同。不知道这两个值是不是该相同,网络上有的说这个值只是作为证书接收方与发证方人工核对之用,程序上不会用,程序核对只要证书签名。没有一个权威的说法。
现在又拦在对根证书的核对上。keystore类上的封装函数站在我的思路上,除了提取公钥和签名两个方法外,剩下的都没有用处。网络上又讲,这个根证书的发证方是变动的,比如破产,被收购。所以签名也会变动,不变的只有它们的公钥,所以准备用它们的公钥作比对。
这几天看JDK,看得云里雾里的。国内的大佬有谁能写一本详细的用法指南就好了。绝对大卖。
我现在认为真正的认证可能不是走的我现在的路线,看jdk上有一几个PKI类,可能是用这几个类来认证的。这都是封装好的,如用封装类肯定简单,但具体细节又不清楚了。
考虑了一下,试着全面学一遍JDK中与https有关的包,回来再继续写https认证。接下来把学习的过程和想法写下来。目前知道的有:
java.security
java.security.cert
javax.crypto
javax.security.cert
这4个包已占了java.base 模块的三分之一。可见这几部份的重要性。可这几部份在java书中很少分析介绍。很多是几句带过。慢慢学慢慢理解。感觉这几个包的实用性非常强,如理解清楚了,就能写出安全方面的程序。
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class Sb {
public static void main(String[] args) throws CertificateException {
try {
URL url=new URL("https://cn.bing.com");
HttpsURLConnection hurl=(HttpsURLConnection)url.openConnection();
SSLContext sslc=SSLContext.getInstance("TLSv1.2");
TrustManager[] tm= {new X509()};
sslc.init(null,tm, null);
SSLSocketFactory ssc = sslc.getSocketFactory();
hurl.setSSLSocketFactory(ssc);
InputStream is=hurl.getInputStream();
InputStreamReader isr=new InputStreamReader(is,"UTF-8");
int k;
String s="";
k=isr.read();
while (k!=-1) {
s=s+(char)k;
k=isr.read();
}
// System.out.println(s); //输出bing网页文件
} catch (KeyManagementException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class X509 implements X509TrustManager{
public void checkClientTrusted(X509Certificate[] chain,String authType)throws CertificateException{
}
public void checkServerTrusted(X509Certificate[] chain,String anthType)throws CertificateException{
try {
var Pub1=chain[1].getPublicKey(); //微软二级证书公钥
var Pub0=chain[0].getPublicKey(); //bing网页证书公钥 此公钥对于验证没有用
chain[0].verify(Pub1); //JDK 中的方法:用二级证书公钥验证bing证书
//------------------------------------------
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password = {'c','h','a','n','g','e','i','t'}; //cacerts 密码"changeit"
FileInputStream fis = new FileInputStream("/opt/apps/org.eclipse.java-ee/files/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_17.0.1.v20211116-1657/jre/lib/security/cacerts");
ks.load(fis, password); //deepin 系统中cacerts路径
var iss1=chain[1].getIssuerX500Principal().getName(); //取得微软二级证书的颁发签名方,装化为String类型
// System.out.println(iss1); //CN=Baltimore CyberTrust Root,OU=CyberTrust,O=Baltimore,C=IE
iss1=iss1.toLowerCase();
// System.out.println(iss1); //装化为小写字母 cn=baltimore cybertrust root,ou=cybertrust,o=baltimore,c=ie
String[] string1=iss1.split(","); //和cacerts中的别名比对,从网络得到的别名顺序是反向的,所以把iss1分解为字符串数组,分别比较cn ou o c;
Enumeration enumeration = ks.aliases(); //cacerts全部证书的别名
while(enumeration.hasMoreElements()) {
String alias = enumeration.nextElement();
alias=alias.toLowerCase(); //转换为小写
int zz=0;
for(int n=0;n
用jdk的方法验证: 用微软的二级证书公钥验证bing 证书,用cacerts根证书公钥验证微软二级证书
经过一段时间的学习,写的读取https网页的程序,不验证:
import javax.net.ssl.HttpsURLConnection;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
public class Http {
public static void main(String[] args) throws IOException {
URL url=new URL("https://cn.bing.com");
HttpsURLConnection hc= (HttpsURLConnection) url.openConnection();
InputStream is=hc.getInputStream();
byte[] b=is.readAllBytes();
String s=new String(b);
System.out.println(s);
}
}