前兩節(jié)介紹的加密方法都屬于“可逆”的加密算法,因?yàn)榧用芎蟮拿芪慕?jīng)過(guò)解密的過(guò)程就可以還原出原文。還有一類加密算法屬于“不可逆”的加密算法,是指一般無(wú)法進(jìn)行正常解密還原出原文的加密算法。

不可逆加密算法的應(yīng)用場(chǎng)景也有很多,最典型的是服務(wù)端保存用戶登錄密碼的方式。從保護(hù)用戶隱私角度考慮,服務(wù)器端不應(yīng)該存儲(chǔ)用戶的登錄密碼原文,也不應(yīng)該保存可逆加密后的密文(因?yàn)橐部梢员唤饷苓€原),這時(shí)候?qū)嵺`中常用的方法是在服務(wù)器保存密碼時(shí)將其用不可逆的算法轉(zhuǎn)換成密文存儲(chǔ),下次用戶登錄時(shí)服務(wù)器將發(fā)來(lái)的用戶密碼以同樣的不可逆算法加密后再與保存的密碼密文做比對(duì)來(lái)判斷是否相同。
從這個(gè)例子也可以看出,不可逆加密算法一般要滿足這個(gè)要求:同樣的明文用同樣的方式加密后,得到的密文必須是一樣,否則就無(wú)法用于比對(duì)了。
最簡(jiǎn)單的不可逆加密算法可能就是取模算法了,例如18 % 5 的結(jié)果是3,而23 % 5的結(jié)果也是3,如果18和23代表明文,“% 5”這個(gè)取模操作(也就是求對(duì)于5的余數(shù))代表加密算法,3代表加密后的密文,那么顯然這個(gè)加密算法是不可逆的,因?yàn)閺拿芪摹?”無(wú)法確定原文是18還是23(甚至還可能是其他更多的數(shù)字),而任何數(shù)字對(duì)5取模的結(jié)果都是不變的。那么該算法如果用于對(duì)密碼加密,可以將密碼轉(zhuǎn)換后的字節(jié)切片中的每個(gè)字節(jié)數(shù)值對(duì)5取模后形成新的密文字節(jié)切片,服務(wù)器端保存這個(gè)密文就可以了,以后用戶登錄時(shí)傳上來(lái)的密碼做同樣的加密操作,得到的結(jié)果與保存的密文應(yīng)該是一致的。
當(dāng)然,取模操作作為加密方法來(lái)說(shuō)加密強(qiáng)度太低,實(shí)際應(yīng)用中用的最多的不可逆加密算法是MD5算法,該算法應(yīng)用了散列函數(shù)的原理,可以將任何數(shù)據(jù)轉(zhuǎn)換成固定長(zhǎng)度的字節(jié)序列(一般是16個(gè)字節(jié)),并且與取模操作一樣具有原文對(duì)應(yīng)到密文的唯一性。
取模操作也可以看做散列函數(shù)的一種,可以將任何數(shù)據(jù)散列到[0, n)的范圍內(nèi)(n是取模的模數(shù))。散列函數(shù)一般都具備這樣的特性:原始數(shù)據(jù)與散列后的數(shù)據(jù)是多對(duì)一的關(guān)系,即同樣的原始數(shù)據(jù)用同樣的散列函數(shù)處理后必然得到同樣的結(jié)果,但同樣的結(jié)果不一定對(duì)應(yīng)同一個(gè)原始數(shù)據(jù)。這也是為什么大多數(shù)不可逆算法使用散列函數(shù)作為核心方法的原因。
下面的代碼演示了如何使用Go語(yǔ)言中的crypto/md5等包來(lái)實(shí)現(xiàn)MD5加密算法。
package main
import (
? "crypto/md5"
? "encoding/hex"
? "io"
? "log"
? "os"
? "strings"
? t "github.com/topxeq/goexamples/tools"
)
func main() {
? //原始字符串
? originalTextT := "測(cè)試字符串"
? //對(duì)原始字符串生成md5碼,md5BytesArrayT是[16]byte類型的數(shù)組
? md5BytesArrayT :=md5.Sum([]byte(originalTextT))
? //將數(shù)組轉(zhuǎn)換為切片
? md5BytesT := md5BytesArrayT[:]
? t.Printfln("md5字節(jié)切片(16個(gè)字節(jié)): %#v", md5BytesT)
? //將md5字節(jié)切片轉(zhuǎn)換為16進(jìn)制的文本,將有32個(gè)字符
? //并轉(zhuǎn)換為全大寫字母
? md5TextT := strings.ToUpper(hex.EncodeToString(md5BytesT))
? t.Printfln("md5文本(32位): %#v", md5TextT)
? //用流式方法對(duì)字符串進(jìn)行md5編碼
? md5Encoder := md5.New()
? //向md5Encoder中寫入字符串
? io.WriteString(md5Encoder, originalTextT)
? //調(diào)用Sum函數(shù)進(jìn)行md5編碼,并轉(zhuǎn)換為十六進(jìn)制字符串
? md5TextT = hex.EncodeToString(md5Encoder.Sum(nil))
? t.Printfln("流式編碼的md5文本(小寫): %#v", md5TextT)
? //直接用流式方法對(duì)一個(gè)文件進(jìn)行md5編碼
? fileT, errT := os.Open(`c:\test\long.txt`)
? if errT != nil {
?????? log.Fatal(errT)
? }
? defer fileT.Close()
? md5Encoder = md5.New()
? //帶有初始化語(yǔ)句的條件判斷
? if _, errT = io.Copy(md5Encoder, fileT); errT!= nil {
?????? log.Fatal(errT)
? }
? t.Printfln("文件的md5碼:%x", md5Encoder.Sum(nil))
}
代碼 14?3 MD5加密示例
代碼14?3中已經(jīng)有詳盡的解釋,再附加幾點(diǎn)解釋如下:
-> 代碼中用到的md5包中的函數(shù)最主要的是md5.Sum函數(shù),該函數(shù)實(shí)現(xiàn)了將任意一個(gè)字節(jié)切片編碼為MD5,編碼結(jié)果的數(shù)據(jù)類型是[16]byte,即16個(gè)字節(jié)的字節(jié)數(shù)組;
->?由于字節(jié)數(shù)組與字節(jié)切片并不是同一個(gè)類型,所以還需要進(jìn)行轉(zhuǎn)換,將[16]byte類型的數(shù)據(jù)轉(zhuǎn)換為[]byte類型才能符合后面hex.EncodeToString等函數(shù)的傳入?yún)?shù)類型要求;
->?目前一般使用時(shí)都是用MD碼的十六進(jìn)制文本形式,這會(huì)有32個(gè)字符長(zhǎng)度;
->?代碼后面也演示了如何用流式方法來(lái)直接編碼一個(gè)字符串為MD碼以及直接編碼一個(gè)文件;md5.New函數(shù)將新建一個(gè)MD5的編碼器(hash.Hash類型),該編碼器支持流式寫入,寫入完所有內(nèi)容后,調(diào)用該編碼器的Sum函數(shù)就會(huì)進(jìn)行MD5編碼;
代碼14?3的運(yùn)行結(jié)果是:
md5字節(jié)切片(16個(gè)字節(jié)): []byte{0x1f, 0x3c, 0xa0, 0x51, 0x2, 0x8d, 0x1d, 0x1e, 0x95, 0xa6, 0xf4, 0xe2, 0x69, 0xd7, 0x27, 0xab}
md5文本(32位): "1F3CA051028D1D1E95A6F4E269D727AB"
流式編碼的md5文本(小寫): "1f3ca051028d1d1e95a6f4e269d727ab"
文件的md5碼:9da81dede7a381a6d9fecc0cd69a81a9
可以看出,無(wú)論使用普通方式還是流式方式進(jìn)行MD5編碼,對(duì)同樣的輸入字符串的結(jié)果都是一樣的(注意,十六進(jìn)制文本的大小寫一般在各個(gè)系統(tǒng)中都可以識(shí)別,目前的趨勢(shì)更多的是使用小寫形式)。
類似MD5這樣的不可逆加密,也不需要使用密碼就可以直接進(jìn)行加密。
最后順便提一下,任何加密理論上都可以被破解,即使是不可逆的加密算法也是有辦法破解的。MD5的破解方法已經(jīng)在網(wǎng)絡(luò)上有所流傳,實(shí)質(zhì)上是窮舉法的破解方式,就是不停地收集和計(jì)算各種明文的MD5編碼并將這兩者的成對(duì)保存起來(lái),然后根據(jù)保存的海量數(shù)據(jù),對(duì)MD5密文進(jìn)行反查,這需要大量的積累,消耗的資源也較大,查出來(lái)的結(jié)果也可能是多個(gè)(MD5的密文與明文是一對(duì)多的關(guān)系),但確實(shí)也算是一種方法,在此說(shuō)明一下供大家了解。