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

相關文章
相關標籤/搜索