溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JavaSE 6基于JSR105的XML簽名是怎樣實(shí)現(xiàn)的

發(fā)布時(shí)間:2021-12-14 14:57:01 來源:億速云 閱讀:168 作者:柒染 欄目:編程語言

這篇文章將為大家詳細(xì)講解有關(guān)JavaSE 6基于JSR105的XML簽名是怎樣實(shí)現(xiàn)的,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

我們開始分析一個(gè)實(shí)際的XML簽名示例應(yīng)用程序。

  一、 密碼學(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>
 ?。糳esc>Applied Cryptography</desc>
  <type>book</type>
 ?。紆nitprice>44.50</unitprice>
  <quantity>1</quantity>
?。?item>
</items>
<creditcard>
 <number>123456789</number>
?。糴xpiry>10/20/2009</expiry>
 <lastname>John</lastname>
?。糵irstname>Smith</firstname>
</creditcard>
</invoice>


  我們計(jì)劃使用一個(gè)XML簽名對(duì)它進(jìn)行簽名并且希望使用一個(gè)基于一個(gè)公共密鑰的簽名方法。

  讓我們先生成密碼學(xué)密鑰。為此,我們可以使用JDK中提供的keytool工具-把該程序移動(dòng)到./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


  這個(gè)命令能夠創(chuàng)建密鑰并預(yù)以存儲(chǔ)-名字為bizkeystore,存儲(chǔ)在工作目錄./etc下,并且指定它的口令為sp1234。它還生成一個(gè)針對(duì)實(shí)體(它包含有一個(gè)卓著的名字-Young Yang)的公有/私有密鑰對(duì)?!咀⒁狻?,這里使用DSA密鑰生成算法來創(chuàng)建公有/私有密鑰-都為512位長(zhǎng)。
上面的命令進(jìn)一步創(chuàng)建了一個(gè)自簽名的證書,這是使用SHA1的DSA算法(JSR-105注釋中的DSA_SHA1,其中包括了公共密鑰和前面那個(gè)卓著名字信息)實(shí)現(xiàn)的。這個(gè)證書將保持180天的有效期并且關(guān)聯(lián)與一個(gè)密鑰存儲(chǔ)文件(此處引用的別名為"biz")中的私有密鑰。該私有密鑰被賦予口令kp1234。

  我們的示例中包括一個(gè)簡(jiǎn)單的Java類-KeyStoreInfo,用于把存儲(chǔ)于前面的密鑰存儲(chǔ)文件中的密鑰和證書信息輸出到System.out;這個(gè)類也用于應(yīng)用程序從中取得密鑰對(duì)-這里的私有和公共密鑰匹配作為輸入?yún)?shù)指定的條件。為了試驗(yàn)它能夠輸出包含在前面存儲(chǔ)文件bizkeystore中的信息,讀者可以運(yùn)行Ant目標(biāo)ksInfo。

  下列代碼片斷顯示KeyStoreInfo中的用來檢索一個(gè)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;
 }
}


  借助于一個(gè)KeyPair,我們可以容易地得到PrivateKey和PublicKey-通過調(diào)用相應(yīng)的操作getPrivate()和getPublic()實(shí)現(xiàn)。

  為了從KeyStore中得到一個(gè)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)是一個(gè)工具函數(shù),用于實(shí)例化一個(gè)KeyStore對(duì)象,并且從文件系統(tǒng)加載入口。我們以如下方式實(shí)現(xiàn)它:

private static KeyStore loadKeyStore(String store, String sPass)
throws KeyStoreException,
NoSuchAlgorithmException,
CertificateException,
IOException{
 KeyStore myKS = KeyStore.getInstance("JKS");
 FileInputStream fis = new FileInputStream(store);
 myKS.load(fis,sPass.toCharArray());
 fis.close();
 return myKS;
}


  伴隨JDK提供的keytool還可以把存儲(chǔ)在一個(gè)密鑰儲(chǔ)存文件內(nèi)的證書輸出到系統(tǒng)文件中。例如,為了創(chuàng)建一個(gè)包含X509證書(關(guān)聯(lián)于別名為biz的密鑰入口)的biz.cer文件,我們可以從文件夾./etcdirectory下運(yùn)行下列命令:

keytool -export -alias biz -file biz.cer -keystore bizkeystore -storepass sp1234

  這個(gè)證書實(shí)現(xiàn)認(rèn)證我們討論上面的公共密鑰。

  我們還在示例中包括了一個(gè)Java類-CertificateInfo,用于把一個(gè)證書中的一些有趣的信息輸出到System.out。為了試驗(yàn)這一點(diǎn),讀者可以運(yùn)行Ant目標(biāo)certInfo。然而,要理解該代碼及其輸出,必須具有DSA和RSA算法的基本知識(shí)。當(dāng)然,讀者可以安全地繞過這個(gè)程序而繼續(xù)閱讀本文后面的內(nèi)容。

二、 生成一個(gè)Enveloping簽名  這一節(jié)討論借助于JSR-105 API及其缺省實(shí)現(xiàn)來實(shí)現(xiàn)對(duì)invoice.xml文件的簽名。

  我們的示例中創(chuàng)建了一個(gè)enveloping簽名。注意,當(dāng)你想使用在一種detached或enveloped簽名情形下時(shí),也僅需對(duì)本例作一些細(xì)微修改。

  下面,讓我們分析程序Sign.java,它能夠生成invoice.xml文件的XML簽名。

public class Sign {
 public static void main(String[] args) throws Exception {
  String input = "./etc/invoice.xml ";
  String output = "./etc/signature.xml";
  if (args.length > 2) {
   input = args[0];
   output = args[1];
  }
  //準(zhǔn)備
  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  dbf.setNamespaceAware(true);
  //步驟1
  String providerName = System.getProperty("jsr105Provider","org.jcp.XML.dsig.internal.dom.XMLDSigRI");
  XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",(Provider) Class.forName(providerName).newInstance());
  //步驟2
  Reference ref = fac.newReference("#invoice",fac.newDigestMethod(DigestMethod.SHA1, null));
  //步驟3
  Document XML = dbf.newDocumentBuilder().parse(new File(input));
  Node invoice = XML.getDocumentElement();
  XMLStructure content = new DOMStructure(invoice);
  XMLObject obj = fac.newXMLObject(Collections.singletonList(content),"invoice", null, null);
  //步驟4
  SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
(C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null),
Collections.singletonList(ref));
  //步驟5,分為情形5.0或5.1
  PrivateKey privateKey = null;
  //情形5.0
  privateKey = KeyStoreInfo.getPrivateKey("./etc/bizkeystore","sp1234","kp1234", "biz");
  //情形5.1,分為情形5.1.1或5.1.2

  //情形5.1.1
  //KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
  //kpg.initialize(512);
  //KeyPair kp = kpg.generateKeyPair();

  //情形5.1.2
  // KeyPair kp = KeyStoreInfo.getKeyPair("./etc/bizkeystore", "sp1234",
  // "kp1234","biz");

  //如果針對(duì)情形5.1,請(qǐng)去掉下面一行中的注釋
  // privateKey = kp.getPrivate();

  //步驟6,分為情形6.0,6.1或6.2

  //情形6.0,如果針對(duì)情形6.1或6.2也使用下面這一行
  KeyInfo ki = null;

  //如果針對(duì)情形6.1或6.2請(qǐng)去掉下面一行中的注釋
  // KeyInfoFactory kif = fac.getKeyInfoFactory();

  //情形6.1
  // KeyValue kv = kif.newKeyValue(kp.getPublic());
  // ki = kif.newKeyInfo(Collections.singletonList(kv));

  //情形6.2
  // CertificateFactory cf = CertificateFactory.getInstance("X.509");
  // FileInputStream fis = new FileInputStream("./etc/biz.cer");
  // java.security.cert.Certificate cert = cf.generateCertificate(fis);
  // fis.close();
  // X509Data x509d = kif.newX509Data(Collections.singletonList(cert));
  // ki = kif.newKeyInfo(Collections.singletonList(x509d));

  //步驟7
  XMLSignature signature = fac.newXMLSignature(si, ki,Collections.singletonList(obj), null, null);

  //步驟8
  Document doc = dbf.newDocumentBuilder().newDocument();
  DOMSignContext dsc = new DOMSignContext(privateKey, doc);

  //步驟9
  signature.sign(dsc);
  //轉(zhuǎn)換成一個(gè)xml文檔
  TransformerFactory tf = TransformerFactory.newInstance();
  Transformer trans = tf.newTransformer();
  trans.transform(new DOMSource(doc),new StreamResult(new FileOutputStream(output)));
 }
}


  為了試驗(yàn)這個(gè)程序,讀者可以運(yùn)行Ant目標(biāo)簽名-它將創(chuàng)建一個(gè)XML文檔./etc/signature.xml。這就是所謂的XML簽名。為了保持我們的代碼更為整潔和集中,我們省略了分析XML和轉(zhuǎn)換DOM樹中所有相關(guān)的格式設(shè)置。結(jié)果是,signature.xml文件成為一個(gè)有些凌亂的文本文件。

  現(xiàn)在,讓我們?cè)敿?xì)分析一下這個(gè)程序來說明如何在JSR-105中對(duì)一個(gè)XML簽名進(jìn)行簽名。

  簽名invoice.xm的過程可以分解為如下九個(gè)步驟。

  【步驟1】加載一個(gè)XMLSignatureFactory實(shí)例。這個(gè)工廠類將負(fù)責(zé)構(gòu)建幾乎所有主要的對(duì)象-我們?cè)贘SR-105中API中處理XML簽名時(shí)需要使用這些對(duì)象,除了那些與KeyInfo相關(guān)的對(duì)象之外。

  【步驟2】選擇一個(gè)digest方法并創(chuàng)建相應(yīng)的Reference對(duì)象。我們使用在〖步驟1〗中創(chuàng)建的XMLSignatureFactory實(shí)例來創(chuàng)建DigestMethod和Reference對(duì)象。

  在XMLSignatureFactory中的針對(duì)DigestMethod對(duì)象的工廠操作如下所示:

public abstract DigestMethod newDigestMethod(String algorithm,
DigestMethodParameterSpec params) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException


  【注意】這個(gè)params參數(shù)用來指定digest算法可能需要的參數(shù);在SHA-1,SHA-256或SHA-512的情況下,我們可以使用null。

  為了創(chuàng)建一個(gè)Reference對(duì)象,XMLSignatureFactory提供了四種操作:

public abstract Reference newReference(String uri, DigestMethod dm);
public abstract Reference newReference(String uri, DigestMethod dm,
List transforms, String type, String id);
public abstract Reference newReference(String uri, DigestMethod dm,
List transforms, String type, String id, byte[] digestValue);
......


  為了全面地理解在那些操作中的輸入?yún)?shù)的意思,我們需要分析一下在W3C建議中的Reference元素的XML模式定義:

<element name="Reference" type="ds:ReferenceType"/>
<complexType name="ReferenceType">
 <sequence>
 ?。糴lement ref="ds:Transforms" minOccurs="0"/>
 ?。糴lement ref="ds:DigestMethod"/>
 ?。糴lement ref="ds:DigestValue"/>
 </sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="URI" type="anyURI" use="optional"/>
<attribute name="Type" type="anyURI" use="optional"/>
</complexType>


  其中,URI屬性參考Reference相應(yīng)的數(shù)據(jù)對(duì)象。

  對(duì)于我們的示例來說,我們使用SHA-1作為digest方法,并且使用#invoice來在相同的XML簽名文檔(它包含這個(gè)Reference對(duì)象的XML描述)中引用一個(gè)元素。由#invoice所引用的元素正是我們要在下一步所要討論的內(nèi)容。

[@more@]【步驟3】加載invoice.xml并且用一個(gè)XMLObject對(duì)象把它包裝起來。注意,并非所有的簽名生成過程都要求這個(gè)步驟。XMLObject在JSR-105中對(duì)于我們以前簡(jiǎn)短地討論過的可選的Object元素進(jìn)行建模。該Object元素具有下列模式定義:

<element name="Object" type="ds:ObjectType"/>
<complexType name="ObjectType" mixed="true">
?。約equence minOccurs="0" maxOccurs="unbounded">
  <any namespace="##any" processContents="lax"/>
?。?sequence>
 <attribute name="Id" type="ID" use="optional"/>
?。糰ttribute name="MimeType" type="string" use="optional"/>
 <attribute name="Encoding" type="anyURI" use="optional"/>
</complexType>


  XMLSignatureFactory提供下列方法來創(chuàng)建一個(gè)XMLObject實(shí)例:

public abstract XMLObject newXMLObject(List content, String id,String mimeType,String encoding)

  我們使用一個(gè)DOMStructure對(duì)象來包裝invoice.xml的根結(jié)點(diǎn)。在JSR-105中定義的DOMStructure可以幫助從原始待簽名的XML文檔中把結(jié)點(diǎn)導(dǎo)入到JSR-105運(yùn)行時(shí)刻。

  我們指定#invoice作為結(jié)果對(duì)象元素的id。JSR-105實(shí)現(xiàn)知道在步驟2中創(chuàng)建的引用對(duì)象參考invoice.xml文檔,因?yàn)檫@個(gè)id把它們鏈接在一起(在Reference一邊,URI屬性指向這個(gè)id)。

  【步驟4】創(chuàng)建SignedInfo對(duì)象。在W3C建議中,SignedInfo元素具有下列模式定義:

<element name="SignedInfo" type="ds:SignedInfoType"/>
<complexType name="SignedInfoType">
 <sequence>
 ?。糴lement ref="ds:CanonicalizationMethod"/>
  <element ref="ds:SignatureMethod"/>
 ?。糴lement ref="ds:Reference" maxOccurs="unbounded"/>
 </sequence>
?。糰ttribute name="Id" type="ID" use="optional"/>
</complexType>


  為了創(chuàng)建一個(gè)SignedInfo對(duì)象,我們需要在〖步驟2〗中創(chuàng)建的Reference;我們還需要兩個(gè)實(shí)例-一個(gè)是CanonicalizationMethod的實(shí)例,另一個(gè)是SignatureMethod的實(shí)例。我們建議感興趣的讀者參考一下規(guī)范說明書從而對(duì)這四種XML規(guī)范算法有一個(gè)更為精確的了解;在此,我們只是簡(jiǎn)單地指出,在我們決定選擇一個(gè)特定的算法-alg后,后面對(duì)XMLSignatureFactory的一個(gè)實(shí)例(即fac)的調(diào)用將會(huì)創(chuàng)建CanonicalizationMethod的實(shí)例:

fac.newCanonicalizationMethod(alg,null)


  我們可以創(chuàng)建一個(gè)SignatureMethod實(shí)例-通過調(diào)用下列在XMLSigantureFactory中定義的操作:

public abstract SignatureMethod newSignatureMethod(String algorithm,
SignatureMethodParameterSpec params) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException


  在我們的示例中,我們擁有一個(gè)DSA類型密鑰對(duì);因此,我們需要選擇一個(gè)基于DSA的算法-例如DSA_SHA1。對(duì)于DSA-SHA1,我們可以把params參數(shù)設(shè)置為null。

  為了創(chuàng)建一個(gè)SignedInfo實(shí)例,XMLSignatureFactory定義了如下兩個(gè)工廠方法:

public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm,
SignatureMethod sm, List references);
public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm,
SignatureMethod sm, List references, String id).


  在第二個(gè)工廠方法中的第二個(gè)參數(shù)-id響應(yīng)于XML簽名文檔中的SignedInfo元素的Id屬性。

【步驟5】-獲得簽名私有密鑰。

  在我們的示例中,我們展示了三種不同的方法來得到該私有密鑰。在第一種方法中,我們調(diào)用我們的KeyStoreInfo類的getPrivateKey()方法來檢索我們使用keytool創(chuàng)建的DSA類型私有密鑰,并且把它儲(chǔ)存在密鑰存儲(chǔ)文件-bizkeystore(前面的5.0情形)中。為了獲得該私有密鑰,我們還可以從bizkeystore中檢索該KeyPair-通過調(diào)用KeyStoreInfo的getKeyPair()方法,然后調(diào)用KeyPair實(shí)例(5.1.2情形)的getPrivate()。另一方面,JCA提供了一個(gè)名字為KeyPairGenerator的類用于根據(jù)需要隨時(shí)動(dòng)態(tài)地創(chuàng)建一個(gè)KeyPair,這正是Sign.java中的情形5.1.1提到的情況。

  讀者還應(yīng)該注意,JSR-105允許通過一個(gè)KeySelector對(duì)象獲得私有密鑰。我們?cè)谙鹿?jié)討論KeySelector時(shí)還要詳細(xì)分析。

  【步驟6】創(chuàng)建一個(gè)KeyInfo對(duì)象。這一步是可選的,就象KeyInfo作為簽名元素中的一個(gè)元素是可選的一樣。在我們的示例的情形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)建一個(gè)KeyValue對(duì)象,并且把它放到一個(gè)KeyInfo對(duì)象。后面,當(dāng)討論我們的核心校驗(yàn)程序時(shí),我們將看到它如何使用這樣的一個(gè)KeyInfo對(duì)象來檢索公共密鑰以用于簽名校驗(yàn)。

  在JSR-105中,我們通過調(diào)用一個(gè)KeyInfoFactory實(shí)例中的操作創(chuàng)建了KeyValue和KeyInfo對(duì)象。其中,KeyInfoFactory負(fù)責(zé)創(chuàng)建所有主要的與KeyInfo相關(guān)的對(duì)象-例如KeyName,KeyValue,X509Data等。我們可以以與我們?cè)凇疾襟E1〗得到XMLSignatureFactory實(shí)例相同的方式得到一個(gè)KeyInfoFactory實(shí)例。我們的示例調(diào)用XMLSignatureFactory對(duì)象的getKeyInfoFactory()方法取得KeyInfoFactory實(shí)例。

  我們的示例的情形6.2將創(chuàng)建一個(gè)X509Data對(duì)象-使用我們以前借助于工具keytool從bizkeystore中導(dǎo)出的證書biz.cer,然后把這個(gè)對(duì)象作為內(nèi)容放入一個(gè)KeyInfo對(duì)象中。再次,后面我們將討論的核心校驗(yàn)程序?qū)⒆C明我們?nèi)绾螐倪@樣的一個(gè)KeyInfo對(duì)象中取得用于簽名校驗(yàn)的公共密鑰。

  【步驟7】創(chuàng)建一個(gè)XMLSignature對(duì)象。在JSR-105中,XMLSignature接口為W3C中建議的簽名元素實(shí)現(xiàn)了建模。我們已經(jīng)在前面看到該簽名元素的結(jié)構(gòu)。為了創(chuàng)建一個(gè)XMLSiganture實(shí)例,我們可以在XMLSignatureFactory中調(diào)用下列兩個(gè)方法之一:

public abstract XMLSignature newXMLSignature(SignedInfo si, KeyInfo ki);
public abstract XMLSignature newXMLSignature(SignedInfo si, KeyInfo ki,
List objects, String id, String signatureValueId).


  第二個(gè)方法中的id和signatureValueId參數(shù)將成為結(jié)果XML簽名文檔中的XML元素ID。在我們的示例中,該XML簽名將擁有一個(gè)Object元素;因此,我們需要使用第二個(gè)工廠方法。

  【步驟8】實(shí)例化一個(gè)DOMSignContext對(duì)象,并且使用它注冊(cè)私有密鑰。XMLSignContext接口(DOMSignContext實(shí)現(xiàn)它)包含用于生成XML的上下文信息簽名。

  DOMSignContext提供了幾種形式的構(gòu)造器-簽名應(yīng)用程序用來注冊(cè)要使用的私有密鑰,并且這也是我們的示例中所采用的方法。

  在繼續(xù)討論簽名過程的最后步驟之前,我們需要指出XMLSignContext和DOMSignContext實(shí)例都可能包含特定于它們所使用的XML簽名結(jié)構(gòu)的信息和狀態(tài)。該JSR-105規(guī)范中聲明:如果一個(gè)XMLSignContext(或DOMSignContext)與不同的簽名結(jié)構(gòu)一起使用,那么,結(jié)果將是無法預(yù)料的。例如,我們不應(yīng)該使用相同的XMLSignContext(或DOMSignContext)實(shí)例來簽名兩個(gè)不同的XMLSignature對(duì)象。

  【步驟9】簽名。XMLSignature接口中的sign()操作實(shí)現(xiàn)簽名XMLSignature。其實(shí),該方法還實(shí)現(xiàn)若干操作,包括基于相應(yīng)的digest方法計(jì)算所有引用的digest值,并且基于該簽名方法和私有密鑰計(jì)算簽名值。該簽名值被XMLSignature實(shí)例中的嵌入式SignatureValue類所捕獲,而對(duì)XMLSignature實(shí)例的getSignatureValue()方法的調(diào)用將返回使用結(jié)果值填充的SignatureValue對(duì)象。

  在我們的簽名程序的最后,我們把XMLSignature編排成一個(gè)XML文檔-signature.xml。

三、 XML簽名核心校驗(yàn)  在前面一節(jié)中,我們把invoice.xml文檔簽名成一個(gè)在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);
   //每一個(gè)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)這個(gè)程序,讀者可以運(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的過程可以分解成六個(gè)步驟。

  步驟1-加載一個(gè)XMLSignatureFactory實(shí)例,這一步與在簽名程序中是一樣的。

  步驟2-加載要校驗(yàn)的XML簽名。在這一步中,我們需要把包含XML簽名的XML加載到內(nèi)存中并且把該XML文檔轉(zhuǎn)換成一棵DOM樹。

  步驟3-識(shí)別DOM樹中的簽名結(jié)點(diǎn)。簽名是在命名空間http://www.w3.org/2000/09/XMLdsig#中定義的,它被描述為在JSR-105中的XMLSignature接口的靜態(tài)變量XMLNS。

  步驟4-創(chuàng)建一個(gè)DOMValidateContext實(shí)例。

  一個(gè)校驗(yàn)上下文中的一項(xiàng)最關(guān)鍵的信息顯然是密鑰。我們可以使用DOMValidateContext并通過兩種不同的方法來注冊(cè)公共密鑰。在第一種方法中,如果校驗(yàn)應(yīng)用程序已經(jīng)擁有公共密鑰,它可以把該密鑰直接通過下列DOMValidateContext的構(gòu)造器放入上下文中:

public DOMValidateContext(Key validatingKey,Node node)


  這正是在我們的示例中的情形4.3。

  第二個(gè)方法將使用DOMValidateContext注冊(cè)一個(gè)KeySelector,并且讓該KeySelector選擇公共密鑰-基于在要校驗(yàn)的XMLSignature對(duì)象中可用的信息。在JSR-105中,KeySelector是一個(gè)定義了兩個(gè)操作的抽象類:

public abstract KeySelectorResult select(KeyInfo keyInfo, Purpose purpose,
AlgorithmMethod method, XMLCryptoContext context)
throws KeySelectorException
public static KeySelector singletonKeySelector(Key key)


  第二個(gè)操作創(chuàng)建一個(gè)總是返回相同密鑰的KeySelector。第一個(gè)操作試圖選擇一個(gè)密鑰-它能夠滿足作為輸出傳遞的要求。

  KeySelectorResult是JSR-105中的一個(gè)接口-該規(guī)范中要求這個(gè)接口包含一個(gè)使用KeySelector選擇的Key值。在我們的示例中,我們使用SimpleKeySelectorResult類實(shí)現(xiàn)這個(gè)接口-簡(jiǎn)單地包裝選擇的公共密鑰。

  在我們的示例中,我們實(shí)現(xiàn)并利用三個(gè)不同的KeySelectors來說明一個(gè)校驗(yàn)應(yīng)用程序工作的一些情形。

  在情形4.0中,KeyStoreKeySelector基于輸入?yún)?shù)從一個(gè)Key存儲(chǔ)中檢索公共密鑰。

  在情形4.1中,KeyValueKeySelector基于在輸入KeyInfo對(duì)象(它應(yīng)該包含一個(gè)KeyValue對(duì)象作為它的內(nèi)容的一部分;請(qǐng)參考Sign.java中的情形6.1)中的KeyValue信息選擇一個(gè)鍵值。

  在情形4.2中,X509KeySelector基于包含在KeyInfo對(duì)象(它應(yīng)該包含一個(gè)X509Data對(duì)象作為它的內(nèi)容的一部分;請(qǐng)參考Sign.java中的情形6.2)中的X509Data及其它信息選擇一個(gè)鍵。我們使用的是JSR-105中的X509KeySelector-其原作者是Sean Mullan。在此,我們稍微修改了一下其中的私有certSelect()方法以便它可以適合于我們使用keytool生成的證書。

  既然簽名中的KeyInfo可能包含各種信息,顯然,一個(gè)應(yīng)用程序必須選擇一個(gè)KeySelector實(shí)現(xiàn)-由它來使用包含在它將處理的KeyInfos中的信息。

  步驟5-把簽名結(jié)點(diǎn)反編排成一個(gè)XMLSiganture對(duì)象。在上一步驟中,我們把signature.xml文件加載進(jìn)一棵DOM樹-由相應(yīng)于樹中的Signature元素的結(jié)點(diǎn)所標(biāo)識(shí),并且使用一個(gè)DOMValidateContext和KeySelector(或私有密鑰)注冊(cè)該結(jié)點(diǎn)。為了校驗(yàn)該XML簽名,我們需要把Signature結(jié)點(diǎn)反編排為一個(gè)XMLSignature對(duì)象。這是通過調(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簽名。如前面所提及,這個(gè)過程包括兩個(gè)部分。其一是校驗(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對(duì)象的validate()方法實(shí)現(xiàn),并以相關(guān)的校驗(yàn)上下文作為輸入?yún)?shù)。

  在我們的示例中,我們使用這樣的知識(shí)來輸出每一個(gè)Reference和SigantureValue的校驗(yàn)狀態(tài)-當(dāng)XML簽名(核心)校驗(yàn)失敗時(shí);這樣以來,我們就可以得到導(dǎo)致失敗的更為詳細(xì)的信息。

四、 修改XML簽名  為了表明該校驗(yàn)程序確實(shí)能夠捕獲對(duì)生成的XML簽名的修改,我們可以在我們的示例中創(chuàng)建一個(gè)Tamper.java程序,允許我們修改清單中的信用卡號(hào)(或signature.xml文件中的SignatureValue元素)。

  這個(gè)程序使用XML簽名文檔和一個(gè)布爾值作為參數(shù)。當(dāng)該布爾參數(shù)為true時(shí),程序改變信用卡號(hào);否則,它修改簽名值。

public class Tamper {
 public static void main(String[] args) throws Exception {
  String sigfile = "etc/signature.xml";

  //決定要修改的標(biāo)志-Reference或SignatureValue
  boolean tamperRef = true ;

  if (args.length >= 2) {
   sigfile = args[0];
   tamperRef = Boolean.parseBoolean(args[1]);
  }
  File file = new File(sigfile);
  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  dbf.setNamespaceAware(true);
  Document signature = dbf.newDocumentBuilder().parse(file);

  if (tamperRef){
   //修改信用卡號(hào)
   NodeList targets =signature.getDocumentElement().getElementsByTagName("number");
   Node number = targets.item(0);
   if (!number.getTextContent().equals("987654321")){
    number.setTextContent("987654321");
   }else{
    number.setTextContent("000000000");
   }
  }else{
   //修改SignatureValue(第一字節(jié))
   BASE64Encoder en = new BASE64Encoder();
   BASE64Decoder de = new BASE64Decoder();
   NodeList sigValues =signature.getDocumentElement().getElementsByTagName("SignatureValue");
   Node sigValue = sigValues.item(0);
   byte[] oldValue = de.decodeBuffer(sigValue.getTextContent());
   if (oldValue[0]!= 111){
    oldValue[0] = (byte)111;
   }else{
    oldValue[0] = (byte)112;
   }
   sigValue.setTextContent(en.encode(oldValue));
  }
  TransformerFactory tf = TransformerFactory.newInstance();
  Transformer trans = tf.newTransformer();
  trans.transform(new DOMSource(signature),new StreamResult(new FileOutputStream(file)));
 }
}


  為了運(yùn)行它,讀者可以執(zhí)行經(jīng)修改的Ant目標(biāo)。在運(yùn)行這個(gè)修改的程序后,如果我們?cè)俅芜\(yùn)行該校驗(yàn)程序,核心校驗(yàn)將失敗,并且System.out將分別輸出引用和簽名校驗(yàn)的狀態(tài)。隨著第二個(gè)Boolean輸入?yún)?shù)值的不同,引用與/或簽名校驗(yàn)可能報(bào)告失敗。

關(guān)于JavaSE 6基于JSR105的XML簽名是怎樣實(shí)現(xiàn)的就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI