密碼學05--go語言訊息認證
目錄
2.1.1 呼叫hmac包中的New方法,生成hash雜湊物件
2.3.1 呼叫hmac包中的New方法,生成hash雜湊物件
1.訊息認證MAC
1.1 概念
訊息認證可以認為就是單向雜湊函式在實際中的一種應用,其目的是為了確認資料的完整性。
1.2 術語
MAC,即message authentication code,訊息認證編碼。
1.3 原理
假設有【傳送方Frank】【接收方Alex】兩人進行資料通訊,兩人之間存在一個相同的共享金鑰(例如對稱加密金鑰)而且兩人之間存在一個相同的雜湊函式(例如都是用SHA256單向雜湊函式),那麼兩者之間的訊息認證如圖所示:
圖中對於劫持者而言:由於單向雜湊函式未知,所以最終計算出的雜湊值X必然和雜湊值F不同。而且即使雜湊函式也被劫持者獲知,那麼如果一旦對原始資料作出任何修改,根據雜湊函式的雪崩效應最終生成的雜湊值X必然和雜湊值F天差地別,Alex一樣可以鑑定出資料是否曾被劫持過。
1.4 應用
訊息認證的目的是為了確保資料的完整性,因此實際應用時會經常使用在通訊領域範疇中,用於確認資訊是否被篡改。訊息認證相較於資訊加密多了一層安全性方面的保障。顯然下圖就是典型的資訊加密,如果一旦出現密文篡改資訊被劫持的情況,那麼無論是甲還是乙都沒有辦法知道資訊究竟是否被篡改過。
而在1.3的圖中則能夠明顯看到,即便劫持者對訊息進行劫持,但只要劫持者對內容進行哪怕一個bit位的篡改都會將雜湊值結果以雪崩效應方式放大無數倍,進而導致最終的MAC與初始MAC完全不同。
可能有人這裡會出現疑問:如果劫持者連MAC和訊息內容一起篡改呢?千萬不要忘記共享金鑰和單向雜湊函式的存在,這兩者是隻有資訊雙方知道的內容。那麼如果劫持者對MAC和訊息內容一起進行了篡改,那麼到訊息接受者Alex中的時候,Alex使用被篡改的訊息和共享金鑰以及單向雜湊函式計算出的MAC與劫持者提供的MAC必然完全不同。依然可以判斷出資訊已經被篡改的事實。
1.5 弊端
訊息認證的關鍵點很顯然就是共享金鑰和單向雜湊函式演算法,所以訊息認證和對稱加密一樣問題在於金鑰分發困難。
1.6 缺陷
無法藉助第三方公正,換句話說仍然只有資訊收發雙方可以獲知資料。如果一旦資訊收發雙方的某一方毀約(例如支付操作中商家不承認使用者曾向自己支付...想想都是一個災難),在沒有第三方公正的情況下,另一方就只能吃啞巴虧。所以一般訊息認證都會聯合數字簽名一起使用,來保證多方公正。
2.go語言中的MAC
因為MAC本質上其實就是利用單向雜湊函式對資訊進行的加密,而go語言中的單向雜湊函式又稱為Hash雜湊函式,所以在go語言中的訊息認證技術就被稱為HMAC,即Hash MAC雜湊訊息驗證碼(是不是發現老外起名能力也就那樣?)。HMAC需要使用的包是go語言內的crypto/hmac包中的函式。
2.1 生成MAC流程
2.1.1 呼叫hmac包中的New方法,生成hash雜湊物件
// New returns a new HMAC hash using the given hash.Hash type and key.
// Note that unlike other hash implementations in the standard library,
// the returned Hash does not implement encoding.BinaryMarshaler
// or encoding.BinaryUnmarshaler.
//
// 引數1:sha256.new/sha1.new/md5.new這樣的hash演算法函式物件
// 引數2:共享金鑰
// 返回值:Hash型別物件,就是包括io.Writer指標和Sum()的那個雜湊值中的Hash型別物件。
func New(h func() hash.Hash, key []byte) hash.Hash {
hm := new(hmac)
hm.outer = h()
hm.inner = h()
hm.size = hm.inner.Size()
hm.blocksize = hm.inner.BlockSize()
hm.ipad = make([]byte, hm.blocksize)
hm.opad = make([]byte, hm.blocksize)
if len(key) > hm.blocksize {
// If key is too big, hash it.
hm.outer.Write(key)
key = hm.outer.Sum(nil)
}
copy(hm.ipad, key)
copy(hm.opad, key)
for i := range hm.ipad {
hm.ipad[i] ^= 0x36
}
for i := range hm.opad {
hm.opad[i] ^= 0x5c
}
hm.inner.Write(hm.ipad)
return hm
}
返回的Hash物件就是下面這個:
// Hash is the common interface implemented by all hash functions.
//
// Hash implementations in the standard library (e.g. hash/crc32 and
// crypto/sha256) implement the encoding.BinaryMarshaler and
// encoding.BinaryUnmarshaler interfaces. Marshaling a hash implementation
// allows its internal state to be saved and used for additional processing
// later, without having to re-write the data previously written to the hash.
// The hash state may contain portions of the input in its original form,
// which users are expected to handle for any possible security implications.
//
// Compatibility: Any future changes to hash or crypto packages will endeavor
// to maintain compatibility with state encoded using previous versions.
// That is, any released versions of the packages should be able to
// decode data written with any previously released version,
// subject to issues such as security fixes.
// See the Go compatibility document for background: https://golang.org/doc/go1compat
type Hash interface {
// Write (via the embedded io.Writer interface) adds more data to the running hash.
// It never returns an error.
io.Writer
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
Sum(b []byte) []byte
// Reset resets the Hash to its initial state.
Reset()
// Size returns the number of bytes Sum will return.
Size() int
// BlockSize returns the hash's underlying block size.
// The Write method must be able to accept any amount
// of data, but it may operate more efficiently if all writes
// are a multiple of the block size.
BlockSize() int
}
2.1.2 向hash雜湊物件內新增明文資料
沒啥說的,直接呼叫hash物件的Writer()方法就行,然後把要進行雜湊值計算的明文資料當引數傳進去就行。
2.1.3 計算明文資料雜湊值
依然沒啥說的,直接呼叫hash物件的Sum()方法就行,引數設定為nil即可。
2.1.4 將生成的雜湊值進行十六進位制轉碼
這一步注意一下,如果需要將雜湊值進行網路傳輸就加上,如果就是簡單本地測試使用就不用加。新增也只是新增一句函式呼叫,呼叫encoding/hex十六進位制編碼包中的EncodeToString()方法將二進位制的雜湊值格式話編碼為十六進位制可顯示字串。
//將資料src編碼為字串s。
func EncodeToString(src []byte) string
2.2 生成MAC模板
//封裝函式用來生成MAC
//引數分別是:原始明文和共享金鑰
func generateMAC(plainText,key []byte) string{
//1.生成hash物件
frankHash := hmac.New(sha256.New,key)
//2.對hash物件新增資料
frankHash.Write(plainText)
//3.生成雜湊值(應該更名位MAC值,但本質還是雜湊值)
frankMac := frankHash.Sum(nil)
//4.十六進位制轉換(有需要就加)
frankMacString := hex.EncodeToString(frankMac)
//5.返回生成的MAC
return frankMacString
}
func main() {
//生成MAC
plainText := []byte("這是一條原始明文helloworld今天天氣好晴朗處處好風光")
key := []byte("1234abcd")
oldMacString := generateMAC(plainText, key)
fmt.Printf("%s\n", oldMac)
}
顯而易見的執行結果是一個base64編碼後的MAC訊息認證碼。
2.3 校驗MAC流程
2.3.1 呼叫hmac包中的New方法,生成hash雜湊物件
注意引數也要和生成MAC時一致,畢竟一致的單向雜湊值演算法和一致的共享金鑰才能的到一致的最終雜湊值結果。
2.3.2 向hash雜湊物件內新增接收的明文資料
直接呼叫hash物件的Writer()方法就行,然後把要進行雜湊值計算的接收到的明文資料當引數傳進去就行。
2.3.3 計算接收的明文資料雜湊值
直接呼叫hash物件的Sum()方法就行,引數設定為nil即可。
2.3.4 將接收的十六進編碼進行反向編碼轉換為雜湊值
在接收資料的時候很顯然接收到的是資料傳送方發來的base64編碼,而本地計算出來的雜湊值是二進位制流,因此我們需要藉助encoding/hex包中的DecodeString()方法來將base64編碼反序列化回二進位制流雜湊值。
//返回hex編碼的字串s代表的資料
func DecodeString(s string) ([]byte, error)
2.3.5 呼叫hmac包中的Equal方法進行驗證
通過反序列化能夠或者資料傳送方發來的mac,通過計算明文資料能夠獲知本地計算出來的mac。兩者比較如果相同即驗證成功。
// Equal compares two MACs for equality without leaking timing information.
func Equal(mac1, mac2 []byte) bool {
// We don't have to be constant time if the lengths of the MACs are
// different as that suggests that a completely different hash function
// was used.
return subtle.ConstantTimeCompare(mac1, mac2) == 1
}
2.4 校驗MAC模板
//封裝函式用來生成MAC
//引數分別是:原始明文和共享金鑰
func generateMAC(plainText,key []byte) string{
//1.生成hash物件
frankHash := hmac.New(sha256.New,key)
//2.對hash物件新增資料
frankHash.Write(plainText)
//3.生成雜湊值(應該更名位MAC值,但本質還是雜湊值)
frankMac := frankHash.Sum(nil)
//4.十六進位制轉換(有需要就加)
frankMacString := hex.EncodeToString(frankMac)
//5.返回生成的MAC
return frankMacString
}
//封裝函式用來校驗MAC
//引數分別是:接收到的原始明文,共享金鑰和接收到的MAC碼字串
func verifyMAC(plainText,key []byte, oldMacString string) bool{
//1.生成hash物件
frankHash := hmac.New(sha256.New,key)
//2.對hash物件新增資料
frankHash.Write(plainText)
//3.生成雜湊值(應該更名位MAC值,但本質還是雜湊值)
frankMac := frankHash.Sum(nil)
//4.十六進位制轉換(需要就寫)
oldMac,_ := hex.DecodeString(oldMacString)
//5.將資訊校驗結果返回
return hmac.Equal(oldMac, frankMac)
}
func main() {
//生成MAC
plainText := []byte("這是一條原始明文helloworld今天天氣好晴朗處處好風光")
key := []byte("1234abcd")
oldMacString := generateMAC(plainText, key)
fmt.Printf("%s\n", oldMac)
//校驗MAC firstTime
result1 := verifyMAC(plainText, key, oldMac)
fmt.Printf("%t\n",result1)
//校驗MAC secondTime
result2 := verifyMAC(plainText, []byte("1234abce"), oldMacString)
fmt.Printf("%t\n",result2)
}
能夠看到第二次校驗的時候模擬了key值被篡改的場景,所以第一次認證成功,而第二次則認證失敗。