準備
公開鍵暗号方式に関して概念的でもいいからさらっておきましょう。
これがわかってないと何やっているかわからなくなると思います。
(プログラムのロジック自体はそれほど難しくはありません)
ワイは X509 関係の知識があやふやだったので
『図解 X.509 証明書』あたりで確認。
特にこの図は素晴らしい。
ただ、後半の自己証明書の作成は、使っているコマンドがモダンすぎて既存の Java コードでは正しく動かなかったりする。(『Mac で OpenSSL』など参照)
ここでも Mac で作業するので、openssl を入れておきます。
brew install openssl
X509 自己署名証明書(オレオレ証明書)を作成する
まずは、秘密鍵を作成。
openssl genrsa 2048 > privatekey.pem
なお、pem は base64 エンコードのテキスト形式です。バイナリ(der 形式)の秘密鍵が欲しい場合は
openssl pkcs8 -in privatekey.pem -outform DER -out privatekey.pk8 -topk8 -nocrypt
などのコマンドで変換しておきます。
次に CSR 証明書を作成します。
openssl req -new -key privatekey.pem -out csr.pem
以下のように質問されますので、適当に答えておきましょう。
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
秘密鍵と CSR を使って X509 証明書を作成します。
openssl x509 -req -days 365 -in csr.pem -signkey privatekey.pem -out public.crt
正常に各種証明書が作成されるとフォルダはこんな感じになると思います。
サンプルコード
これで準備が整ったので、コーディング開始。
なんですが、ネット上に参考になるコードがそれなりにあります。
今回は『JavaのXMLデジタル署名APIを利用してXML署名』のコードを使います。
ただし、記事作成時期が古く今となってそのままでは動かないため、以下のように改変。
public class XMLsignB {
public static void main(String[] args) {
System.out.println("signed XML");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Document doc = null;
try {
doc = dbf.newDocumentBuilder().parse(new FileInputStream("product.xml"));
}catch (SAXException ex) {
Logger.getLogger(XMLsignB.class.getName()).log(Level.SEVERE, null, ex);
}catch (IOException ex) {
Logger.getLogger(XMLsignB.class.getName()).log(Level.SEVERE, null, ex);
} catch (ParserConfigurationException ex) {
Logger.getLogger(XMLsignB.class.getName()).log(Level.SEVERE, null, ex);
}
signature(doc);
}
private static void signature(Document xmlDom) {
try {
// 秘密鍵の取得
PrivateKey privateKey = getPrivateKey();
// 証明書の取得
X509Certificate cert = getCert();
// 対象要素の親要素に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 refTransformList = new ArrayList();
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();
OutputStream os = new FileOutputStream("result.xml");
trans.transform(new DOMSource(xmlDom), new StreamResult(System.out));
//trans.transform(new DOMSource(xmlDom), new StreamResult(os));//ファイル書き出し
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 秘密鍵の読込
*
* @return
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws InvalidKeySpecException
*/
private static PrivateKey getPrivateKey() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
byte[] key = readkeyFile("privatekey.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 static X509Certificate getCert() throws IOException, CertificateException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(new FileInputStream("public.crt"));
return cert;
}
/**
* バイナリファイルの読込
*
* @param fileName
* @return
* @throws IOException
*/
private static 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;
}
}
Java17 以上で動くと思います。
ファイル書き出しを選んだ場合は、以下のような result.xml が作成されます。
参考
実は、公式ドキュメントがわかりやすかったりする。
巷にある解説記事のほとんどは、ここのサンプルコードが素。
時間的に余裕のある人は、XML 署名の公式ページ読みましょう。こことか。
より理解が深まると思います。
(適宜情報追加予定)
“Java XML 署名 API の使い方 -署名-” への1件の返信