一、 密碼學(xué)密鑰和證書
現(xiàn)在,我們已經(jīng)準(zhǔn)備好我們的XML簽名示例應(yīng)用程序。
讓我們首先分析下列XML文檔-./etc/invoice.xml:
<?XML version="1.0" encoding="UTF-8" standalone="no"?> <invoice XMLns="http://www.company.com/accounting"> <items> <item> <desc>Applied Cryptography</desc> <type>book</type> <unitprice>44.50</unitprice> <quantity>1</quantity> </item> </items> <creditcard> <number>123456789</number> <expiry>10/20/2009</expiry> <lastname>John</lastname> <firstname>Smith</firstname> </creditcard> </invoice> |
我們計劃使用一個XML簽名對它進(jìn)行簽名并且希望使用一個基于一個公共密鑰的簽名方法。
讓我們先生成密碼學(xué)密鑰。為此,我們可以使用JDK中提供的keytool工具-把該程序移動到./etc文件夾下,并且執(zhí)行下列命令:
keytool -genkey -keysize 512 -sigalg DSA -dname "cn=Young Yang, ou=Architecture, o=Company, L=New York, ST=NY, c=US" -alias biz -keypass kp1234 -keystore bizkeystore -storepass sp1234 -validity 180
這個命令能夠創(chuàng)建密鑰并預(yù)以存儲-名字為bizkeystore,存儲在工作目錄./etc下,并且指定它的口令為sp1234。它還生成一個針對實(shí)體(它包含有一個卓著的名字-Young Yang)的公有/私有密鑰對。【注意】,這里使用DSA密鑰生成算法來創(chuàng)建公有/私有密鑰-都為512位長。
上面的命令進(jìn)一步創(chuàng)建了一個自簽名的證書,這是使用SHA1的DSA算法(JSR-105注釋中的DSA_SHA1,其中包括了公共密鑰和前面那個卓著名字信息)實(shí)現(xiàn)的。這個證書將保持180天的有效期并且關(guān)聯(lián)與一個密鑰存儲文件(此處引用的別名為"biz")中的私有密鑰。該私有密鑰被賦予口令kp1234。我們的示例中包括一個簡單的Java類-KeyStoreInfo,用于把存儲于前面的密鑰存儲文件中的密鑰和證書信息輸出到System.out;這個類也用于應(yīng)用程序從中取得密鑰對-這里的私有和公共密鑰匹配作為輸入?yún)?shù)指定的條件。為了試驗(yàn)它能夠輸出包含在前面存儲文件bizkeystore中的信息,讀者可以運(yùn)行Ant目標(biāo)ksInfo。
下列代碼片斷顯示KeyStoreInfo中的用來檢索一個KeyPair的方法:
public static KeyPair getKeyPair(String store,String sPass,String kPass,String alias) throws CertificateException, IOException, UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException{ KeyStore ks = loadKeyStore(store,sPass); KeyPair keyPair = null; Key key = null; PublicKey publicKey = null; PrivateKey privateKey = null; if (ks.containsAlias(alias)){ key = ks.getKey(alias,kPass.toCharArray()); if (key instanceof PrivateKey){ Certificate cert = ks.getCertificate(alias); publicKey = cert.getPublicKey(); privateKey = (PrivateKey)key; return new KeyPair(publicKey,privateKey); }else{ return null; } } else { return null; } } |
借助于一個KeyPair,我們可以容易地得到PrivateKey和PublicKey-通過調(diào)用相應(yīng)的操作getPrivate()和getPublic()實(shí)現(xiàn)。
為了從KeyStore中得到一個PublicKey,我們并不真正需要在上面的方法中所要求的密鑰口令,而這正是下列方法所實(shí)現(xiàn)的:
public static PublicKey getPublicKey(String store, String sPass, String alias) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException{ KeyStore ks = loadKeyStore(store, sPass); Certificate cert = ks.getCertificate(alias); return cert.getPublicKey(); }在上面兩部分代碼片斷中,方法KeyStore loadKeyStore(String store,String sPass)是一個工具函數(shù),用于實(shí)例化一個KeyStore對象,并且從文件系統(tǒng)加載入口。我們以如下方式實(shí)現(xiàn)它:
伴隨JDK提供的keytool還可以把存儲在一個密鑰儲存文件內(nèi)的證書輸出到系統(tǒng)文件中。例如,為了創(chuàng)建一個包含X509證書(關(guān)聯(lián)于別名為biz的密鑰入口)的biz.cer文件,我們可以從文件夾./etcdirectory下運(yùn)行下列命令: keytool -export -alias biz -file biz.cer -keystore bizkeystore -storepass sp1234 這個證書實(shí)現(xiàn)認(rèn)證我們討論上面的公共密鑰。 我們還在示例中包括了一個Java類-CertificateInfo,用于把一個證書中的一些有趣的信息輸出到System.out。為了試驗(yàn)這一點(diǎn),讀者可以運(yùn)行Ant目標(biāo)certInfo。然而,要理解該代碼及其輸出,必須具有DSA和RSA算法的基本知識。當(dāng)然,讀者可以安全地繞過這個程序而繼續(xù)閱讀本文后面的內(nèi)容。 二、 生成一個Enveloping簽名 這一節(jié)討論借助于JSR-105 API及其缺省實(shí)現(xiàn)來實(shí)現(xiàn)對invoice.xml文件的簽名。 我們的示例中創(chuàng)建了一個enveloping簽名。注意,當(dāng)你想使用在一種detached或enveloped簽名情形下時,也僅需對本例作一些細(xì)微修改。 下面,讓我們分析程序Sign.java,它能夠生成invoice.xml文件的XML簽名。
|
<element name="Object" type="ds:ObjectType"/> <complexType name="ObjectType" mixed="true"> <sequence minOccurs="0" maxOccurs="unbounded"> <any namespace="##any" processContents="lax"/> </sequence> <attribute name="Id" type="ID" use="optional"/> <attribute name="MimeType" type="string" use="optional"/> <attribute name="Encoding" type="anyURI" use="optional"/> </complexType> |
XMLSignatureFactory提供下列方法來創(chuàng)建一個XMLObject實(shí)例:
public abstract XMLObject newXMLObject(List content, String id,String mimeType,String encoding)
我們使用一個DOMStructure對象來包裝invoice.xml的根結(jié)點(diǎn)。在JSR-105中定義的DOMStructure可以幫助從原始待簽名的XML文檔中把結(jié)點(diǎn)導(dǎo)入到JSR-105運(yùn)行時刻。
我們指定#invoice作為結(jié)果對象元素的id。JSR-105實(shí)現(xiàn)知道在步驟2中創(chuàng)建的引用對象參考invoice.xml文檔,因?yàn)檫@個id把它們鏈接在一起(在Reference一邊,URI屬性指向這個id)。
【步驟4】創(chuàng)建SignedInfo對象。在W3C建議中,SignedInfo元素具有下列模式定義:
<element name="SignedInfo" type="ds:SignedInfoType"/> <complexType name="SignedInfoType"> <sequence> <element ref="ds:CanonicalizationMethod"/> <element ref="ds:SignatureMethod"/> <element ref="ds:Reference" maxOccurs="unbounded"/> </sequence> <attribute name="Id" type="ID" use="optional"/> </complexType> |
為了創(chuàng)建一個SignedInfo對象,我們需要在〖步驟2〗中創(chuàng)建的Reference;我們還需要兩個實(shí)例-一個是CanonicalizationMethod的實(shí)例,另一個是SignatureMethod的實(shí)例。我們建議感興趣的讀者參考一下規(guī)范說明書從而對這四種XML規(guī)范算法有一個更為精確的了解;在此,我們只是簡單地指出,在我們決定選擇一個特定的算法-alg后,后面對XMLSignatureFactory的一個實(shí)例(即fac)的調(diào)用將會創(chuàng)建CanonicalizationMethod的實(shí)例:
fac.newCanonicalizationMethod(alg,null) |
我們可以創(chuàng)建一個SignatureMethod實(shí)例-通過調(diào)用下列在XMLSigantureFactory中定義的操作:
public?abstract?SignatureMethod?newSignatureMethod(String?algorithm,
SignatureMethodParameterSpec?params)?throws?NoSuchAlgorithmException,
InvalidAlgorithmParameterException
為了創(chuàng)建一個SignedInfo實(shí)例,XMLSignatureFactory定義了如下兩個工廠方法:
public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm, SignatureMethod sm, List references); public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm, SignatureMethod sm, List references, String id). |
在第二個工廠方法中的第二個參數(shù)-id響應(yīng)于XML簽名文檔中的SignedInfo元素的Id屬性。
【步驟5】-獲得簽名私有密鑰。
在我們的示例中,我們展示了三種不同的方法來得到該私有密鑰。在第一種方法中,我們調(diào)用我們的KeyStoreInfo類的getPrivateKey()方法來檢索我們使用keytool創(chuàng)建的DSA類型私有密鑰,并且把它儲存在密鑰存儲文件-bizkeystore(前面的5.0情形)中。為了獲得該私有密鑰,我們還可以從bizkeystore中檢索該KeyPair-通過調(diào)用KeyStoreInfo的getKeyPair()方法,然后調(diào)用KeyPair實(shí)例(5.1.2情形)的getPrivate()。另一方面,JCA提供了一個名字為KeyPairGenerator的類用于根據(jù)需要隨時動態(tài)地創(chuàng)建一個KeyPair,這正是Sign.java中的情形5.1.1提到的情況。
讀者還應(yīng)該注意,JSR-105允許通過一個KeySelector對象獲得私有密鑰。我們在下節(jié)討論KeySelector時還要詳細(xì)分析。
【步驟6】創(chuàng)建一個KeyInfo對象。這一步是可選的,就象KeyInfo作為簽名元素中的一個元素是可選的一樣。在我們的示例的情形6.0下,我們使KeyInfo成為null;這樣以來,可以完全從結(jié)果XML簽名中忽略它。
W3C建議和JSR-105定義RSA的KeyValues以及DSA類型for wrapping,respectively,RSA和DSA公共密鑰,并允許它們成為KeyInfo的內(nèi)容。我們的示例中的情形6.1從我們以前使用JDK keytool生成的公共密鑰中創(chuàng)建一個KeyValue對象,并且把它放到一個KeyInfo對象。后面,當(dāng)討論我們的核心校驗(yàn)程序時,我們將看到它如何使用這樣的一個KeyInfo對象來檢索公共密鑰以用于簽名校驗(yàn)。
在JSR-105中,我們通過調(diào)用一個KeyInfoFactory實(shí)例中的操作創(chuàng)建了KeyValue和KeyInfo對象。其中,KeyInfoFactory負(fù)責(zé)創(chuàng)建所有主要的與KeyInfo相關(guān)的對象-例如KeyName,KeyValue,X509Data等。我們可以以與我們在〖步驟1〗得到XMLSignatureFactory實(shí)例相同的方式得到一個KeyInfoFactory實(shí)例。我們的示例調(diào)用XMLSignatureFactory對象的getKeyInfoFactory()方法取得KeyInfoFactory實(shí)例。
我們的示例的情形6.2將創(chuàng)建一個X509Data對象-使用我們以前借助于工具keytool從bizkeystore中導(dǎo)出的證書biz.cer,然后把這個對象作為內(nèi)容放入一個KeyInfo對象中。再次,后面我們將討論的核心校驗(yàn)程序?qū)⒆C明我們?nèi)绾螐倪@樣的一個KeyInfo對象中取得用于簽名校驗(yàn)的公共密鑰。
【步驟7】創(chuàng)建一個XMLSignature對象。在JSR-105中,XMLSignature接口為W3C中建議的簽名元素實(shí)現(xiàn)了建模。我們已經(jīng)在前面看到該簽名元素的結(jié)構(gòu)。為了創(chuàng)建一個XMLSiganture實(shí)例,我們可以在XMLSignatureFactory中調(diào)用下列兩個方法之一:
public abstract XMLSignature newXMLSignature(SignedInfo si, KeyInfo ki); public abstract XMLSignature newXMLSignature(SignedInfo si, KeyInfo ki, List objects, String id, String signatureValueId). |
第二個方法中的id和signatureValueId參數(shù)將成為結(jié)果XML簽名文檔中的XML元素ID。在我們的示例中,該XML簽名將擁有一個Object元素;因此,我們需要使用第二個工廠方法。【步驟8】實(shí)例化一個DOMSignContext對象,并且使用它注冊私有密鑰。XMLSignContext接口(DOMSignContext實(shí)現(xiàn)它)包含用于生成XML的上下文信息簽名。
DOMSignContext提供了幾種形式的構(gòu)造器-簽名應(yīng)用程序用來注冊要使用的私有密鑰,并且這也是我們的示例中所采用的方法。
在繼續(xù)討論簽名過程的最后步驟之前,我們需要指出XMLSignContext和DOMSignContext實(shí)例都可能包含特定于它們所使用的XML簽名結(jié)構(gòu)的信息和狀態(tài)。該JSR-105規(guī)范中聲明:如果一個XMLSignContext(或DOMSignContext)與不同的簽名結(jié)構(gòu)一起使用,那么,結(jié)果將是無法預(yù)料的。例如,我們不應(yīng)該使用相同的XMLSignContext(或DOMSignContext)實(shí)例來簽名兩個不同的XMLSignature對象。
【步驟9】簽名。XMLSignature接口中的sign()操作實(shí)現(xiàn)簽名XMLSignature。其實(shí),該方法還實(shí)現(xiàn)若干操作,包括基于相應(yīng)的digest方法計算所有引用的digest值,并且基于該簽名方法和私有密鑰計算簽名值。該簽名值被XMLSignature實(shí)例中的嵌入式SignatureValue類所捕獲,而對XMLSignature實(shí)例的getSignatureValue()方法的調(diào)用將返回使用結(jié)果值填充的SignatureValue對象。
在我們的簽名程序的最后,我們把XMLSignature編排成一個XML文檔-signature.xml。
三、 XML簽名核心校驗(yàn)
在前面一節(jié)中,我們把invoice.xml文檔簽名成一個在signature.xml文件中捕獲的enveloping XML簽名。
為了校驗(yàn)該簽名,我們可以使用下列程序-Validate.java:
public class Validate { public static void main(String[] args) throws Exception { //第一步 String providerName = System.getProperty("jsr105Provider","org.jcp.XML.dsig.internal.dom.XMLDSigRI"); XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",(Provider) Class.forName(providerName).newInstance()); //第二步 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(args[0])); //第三步 NodeList nl = doc.getElementsByTagNameNS(XMLSignature.xmlNS,"Signature"); if (nl.getLength() == 0) { throw new Exception("Cannot find Signature element!"); } //第四步,分為情形4.0,4.1,4.2或4.3 //第4.0種情形 DOMValidateContext valContext = new DOMValidateContext(new KeyStoreKeySelector(), nl.item(0)); //第4.1種情形,需要Sign.java中的第6.1種情形 // DOMValidateContext valContext = new DOMValidateContext( // new KeyValueKeySelector(), nl.item(0)); //第4.2種情形,需要Sign.java中的第6.2種情形 // KeyStore ks = KeyStore.getInstance("JKS"); // FileInputStream fis = new FileInputStream("./etc/bizkeystore"); // ks.load(fis,"sp1234".toCharArray()); // fis.close(); // X509KeySelector x509ks = new X509KeySelector(ks); // DOMValidateContext valContext = new DOMValidateContext(x509ks, nl.item(0)); //第4.3中情形 // PublicKey pKey = KeyStoreInfo.getPublicKey("./etc/bizkeystore", // "sp1234", "biz"); //第五步 XMLSignature signature = fac.unmarshalXMLSignature(valContext); //XMLSignature signature = fac.unmarshalXMLSignature(new DOMStructure(nl.item(0))); //第六步 boolean coreValidity = signature.validate(valContext); //檢查核心校驗(yàn)狀態(tài) if (coreValidity == false) { System.err.println("Signature failed core validation!"); boolean sv = signature.getSignatureValue().validate(valContext); System.out.println("Signature validation status: " + sv); //每一個Reference的檢查校驗(yàn)狀態(tài) Iterator i = signature.getSignedInfo().getReferences().iterator(); for (int j = 0; i.hasNext(); j++) { boolean refValid = ((Reference) i.next()).validate(valContext); System.out.println("Reference (" + j + ") validation status: "+ refValid); } } else { System.out.println("Signature passed core validation!"); } } } |
要試驗(yàn)這個程序,讀者可以運(yùn)行Ant目標(biāo)校驗(yàn)。該程序把核心校驗(yàn)狀態(tài)打印到System.out。如果簽名是有效的,將輸出"Signature passed core validation!";否則,輸出結(jié)果中將展示引用和簽名的校驗(yàn)狀態(tài);而這樣以來,我們就可以準(zhǔn)確地搞清楚是它們其中的哪一些導(dǎo)致了此次失敗。
校驗(yàn)signature.xml的過程可以分解成六個步驟。
步驟1-加載一個XMLSignatureFactory實(shí)例,這一步與在簽名程序中是一樣的。
步驟2-加載要校驗(yàn)的XML簽名。在這一步中,我們需要把包含XML簽名的XML加載到內(nèi)存中并且把該XML文檔轉(zhuǎn)換成一棵DOM樹。
步驟3-識別DOM樹中的簽名結(jié)點(diǎn)。簽名是在命名空間http://www.w3.org/2000/09/XMLdsig#中定義的,它被描述為在JSR-105中的XMLSignature接口的靜態(tài)變量XMLNS。
步驟4-創(chuàng)建一個DOMValidateContext實(shí)例。
一個校驗(yàn)上下文中的一項(xiàng)最關(guān)鍵的信息顯然是密鑰。我們可以使用DOMValidateContext并通過兩種不同的方法來注冊公共密鑰。在第一種方法中,如果校驗(yàn)應(yīng)用程序已經(jīng)擁有公共密鑰,它可以把該密鑰直接通過下列DOMValidateContext的構(gòu)造器放入上下文中:
public?DOMValidateContext(Key?validatingKey,Node?node) |
這正是在我們的示例中的情形4.3。
第二個方法將使用DOMValidateContext注冊一個KeySelector,并且讓該KeySelector選擇公共密鑰-基于在要校驗(yàn)的XMLSignature對象中可用的信息。在JSR-105中,KeySelector是一個定義了兩個操作的抽象類:
public abstract KeySelectorResult select(KeyInfo keyInfo, Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException public static KeySelector singletonKeySelector(Key key) |
第二個操作創(chuàng)建一個總是返回相同密鑰的KeySelector。第一個操作試圖選擇一個密鑰-它能夠滿足作為輸出傳遞的要求。
KeySelectorResult是JSR-105中的一個接口-該規(guī)范中要求這個接口包含一個使用KeySelector選擇的Key值。在我們的示例中,我們使用SimpleKeySelectorResult類實(shí)現(xiàn)這個接口-簡單地包裝選擇的公共密鑰。
在我們的示例中,我們實(shí)現(xiàn)并利用三個不同的KeySelectors來說明一個校驗(yàn)應(yīng)用程序工作的一些情形。
在情形4.0中,KeyStoreKeySelector基于輸入?yún)?shù)從一個Key存儲中檢索公共密鑰。
在情形4.1中,KeyValueKeySelector基于在輸入KeyInfo對象(它應(yīng)該包含一個KeyValue對象作為它的內(nèi)容的一部分;請參考Sign.java中的情形6.1)中的KeyValue信息選擇一個鍵值。
在情形4.2中,X509KeySelector基于包含在KeyInfo對象(它應(yīng)該包含一個X509Data對象作為它的內(nèi)容的一部分;請參考Sign.java中的情形6.2)中的X509Data及其它信息選擇一個鍵。我們使用的是JSR-105中的X509KeySelector-其原作者是Sean Mullan。在此,我們稍微修改了一下其中的私有certSelect()方法以便它可以適合于我們使用keytool生成的證書。
既然簽名中的KeyInfo可能包含各種信息,顯然,一個應(yīng)用程序必須選擇一個KeySelector實(shí)現(xiàn)-由它來使用包含在它將處理的KeyInfos中的信息。
步驟5-把簽名結(jié)點(diǎn)反編排成一個XMLSiganture對象。在上一步驟中,我們把signature.xml文件加載進(jìn)一棵DOM樹-由相應(yīng)于樹中的Signature元素的結(jié)點(diǎn)所標(biāo)識,并且使用一個DOMValidateContext和KeySelector(或私有密鑰)注冊該結(jié)點(diǎn)。為了校驗(yàn)該XML簽名,我們需要把Signature結(jié)點(diǎn)反編排為一個XMLSignature對象。這是通過調(diào)用下列XMLSignatureFactory操作實(shí)現(xiàn)的:
public?abstract?XMLSignature?unmarshalXMLSignature(XMLValidateContext?context) throws?MarshalException |
步驟6-校驗(yàn)XML簽名。這是通過調(diào)用XMLSignature實(shí)例的validate()方法實(shí)現(xiàn)的-以DOMValidateContext作為唯一的輸入?yún)?shù)。該validate()方法根據(jù)在W3C建議中定義的核心校驗(yàn)過程校驗(yàn)XML簽名。如前面所提及,這個過程包括兩個部分。其一是校驗(yàn)所有的參考。在JSR-105中,這可以通過調(diào)用Reference接口的validate()操作來實(shí)現(xiàn)-以相關(guān)的校驗(yàn)上下文作為輸入?yún)?shù)。
該核心校驗(yàn)的第二部分是簽名校驗(yàn)-校驗(yàn)規(guī)范的SignedInfo元素的簽名值。借助于JSR-105,我們可以顯式地完成這一部分-通過調(diào)用與XMLSignature實(shí)例相關(guān)聯(lián)的SignatureValue對象的validate()方法實(shí)現(xiàn),并以相關(guān)的校驗(yàn)上下文作為輸入?yún)?shù)。
在我們的示例中,我們使用這樣的知識來輸出每一個Reference和SigantureValue的校驗(yàn)狀態(tài)-當(dāng)XML簽名(核心)校驗(yàn)失敗時;這樣以來,我們就可以得到導(dǎo)致失敗的更為詳細(xì)的信息。
四、 修改XML簽名
為了表明該校驗(yàn)程序確實(shí)能夠捕獲對生成的XML簽名的修改,我們可以在我們的示例中創(chuàng)建一個Tamper.java程序,允許我們修改清單中的信用卡號(或signature.xml文件中的SignatureValue元素)。
這個程序使用XML簽名文檔和一個布爾值作為參數(shù)。當(dāng)該布爾參數(shù)為true時,程序改變信用卡號;否則,它修改簽名值。
public class Tamper { //決定要修改的標(biāo)志-Reference或SignatureValue if (args.length >= 2) { if (tamperRef){ |
為了運(yùn)行它,讀者可以執(zhí)行經(jīng)修改的Ant目標(biāo)。在運(yùn)行這個修改的程序后,如果我們再次運(yùn)行該校驗(yàn)程序,核心校驗(yàn)將失敗,并且System.out將分別輸出引用和簽名校驗(yàn)的狀態(tài)。隨著第二個Boolean輸入?yún)?shù)值的不同,引用與/或簽名校驗(yàn)可能報告失敗。
至此,我們已經(jīng)討論完本文中的示例應(yīng)用程序。
五、 結(jié)論
XML簽名和JSR-105中都包含了大量的內(nèi)容,在一篇小小的文章中我們是無法全面涉及它們(例如transforms,canonicalization方法,還有Manifest和SignatureProperties元素)的。而且,我們也根本沒有深入分析digest和簽名算法。感興趣的讀者應(yīng)該進(jìn)一步參考W3C建議,JSR-105 API文檔以及相關(guān)密碼學(xué)信息。
W3C建議中僅要求實(shí)現(xiàn)基于SHA-1哈希函數(shù)支持digest和簽名方法。注意,一個由王小云教授率領(lǐng)的中國數(shù)學(xué)研究小組已經(jīng)攻克了一些包括SHA-1在內(nèi)廣泛應(yīng)用的哈希函數(shù)中的安全問題。盡管他們的結(jié)果還不是立即意味著-現(xiàn)在SHA-1在XML簽名方面的使用還不是不安全的;但是,密碼學(xué)專家們確實(shí)推薦在新的應(yīng)用程序和系統(tǒng)中應(yīng)該考慮使用更為安全的算法。
讀者還應(yīng)該明白,盡管JCP計劃隨同JAVA SE 6一起發(fā)行JSR-105,但是JSR-105要求全面兼容實(shí)現(xiàn)對JDK1.4及其高版本的支持。因此,在使用JDK 1.4開發(fā)的應(yīng)用程序中使用這一技術(shù)應(yīng)該是沒有問題的。
凡是有該標(biāo)志的文章,都是該blog博主Caoer(草兒)原創(chuàng),凡是索引、收藏
、轉(zhuǎn)載請注明來處和原文作者。非常感謝。