本文转自:http://www.fengfly.com/plus/view-210090-1.html
一、通过用户名和密码来进行认证的弊病
我们有一个网站,为了保证用户在线交易传输数据的安全性,我们会启用一个HTTPS/SSL:
但是,对于一些网上银行或者是网购来说,黑客特别喜欢攻击这样的网站, 有一种攻击手法叫MIMAT(中间者攻击), 伪造SSL证书,让客户端的HTTP流,流到他那边去, 然后再进一步用暴力破解,来破解你HTTP传输时的密码。
一、改进的交易流程
我们假设密码已经被MIM拿到了,拿到就拿到呗,大家知道工商银行网上转贴划款时除了输入用户名和密码外,还会在点”下一步”时,跳出一个页面,让你插上你的U盾,然后再送一下交易密码的过程吧?
这个就是”电子签名认证”
二、先来回顾一下什么叫电子签名:
公钥加密,私钥解密
私钥签名,公钥认证
举例:
1. A用自己的私钥,对abcdefg进行sign,sign()函数返回一个byte[],这就是电子签名。
2. 把A的公钥和签名送到公行后台
3. 工行先看A的密码输的对不对,做一个数据库校验
工行用A的公钥对A的签名做verify运算,也得到一个byte[]
4. 工行把工发过来的签名byte[]和用A的公钥做verify()后的byte[], 两个byte[]进行booleanverified = sig.verify(dcByPriv);
5. 如果verified为true,代表A一定是客户A本人且是工行的客户(当然,A如果被人杀了,并且A的私钥被杀他的人获得了这个不能算工行的责任)
三、用JAVA实现签名过程
于是, 根据上述过程先做一个POC, 用JAVA来做电子签名认证,代码如下:
import java.security.*;
public class SimpleSignature {
private static void digitalSign(String text)throws Exception{
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair keyPair = kpg.generateKeyPair();
byte[] data = text.getBytes("UTF8");
Signature sig = Signature.getInstance("MD5WithRSA");
sig.initSign(keyPair.getPrivate());
sig.update(data);
byte[] signatureBytes=sig.sign();
System.out.println("Signature:\n"+Base64.encode(signatureBytes));
sig.initVerify(keyPair.getPublic());
sig.update(data);
boolean verified = false;
try{
verified = sig.verify(signatureBytes);
}catch(SignatureException se){
se.printStackTrace();
verified = false;
}
if(verified){
System.out.println("Signature verified.");
}else{
System.out.println("Signature did not match.");
}
}
public static void main(String[] args){
try{
String text="abc";
digitalSign(text);
}catch(Exception e){
e.printStackTrace();
}
}
}
四、运用证书解决公钥,私钥传输的问题
1. 生成自签名CA根证书
openssl genrsa -des3 -out ca.key 1024
openssl rsa -in server.key -out ca.key
openssl req -new -x509 -keyout ca.key -out ca.crt
2. 生成Web服务器证书
openssl genrsa -des3 -out shnlap93.key 1024
openssl rsa -in shnlap93.key -out shnlap93.key
openssl req -new -key shnlap93.key -out shnlap93.csr
openssl ca -in shnlap93.csr -out shnlap93.crt -cert ca.crt -keyfileca.key
3. 生成客户端证书
openssl genrsa -des3 -out client.key 1024
openssl rsa -in shnlap93.key -out client.key
openssl req -new -key client.key -out client.csr
openssl ca -in client.csr -out client.crt -cert ca.crt -keyfile ca.key
4. 把shnlap93.crt装在服务器上
客户端的IE导入client.crt(此处必须把crt再转成p12格式)导入:
openssl pkcs12 -export -inkey client.key -in client.crt -out client.p12
1. 大家看到第4步中的那个key了吧,这个key就是客户端的私钥
大家看到第4步中的那个crt文件了吧?那个文件里存着客户端的公钥(不是那个.key文件啊)
2. 写一个servlet,客户端访问这个servlet时,该servlet自动从客户端的IE获取client.p12,然后把里面的公钥抽出来(由于是公钥,公开的,所以这个不存在安全不安全的因素)
3. 服务器拿着该客户的私钥(此处我们先用这种方法来做),下面会讲更高级的U盾存客户端私钥的做法)
一、然后套用(用JAVA实现签名过程)中的算法,就可以实现使用证书来进行客户端和服务器的认证啦
需要解决的问题:
1. Servlet如何读客户端的认证
很多网上的朋友都说
“我用X509Certificate[]certs = (X509Certificate[]) request .getAttribute("javax.servlet.request.X509Certificate");
得到的证书是个null”
几乎没有答案,这边给出解决方案
a. 客户端访问这个servlet,客户端和放这个servlet的j2eeapp必须实现“双向认证”
b. J2ee app端(假设我们这边用TOMCAT实现),在实现双向认证后,其实还不够,需要加一个参数,很多人可能没注意到这个参数,下面给出方案:
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
enableLookups="false"disableUploadTimeout="true"
useURIValidationHack="false"
scheme="https"secure="true"
keystoreFile="D:/tomcat/conf/shnlap93.jks"keystorePass="xxxxxxx"
truststoreFile="D:/tomcat/conf/truststore.jks"truststorePass="aaaaaa"
truststoreType="JKS"
clientAuth="true"sslProtocol="TLS" />
看到上面那个标红的地方了吧?就是这个参数没加,因此很多人就算启用了双向认证,你的servlet在拿ie端的证书时还是会得到null值
2. 好,现在客户端的公钥拿到了,怎么拿私钥?
前面说了,我们先做一个简单的,写死的,就是把客户端的私钥放在我们的网站的某个目录下,然后用程序去读出来。
因此我们的过程如下:
a. 客户端通过IE输入他的交易密码
b. 然后点“提交”按钮,POST到我们的这个servlet
c. Servlet先读放在网站某个目录下的该客户的私钥,loadPrivateKey后用私钥对客户提交的form里的密码进行签名。
d. Servlet获得客户端IE里的证书,把公钥拿出来,然后用公钥对签完名的byte[]进行verify, 得到true代表认签成功,false认签失败,下面是我们的servlet
此处需要注意的是我们用openssl签出的private key是不能直接被java所访问的,因为它含用:
#begin certificate
…
#end certificate
这样的东西,而JAVA只认#begin…#end当中的那块东西,怎么办:
使用下面这条使用把openssl签出的key转成我们java可以认的rsa的KEY
opensslpkcs8 -topk8 -inform PEM -outform DER -in shnlap93.key -out pkcs8_der.key –nocrypt
下面是我们的servlet的核心片段, 拿客户端IE的公钥,拿网站某个目录摆放的私钥,然后调用标准的JAVA电子签名
private PublicKey getPubKeyFromIE(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
System.out.println("...security receive done..." + request.getScheme());