基於自簽名的X 509數字證書生成及驗證
基於自簽名的X.509數字證書生成及驗證
數字證書用於標誌網路使用者身份,在Web應用中,數字證書的應用十分廣泛,如:安全電子郵件、訪問安全站點、安全電子事務處理和安全電子交易等。
數字證書的格式一般採用X.509國際標準。目前,數字證書認證中心主要簽發安全電子郵件證書、個人和企業身份證書、伺服器證書以及程式碼簽名證書等幾種型別證書。
數字證書由證書機構簽發,證書機構通常需經權威認證機構註冊認證。在企業應用中,也常用企業自身作為發證機構(未經過認證)簽發數字證書,證書的使用範圍也常是企業內部,這樣的證書就是所謂的“自簽名”的。
數字證書採用公鑰密碼體制,每個使用者擁有一把僅為本人所掌握的私有金鑰(私鑰),用它進行解密和簽名
下面介紹我們在專案中常用的自簽名證書的生成及驗證方法。為簡單起見,我們假設所有網站使用者使用同一個數字證書clientCA。
一、伺服器端
登入遠端伺服器,在伺服器端生成證書並提供下載。伺服器上應當安裝jdk1.5以上,因為我們需要使用jdk自帶的keytool工具,keytool工具位於jdk安裝目錄的bin目錄下。
1、生成金鑰資料庫及根證書
如果是第1次使用數字證書,那麼很可能伺服器上還沒有金鑰資料庫。我們可以使用下列命令成金鑰資料庫(keystore)。
keytool -genkey -dname "CN=發證人姓名,OU=發證人所屬部門
執行指令碼後,會在當前使用者主目錄(如:C:/Documents and Settings/Administrator目錄,window系統)下生成金鑰資料庫檔案IPCCCA。注意,需要設定兩個密碼,一個是根證書密碼keypass,一個是庫密碼storepass,如果你不是很確定二者間的區別,最好兩個都設定成一樣。
2、生成自簽名證書
執行下列命令,生成一個自簽名證書:
keytool -genkey -dname "CN=
這個證書是一個自簽名證書,該證書的別名為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,以提供自簽名證書的下載:
privatestatic final long serialVersionUID = 1L;
// 有效期天數
privatestatic final int Max_Days = 1;
//keystore密碼
privatestatic final char[] password = "[email protected]".toCharArray();
//keystore檔案路徑
privatestatic final String keystoreFilename = "C://Documents andSettings//Administrator//IPCCCA";
// 證書檔名
privatestatic final String certFilename="client.cer";
//證書別名
privatestatic final String alias = "clientCA";
privateKeyStore keystore;
private String sigAlgrithm;
//讀取keystore
privateKeyStore loadKeystore(String keystorepath) {
KeyStore ks = null;
try {
FileInputStreamfIn = new FileInputStream(keystorepath);
ks =KeyStore.getInstance("JKS");
ks.load(fIn,password);
fIn.close();
returnks;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return ks;
}
// 獲得CertInfo
privateX509CertInfo getCertInfo(Certificate c, String alias) {
X509CertInfo certInfo = null;
try {
// 從待簽發的證書中提取證書資訊
byte[]encod2 = c.getEncoded();//獲取 證書內容(經過編碼的位元組)
X509CertImplcimp2 = 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;
}
// 修改有效期
privatevoid updateValidity(X509CertInfo cinfo, int days) {
// 獲取當前時間
Date d1 = new Date();
// 有效期為當前日期後延n天
Date d2 = new Date(d1.getTime() + days * 24 *60 * 60 * 1000L);
// 建立有效期物件
CertificateValidity cv = newCertificateValidity(d1, d2);
try {
cinfo.set(X509CertInfo.VALIDITY,cv);//設定有效期
} catch (Exception e) {
e.printStackTrace();
}
}
//儲存證書
privatevoid saveCert(KeyStore ks, char[] storepass, String alias,
PrivateKeypKey, char[] keypass, X509CertInfo cinfo,String algrithm) {
try {
X509CertImplcert = new X509CertImpl(cinfo);// 新建證書
cert.sign(pKey,algrithm); //使用CA私鑰對其簽名
//獲取別名對應條目的證書鏈
Certificate[]chain = new Certificate[] { cert };
// 向金鑰庫中新增條目,使用已存在別名將覆蓋已存在條目
ks.setKeyEntry(alias,pKey, keypass, chain);
// 將keystore儲存至檔案
FileOutputStreamfOut = new FileOutputStream(keystoreFilename);
keystore.store(fOut,password);
fOut.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 匯出證書
privatevoid 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-generatedconstructor stub
}
/**
* @seeHttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protectedvoid doGet(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException {
try{
keystore= loadKeystore(keystoreFilename); //讀取keystore
Certificatec = keystore.getCertificate(alias);// 讀取證書
X509CertInfocinfo = getCertInfo(c, alias);// 獲得證書的CertInfo
updateValidity(cinfo,Max_Days);// 修改證書有效期
// 從金鑰庫中讀取CA的私鑰
PrivateKeypKey = (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();
}
}
/**
* @seeHttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protectedvoid doPost(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException {
doGet(request,response);
}
}
這個servlet的作用是:
當用戶請求該serlvet,從金鑰庫中提取clientCA證書,將證書有效期修改為當前日期到下一天。
也就是說,當用戶客戶端請求該servlet,即可獲得一個新的證書,這個證書的有效期已經向後延了一天。
我們的目的是,使用者每次登入後,檢查使用者下載的證書,如證書已過了有效期,則請求此servlet即可獲得一個有效的新證書。
二、客戶端
客戶端可能是任何裝置,包括pc、移動終端。我們假設客戶端是基於Android1.6以上的移動終端,則以下是java客戶端的證書驗證類MyCertificate:
public class MyCertificate {
privatestatic String tag="MyCertificate";
publicstatic Certificate readCert(File file){
Certificate c=null;
try{
CertificateFactorycf = CertificateFactory.getInstance("X.509");
FileInputStreamin1 = new FileInputStream(file);
c =cf.generateCertificate(in1);
in1.close();
} catch (Exception e) {
Log.e(tag,e.toString());
}
return c;
}
// 驗證證書的有效性
publicstatic boolean verifyCert(Certificate c){
PublicKey pbk=c.getPublicKey();
try{
c.verify(pbk);
returntrue;
}catch(Exception e){
Log.e(tag,"Certificateis invalid");
}
return false;
}
// 驗證證書有效期
publicstatic int verifyCertValidity(Date date,Certificate c){
int i=0;
X509Certificate t=(X509Certificate)c;
try {//有效
t.checkValidity(date);
} catch (CertificateExpiredException e) {// 過期
Log.e(tag,"CertificateExpired");
i=-1;
} catch (CertificateNotYetValidException e) {//尚未生效
Log.e(tag,"CertificateToo early");
i=-2;
}
return i;
}
publicstatic boolean verify(Context ctx){
Activity act=(Activity)ctx;
boolean b=false;
// 檢查證書檔案是否存在
File file=newFile(Environment.getExternalStorageDirectory()+act.getString(R.string.CERT_DIR)+act.getString(R.string.CERT_FILE));
if(!file.exists()){
act.showDialog(1);
}else{
Dated=new Date();//取當前時間
Certificatec=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 = newMainLoginHandler();
modules = SaxHelper.getModules(url, handler);
// Log.i(tag,systems.toString());
Log.i("modules:", "" +modules);
if (modules != null) {
Stringstatus = (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 booleanverifyCert() {
return MyCertificate.verify(this);
}
// 建立activity託管對話方塊
protectedDialog onCreateDialog(int id) {
Log.e("::::","showdialog!");
String msg = "";
switch (id) {
case 1:
msg ="證書未下載!請點選“是”以下載證書。";
break;
case 2:
msg ="證書已過期!請點選“是”重新下載證書。";
break;
case 3:
msg ="證書尚未生效!請等證書生效後再重新登入。";
// 對於未生效的證書,無需重新下載,等證書生效即可
returnnew AlertDialog.Builder(this)
.setMessage(msg)
.setNegativeButton("是",
newDialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();//removeDialog(0);移除對話方塊
}
}).create();
case 4:
returnnew AlertDialog.Builder(this)
.setMessage("位置源未設定!是否現住設定位置源?")
.setPositiveButton("是",
newDialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
// 轉至GPS設定介面
Intentintent = new Intent(
Settings.ACTION_SECURITY_SETTINGS);
startActivityForResult(intent,0);
}
})
.setNegativeButton("不",
newDialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();//removeDialog(0);移除對話方塊
}
}).create();
default:
msg ="無效的證書!請點選“是”重新下載證書。";
}
return newAlertDialog.Builder(this).setMessage(msg)
.setPositiveButton("是", newDialogInterface.OnClickListener() {
publicvoid onClick(DialogInterface dialog, int which) {
// 開始下載證書
downloadCert();
}
})
.setNegativeButton("不", newDialogInterface.OnClickListener() {
publicvoid onClick(DialogInterface dialog, int which) {
dialog.dismiss();// removeDialog(0);移除對話方塊
}
}).create();
}
紅色加粗部分的程式碼呼叫了MyCertificate.verify()。onCreateDialog方法則通過對話方塊方式返回證書校驗的結果。