Go语言中比较PHP生成的MD5值
在一些验证的场景经常会用到MD5或SHA1值的比较,Go语言中并没有PHP那样直接计算MD5和SHA1的函数,该如何得到和PHP格式一致的计算结果并进行比较呢?这篇文章描述这一问题的解决过程,顺便也演示一段代码是如何逐步优化性能的。
首先我们需要满足基本需求,计算出MD5值。我们知道MD5是通用算法,语言虽然不同但是算法相同,最终是可以得到相同结果的。例如以前我就贴过一篇笔记《C#中与PHP格式对应的md5函数》。
Go语言中提供了一个md5包,用法如下:
package main import "fmt" import "crypto/md5" func main() { h := md5.New() h.Write([]byte("1234")) x := h.Sum(nil) fmt.Println(x) }
用以计算MD5的PHP实验代码:
php -r "echo md5('1234').\"\n\";"
我们用PHP计算字符串"1234"的MD5值,得到的是:
81dc9bdb52d04dc20036dbd8313ed055
Go语言计算出来的是一个字节数组,而PHP计算出来的是16进制表示的数据,如何把字节数组编码成16进制格式呢?
在fmt包中我找到一个办法:
package main import "fmt" import "crypto/md5" func main() { h := md5.New() h.Write([]byte("1234")) x := h.Sum(nil) y := fmt.Sprintf("%x", x) fmt.Println(y) }
经过格式化后,y的值跟我们用PHP计算的结果就是一样的了。
那是不是可以打完收工了呢?当然不是。
fmt模块是通用化设计的,它有两个问题。
首先,进行格式化的时候内部需要解析理解"%x",然后再采取对应措施对x进行编码。
其次,我们计算的MD5结果长度是固定的,而fmt内部为了通用化必然是动态分配长度的。
以上两点都是优化空间,所以我继续在文档中找,终于找到encoding/hex包,它专门用来做16进制编码。
实验代码如下:
package main import "fmt" import "crypto/md5" import "encoding/hex" func main() { h := md5.New() h.Write([]byte("1234")) x := h.Sum(nil) y := make([]byte, 32) hex.Encode(y, x) fmt.Println(string(y)) }
换成hex模块后,避免了动态解析,也利用了MD5值长度固定的特性,但是否还可以继续优化呢?
答案是可以。还有一个优化点没有体现在上面的代码中。
我们虽然计算出了和PHP格式相同的MD5值,但是如何才能高效的进行比较呢?
我们先用基本的方式,hex模块计算出的是字节数组,用bytes模块的函数进行比较,代码如下:
package main import "fmt" import "bytes" import "crypto/md5" import "encoding/hex" func main() { h := md5.New() h.Write([]byte("1234")) x := h.Sum(nil) y := make([]byte, 32) hex.Encode(y, x) z := []byte("81dc9bdb52d04dc20036dbd8313ed055") fmt.Println(bytes.Equal(y, z)) }
假设整个比较过程是我们自己实现的话,上面的代码有两个优化点。
首先,可以避免掉比较时和编码时的字节数组遍历,可以一边编码一边比较,减少遍历次数。
其次,当我们手头有比较目标的时候,我们可以不需要再创建一个存放计算结果的字节数组,拿编码结果和目标进行比较就可以。
按以上两个思路,我剖开hex模块的代码,取出Encode函数的实现代码,并进行改造,结果如下:
package main import "fmt" import "crypto/md5" func main() { h := md5.New() h.Write([]byte("1234")) x := h.Sum(nil) z := []byte("81dc9bdb52d04dc20036dbd8313ed055") fmt.Println(HexEncodeEqual(z, x)) } const hextable = "0123456789abcdef" func HexEncodeEqual(dst, src []byte) bool { for i, v := range src { if dst[i*2] != hextable[v>>4] || dst[i*2+1] != hextable[v&0x0f] { return false } } return true }
以上就是一个简单的MD5值比较代码的优化过程。
运行效率和负载能力之于服务端,就像表现逻辑和表现能力之于客户端,追求更高的运行效率是服务端程序员的使命。
性能需要一点一点从细节中抠出来,但优化不是凭空的,优化是建立在需求的基础上的。过度的设计,会像内置的通用模块一样增加一些不必要的消耗。
通用意味着封装,封装意味着消耗,在功能性模块中进行通用化设计往往就变成通通不好用,只有正确认识当前场景下我们需要什么不需要什么,才能做出恰到好处的设计。