在不安全的网络环境下进行密钥交互(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;
}
}