`
wangpengfei360
  • 浏览: 1056333 次
文章分类
社区版块
存档分类
最新评论

基于自签名的X.509数字证书生成及验证

 
阅读更多

基于自签名的 X.509 数字证书生成及验证

数字证书用于标志网络用户身份,在 Web 应用中,数字证书的应用十分广泛,如:安全电子邮件、访问安全站点、安全电子事务处理和安全电子交易等。

数字证书的格式一般采用 X.509 国际标准。目前,数字证书认证中心主要签发安全电子邮件证书、个人和企业身份证书、服务器证书以及代码签名证书等几种类型证书。

数字证书由证书机构签发,证书机构通常需经权威认证机构注册认证。在企业应用中,也常用企业自身作为发证机构(未经过认证)签发数字证书,证书的使用范围也常是企业内部,这样的证书就是所谓的“自签名”的。

数字证书采用公钥密码体制,每个用户拥有一把仅为本人所掌握的私有密钥 ( 私钥 ) ,用它进行解密和签名 ; 同时拥有一把公共密钥 ( 公钥 ) 并可以对外公开,用于加密和验证签名。

下面介绍我们在项目中常用的自签名证书的生成及验证方法。为简单起见,我们假设所有网站用户使用同一个数字证书 clientCA

一、 服务器端

登录远程服务器,在服务器端生成证书并提供下载。服务器上应当安装 jdk1.5 以上,因为我们需要使用 jdk 自带的 keytool 工具, keytool 工具位于 jdk 安装目录的 bin 目录下。

1、 生成密钥数据库及根证书

如果是第 1 次使用数字证书,那么很可能服务器上还没有密钥数据库。我们可以使用下列命令成密钥数据库( keystore )。

keytool -genkey -dname "CN= 发证人姓名 ,OU= 发证人所属部门 ,O= 发证人所属公司 ,L= 昆明市 ,ST= 云南省 ,C= 中国 " -alias IPCCCA -keyalg RSA -keysize 1024 -keystore IPCCCA -keypass 根证书密码 -storepass 库密码

运行脚本后,会在当前用户主目录(如: C:/Documents and Settings/Administrator 目录, window 系统)下生成密钥数据库文件 IPCCCA 。注意,需要设置两个密码,一个是根证书密码 keypass ,一个是库密码 storepass ,如果你不是很确定二者间的区别,最好两个都设置成一样。

2、 生成自签名证书

运行下列命令,生成一个自签名证书:

keytool -genkey -dname "CN= 发证人姓名 ,OU= 发证人所属部门 ,O= 发证人所属公司 ,L= 昆明市 ,ST= 云南省 ,C= 中国 " -alias clientCA -keyalg RSA -keysize 1024 -keystore IPCCCA -keypass 123456 -storepass 库密码 -validity 1

这个证书是一个自签名证书,该证书的别名为 clientCA ,存储在前面生成的那个密钥库文件 (IPCCCA) 中。这需要提供访问该库的库密码 (storepass) ,必须跟第 1 步中的一样。 Kepass 是该证书的公钥,验证证书时需要提供该密钥。

并且为了便于测试,我们把证书有效期设置为 1 天。这样每过一天,用户必须重新下载证书。

3、 查看密钥库

你可以用下列命令查看生成的两个密钥:

keytool -list -keystore IPCCCA –storepass 库密码

结果会列出两个密钥,类似如下:

您的 keystore 包含 2 输入

clientca, 2011-3-30, keyEntry,

认证指纹 (MD5) 10:B8:51:54:7B:1C:60:7C:89:E7:B6:8E:71:E5:E1:E7

ipccca, 2011-3-30, keyEntry,

认证指纹 (MD5) C3:E3:7D:7C:9B:AA:05:84:92:AF:93:18:42:D2:1C:07

4、 提供证书下载

我们可以在服务器上放一个 servlet ,以提供自签名证书的下载:

private static final long serialVersionUID = 1L;

// 有效期天数

private static final int Max_Days = 1;

// keystore 密码

private static final char[] password = "ipcc@95598".toCharArray();

// keystore 文件路径

private static final String keystoreFilename = "C://Documents and Settings//Administrator//IPCCCA";

// 证书文件名

private static final String certFilename="client.cer";

// 证书别名

private static final String alias = "clientCA";

private KeyStore keystore;

private String sigAlgrithm;

// 读取 keystore

private KeyStore loadKeystore(String keystorepath) {

KeyStore ks = null;

try {

FileInputStream fIn = new FileInputStream(keystorepath);

ks = KeyStore.getInstance("JKS");

ks.load(fIn, password);

fIn.close();

return ks;

} catch (Exception e) {

System.out.println(e.getMessage());

}

return ks;

}

// 获得 CertInfo

private X509CertInfo getCertInfo(Certificate c, String alias) {

X509CertInfo certInfo = null;

try {

// 从待签发的证书中提取证书信息  

byte[] encod2 = c.getEncoded();// 获取 证书内容(经过编码的字节)

X509CertImpl cimp2 = new X509CertImpl(encod2);// 创建 X509CertImpl

sigAlgrithm=cimp2.getSigAlgName();

// 获取 X509CertInfo 对象

certInfo = (X509CertInfo) cimp2.get(X509CertImpl.NAME

+ "." + X509CertImpl.INFO);

} catch (Exception e) {

System.out.println(e.getMessage());

}

return certInfo;

}

// 修改有效期

private void updateValidity(X509CertInfo cinfo, int days) {

// 获取当前时间

Date d1 = new Date();

// 有效期为当前日期后延 n

Date d2 = new Date(d1.getTime() + days * 24 * 60 * 60 * 1000L);

// 创建有效期对象

CertificateValidity cv = new CertificateValidity(d1, d2);

try {

cinfo.set(X509CertInfo.VALIDITY, cv);// 设置有效期

} catch (Exception e) {

e.printStackTrace();

}

}

// 存储证书

private void saveCert(KeyStore ks, char[] storepass, String alias,

PrivateKey pKey, char[] keypass, X509CertInfo cinfo,String algrithm) {

try {

X509CertImpl cert = new X509CertImpl(cinfo);// 新建证书

cert.sign(pKey, algrithm); // 使用 CA 私钥对其签名

// 获取别名对应条目的证书链

Certificate[] chain = new Certificate[] { cert };

// 向密钥库中添加条目 , 使用已存在别名将覆盖已存在条目

ks.setKeyEntry(alias, pKey, keypass, chain);

// keystore 存储至文件

FileOutputStream fOut = new FileOutputStream(keystoreFilename);

keystore.store(fOut, password);

fOut.close();

} catch (Exception e) {

e.printStackTrace();

}

}

// 导出证书

private void exportCert(KeyStore ks,String alias,HttpServletResponse response){

try{

Certificate cert = keystore.getCertificate(alias);

// 得到证书内容(以编码过的格式)

byte[] buf = cert.getEncoded();

// 写证书文件

response.setContentType("application/x-download");

response.addHeader("Content-Disposition", "attachment;filename="

+ certFilename);

OutputStream out = response.getOutputStream();

out.write(buf);

out.close();

}catch(Exception e){

e.printStackTrace();

}

}

/**

* @see HttpServlet#HttpServlet()

*/

public GetNewCert() {

super();

// TODO Auto-generated constructor stub

}

/**

* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

*/

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

try{

keystore = loadKeystore(keystoreFilename); // 读取 keystore

Certificate c = keystore.getCertificate(alias);// 读取证书

X509CertInfo cinfo = getCertInfo(c, alias);// 获得证书的 CertInfo

updateValidity(cinfo, Max_Days);// 修改证书有效期

// 从密钥库中读取 CA 的私钥

PrivateKey pKey = (PrivateKey) keystore.getKey(alias, "123456"

.toCharArray());

// keystore 存储至 keystore 文件

saveCert(keystore, password, alias, pKey, "123456".toCharArray(),cinfo,sigAlgrithm);

exportCert(keystore,alias,response);

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

*/

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doGet(request,response);

}

}

这个 servlet 的作用是:

当用户请求该 serlvet ,从密钥库中提取 clientCA 证书,将证书有效期修改为当前日期到下一天。

也就是说,当用户客户端请求该 servlet ,即可获得一个新的证书,这个证书的有效期已经向后延了一天。

我们的目的是,用户每次登录后,检查用户下载的证书,如证书已过了有效期,则请求此 servlet 即可获得一个有效的新证书。

二、 客户端

客户端可能是任何设备,包括 pc 、移动终端。我们假设客户端是基于 Android1.6 以上的移动终端,则以下是 java 客户端的证书验证类 MyCertificate

public class MyCertificate {

private static String tag="MyCertificate";

public static Certificate readCert(File file){

Certificate c=null;

try{

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

FileInputStream in1 = new FileInputStream(file);

c = cf.generateCertificate(in1);

in1.close();

} catch (Exception e) {

Log.e(tag, e.toString());

}

return c;

}

// 验证证书的有效性

public static boolean verifyCert(Certificate c){

PublicKey pbk=c.getPublicKey();

try{

c.verify(pbk);

return true;

}catch(Exception e){

Log.e(tag,"Certificate is invalid");

}

return false;

}

// 验证证书有效期

public static int verifyCertValidity(Date date,Certificate c){

int i=0;

X509Certificate t=(X509Certificate)c;

try {// 有效

t.checkValidity(date);

} catch (CertificateExpiredException e) {// 过期

Log.e(tag,"Certificate Expired");

i=-1;

} catch (CertificateNotYetValidException e) {// 尚未生效

Log.e(tag,"Certificate Too early");

i=-2;

}

return i;

}

public static boolean verify(Context ctx){

Activity act=(Activity)ctx;

boolean b=false;

// 检查证书文件是否存在

File file=new File(Environment.getExternalStorageDirectory()+act.getString(R.string.CERT_DIR)+act.getString(R.string.CERT_FILE));

if(!file.exists()){

act.showDialog(1);

}else{

Date d=new Date();// 取当前时间

Certificate c=MyCertificate.readCert(file);// 读取证书文件

// 校验证书有效性

if(!MyCertificate.verifyCert(c)){

act.showDialog(0);// 无效证书

}else{

// 校验证书有效期

int i=MyCertificate.verifyCertValidity(d,c);

switch(i){

case 0:// 有效

b=true;

break;

case -1:// 过期

act.showDialog(2);

break;

case -2:// 未生效

act.showDialog(3);

break;

}

}

}

return b;

}

}

在相关 activity 中可以这样使用它:

private void login(String acc, String pass) {

String url = this.getString(R.string.PORT_LOGIN_URL);

url = String.format(url, acc, pass);

// Log.i(tag,url);

MainLoginHandler handler = new MainLoginHandler();

modules = SaxHelper.getModules(url, handler);

// Log.i(tag,systems.toString());

Log.i("modules:", "" + modules);

if (modules != null) {

String status = (String) modules.get("loginstatus");

if ("true".equals(status)) {// 登录成功

if (!verifyCert()) {

return;

}

Bundle bundle = new Bundle();

bundle.putSerializable("data",

(Serializable) modules.get("modules"));

gotoActivity(main.class, bundle);

} else {

Toast.makeText(getBaseContext(), " 用户名或密码错误! ",

Toast.LENGTH_SHORT).show();

}

}

}

private boolean verifyCert() {

return MyCertificate.verify(this);

}

// 创建 activity 托管对话框

protected Dialog onCreateDialog(int id) {

Log.e("::::", "showdialog!");

String msg = "";

switch (id) {

case 1:

msg = " 证书未下载!请点击“是”以下载证书。 ";

break;

case 2:

msg = " 证书已过期!请点击“是”重新下载证书。 ";

break;

case 3:

msg = " 证书尚未生效!请等证书生效后再重新登录。 ";

// 对于未生效的证书,无需重新下载,等证书生效即可

return new AlertDialog.Builder(this)

.setMessage(msg)

.setNegativeButton(" ",

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog,

int which) {

dialog.dismiss();// removeDialog(0); 移除对话框

}

}).create();

case 4:

return new AlertDialog.Builder(this)

.setMessage(" 位置源未设置!是否现住设置位置源? ")

.setPositiveButton(" ",

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog,

int which) {

// 转至 GPS 设置界面

Intent intent = new Intent(

Settings.ACTION_SECURITY_SETTINGS);

startActivityForResult(intent, 0);

}

})

.setNegativeButton(" ",

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog,

int which) {

dialog.dismiss();// removeDialog(0); 移除对话框

}

}).create();

default:

msg = " 无效的证书!请点击“是”重新下载证书。 ";

}

return new AlertDialog.Builder(this).setMessage(msg)

.setPositiveButton(" ", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

// 开始下载证书

downloadCert();

}

})

.setNegativeButton(" ", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();// removeDialog(0); 移除对话框

}

}).create();

}

红色加粗部分的代码调用了 MyCertificate.verify() onCreateDialog 方法则通过对话框方式返回证书校验的结果。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics