前兩節(jié)介紹的加密方法都屬于“可逆”的加密算法,因?yàn)榧用芎蟮拿芪慕?jīng)過解密的過程就可以還原出原文册招。還有一類加密算法屬于“不可逆”的加密算法岔激,是指一般無法進(jìn)行正常解密還原出原文的加密算法。
不可逆加密算法的應(yīng)用場景也有很多是掰,最典型的是服務(wù)端保存用戶登錄密碼的方式虑鼎。從保護(hù)用戶隱私角度考慮,服務(wù)器端不應(yīng)該存儲用戶的登錄密碼原文键痛,也不應(yīng)該保存可逆加密后的密文(因?yàn)橐部梢员唤饷苓€原)炫彩,這時(shí)候?qū)嵺`中常用的方法是在服務(wù)器保存密碼時(shí)將其用不可逆的算法轉(zhuǎn)換成密文存儲,下次用戶登錄時(shí)服務(wù)器將發(fā)來的用戶密碼以同樣的不可逆算法加密后再與保存的密碼密文做比對來判斷是否相同絮短。
從這個例子也可以看出江兢,不可逆加密算法一般要滿足這個要求:同樣的明文用同樣的方式加密后,得到的密文必須是一樣丁频,否則就無法用于比對了杉允。
最簡單的不可逆加密算法可能就是取模算法了,例如18 % 5 的結(jié)果是3席里,而23 % 5的結(jié)果也是3叔磷,如果18和23代表明文,“% 5”這個取模操作(也就是求對于5的余數(shù))代表加密算法奖磁,3代表加密后的密文改基,那么顯然這個加密算法是不可逆的,因?yàn)閺拿芪摹?”無法確定原文是18還是23(甚至還可能是其他更多的數(shù)字)署穗,而任何數(shù)字對5取模的結(jié)果都是不變的寥裂。那么該算法如果用于對密碼加密,可以將密碼轉(zhuǎn)換后的字節(jié)切片中的每個字節(jié)數(shù)值對5取模后形成新的密文字節(jié)切片案疲,服務(wù)器端保存這個密文就可以了封恰,以后用戶登錄時(shí)傳上來的密碼做同樣的加密操作,得到的結(jié)果與保存的密文應(yīng)該是一致的褐啡。
當(dāng)然诺舔,取模操作作為加密方法來說加密強(qiáng)度太低,實(shí)際應(yīng)用中用的最多的不可逆加密算法是MD5算法,該算法應(yīng)用了散列函數(shù)的原理低飒,可以將任何數(shù)據(jù)轉(zhuǎn)換成固定長度的字節(jié)序列(一般是16個字節(jié))许昨,并且與取模操作一樣具有原文對應(yīng)到密文的唯一性。
取模操作也可以看做散列函數(shù)的一種褥赊,可以將任何數(shù)據(jù)散列到[0, n)的范圍內(nèi)(n是取模的模數(shù))糕档。散列函數(shù)一般都具備這樣的特性:原始數(shù)據(jù)與散列后的數(shù)據(jù)是多對一的關(guān)系,即同樣的原始數(shù)據(jù)用同樣的散列函數(shù)處理后必然得到同樣的結(jié)果拌喉,但同樣的結(jié)果不一定對應(yīng)同一個原始數(shù)據(jù)速那。這也是為什么大多數(shù)不可逆算法使用散列函數(shù)作為核心方法的原因。
下面的代碼演示了如何使用Go語言中的crypto/md5等包來實(shí)現(xiàn)MD5加密算法尿背。
package main
import (
? "crypto/md5"
? "encoding/hex"
? "io"
? "log"
? "os"
? "strings"
? t "github.com/topxeq/goexamples/tools"
)
func main() {
? //原始字符串
? originalTextT := "測試字符串"
? //對原始字符串生成md5碼端仰,md5BytesArrayT是[16]byte類型的數(shù)組
? md5BytesArrayT :=md5.Sum([]byte(originalTextT))
? //將數(shù)組轉(zhuǎn)換為切片
? md5BytesT := md5BytesArrayT[:]
? t.Printfln("md5字節(jié)切片(16個字節(jié)): %#v", md5BytesT)
? //將md5字節(jié)切片轉(zhuǎn)換為16進(jìn)制的文本,將有32個字符
? //并轉(zhuǎn)換為全大寫字母
? md5TextT := strings.ToUpper(hex.EncodeToString(md5BytesT))
? t.Printfln("md5文本(32位): %#v", md5TextT)
? //用流式方法對字符串進(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)
? //直接用流式方法對一個文件進(jìn)行md5編碼
? fileT, errT := os.Open(`c:\test\long.txt`)
? if errT != nil {
?????? log.Fatal(errT)
? }
? defer fileT.Close()
? md5Encoder = md5.New()
? //帶有初始化語句的條件判斷
? 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)了將任意一個字節(jié)切片編碼為MD5汽久,編碼結(jié)果的數(shù)據(jù)類型是[16]byte鹤竭,即16個字節(jié)的字節(jié)數(shù)組;
->?由于字節(jié)數(shù)組與字節(jié)切片并不是同一個類型景醇,所以還需要進(jìn)行轉(zhuǎn)換诺擅,將[16]byte類型的數(shù)據(jù)轉(zhuǎn)換為[]byte類型才能符合后面hex.EncodeToString等函數(shù)的傳入?yún)?shù)類型要求;
->?目前一般使用時(shí)都是用MD碼的十六進(jìn)制文本形式啡直,這會有32個字符長度烁涌;
->?代碼后面也演示了如何用流式方法來直接編碼一個字符串為MD碼以及直接編碼一個文件;md5.New函數(shù)將新建一個MD5的編碼器(hash.Hash類型)酒觅,該編碼器支持流式寫入撮执,寫入完所有內(nèi)容后,調(diào)用該編碼器的Sum函數(shù)就會進(jìn)行MD5編碼舷丹;
代碼14?3的運(yùn)行結(jié)果是:
md5字節(jié)切片(16個字節(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
可以看出抒钱,無論使用普通方式還是流式方式進(jìn)行MD5編碼,對同樣的輸入字符串的結(jié)果都是一樣的(注意颜凯,十六進(jìn)制文本的大小寫一般在各個系統(tǒng)中都可以識別谋币,目前的趨勢更多的是使用小寫形式)。
類似MD5這樣的不可逆加密症概,也不需要使用密碼就可以直接進(jìn)行加密蕾额。
最后順便提一下胁附,任何加密理論上都可以被破解谍珊,即使是不可逆的加密算法也是有辦法破解的。MD5的破解方法已經(jīng)在網(wǎng)絡(luò)上有所流傳桑包,實(shí)質(zhì)上是窮舉法的破解方式,就是不停地收集和計(jì)算各種明文的MD5編碼并將這兩者的成對保存起來调炬,然后根據(jù)保存的海量數(shù)據(jù)语盈,對MD5密文進(jìn)行反查,這需要大量的積累缰泡,消耗的資源也較大刀荒,查出來的結(jié)果也可能是多個(MD5的密文與明文是一對多的關(guān)系),但確實(shí)也算是一種方法棘钞,在此說明一下供大家了解照棋。