電子商務系統平臺開發中涉及的安全問題研究

我們說的信息安全是相對於系統安全而言的,它更側重於加密,解密,數字簽名,驗證,證書等等.而系統安全主要側重於系統本身是否有安全漏洞,如常見的由於軟件設計的不完善而導致的滿天飛的緩衝區溢出等等.<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Java語言中負責加密功能的部件是JCE(Java Crypto Extenstion),它使用開放式的思想,可以允許用戶自己編寫加密算法的具體實現的模塊等.這些東西被稱爲JCE Provider,JCE的提供者.SUN公司本身提供了一些Provider.不過我推薦使用Bouncy Castle的Provider.原因是它實現的加密算法多,連比較新的橢圓曲線(ECC)算法都有了.去http://www.bouncycastle.org/可以找到你所希望的.Bouncy Castle除了提供Provider本身以外,還包括了一個S/MIME和一個open pgp 的jar包只有Provider本身是必要的,後兩個包是方便你編程而提供的.例如有了S/MIME包後,你就不再需要爲諸如"加密一個字符串或者一片文章然後簽名"之類的很現實的應用程序寫上一大堆代碼了,只要引用S/MIME包中的某幾個類,很快就可以搞定.而open pgp的存在,使你在用Java編寫和PGP/GPG交互的程序時方便多了.

Java寫信息安全方面的程序需要做的準備工作已經說清楚了.現在假設你已經是一個對Java語言本身比較熟悉,能夠用Java寫一些程序的人,並且這些該下載的jar包已經下載好了,可以正式開工了.

在所有的此類程序的開頭(無論是在類的構造函數中也好,在初始化函數中也好),都要先來上這麼一句:Security.addProvider(new BouncyCastleProvider());將BouncyCaslte的Provider添加到系統中,這樣以後系統在運行相關程序的時候調用的就是這個Provider中的加密算法.

然後我們就可以開始開CA了.首先,作爲一個CA要有自己的一對公鑰和私鑰,我們先要生成這麼一對.使用KeyPairGenerator對象就可以了,調用KeyPairGenerator.getInstance方法可以根據要生成的密鑰類型來產生一個合適的實例,例如常用的RSA,DSA等.然後調用該對象的initialize方法和generateKeyPair方法就可以產生一個KeyPair對象了.然後調用KeyPair對象中的相應方法就可以獲取生成的密鑰對中的公鑰和私鑰了.

有了公鑰和私鑰對以後,下面的一個很現實問題就是如何把它們儲存起來.通常我們要對這些安全對象,如公鑰,私鑰,證書等先進行編碼.編碼的目的是爲了把結構複雜的安全對象變成字節流以便存儲和讀取,如DER編碼.另外,通常還把DER編碼後的字節流再次進行base64編碼,以便使字節流中所有的字節都變成可打印的字節.

Java語言中,這些安全對象基本上都有getEncoded()方法.例如:

byte[] keyBytes = privateKey.getEncoded();

這樣就把一個私鑰進行了DER編碼後的結果保存到一個byte數組中了.然後就可以把這個byte數組保存到任何介質中.如果有必要的話,可以使用BC Provider中的Base64編碼解碼器類進行編碼,就像這樣:

byte data[] = Base64.encode(keyBytes);

要從文件中讀取私鑰則應該這樣:

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyData);
KeyFactory kfac = KeyFactory.getInstance("RSA");
privateKey = kfac.generatePrivate(spec);

這裏說明一下,對RSA私鑰進行編碼就會自動地按照PKCS8進行.因此讀取的時候將包含編碼的字節數組作爲PKCS8EncodedKeySpec對象的構造函數的參數就可以生成一個該類型的對象.然後創建一個密鑰工廠對象就可以按照要求生成一個RSA私鑰了.很顯然這裏的keyData應該是和上面的keyBytes內容相同.

爲了提高系統的安全性,通常私鑰在經過DER編碼後,還會使用一個口令進行加密,然後再儲存在文件系統中.在使用私鑰的時候,如果沒有正確的口令,是無法把私鑰還原出來的.

保存證書和保存私鑰類似.Certificate對象中也有一個getEncoded的方法.

這次就講這些.大家應該可以把這些安全對象很熟練地從文件系統和內存之間來回地折騰了吧.這對以後實現CA是很重要的.下面我會講一下證書中表示主體的方法:DN.

上面我們講到如何生成密鑰對,以及如何將諸如公鑰,私鑰,證書等這一類安全對象在文件系統和內存之間來回轉換.這些是準備開CA的基本功,現在我們講一下CA的基本原理以及如何使用主體名稱結構DN(Distinguish Name)來表示每一個證書中的主體.

一份證書就是一個權威機構對一個主體的身份的確認的證明.即一份證書表示一個權威機構確認了一個主體就是它自己,而不是其它的冒名頂替者.主體可以是一個個人,也可以不是,例如,在需要安全服務的時候,需要爲一臺網站的服務器頒發證書,這種證書的主體就是一臺服務器.簽署證書的權威機構就叫做CA,該權威機構本身也是一個主體.權威機構通過對包含有待認證的主體的一系列信息的待簽名證書"(TBS,to be signed)進行數字簽名來表示該機構對它的認可.一份包含了待認證的主體的相關信息的TBS再加上CA使用自己的私鑰進行簽名產生的字節流放在一起,就構成了一份標準的X509證書.

一個TBS中包含了以下這些主要信息:

證書的版本,通常是3(X509v3)

證書的序列號,RFC3280中規定,每個CA必須確保它頒發的每一份證書的序列號都是唯一的,並且序列號只能使用非負整數.

簽發者(CA)的主體名稱,一個DN對象.

證書的有效期,表示方法是兩個時間值,表示了該證書從何時開始生效,何時開始作廢.

待認證的主體的主體名稱,也是一個DN對象.

待認證的主體的公鑰,任何安全應用在確認完證書的有效性後,就可以開始使用該主體的公鑰與之進行安全通信.

如果是X509v3證書,即版本號是3的話,後面還有一個證書擴展信息字段,可以在證書裏面添加一些其它的信息.

下面我們來看一下表示主體的主體名稱結構:DN.這個結就構是一個屬性的集合.每個屬性有屬性名稱和屬性值.它的作用就是用來表示"我是誰",也就是說,這個證書到底是誰頒發給誰的,這個證書對應的公鑰是誰擁有的.

通常使用一個字符串來表示DN結構,這種字符串說明了這種結構中的各個屬性的類型和值:

C=CN;S=<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />BeiJing;L=BeiJing;O=PKU;OU=ICST;CN=wolfenstein

這裏C是國家和地區代碼,S和L都是地區代碼,S相當於省或者州這樣的級別,L相當於城市級別,O是組織機構名稱,OU是次級組織機構名稱,CN是主體的通用名(common name).在這裏,C,S,L等等屬性的類型都是相對固定的,例如C一般就是用來表示國家和地區代碼,在DN結構中還可以添加一些其它類型的信息,一般也都是以"xxx=xxx"這樣來表示的.

下面我們來說明如何在Java語言中構造出一個主體名稱對象.

BC Provider中使用X509Name對象來表示DN,構造一個X509Name的步驟大體如下:

先構造兩個vector對象,用來表示屬性名稱和屬性值:

Vector oids = new Vector();
 Vector attributes = new Vector();

然後在oids這個用來表示屬性名稱的vector對象中將屬性名稱一個一個添加進去:

oids.addElement(X509Name.C);

......

oids.addElement(X509Name.CN);

X509Name對象裏面有若干常量,例如上面的X509Name.C.還有X509Name.ST等等,都可以和上面舉的例子對應起來.

    然後將屬性值添加到attributes對象中:

    attributes.addElement("CN");

    ......

    attributes.addElement("Wolfenstein");

    最後就可以構造出一個X509Name對象:

    X509Name SubjectDN = new X509Name(oids, attributes);

    這樣,我們的主體名稱結構就確立起來了.

    下面我們就可以講關鍵的部分了,那就是如何用Java程序完成CA最重要的功能,簽署證書.

    要做CA,第一步要準備好自己的證書和私鑰.私鑰如何從文件裏面讀取出來前面已經講過了.從文件系統中讀出證書的代碼如下:

    CertificateFactory certCF = CertificateFactory.getInstance("X.509");
    X509Certificate caCert = certCF.generateCertificate(certBIS);

    這裏cerBIS是一個InputStream類型的對象.例如一個標準的X509v3格式的證書文件所形成的輸入流.

    第二步就是從用戶那裏獲取輸入,然後構造主體名稱結構DN,如何構造DN上次已經說過了,如何從用戶那裏獲取輸入,這個不在本文討論範圍之內.

    下一步就是獲取用戶的公鑰,好和他所需要的證書對應起來.也有不少CA的做法就是在這裏替用戶現場生成一對密鑰對,然後把公鑰放到證書中籤發給用戶.這個應該看實際需要選擇合適的方式.

    現在一切信息都已經準備好了,可以簽發證書了,下面的代碼說明了這個過程:

    //構造一個證書生成器對象

    X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

    // 從CA的證書中獲取簽發者的主體名稱(DN)
    // 這裏有一點小技巧,我們要把JCE中定義的
    // 用來表示DN的對象
X500Principal轉化成在
    // BC Provider中的相應的對象
X509Name
    // 先從CA的證書中讀取出CA的DN進行DER編碼
    DERInputStream dnStream =
                 new DERInputStream(
      new ByteArrayInputStream(
       caCert.getSubjectX500Principal().
        getEncoded()));
    // 馬上又從編碼後的字節流中讀取DER編碼對象
    DERConstructedSequence  dnSequence =
     (DERConstructedSequence)dnStream.readObject();
    // 利用讀取出來的DER編碼對象創建X509Name
    // 對象,並設置爲證書生成器中的"簽發者DN"
    certGen.setIssuerDN(new X509Name(dnSequence));
    // 設置好證書生成器中的"接收方DN"
    certGen.setSubjectDN(subjectDN);
    // 設置好一些擴展字段,包括簽發者和
    // 接收者的公鑰標識
    certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
    createSubjectKeyId(keyToCertify));
    certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
    createAuthorityKeyId(caCert.getPublicKey()));
    // 設置證書的有效期和序列號
    certGen.setNotBefore(startDate);
    certGen.setNotAfter(endDate);
    certGen.setSerialNumber(serialNumber);
    // 設置簽名算法,本例中使用MD5hash後RSA
    // 簽名,並且設置好主體的公鑰
    certGen.setSignatureAlgorithm("MD5withRSA");
    certGen.setPublicKey(keyToCertify);

    // 如果以上一切都正常地話,就可以生成證書了
    X509Certificate cert = null;
    cert = certGen.generateX509Certificate(caPrivateKey);

    這裏是上面用到的生成簽發者公鑰標識的函數: 

    protected AuthorityKeyIdentifier createAuthorityKeyId(PublicKey pubKey)
    {
    AuthorityKeyIdentifier authKeyId = null;

    try
    {
    ByteArrayInputStream bIn = new ByteArrayInputStream(pubKey.getEncoded());
    SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
        (DERConstructedSequence)new DERInputStream(bIn).readObject());
    authKeyId = new AuthorityKeyIdentifier(info);
    }
    catch (IOException e)
    {
    System.err.println("Error generating SubjectKeyIdentifier:  " +
        e.toString());
    System.exit(1);
    }

    return authKeyId;
    }

    生成主體公鑰標識的函數和上面的類似,把AuthorityKeyIdentifier替換成SubjectKeyIdentifier就可以了.

    這裏要注意的是,CA的公鑰也是在一份證書裏,這種證書的特點是簽發者DN和接收者DN一樣,也就是說,這種證書是CA自己給自己頒發的證書,也就是"自簽名證書",它上面的公鑰是CA自身的公鑰,用來簽名的私鑰就是該公鑰對應的私鑰.一般每個CA都要有這麼一份證書,除非該CA不是根CA,即它的權威性不是由它自己證明,而是由它的上級CA證明.但是,最後總歸要有一個根CA,它爲各個安全應用程序的用戶所信賴.

    到這裏我們已經把CA最基本的功能如何用Java實現講完了,下面講如何從PKCS#10格式證書請求文件中讀取出用戶信息,然後直接簽發公鑰.

前面已經把如何用Java實現一個CA基本上講完了.但是他們都有一個特點,就是用戶的信息都是在現場獲取的,不能做申請和簽發相分離.現在我們要講述的是PKCS#10證書請求文件.它的作用就是可以使申請和簽發相分離.

    PKCS#10證書請求結構中的主要信息包含了被簽發者(證書申請者)的主體名稱(DN)和他的公鑰.因此一個CA在獲取到一個PKCS#10證書請求後,就可以從中獲取到任何和簽發證書有關的信息,然後用它自己的私鑰簽發證書.

    使用BC Provider在Java中構造一個證書請求格式的對象調用其構造函數即可,這個函數如下:

    PKCS10CertificationRequest(java.lang.String signatureAlgorithm, X509Name subject, java.security.PublicKey key, ASN1Set attributes, java.security.PrivateKey signingKey)

    它的參數是自簽名算法,證書申請者的DN,證書申請者的公鑰,額外的屬性集(就是要申請的證書的擴展信息),申請證書者的私鑰.申請證書者的私鑰僅僅是用來進行一下自簽名,並不出現在證書請求中,需要自簽名的目的是保證該公鑰確實爲申請者所有.

    調用該對象的getEncoded()方法可以將其進行DER編碼,然後儲存起來,該對象還有另一個構造函數:
    PKCS10CertificationRequest(byte[] bytes)
    這個構造函數的作用就是直接從儲存的DER編碼中把這個對象還原出來.

    利用證書請求結構進行證書籤發的代碼如下,這裏假設CSR是一個已經獲取出來的PKCS10CertificationRequest結構:

    PublicKey SubjectPublicKey = CSR.getPublicKey();
    CertificationRequestInfo CSRInfo = CSR.getCertificationRequestInfo();
    X509Name SubjectDN = CSRInfo.getSubject();
    ASN1Set Attributes = CSRInfo.getAttributes();

    這樣,申請者的主體DN,申請者的公鑰,申請者希望在證書擴展信息中填寫的屬性都得到了,剩下的事情就和用戶在現場輸入時一樣了,其它的信息一般是申請者不能決定的.另外證書請求格式中有一樣信息沒有明確給出來,那就是證書的有效期,這個應該單獨詢問用戶,或者用其它的方法保存起來.

ezerg 編程小語

發佈了108 篇原創文章 · 獲贊 1 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章