UsernameToken

使用用戶名和密碼來驗證用戶的身份是最普通也最常見的方法,雖然在安全性方面也比較弱,由於其運用的廣泛性還是成爲了WS-Security目前所支持的Security Token之一。其原理非常簡單,用戶在發送請求的時候,在Soap head中加入自己的用戶名以及密碼,接受請求的Service通過之前與Client建立的共享密碼來驗證密碼的合法性從而實現鑑別用戶的功能。

不過實際運用起來就不能考慮的那麼簡單了,該方法主要存在兩個問題:
1. 在SOAP包中傳輸密碼怎麼保證密碼的安全性?
2. 怎麼從用戶名密碼中獲得簽名和加密所需要的密鑰?

針對第一個問題有三種解決方案:
1. 使用運輸層的安全協議(如SSL)來保證明文密碼的安全性。
2. 對明文密碼做摘要後再傳送給Service。
3. 利用從密碼派生出來的密鑰來代替直接使用密碼來實現身份鑑別。

第一種方法採用了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。

通過這種間接證明擁有密鑰的方法,我們同時解決了文章一開始提出的第二個問題:
怎麼從用戶名密碼中獲得簽名和加密所需要的密鑰?

只要對密碼做一些處理就可以從中派生出密鑰。當然爲了安全起見我們希望每次派生出來的密鑰都不一樣,這樣就可以避免多次使用同一密鑰而導致密鑰被破解。下面就是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)

可以看到此時在UsernameToken已經不再包含Password元素,因爲Client將通過使用從Password派生出密鑰做簽名做加密的方式來證明它擁有密鑰,從而證明自己的身份。

由此看出第三種辦法相對來說安全性大大提高了,但是在實際應用中以上介紹的三種的方法都不被推薦使用。
第三種方法仍舊有兩個缺陷:
1. 直接使用密碼派生密鑰,同以往臨時產生的會話密鑰相比,密碼一旦破解,所有由改密碼派生的密鑰也被破解。由於密碼長期不變, 那麼隨後所有使用該密碼加密的消息都沒有安全性可言。而且該密碼可能還被用於Client與其他Service的交互,那麼被破解後帶來的損失就大多了。
2. 用戶密碼必須以明文形式保存在Service端。

因此,在微軟的WSE對安全的默認支持方式中採用了UsernameToken和Service端Certification的組合的方式來表示Security Token。下圖就是WSE中已經實現的UsernameForCertificate對SOAP Envelop的擴展結構。

             

從中可以看到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


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章