在不安全的网络环境下进行密钥交互(1/3,前面那一节),容易遭受中间人攻击,什么是中间人攻击,请google it。

 

通信的双方必须是相互信任的,在这个基础上再进行密钥协商才是可靠的。那么,如何建立信任关系呢?

 

我以前的几篇博文介绍了用如何 用  Java编程方式生成CA证书 以及用CA证书签发客户证书。

 

现在假设,Alice和Bob的证书都是被同一个CA atlas签发的(见我前面的博文),那么他们之间如何安全的交互密钥呢?

 

Alice:

 

  • 发送自己的证书Certa;

  • 发送DH 密钥对的公钥PDa;

  • 发送用自己的私钥 对DH公钥的签名Sa。

Bob与Alice执行相同的步骤,下面是Bob处理Alice发送的东西的步骤:

  • 接收Alice的公钥Certa、DH公钥PDa、和签名Sa

  • 使用CA的证书验证Certa,如果通过,就证明了Alice的证书Certa是合法的。

  • 使用证书Certa验证签名Sa,如果通过,就证明了Certa确实是Alice发送过来的,因为只有Alice用自己的私钥才可以对PDa进行签名。

Alice和Bob是对等的。

经过上面的过程,可以看出Alice和Bob在相互信任的基础上交互了DH算法的公钥,即通过这种方式避免了中间人攻击。

下面该上代码了:

  1. /** 

  2.  * 安全的密钥交互类。 

  3.  * 这个交互工具可以校验双方的身份,并对发送的DH公钥进行签名,防止中间者攻击。 

  4.  * @author atlas 

  5.  * @date 2012-9-6 

  6.  */  

  7. public class SecureKeyExchanger extends DHKeyExchanger {  

  8.     /** 

  9.      * 签名算法 

  10.      */  

  11.     private static String signAlgorithm = "SHA1withRSA";  

  12.   

  13.     /** 

  14.      * 此方的私钥 

  15.      */  

  16.     private PrivateKey privateKey;  

  17.     /** 

  18.      * 此方的证书 

  19.      */  

  20.     private Certificate certificate;  

  21.     /** 

  22.      * 用于校验彼方公钥的CA证书,此证书来自此方的CA或者此方可信任的CA 

  23.      */  

  24.     private Certificate caCertificate;  

  25.       

  26.     /** 

  27.      * 彼方的证书,在DH公钥交换之前进行交互获取的 

  28.      */  

  29.     private Certificate peerCert;  

  30.       

  31.     /** 

  32.      *  

  33.      * @param out 

  34.      * @param in 

  35.      * @param privateKey 此方的私钥 

  36.      * @param certificate 此方的证书 

  37.      * @param caCertificate 用于校验彼方公钥的CA证书,此证书来自此方的CA或者此方可信任的CA 

  38.      */  

  39.     public SecureKeyExchanger(Pipe pipe,  

  40.             PrivateKey privateKey, Certificate certificate,  

  41.             Certificate caCertificate) {  

  42.         super(pipe);  

  43.         this.privateKey = privateKey;  

  44.         this.certificate = certificate;  

  45.         this.caCertificate = caCertificate;  

  46.     }  

  47.   

  48.     // Send the public key.  

  49.     public void sendPublicKey() throws IOException,  

  50.             CertificateEncodingException {  

  51.         byte[] keyBytes = certificate.getEncoded();  

  52.         write(keyBytes);  

  53.     }  

  54.   

  55.     public void receivePublicKey() throws IOException, SkipException {  

  56.         byte[] keyBytes =read();  

  57.         try {  

  58.             CertificateFactory cf = CertificateFactory.getInstance("X.509");  

  59.             peerCert = cf  

  60.                     .generateCertificate(new ByteArrayInputStream(keyBytes));  

  61.             peerCert.verify(caCertificate.getPublicKey());  

  62.         } catch (CertificateException e) {  

  63.             throw new SkipException("Unsupported certificate type X.509", e);  

  64.         } catch (InvalidKeyException e) {  

  65.             throw new SkipException(  

  66.                     "Peer's certificate was not invlaid or not signed by current CA.",  

  67.                     e);  

  68.         } catch (NoSuchAlgorithmException e) {  

  69.             throw new SkipException("Signature algorithm not supported.", e);  

  70.         } catch (NoSuchProviderException e) {  

  71.             throw new SkipException("No signature Provider.", e);  

  72.         } catch (SignatureException e) {  

  73.             throw new SkipException(  

  74.                     "Peer's certificate was not invlaid or not signed by current CA.",  

  75.                     e);  

  76.         }  

  77.     }  

  78.   

  79.     @Override  

  80.     public void receiveDHPublicKey() throws IOException, SkipException {  

  81.         // receiver public key  

  82.         receivePublicKey();  

  83.   

  84.         // receive dh public key  

  85.         byte[] publicKeyBytes = read();  

  86.   

  87.         // receive signature of dh public key  

  88.         byte[] sign = read();  

  89.         KeyFactory kf;  

  90.         try {  

  91.             // verify signature using peer certificate  

  92.             Signature sig = Signature.getInstance(signAlgorithm);  

  93.             sig.initVerify(peerCert);  

  94.             sig.verify(sign);  

  95.             kf = KeyFactory.getInstance("DH");  

  96.             X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(publicKeyBytes);  

  97.             peerDHPublicKey = kf.generatePublic(x509Spec);  

  98.         } catch (NoSuchAlgorithmException e) {  

  99.             throw new SkipException("Signature algorithm " + signAlgorithm  

  100.                     + " not supported.", e);  

  101.         } catch (InvalidKeySpecException e) {  

  102.             throw new SkipException("Peer's public key invalid.", e);  

  103.         } catch (InvalidKeyException e) {  

  104.             throw new SkipException("Peer's public key invalid.", e);  

  105.         } catch (SignatureException e) {  

  106.             throw new SkipException("Invalid signature.", e);  

  107.         }  

  108.     }  

  109.   

  110.     @Override  

  111.     public void sendDHPublicKey() throws IOException, SkipException {  

  112.         try {  

  113.             // send public key  

  114.             sendPublicKey();  

  115.             // send dh public key  

  116.             byte[] keyBytes = dhKeyPair.getPublic().getEncoded();  

  117.             write(keyBytes);  

  118.               

  119.             // sign dh public key using my private key and send the signature  

  120.             Signature sig;  

  121.             sig = Signature.getInstance(signAlgorithm);  

  122.             sig.initSign(privateKey);  

  123.             sig.update(keyBytes);  

  124.             byte[] sign = sig.sign();  

  125.             write(sign);  

  126.         } catch (NoSuchAlgorithmException e) {  

  127.             throw new SkipException("Signature algorithm " + signAlgorithm  

  128.                     + " not supported.", e);  

  129.         } catch (InvalidKeyException e) {  

  130.             throw new SkipException("My private key invalid.", e);  

  131.         } catch (SignatureException e) {  

  132.             throw new SkipException(  

  133.                     "Signature exception when sending dh public key.", e);  

  134.         } catch (CertificateEncodingException e) {  

  135.             throw new SkipException("error when sending dh public key.", e);  

  136.         }  

  137.   

  138.     }  

  139. }  

 

测试代码: 

  1. public class KeyInfo {  

  2.     PrivateKey privateKey;  

  3.     Certificate certificate;  

  4.     Certificate caCertificate;  

  5.   

  6.     public KeyInfo(PrivateKey privateKey, Certificate certificate,  

  7.             Certificate caCertificate) {  

  8.         super();  

  9.         this.privateKey = privateKey;  

  10.         this.certificate = certificate;  

  11.         this.caCertificate = caCertificate;  

  12.     }  

  13.   

  14.     public Certificate getCaCertificate() {  

  15.         return caCertificate;  

  16.     }  

  17.   

  18.     public Certificate getCertificate() {  

  19.         return certificate;  

  20.     }  

  21.   

  22.     public PrivateKey getPrivateKey() {  

  23.         return privateKey;  

  24.     }  

  25. }  

Java代码  java-在非安全网络上建立可信任安全的通道(2/3)_java 安全通道

  1. public class Server4Alice {  

  2.     public static void main(String[] args) throws Exception {  

  3.         int port = Integer.parseInt("1111");  

  4.         System.out.println(Base64.encode(exchangeFrom(port)));  

  5.     }  

  6.   

  7.     public static byte[] exchangeFrom(int port) throws SkipException,  

  8.             IOException {  

  9.         InputStream file = SkipServer4Alice.class  

  10.                 .getResourceAsStream("atlas-alice.jks");  

  11.         KeyInfo key = Reader.read(file, "alice""alice");  

  12.   

  13.         ServerSocket ss = new ServerSocket(port);  

  14.         // Wait for a connection.  

  15.         Socket s = ss.accept();  

  16.         DataOutputStream out = new DataOutputStream(s.getOutputStream());  

  17.         DataInputStream in = new DataInputStream(s.getInputStream());  

  18.         Pipe pipe = new DataPipe(in, out);  

  19.         KeyExchanger exchanger = new SecureKeyExchanger(pipe,  

  20.                 key.getPrivateKey(), key.getCertificate(),  

  21.                 key.getCaCertificate());  

  22.         exchanger.exchange();  

  23.         s.close();  

  24.         ss.close();  

  25.         return exchanger.getKey();  

  26.     }  

  27. }  

Java代码  java-在非安全网络上建立可信任安全的通道(2/3)_java 安全通道

  1. public class Client4Bob {  

  2.     public static void main(String[] args) throws Exception {  

  3.         String host = "localhost";  

  4.         int port = Integer.parseInt("1111");  

  5.         // Open the network connection.  

  6.         byte[] key = exchangeFrom(host, port);  

  7.         System.out.println(Base64.encode(key));  

  8.     }  

  9.   

  10.     public static byte[] exchangeFrom(String host, int port)  

  11.             throws SkipException, IOException {  

  12.         InputStream file = SkipServer4Alice.class  

  13.                 .getResourceAsStream("atlas-bob.jks");  

  14.         KeyInfo key = Reader.read(file, "bob""bob");  

  15.         Socket s = new Socket(host, port);  

  16.         DataOutputStream out = new DataOutputStream(s.getOutputStream());  

  17.         DataInputStream in = new DataInputStream(s.getInputStream());  

  18.         Pipe pipe = new DataPipe(in, out);  

  19.         KeyExchanger exchanger = new SecureKeyExchanger(pipe,  

  20.                 key.getPrivateKey(), key.getCertificate(),  

  21.                 key.getCaCertificate());  

  22.         exchanger.exchange();  

  23.         s.close();  

  24.         return exchanger.getKey();  

  25.     }  

  26. }  

 

几个JKS文件:

 

atlas-alice.jks:包含一个alice的私钥和证书,证书是用atlas的CA签发的

 

atlas-bob.jks:包含一个bob的私钥和证书,证书是用atlas的CA签发的

 

CA atlas的证书分别在alice和bob的信任证书列表里面有一个copy

 

Reader.read()是个工具方法,负责把jks文件里面的证书信息读取出来:

 

Java代码  java-在非安全网络上建立可信任安全的通道(2/3)_java 安全通道

  1. public class Reader {  

  2.   

  3.     public static KeyInfo read(InputStream file, String alias, String password) {  

  4.         try {  

  5.             KeyStore store = KeyStore.getInstance("JKS");  

  6.             store.load(file, password.toCharArray());  

  7.             PrivateKeyEntry ke = (PrivateKeyEntry) store.getEntry(alias,  

  8.                     new PasswordProtection(password.toCharArray()));  

  9.             KeyInfo info = new KeyInfo(ke.getPrivateKey(), ke.getCertificate(),  

  10.                     ke.getCertificateChain()[1]);  

  11.             return info;  

  12.         } catch (Exception e) {  

  13.             e.printStackTrace();  

  14.         }  

  15.         return null;  

  16.     }  

  17. }