在不安全的网络环境下进行密钥交互(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算法的公钥,即通过这种方式避免了中间人攻击。


下面该上代码了:


/**
 * 安全的密钥交互类。
 * 这个交互工具可以校验双方的身份,并对发送的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;
	}
}