在不安全的网络环境下进行密钥交互(1/3,前面那一节),容易遭受中间人攻击,什么是中间人攻击,请google it。
通信的双方必须是相互信任的,在这个基础上再进行密钥协商才是可靠的。那么,如何建立信任关系呢?
我以前的几篇博文介绍了用如何 用 Java编程方式生成CA证书 以及用CA证书签发客户证书。
现在假设,Alice和Bob的证书都是被同一个CA atlas签发的(见我前面的博文),那么他们之间如何安全的交互密钥呢?
Alice:
/** * 安全的密钥交互类。 * 这个交互工具可以校验双方的身份,并对发送的DH公钥进行签名,防止中间者攻击。 * @author atlas * @date 2012-9-6 */ public class SecureKeyExchanger extends DHKeyExchanger { /** * 签名算法 */ private static String signAlgorithm = "SHA1withRSA"; /** * 此方的私钥 */ private PrivateKey privateKey; /** * 此方的证书 */ private Certificate certificate; /** * 用于校验彼方公钥的CA证书,此证书来自此方的CA或者此方可信任的CA */ private Certificate caCertificate; /** * 彼方的证书,在DH公钥交换之前进行交互获取的 */ private Certificate peerCert; /** * * @param out * @param in * @param privateKey 此方的私钥 * @param certificate 此方的证书 * @param caCertificate 用于校验彼方公钥的CA证书,此证书来自此方的CA或者此方可信任的CA */ public SecureKeyExchanger(Pipe pipe, PrivateKey privateKey, Certificate certificate, Certificate caCertificate) { super(pipe); this.privateKey = privateKey; this.certificate = certificate; this.caCertificate = caCertificate; } // Send the public key. public void sendPublicKey() throws IOException, CertificateEncodingException { byte[] keyBytes = certificate.getEncoded(); write(keyBytes); } public void receivePublicKey() throws IOException, SkipException { byte[] keyBytes =read(); try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); peerCert = cf .generateCertificate(new ByteArrayInputStream(keyBytes)); peerCert.verify(caCertificate.getPublicKey()); } catch (CertificateException e) { throw new SkipException("Unsupported certificate type X.509", e); } catch (InvalidKeyException e) { throw new SkipException( "Peer's certificate was not invlaid or not signed by current CA.", e); } catch (NoSuchAlgorithmException e) { throw new SkipException("Signature algorithm not supported.", e); } catch (NoSuchProviderException e) { throw new SkipException("No signature Provider.", e); } catch (SignatureException e) { throw new SkipException( "Peer's certificate was not invlaid or not signed by current CA.", e); } } @Override public void receiveDHPublicKey() throws IOException, SkipException { // receiver public key receivePublicKey(); // receive dh public key byte[] publicKeyBytes = read(); // receive signature of dh public key byte[] sign = read(); KeyFactory kf; try { // verify signature using peer certificate Signature sig = Signature.getInstance(signAlgorithm); sig.initVerify(peerCert); sig.verify(sign); kf = KeyFactory.getInstance("DH"); X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(publicKeyBytes); peerDHPublicKey = kf.generatePublic(x509Spec); } catch (NoSuchAlgorithmException e) { throw new SkipException("Signature algorithm " + signAlgorithm + " not supported.", e); } catch (InvalidKeySpecException e) { throw new SkipException("Peer's public key invalid.", e); } catch (InvalidKeyException e) { throw new SkipException("Peer's public key invalid.", e); } catch (SignatureException e) { throw new SkipException("Invalid signature.", e); } } @Override public void sendDHPublicKey() throws IOException, SkipException { try { // send public key sendPublicKey(); // send dh public key byte[] keyBytes = dhKeyPair.getPublic().getEncoded(); write(keyBytes); // sign dh public key using my private key and send the signature Signature sig; sig = Signature.getInstance(signAlgorithm); sig.initSign(privateKey); sig.update(keyBytes); byte[] sign = sig.sign(); write(sign); } catch (NoSuchAlgorithmException e) { throw new SkipException("Signature algorithm " + signAlgorithm + " not supported.", e); } catch (InvalidKeyException e) { throw new SkipException("My private key invalid.", e); } catch (SignatureException e) { throw new SkipException( "Signature exception when sending dh public key.", e); } catch (CertificateEncodingException e) { throw new SkipException("error when sending dh public key.", e); } } }
public class KeyInfo { PrivateKey privateKey; Certificate certificate; Certificate caCertificate; public KeyInfo(PrivateKey privateKey, Certificate certificate, Certificate caCertificate) { super(); this.privateKey = privateKey; this.certificate = certificate; this.caCertificate = caCertificate; } public Certificate getCaCertificate() { return caCertificate; } public Certificate getCertificate() { return certificate; } public PrivateKey getPrivateKey() { return privateKey; } }
public class Server4Alice { public static void main(String[] args) throws Exception { int port = Integer.parseInt("1111"); System.out.println(Base64.encode(exchangeFrom(port))); } public static byte[] exchangeFrom(int port) throws SkipException, IOException { InputStream file = SkipServer4Alice.class .getResourceAsStream("atlas-alice.jks"); KeyInfo key = Reader.read(file, "alice", "alice"); ServerSocket ss = new ServerSocket(port); // Wait for a connection. Socket s = ss.accept(); DataOutputStream out = new DataOutputStream(s.getOutputStream()); DataInputStream in = new DataInputStream(s.getInputStream()); Pipe pipe = new DataPipe(in, out); KeyExchanger exchanger = new SecureKeyExchanger(pipe, key.getPrivateKey(), key.getCertificate(), key.getCaCertificate()); exchanger.exchange(); s.close(); ss.close(); return exchanger.getKey(); } }
public class Client4Bob { public static void main(String[] args) throws Exception { String host = "localhost"; int port = Integer.parseInt("1111"); // Open the network connection. byte[] key = exchangeFrom(host, port); System.out.println(Base64.encode(key)); } public static byte[] exchangeFrom(String host, int port) throws SkipException, IOException { InputStream file = SkipServer4Alice.class .getResourceAsStream("atlas-bob.jks"); KeyInfo key = Reader.read(file, "bob", "bob"); Socket s = new Socket(host, port); DataOutputStream out = new DataOutputStream(s.getOutputStream()); DataInputStream in = new DataInputStream(s.getInputStream()); Pipe pipe = new DataPipe(in, out); KeyExchanger exchanger = new SecureKeyExchanger(pipe, key.getPrivateKey(), key.getCertificate(), key.getCaCertificate()); exchanger.exchange(); s.close(); return exchanger.getKey(); } }
几个JKS文件:
atlas-alice.jks:包含一个alice的私钥和证书,证书是用atlas的CA签发的
atlas-bob.jks:包含一个bob的私钥和证书,证书是用atlas的CA签发的
CA atlas的证书分别在alice和bob的信任证书列表里面有一个copy
Reader.read()是个工具方法,负责把jks文件里面的证书信息读取出来:
public class Reader { public static KeyInfo read(InputStream file, String alias, String password) { try { KeyStore store = KeyStore.getInstance("JKS"); store.load(file, password.toCharArray()); PrivateKeyEntry ke = (PrivateKeyEntry) store.getEntry(alias, new PasswordProtection(password.toCharArray())); KeyInfo info = new KeyInfo(ke.getPrivateKey(), ke.getCertificate(), ke.getCertificateChain()[1]); return info; } catch (Exception e) { e.printStackTrace(); } return null; } }