1. 程式人生 > >基於自簽名的X 509數字證書生成及驗證

基於自簽名的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,以提供自簽名證書的下載:

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方法則通過對話方塊方式返回證書校驗的結果。