下载代码:https://github.com/woojean/demos/tree/master/php-xml-signature
概念
-
《XML数字签名规范》
W3C XML signature recommendation on 12 February 2002 -
规范化XML 预处理XML文档以实现纯文本比较和数字签名。
-
C14N规范 Canonical XML,一种生成XML文档物理表示的标准方法。
-
封内加签 元素成为被签名数据的子元素。
-
X.509标准 数字证书标准,一种非常通用的证书格式。一份X.509证书是一些标准字段的集合(含证书持有人的公钥信息及所使用的加密算法)。 常用文件扩展名:
.cer
、.crt
- 通常被用于二进制的DER文件格式(同于.der
),不过也被用于Base64编码的文件 (例如.pem
). -
SHA 安全哈希算法(Secure Hash Algorithm),主要适用于数字签名标准(Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。 经过权威机构证实,sha-1加密算法的不安全性越来越高,sha-1指纹造假成本越来越低,随即微软、谷歌等IT巨头相继发布弃用sha-1加密算法声明,第三方认证机构自2016年1月1日起,将全面停止签发sha-1算法的数字证书。这一切表明都表明从1995年诞生至今的SHA1算法将被sha-256所取代。
-
RSA 一种公钥加密算法(非对称加密算法)。命名取自3个开发者姓名的首字母。 RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
-
自签名的证书 数字证书由证书机构签发,证书机构通常需经权威认证机构注册认证。在企业应用中,也常用企业自身作为发证机构(未经过认证)签发数字证书,证书的使用范围也常是企业内部,这样的证书就是所谓的“自签名”的。
-
公钥加密算法 非对称加密技术。私钥用于进行解密和签名。公钥用于加密和验证签名。
-
消息摘要算法 单向函数或哈希函数,用于创建一个简短的固定长度,或可变长度的消息。可用于验证消息是否被篡改。
-
数字签名 用于安全地发送消息摘要,主要使用私钥来加密消息摘要和其他信息,只有发送方知道私钥,因此只有发件人可以签名。签名包含一个唯一的序列号,这样可以保证发送方无法否认曾经发送过消息(因为只有他可以签名这条消息)。
-
证书 用于发送方确保公钥的正确性,同样,接收方也需要核实用于签名该消息的私钥属于发送方。 证书的主要内容包括:公钥、真实身份识别信息(DN)、认证和签发的CA、有效期等。
-
DN Distinguished Name,用来提供对某个特定背景下的身份信息,由X.509标准定义。 主要的字段包括:CN(Common Name,证书颁发对象名称),O(Organization),OU( Organizational Unit),L( City/locality),ST(State/Province),C(Country)。
-
认证中心(CA) 证书颁发机构,负责认证证书,即给证书进行签名,颁发证书的权威机构。
-
证书链 CA机构有时也会为另外一家CA机构颁发证书。 当检查证书的时候,需要检查每一级证书的父亲证书,一直找到一个能信任的证书为止。
-
根CA 每个证书需要发行者来声明证书拥有者身份的有效性,一直到最顶层CA。 最顶层CA没有发行者,证书采用一种“自签名”的方式,所以证书的发行者就是证书拥有者自己。 用户也可以创建自己的证书颁发机构。
-
中级证书 具有继续颁发下级证书权限的子CA。
-
应用证书 不能用来继续颁发下级证书,只能用来证明个体身份的证书。(证书没有-extensions v3_ca选项)
-
CSR证书申请文件 Cerificate Signing Request,证书申请者在申请数字证书时由CSP(加密服务提供者)在生成私钥的同时也生成证书请求文件,证书申请 者只要把CSR文件提交给证书颁发机构后,证书颁发机构使用其根证书私钥签名就生成了证书公钥文件,也就是颁发给用户的证书。
生成xml报文
待加密的XML内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<RootInfo>
<NS:Item type="1" xmlns:NS="http://www.woojean.com/">
<NS:id>01</NS:id>
<name>woojean</name>
</NS:Item>
</RootInfo>
生成该XML的PHP源码:
<?php
$xml = new XMLWriter();
//$xml->openUri("php://output");
$xml->openURI('test.xml');
$xml->setIndentString(' ');
$xml->setIndent(true);
$xml->startDocument('1.0', 'utf-8');
$xml->startElement("RootInfo");
$xml->startElementNS("NS","Item",'http://www.woojean.com/');
//添加属性
$xml->writeAttribute("type", "1");
$xml->startElementNS("NS","id",null);
$xml->text("01");
$xml->endElement();
$xml->startElement("name");
$xml->text("woojean");
$xml->endElement();
$xml->endElement();
$xml->endElement();
$xml->endDocument();
//header("Content-type: text/xml");
//echo $xml->outputMemory();
生成私钥、证书
准备目录
目录结构取决于/System/Library/OpenSSL/openssl.cnf的配置。
cd /Users/wujian/projects/demo/test
mkdir -p ./demoCA/private
mkdir -p ./demoCA/newcerts
touch ./demoCA/index.txt
echo 01 > ./demoCA/serial
生成自签名的顶级CA
# 生成顶级CA的公钥证书和私钥文件,有效期10年(RSA 1024bits,默认),会提示输入phrase、DN信息
openssl req -new -x509 -days 3650 -keyout CARoot.key -out CARoot.crt
# 为顶级CA的私钥文件去除保护口令(不想在运行过程中还要输入加密口令)
openssl rsa -in CARoot.key -out CARoot.key
生成中级证书与应用证书
# 为应用证书/中级证书生成私钥文件
openssl genrsa -out app.key 2048
# 根据私钥文件,为应用证书/中级证书生成csr文件(证书请求文件)
openssl req -new -key app.key -out app.csr
# 使用CA的私钥文件、公钥证书给csr文件签名,生成应用证书,有效期5年
openssl ca -in app.csr -out app.crt -cert CARoot.crt -keyfile CARoot.key -days 1826 -policy policy_anything
提示:
Certificate is to be certified until Apr 27 06:46:15 2022 GMT (1826 days)
Sign the certificate? [y/n]:Y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
生成中级证书[本例用不到,这里备忘]
# 使用CA的公私钥文件给csr文件签名,生成中级证书,有效期5年
openssl ca -extensions v3_ca -in app.csr -out app.crt -cert CARoot.crt -keyfile CARoot.key -days 1826 -policy policy_anything
生成.pem格式的证书
cat app.key >> app_private.pem
openssl rsa -in app.key -out app_private.pem
openssl x509 -in app.crt -out app_public.pem -outform PEM
.cer/.crt是用于存放证书,它是2进制形式存放的,不含私钥。 .pem跟crt/cer的区别是它以Ascii来表示。
签名XML报文
准备前置数据
<?php
require_once '/Users/wujian/projects/demo/xmlseclibs/src/XMLSecEnc.php';
require_once '/Users/wujian/projects/demo/xmlseclibs/src/XMLSecurityDSig.php';
require_once '/Users/wujian/projects/demo/xmlseclibs/src/XMLSecurityKey.php';
use RobRichards\XMLSecLibs\XMLSecEnc;
use RobRichards\XMLSecLibs\XMLSecurityDSig;
use RobRichards\XMLSecLibs\XMLSecurityKey;
$privateKey = '/Users/wujian/projects/demo/test/app_private.pem';
$publicKey = '/Users/wujian/projects/demo/test/app_public.pem';
$dumpPath = '/Users/wujian/projects/demo/dump3.xml';
$xmlStr = '<?xml version="1.0" encoding="UTF-8"?>
<RootInfo>
<NS:Item type="3" xmlns:NS="http://www.woojean.com/">
<NS:id>021</NS:id>
<name>woojean</name>
</NS:Item>
</RootInfo>';
签名
<?php
$doc = new DOMDocument();
$doc->loadXML($xmlStr);
$objDSig = new XMLSecurityDSig();
$objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
$objDSig->addReference(
$doc,
XMLSecurityDSig::SHA256,
['http://www.w3.org/2000/09/xmldsig#enveloped-signature']
);
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
$objKey->loadKey($privateKey, TRUE);
// $objKey->passphrase = '<passphrase>'; // 密码
$objDSig->sign($objKey);
$keyNameNode = $objDSig->sigNode->ownerDocument->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:KeyName','my_public_key');
$objDSig->appendToKeyInfo($keyNameNode);
$objDSig->add509Cert(file_get_contents($publicKey));
$objDSig->appendSignature($doc->documentElement);
$doc->save($dumpPath);
签名后的报文
<?xml version="1.0" encoding="UTF-8"?>
<RootInfo>
<NS:Item xmlns:NS="http://www.woojean.com/" type="3">
<NS:id>021</NS:id>
<name>woojean</name>
</NS:Item>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>73MIGt50pK6TaTWoCiehnuRHII2HgOgqjjnrmR0+PqA=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>HPYP4AxYhsC6g25Cfd4hMrmPCJmfr+4jw34LAKay2fcU2MzNAItB6bQVrWNaWiQt5wr93ms+CCUF/Q0j5IMolSmJIO7R1NpEj1zpn+/2pqDPoiUNUJbJmZLZH3+dVPrSZqYcCQWz121gBpcjHpPvChLf4OI3+0Nu98BbLZF2XUMwBMUjAainK9QjyDFp13U97zRm50baigjE1rAcLxi1DuZPJljtPmwqPvhy2j7754ekbQUBVhfHe20AnkZs930Y48kOXoCGwq9pD6gsgRT1BA1+DsOlZKo13P/aakuuKoroJ+horPbC88tVU36KQ1aMkAVXdWwdecrfCF4/EEj8KA==</ds:SignatureValue>
<ds:KeyInfo><ds:KeyName>my_public_key</ds:KeyName><ds:X509Data><ds:X509Certificate>MIIDITCCAoqgAwIBAgIBATANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV1UxDTALBgNVBAcTBEpJQU4xCzAJBgNVBAoTAldVMQswCQYDVQQLEwJXVTEQMA4GA1UEAxMHd29vamVhbjAeFw0xNzA0MjcwNjQ2MTVaFw0yMjA0MjcwNjQ2MTVaMFcxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJXVTENMAsGA1UEBxMESklBTjELMAkGA1UEChMCV1UxDTALBgNVBAsTBEpJQU4xEDAOBgNVBAMTB3dvb2plYW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC2JgwHZjRtiXpiQU7SL9VdfqamOJuZ+crD84HwXPuypkHakR+GSVo1aRhbOO2h55xeAG7C5S/qHRfVtJ5KFVckXWFgndTfZNCS2PycZqnu2MFIburpPcp/Bf7e+Z2ldluyC3TDExCma63uk5kGakzbCS/7kpocEVsJrEIYGdzjNTp+JclKJcfmCq/YZ8M3OVhPr4RuO/PdTtzL3uMiyGU9wO17nl9VsTIbgxKYs+/bI2pOEE9iFPHDAEhsaF/k1SBZXlLAmE0x/HVw4SraE5AqL7KLS3Rc/rRp8WveqmHvIqDbX4jv6jgrSMcCznqv+/VUv4IGt7qwbBuPWVGhUsNAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQ2Dq/d05/Egnytyvplxu4ykfnvqTAfBgNVHSMEGDAWgBQhnHeUnjnTvK0UnFLZkaRz4VT4CTANBgkqhkiG9w0BAQUFAAOBgQBaCL75URSWqJMA3uWWcwwjWEF0KFiasXzhEAdyeHOoqu8mZHLAXDgKxVpFPfNHAE6Dq9V5cVzqdYC6j5HVdVO6P3wlZXRCrn3MGMmjtfkFu0PNhbWKz//IpJR16d4NqvF8xLtYYvhqMq1jl5gAyFRHTF2itLW3lHkZLAAZG9+o9g==</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature></RootInfo>
验证报文
<?php
$doc = new DOMDocument();
$doc->load($dumpPath);
$objXMLSecDSig = new XMLSecurityDSig();
$objDSig = $objXMLSecDSig->locateSignature($doc);
$objXMLSecDSig->canonicalizeSignedInfo();
$objKey = $objXMLSecDSig->locateKey();
XMLSecEnc::staticLocateKeyInfo($objKey, $objDSig);
$publicKey = $objKey->getX509Certificate();
$keyAlgorithm = $objKey->getAlgorith();
// Check signature
$ret = $objXMLSecDSig->verify($objKey);
if (1 !== $ret) {
var_dump('wrong!');
return FALSE;
}
else{
var_dump('ok!');
}
// Check references (data)
try {
$objXMLSecDSig->validateReference();
} catch (\Exception $e) {
return FALSE;
}
输出
/Users/wujian/projects/demo/decrypt.php:37:string 'ok!' (length=3)