UsernameToken 【转】

原文:http://idior.cnblogs.com/articles/381534.htmlphp

使用用户名和密码来验证用户的身份是最普通也最多见的方法,虽然在安全性方面也比较弱,因为其运用的普遍性仍是成为了WS-Security目前所支持的Security Token之一。其原理很是简单,用户在发送请求的时候,在Soap head中加入本身的用户名以及密码,接受请求的Service经过以前与Client创建的共享密码来验证密码的合法性从而实现鉴别用户的功能。
不过实际运用起来就不能考虑的那么简单了,该方法主要存在两个问题: 1. 在SOAP包中传输密码怎么保证密码的安全性? 2. 怎么从用户名密码中得到签名和加密所须要的密钥?html

针对第一个问题有三种解决方案: 1. 使用运输层的安全协议(如SSL)来保证实文密码的安全性。 2. 对明文密码作摘要后再传送给Service。 3. 利用从密码派生出来的密钥来代替直接使用密码来实现身份鉴别。c#

第一种方法采用了WS-Security结合运输层的安全协议(SSL)来保证密码的安全,在本系列一开始的文章已经描述过运输层安全协议的缺点,因此在此不对该方法作详细介绍。安全

第二种方法相似于HTTP Digest Authentication,先来看一段使用该方法的示例:
<wsse:UsernameToken>     <wsse:Username>NNK</wsse:Username>     <wsse:Password Type="...#PasswordDigest">          weYI3nXd8LjMNVksCKFV8t3rgHh3Rw==     </wsse:Password>     <wsse:Nonce>WScqanjCEAC4mQoBE07sAQ==</wsse:Nonce>     <wsu:Created>2003-07-16T01:24:32Z</wsu:Created> </wsse:UsernameToken> 从中看出这里使用了PasswordDigest类型的Password,从Password的内容也能够看出这里没有使用明文密码的形式。另外还多出了wsse:Nonce和wsu:Created两个元素。 其中Password的内容的计算公式以下: Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) )
读者可能比较奇怪wsse:Nonce和wsu:Created这两个元素的做用。为何不直接SHA-1(password) ? 这样作是为了不重放(Replay)攻击。假设Alice以摘要的形式向Service发送了密码,若是Bob此时截获了Alice发送的密码摘要,而后再用它向Service发送请求,那么Service将误认为Bob也是合法用户。当咱们加入Nonce和Created元素以后,Service能够检查收到的消息中的Nonce是否已经收到过了,或者在一段时间(5min)内是否收到了相同用户名密码,从而避免重放攻击的危险。不过使用PasswordDigest方式要求Service必须拥有密码的明文形式,也就是说Service能够看到每一个用户的密码,这对用户来讲增长了风险。由于一般状况下用户的密码是以hash的形式保存在Service端的,从而保证用户的信息不被泄漏。 尽管经过PasswordDigest能够避免密码的明文传播,并且经过引入wsse:Nonce和wsu:Created能够避免重放攻击的危险,可是若是Bob可以把传送中的密码摘要彻底的拦截下来(使它没法传送到Service),而后利用拦截下来的密码去冒充Alice去请求Service,那么Service将一筹莫展。为此,咱们引入了第三种方法。
加密

第三种方法和Kerberos协议中KDC向Client传送TGT的方式相似。 咱们能够看出前两种方式用户都将本身的密码发送给Service用于身份鉴别,难道为了证实本身的身份就必须把密钥(这里是密码)直接告诉别人吗?其实问题的关键在于Client能向Service证实它拥有只有C与S知道的密钥。而证实拥有的最直接方法就是告诉对方这个密钥,而后由Service比较这个密钥是否和它所知道的密钥一致,从而鉴别用户的身份。可是这种方法如前所述存在多种缺陷。既然仅仅须要Client证实它知道这个密钥,那么Client能够用这个密钥对一段消息作一个签名,而后将消息和签名同时发送给Service,Service用它所知道的Client的密钥也对一样的消息作一次签名,经过比较两个签名是否一致就能够确认Client是否真的拥有它的密钥。一样经过加密的方法Client也能够向Service证实本身是否真的拥有密钥(由于只有C与S密钥一致Service才能解密出用C密钥加密的消息)。这样一旦Client在消息中加入本身的一些特有信息(好比IP),即使Bob截获了消息可是因为他并不知道真正的密钥,看不到那些特有信息,也就没法冒充Alice。spa

经过这种间接证实拥有密钥的方法,咱们同时解决了文章一开始提出的第二个问题: 怎么从用户名密码中得到签名和加密所须要的密钥?
只要对密码作一些处理就能够从中派生出密钥。固然为了安全起见咱们但愿每次派生出来的密钥都不同,这样就能够避免屡次使用同一密钥而致使密钥被破解。下面就是WS-Security对密钥派生的元素定义:
<wsse:UsernameToken wsse:Id=”…”>     <wsse:Username>…</wsse:Username>     <wsse11:Salt>…</wsse11:Salt>     <wsse11:Iteration>…</wsse11:Iteration> </wsse:UsernameToken> 其中Salt是致使密钥变化的因子,Iteration是密钥派生时Hash的次数。 密码的派生公式以下: K1 = SHA1( password + Salt)  K2 = SHA1( K1)    … Kn = SHA1 ( Kn-1)3d

能够看到此时在UsernameToken已经再也不包含Password元素,由于Client将经过使用从Password派生出密钥作签名作加密的方式来证实它拥有密钥,从而证实本身的身份。xml

由此看出第三种办法相对来讲安全性大大提升了,可是在实际应用中以上介绍的三种的方法都不被推荐使用。 第三种方法仍旧存在如下两个缺陷: 1. 直接使用密码派生密钥,同以往临时产生的会话密钥相比,密码一旦破解,全部由改密码派生的密钥也被破解。因为密码长期不变, 那么随后全部使用该密码加密的消息都没有安全性可言。并且该密码可能还被用于Client与其余Service的交互,那么被破解后带来的损失就大多了。 2. 用户密码必须以明文形式保存在Service端。htm

所以,在微软的WSE对安全的默认支持方式中采用了UsernameToken和Service端Certification的组合的方式来表示Security Token。下图就是WSE中已经实现的UsernameForCertificate对SOAP Envelop的扩展结构。
             blog

从中能够看到SOAP Head中的wsse:UsernameToken已经被加密,被xenc:EncryptedData所替代,查看其Token Reference发现加密使用的Key来自xenc:EncryptedKey。若是你完整阅读了本系列的文章,你将不会对它太陌生,在XML Encryption中曾经对它的来由作了详细介绍。

      

Note因为使用对称密钥加密效率高,因此一般会使用对称密钥来加密数据,可是如何让消息的接受也得到对称密钥则成了一个问题。消息发送方不可能将对称密钥也随消息传递给消息接收方,此时利用非对称密钥来实现加密所用的对称密钥的传递成为了一个比较好的选择。EncrptedKey就是实现此种功能的扩展元素。

Client随机产生了一个对称密钥并用它来加密和签名SOAP Envelop中的其余元素(如UsernameToken),而后经过使用Service(消息接受方)的公钥(因为是公钥能够方便获取)来加密该对称密钥,以保证只有Service可以得到Client随机产生的对称密钥,从而达到验证消息完整性,解密数据以及鉴别用户身份的目的。如下是采用这种方式保证安全的SOAP Envelop的示例:
<?xml version="1.0" encoding="utf-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope"     xmlns:ds="http://www.w3.org/2000/09/xmldsig#"     xmlns:xenc="http://www.w3.org/2001/04/xmlenc"     xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility">     <SOAP-ENV:Header>         <wsse:Security             xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/secext">             <wsse:UsernameToken>                 <wsse:Username>HotelService</wsse:Username>                 <wsse:Password>myword</wsse:Password>                                       </wsse:UsernameToken>             <xenc:EncryptedKey wsu:id="userSysmetricKey">                 <xenc:EncryptionMethod                     Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>                  <ds:KeyInfo>                        <wsse:SecurityTokenReference>                            <wsse:KeyIdentifier                               ValueType="...oasis-wss-soap-message-security-1.1#ThumbPrintSHA1">                                     LKiQ/CmFrJDJqCLFcjlhIsmZ/+0=                            </wsse:KeyIdentifier>                         </wsse:SecurityTokenReference>                     </ds:KeyInfo>                 <xenc:CipherData>                     <xenc:CipherValue>G2wDCq24FsgBGerE...</xenc:CipherValue>                 </xenc:CipherData>                 <xenc:ReferenceList>                     <xenc:DataReference URI="#DiscountResponse"/>                 </xenc:ReferenceList>             </xenc:EncryptedKey>             <ds:Signature>                 <ds:SignedInfo>                     <ds:CanonicalizationMethod                         Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>                     <ds:SignatureMethod                         Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>                     <ds:Reference URI="#DiscountedBookingForPartnersResponse">                         <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>                         <ds:DigestValue>JwFsd3eQc0iXlJm5PkLh7...</ds:DigestValue>                     </ds:Reference>                 </ds:SignedInfo>                 <ds:SignatureValue>BSxlJbSiFdm5Plhk...</ds:SignatureValue>                     <ds:KeyInfo>                                             <wsse:SecurityTokenReference>                                <wsse:Reference URI="#userSysmetricKey"                             ValueType="...oasis-wss-soap-message-security-1.1#EncryptedKey"/>                           </wsse:SecurityTokenReference>                     </ds:KeyInfo>             </ds:Signature>         </wsse:Security>     </SOAP-ENV:Header>     <SOAP-ENV:Body wsu:Id="DiscountedBookingForPartnersResponse">         <s:GetSpecialDiscountedBookingForPartnersResponse                 xmlns:s="http://www.MyHotel.com/partnerservice ">             <xenc:EncryptedData                 wsu:Id="DiscountResponse"                 type="http://www.w3.org/2001/04/xmlenc#Element">                 <xenc:EncryptionMethod                     Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc "/>                 <CipherData>                 <CipherValue>XD6sFa0DrWsHdehrHdhcW0x...</CipherValue>                 </CipherData>             </xenc:EncryptedData>         </s:GetSpecialDiscountedBookingForPartnersResponse>     </SOAP-ENV:Body> </SOAP-ENV:Envelope>     

       

Note以上介绍的方法UsernameForCertificate仅被WS-Security1.1支持。由于在1.1中才支持使用对称密钥签名。

如此,以前所提到的问题均可以解决了,让咱们回顾一下: Q: 在SOAP包中传输密码怎么保证密码的安全性? A: 使用密钥加密,只有接受方能解密密码。

Q: 怎么从用户名密码中得到签名和加密所须要的密钥? A:  随机产生密钥,并经过接受方的公钥加密,保证密钥不被别人所知。

Q: 如何避免重放攻击? A: 因为其余人没法得到密钥,因此即使截获消息也没法冒充。

Q: 直接使用密码派生密钥被破解了怎么办? A: 密钥再也不从密码派生,而是每次随机产生,即使被破解也不会影响其余的消息和其余的服务。

Q: 用户密码必须以明文形式保存在Service端? A: 因为密码被加密而不是作摘要因此不须要Service拥有明文密码。
应用场景: B2C网上购物,每一个用户都有各自的用户名密码,并且能够方便的得到Server端的Certification。(好比Amazon)
参考资料: OASIS Kerberos Token Profile 1.1 Protect Your Web Services Through The Extensible Policy Framework In WSE 3.0

相关文章
相关标签/搜索