目录
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值被篡改的场景,所以第一次认证成功,而第二次则认证失败。