JavaのXMLデジタル署名APIを利用してXML署名

##署名プロセスのイメージ
JavaのXML デジタル署名APIを使用して XML署名を行う時のイメージです。

![2016-09-16_155426.png](https://qiita-image-store.s3.amazonaws.com/0/123138/576462d9-a97e-be20-5f42-4ae666147e79.png)

XML署名検証は、[こちら](http://qiita.com/KevinFQ/items/24f484de8d51b1cc0b46)を参照する。

##必要なもの
- 署名対象XML
- 秘密鍵
- サーバー証明書

##署名対象XML

Product要素配下のProductInfoに対して署名を行う。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Product>
    <ProductInfo>
        <ProductId>PC001</ProductId>
        <ProductName>iPhone</ProductName>
        <ProductPrice>100,000</ProductPrice>
    </ProductInfo>
</Product>
```


## 署名手順

１．XML正規化変換処理用アルゴリズムの指定**（Transforms要素）**
署名対象のダイジェスト値を計算する前に、署名形式および正規化変換するためのアルゴリズムを指定する。
ここは、Enveloped署名（署名要素が署名対象要素の子要素となる署名形式）及びExclusive XML Canonicalization Version 1.0 (omit comments)正規化アルゴリズムを指定する。

```java
ArrayList<Transform> refTransformList = new ArrayList<Transform>();
refTransformList.add(xmlSignFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec)null));
refTransformList.add(xmlSignFactory.newTransform(CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec)null));
```

２．ダイジェスト値計算用アルゴリズムの指定**（DigestMethod要素）**
署名対象の変換プロセス要素の適用後に、ダイジェスト値を計算するアルゴリズムを指定する。
ここは、SHA256アルゴリズムを指定する。

```java
DigestMethod digestMethod = xmlSignFactory.newDigestMethod(DigestMethod.SHA256, null);
```

３．署名対象要素の親要素にID属性の作成

```java
Element targetNode = (Element)xmlDom.getElementsByTagName("ProductInfo").item(0);
Element parentNode = (Element)targetNode.getParentNode();
Attr idAttr = xmlDom.createAttribute("id");
idAttr.setValue("ProductInfo");
parentNode.setAttributeNode(idAttr);
parentNode.setIdAttribute("id", true);
```

４．参照要素の作成**（Reference要素）**
参照要素（Reference element）を生成する。参照要素に署名対象識別子URI、ダイジェストアルゴリズム要素とダイジェスト値要素が含まれている。

```java
Reference ref = xmlSignFactory.newReference("#ProductInfo", digestMethod, refTransformList, null, null);
```

参照要素のイメージは以下に示す。

```xml
<Reference URI="#Product">		
	<Transforms>	
		<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
		<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
	</Transforms>	
	<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />	
	<DigestValue>TJPbOFEZX36qckS2Y40rb36jzxe7xuyD1DS/dJmzzHc=</DigestValue>	
</Reference>		
```

５．正規化アルゴリズムの指定**（CanonicalizationMethod要素）**
署名情報要素の正規化に用いられるアルゴリズムを記述する正規化メソッド要素。
ここは、Exclusive XML Canonicalization Version 1.0 (omit comments)正規化アルゴリズムを指定する。

```java
CanonicalizationMethod cm = xmlSignFactory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec)null);
```

６．署名アルゴリズムの設定**（SignatureMethod要素）**
正規化された署名情報要素から署名値を計算するのに用いられるアルゴリズムを指定する。

```java
SignatureMethod sm = xmlSignFactory.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null);
```

７．署名情報の作成**（SignedInfo要素）**
正規化アルゴリズム、署名アルゴリズム及び参照要素により、署名情報（SignedInfo）オブジェクトを作成する。

```java
SignedInfo signedInfo = xmlSignFactory.newSignedInfo(cm, sm, Collections.singletonList(ref));
```

８．キー情報の作成**（KeyInfo要素）**
このオブジェクトには、受け側が署名の検証に必要な鍵を見つけられるようにする情報が含まれている。

８．１　証明書の読込
DERファイルから証明書を読み込む。

```java
X509Certificate cert = getCertFromDER();	
	
private X509Certificate getCertFromDER() throws IOException, CertificateException {	
	CertificateFactory cf = CertificateFactory.getInstance("X.509");
	X509Certificate cert = (X509Certificate)cf.generateCertificate(new FileInputStream("./resources/server.crt"));
	return cert;
}	
```

８．２　オプションの KeyInfo オブジェクトを作成します。
上記の証明書オブジェクトにより、KeyInfoオブジェクトを作成する。

```java
KeyInfoFactory kif = xmlSignFactory.getKeyInfoFactory();
X509Data x509Data = kif.newX509Data(Collections.singletonList(cert));
KeyInfo keyInfo = kif.newKeyInfo(Collections.singletonList(x509Data));
```

９．XMLSignature オブジェクトの作成
XMLSignature オブジェクトを作成し、先に作成した SignedInfo および KeyInfo オブジェクトをパラメータとして渡す。

```java
XMLSignature signature = xmlSignFactory.newXMLSignature(signedInfo, keyInfo);
```

１０．秘密鍵の読込
DERファイルから秘密鍵を読み込む。

```java
PrivateKey privateKey = getPrivateKeyFromDER();	
	
private PrivateKey getPrivateKeyFromDER() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {	
	byte[] key = readkeyFile("./resources/private_key.pk8");
	PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
	
	KeyFactory kf = KeyFactory.getInstance("RSA"); 
	RSAPrivateKey privateKey = (RSAPrivateKey)kf.generatePrivate(keySpec);
	return privateKey;
}
```

１１．署名コンテキストの作成

```java
DOMSignContext dsc = new DOMSignContext(privateKey, parentNode);
```

１２．署名を行う。

```java
signature.sign(dsc);
```

##署名後XMLのイメージ

```xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>						
<ProductInfo id="Product">
	<Product>
		<ProductId>PC001</ProductId>
		<ProductName>iPhone</ProductName>
		<ProductPrice>100,000</ProductPrice>
	</Product>
	<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
		<SignedInfo>
			<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
			<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
			<Reference URI="#Product">
				<Transforms>
					<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
					<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
				</Transforms>
				<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
				<DigestValue>TJPbOFEZX36qckS2Y40rb36jzxe7xuyD1DS/dJmzzHc=</DigestValue>
			</Reference>
		</SignedInfo>
		<SignatureValue>E+H+n32RWiySjUEw9Dtf0c2D4mRjcm4/B2uezqDU+VXyY7qY7NOyX1HREUm62vJo8yJl7SWazx5Z
			lMYZ19iTADIOzwEPScx1CV98rRkIeYjssKF/6U4Ygu/vHw3Upooe8nV4xZxW+fwxJ389i8MSfg/9
			Y7rLeN1JHQCQiTMhS2VTEvzdzaeaERCTXam1CR5W75T+lLQe7vNc3fLXRLVL5RY9hbxrAXddwWPG
			LKvoTzpfWGJi77qjcDuxY0xV3dXzydptuqHITGIw4s4Q5lwDL1kqByKPf2moQntNAxE7vJ58oavH
			81xdWZpdHXnIIbzzXq4w0FdYW2sWZ9YkhIFf8Q==
		</SignatureValue>
		<KeyInfo>
			<X509Data>
				<X509Certificate>MIID+jCCAuICCQDRm9FEjBNE3TANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCanAxDjAMBgNV
					BAgMBVRva3lvMQ4wDAYDVQQHDAVUb2t5bzEcMBoGA1UECgwTSmFwYW5lc2UgR292ZXJubWVudDE4
					MDYGA1UECwwvTWluaXN0cnkgb2YgSW50ZXJuYWwgQWZmYWlycyBhbmQgQ29tbXVuaWNhdGlvbnMx
					GTAXBgNVBAMMEDA3MTAxMDAwMDAwMDE3MDAxHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20w
					HhcNMTYwOTE0MDU1OTIyWhcNMTYxMDE0MDU1OTIyWjCBvjELMAkGA1UEBhMCanAxDjAMBgNVBAgM
					BVRva3lvMQ4wDAYDVQQHDAVUb2t5bzEcMBoGA1UECgwTSmFwYW5lc2UgR292ZXJubWVudDE4MDYG
					A1UECwwvTWluaXN0cnkgb2YgSW50ZXJuYWwgQWZmYWlycyBhbmQgQ29tbXVuaWNhdGlvbnMxGTAX
					BgNVBAMMEDA3MTAxMDAwMDAwMDE3MDAxHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wggEi
					MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCl9X4r6sAmb7p6vPGS2mKIlChMe87xjHuF0Z6s
					VfmU5cfUEfaTnKY2Wte2++T4EDdbTqkza1VtlA3gsuoAsnbnpSxakY34h8UIUn5MpiXE3IViAFx4
					pGi1Qy3/J+6CEfhSpiLpFHksVY5ruSUD+R9u7crDu/+8nRaMDzofQYxL5andTi5GBT7V62OINijl
					8Tte6YtoNZSMhtDhJDwolTzUu6/RAsJu1Ybpo+yX49yIyEYJIK8eDfzxiGT+o0yA1UKNEnT3eymV
					WW17fLfixEcCFRTFvBfWTklXB7YKn01U/5kc6A5Bsb/VXloPoWKUsqf6viFOAcXJS+iEAgSXtfmp
					AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAB499x9IHQUdEYbj1b95y2t/qxYHLA7mOYuH+YRdPYGr
					v53zw0gR2bSWfrJOkiaD2JKAG9Vf44xtQOz835SlENWRGMLo19FfnDo1nik4oW6SBxANya7XsX1L
					zI6PAgdacvmvNCcqwjtdf4jLpIE4epvUVoEQo47S7baxZs97JiJOEl1glxzz5p5JOrDmpgfrUq/+
					d1PyKzTePmAESjXGL+04oPmoYNg0KkVQvcBCQDAtNp/p1jaVtgT3K2KqlvSBehfY8xJR48M0VRWD
					rEW1hgtNls/X4084VSND9UQWTCc98dmgzz1cXHx0u0aahwT9leCwxi1HQEO+NuFQdQwRaWY=
				</X509Certificate>
			</X509Data>
		</KeyInfo>
	</Signature>
</ProductInfo>
```

##XMLデジタル署名
以下は上記署名手順のまとめです。

```java
/**
 * XML署名
 * 
 * @param xmlDom
 */
public void signature(Document xmlDom) {
	
	try {
		// 秘密鍵の取得
		PrivateKey privateKey = getPrivateKeyFromDER();
		// 証明書の取得
		X509Certificate cert = getCertFromDER();
		
		// 対象要素の親要素にID属性を付与する
		Element targetNode = (Element)xmlDom.getElementsByTagName("ProductInfo").item(0);
		Element parentNode = (Element)targetNode.getParentNode();
		Attr idAttr = xmlDom.createAttribute("id");
		idAttr.setValue("ProductInfo");
		parentNode.setAttributeNode(idAttr);
		parentNode.setIdAttribute("id", true);
		
		// 署名情報の設定
		XMLSignatureFactory xmlSignFactory = XMLSignatureFactory.getInstance("DOM");
		
		// 変換アルゴリズムの作成
		ArrayList<Transform> refTransformList = new ArrayList<Transform>();
		refTransformList.add(xmlSignFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec)null));
		refTransformList.add(xmlSignFactory.newTransform(CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec)null));
		
		// ダイジェスト計算アルゴリズムの生成
		DigestMethod digestMethod = xmlSignFactory.newDigestMethod(DigestMethod.SHA256, null);
		
		// 参照要素の設定
		Reference ref = xmlSignFactory.newReference("#ProductInfo", 
				digestMethod, 
				refTransformList, null, null);
		
		// 正規化アルゴリズムの生成
		CanonicalizationMethod cm = xmlSignFactory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec)null);
		
		// 署名アルゴリズムの生成
		SignatureMethod sm = xmlSignFactory.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null);
		
		// 署名情報の設定
		SignedInfo signedInfo = xmlSignFactory.newSignedInfo(cm, sm, Collections.singletonList(ref));
				
		KeyInfoFactory kif = xmlSignFactory.getKeyInfoFactory();
		X509Data x509Data = kif.newX509Data(Collections.singletonList(cert));
		KeyInfo keyInfo = kif.newKeyInfo(Collections.singletonList(x509Data));
		
		// 署名対象と秘密鍵の設定
		DOMSignContext dsc = new DOMSignContext(privateKey, parentNode);
		
		// XML署名の実施
		XMLSignature signature = xmlSignFactory.newXMLSignature(signedInfo, keyInfo);
		signature.sign(dsc);
		
		// コンソールに結果を表示する。
		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer trans = tf.newTransformer();
		trans.transform(new DOMSource(xmlDom), new StreamResult(System.out));
		
	} catch (Exception e) {
		e.printStackTrace();
	}

}

/**
 * 秘密鍵の読込
 * 
 * @return
 * @throws NoSuchAlgorithmException
 * @throws IOException
 * @throws InvalidKeySpecException
 */
private PrivateKey getPrivateKeyFromDER() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
	
	byte[] key = readkeyFile("./resources/private_key.pk8");
	PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
	
	KeyFactory kf = KeyFactory.getInstance("RSA"); 
	RSAPrivateKey privateKey = (RSAPrivateKey)kf.generatePrivate(keySpec);
	return privateKey;
}

/**
 * サーバー証明書の読込
 * 
 * @return
 * @throws IOException
 * @throws CertificateException
 */
private X509Certificate getCertFromDER() throws IOException, CertificateException {
	
	CertificateFactory cf = CertificateFactory.getInstance("X.509");
	X509Certificate cert = (X509Certificate)cf.generateCertificate(new FileInputStream("./resources/server.crt"));
	
	return cert;
}

/**
 * バイナリファイルの読込
 * 
 * @param fileName
 * @return
 * @throws IOException
 */
private byte[] readkeyFile(String fileName) throws IOException {
	byte[] data = null;
	FileInputStream input = new FileInputStream(fileName);
	data = new byte[input.available()];
	input.read(data);
	input.close();
	
	return data;
}
```