<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
今天給大家推薦的是web應用安全防護方面的另一個包:securecookie。該包給cookie中儲存的敏感資訊進行編、解碼及解密、解密功能,以保證資料的安全。
securecookie小檔案 | |||
---|---|---|---|
star | 595 | used by | - |
contributors | 19 | 作者 | Gorilla |
功能簡介 | 對cookie中儲存的敏感資訊進行編碼、解碼以及加密、解密功能,以保證資料不能被偽造。 | ||
專案地址 | github.com/gorilla/sec… | ||
相關知識 | web安全、加密解密、HMAC編碼解碼、base64編碼 |
go get github.com/gorilla/securecookie
我們先來看下未進行編碼或未加密的cookie輸出是什麼樣的。本文以beego框架為例,當然在beego中已經實現了安全的cookie輸出,稍後再看其具體的實現。這裡主要是來說明cookie中未編碼的輸出和使用securecookie包後cookie的值輸出。
package main import ( "github.com/beego/beego" ) func main() { beego.Router("/", &MainController{}) beego.RunWithMiddleWares(":8080") } type MainController struct { beego.Controller } func (this *MainController) Get() { this.Ctx.Output.Cookie("userid", "1234567") this.Ctx.Output.Body([]byte("Hello World")) }
執行go run main.go,然後在瀏覽器中輸入http://localhost:8080/,檢視cookie的輸出是明文的。如下:
securecookie包的使用也很簡單。首先使用securecookie.New函數範例化一個securecookie範例,在範例化的時候需要傳入一個32位元或64位元的hashkey值。然後呼叫securecookie範例的Encode對明文值進行編碼即可。如下範例:
package main import ( "github.com/beego/beego" "github.com/gorilla/securecookie" ) func main() { beego.Router("/", &MainController{}) beego.RunWithMiddleWares(":8080") } type MainController struct { beego.Controller } func (this *MainController) Get() { // Hash keys should be at least 32 bytes long var hashKey = []byte("keep-it-secret-keep-it-safe-----") // 範例化securecookie var s = securecookie.New(hashKey, nil) name := "userid" value := "1234567" // 對value進行編碼 encodeValue, _ := s.Encode(name, value) // 輸出編碼後的cookie值 this.Ctx.Output.Cookie(name, encodeValue) this.Ctx.Output.Body([]byte("Hello World")) }
以下是經過securecookie編碼後的cookie值輸出結果:
在呼叫securecookie.New時,第一個引數hashKey是必須的,推薦使用32位元組或64位元組長度的key。因為securecookie底層編碼時是使用HMAC演演算法實現的,hmac演演算法在對資料進行雜湊操作時會進行加密。
securecookie包不僅支援對字串的編碼和加密。還支援對結構體及自定義型別進行編碼和加密。下面範例是對一個map[string]string型別進行編/解碼的範例。
package main import ( "fmt" "github.com/beego/beego" "github.com/gorilla/securecookie" ) func main() { beego.Router("/", &MainController{}) beego.RunWithMiddleWares(":8080") } type MainController struct { beego.Controller } func (this *MainController) Get() { // Hash keys should be at least 32 bytes long var hashKey = []byte("keep-it-secret-keep-it-safe-----") // Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long. // Shorter keys may weaken the encryption used. var blockKey = []byte("1234567890123456") // 範例化securecookie var s = securecookie.New(hashKey, blockKey) value := map[string]string{ "id": "1234567", } name := "userid" //value := "1234567" // encodeValue, err := s.Encode(name, value) fmt.Println("encodeValue:", encodeValue, err) // 解析到decodeValue中 decodeValue := make(map[string]string) s.Decode(name, encodeValue, &decodeValue) fmt.Println("decodeValue:", decodeValue) this.Ctx.Output.Cookie(name, encodeValue) this.Ctx.Output.Body([]byte("Hello World")) }
當然,其他型別也是支援的。大家有興趣的可以自行看下原始碼。
securecookie不止可以對明文值進行編碼,而且還可以對編碼後的值進一步加密,使value值更安全。加密也很簡單,就是在呼叫securecookie.New的時候傳入第二個引數:加密祕鑰即可。如下:
// Hash keys should be at least 32 bytes long var hashKey = []byte("keep-it-secret-keep-it-safe-----") // Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long. // Shorter keys may weaken the encryption used. var blockKey = []byte("1234567890123456") // 範例化securecookie var s = securecookie.New(hashKey, blockKey) name := "userid" value := "1234567" encodeValue, err := s.Encode(name, value)
以下是經過securecookie加密後的cookie值輸出結果:
在securecookie包中,是否對cookie值進行加密是可選的。在呼叫New時,如果第二個引數傳nil,則cookie值只進行hash,而不加密。如果給第二個引數傳了一個值,即祕鑰,則該包還會對hash後的值再進行加密處理。這裡需要注意,加密祕鑰的長度必須是16位元組或32位元組,否則會加密失敗。
有編碼就有解碼。在收到請求中的cookie值後,就可以使用相同的securecookie範例對cookie值進行解碼了。如下:
package main import ( "fmt" "github.com/beego/beego" "github.com/gorilla/securecookie" ) func main() { beego.Router("/", &MainController{}) beego.RunWithMiddleWares(":8080") } type MainController struct { beego.Controller } func (this *MainController) Get() { // Hash keys should be at least 32 bytes long var hashKey = []byte("keep-it-secret-keep-it-safe-----") // Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long. // Shorter keys may weaken the encryption used. var blockKey = []byte("1234567890123456") // 範例化securecookie var s = securecookie.New(hashKey, blockKey) encodeValue := this.Ctx.GetCookie("userid") value := "" s.Decode("userid", encodeValue, &value) fmt.Println("decode value is :", value, encodeValue) this.Ctx.Output.Cookie("userid", value) this.Ctx.Output.Body([]byte("Hello World")) }
該範例是我們把上次加密的cookie值傳送給本次請求,伺服器端進行解碼後寫入到cookie中。本次輸出正好是明文“1234567”。
這裡需要注意的是,解碼的時候Decode的第一個引數是cookie的name值。第二個引數才是cookie的value值。這是成對出現的。後面在講編碼的實現原理時會詳細講解。
securecookie包Encode函數的實現主要有兩點:加密和hash轉換。同樣Decode的過程與Encode是相反的。
Encode函數的實現流程如下:
第一步為什麼要把value值進行序列化呢?我們看securecookie.Encode介面,如下:
func (s *SecureCookie) Encode(name string, value interface{}) (string, error)
我們知道cookie中的值是key-value形式的。這裡name就是cookie中的key,value是cookie中的值。我們注意到value的型別是interface{}介面,也就是說value可以是任意資料型別(結構體,map,slice等)。但cookie中的value只能是字串。所以,Encode的第一步就是把value值進行序列化。
序列化有兩種方式,分別是內建的包encoding/json和encoding/gob。securecookie包預設使用gob包進行序列化:
func (e GobEncoder) Serialize(src interface{}) ([]byte, error) { buf := new(bytes.Buffer) enc := gob.NewEncoder(buf) if err := enc.Encode(src); err != nil { return nil, cookieError{cause: err, typ: usageError} } return buf.Bytes(), nil }
知識點:encoding/json和encoding/gob的區別:gob包比json包生成的序列化資料體積更小、效能更高。但gob序列化的資料只適用於go語言編寫的程式之間傳遞(編碼/解碼)。而json包適用於任何語言程式之間的通訊。
如果在編碼過程中想使用json對value值進行序列化,那麼可以通過SetSerialize方法進行設定,如下:
cookie := securecookie.New([]byte("keep-it-secret-keep-it-safe-----") cookie.SetSerializer(securecookie.JSONEncoder{})
加密是可選的。如果在呼叫secrecookie.New的時候指定了第2個引數,那麼就會對序列化後的資料加密操作。如下:
// 2. Encrypt (optional). if s.block != nil { if b, err = encrypt(s.block, b); err != nil { return "", cookieError{cause: err, typ: usageError} } }
加密使用的AES對稱加密。在Go的內建包crypto/aes中。該包有5種加密模式,5種模式之間採用的分塊演演算法不同。有興趣的同學可以自行深入研究。而securecookie包採用的是CTR模式。如下是加密相關程式碼:
func encrypt(block cipher.Block, value []byte) ([]byte, error) { iv := GenerateRandomKey(block.BlockSize()) if iv == nil { return nil, errGeneratingIV } // Encrypt it. stream := cipher.NewCTR(block, iv) stream.XORKeyStream(value, value) // Return iv + ciphertext. return append(iv, value...), nil }
該對稱加密演演算法其實還可以應用其他具有敏感資訊的傳輸中,比如價格資訊、密碼等。
經過上述編碼(或加密)後的資料實際上是一串位元組序列。如果轉換成字串大家可以看到會有亂碼的出現。這裡的亂碼實際上是不可見字元。如果想讓不可見字元變成可見字元,最常用的就是使用base64編碼。 base64編碼是將二進位制位元組轉換成文字的一種編碼方式。該編碼方式是將二進位制位元組轉換成可列印的asc碼。就是先預定義一個可見字元的編碼表,參考RFC4648檔案。然後將原字串的二進位制位元組序列以每6位為一組進行分組,然後再將每組轉換成十進位制對應的數位,在根據該數位從預定義的編碼表中找到對應的字元,最終組成的字串就是經過base64編碼的字串。在base64編碼中有4種模式:
base64編碼的具體應用和實現原理大家可參考我的另外一篇文章:
簡單來講就是對字串做了加密的hash轉換。在上文中我們提到,加密是可選的,hmac才是必需的。如果沒有使用加密,那麼經過上述序列化、base64編碼後的字串依然是明文的。所以無論有沒有加密,都要做一次hash。這裡使用的是內建包crypto/hmac。
做hmac操作時,不是隻對value值進行hash,而是經過了字串的拼接。實際上是對cookie名、日期、value值三部分進行拼接,並用 "|"隔開進行的:
程式碼如下:
// 3. Create MAC for "name|date|value". Extra pipe to be used later. b = []byte(fmt.Sprintf("%s|%d|%s|", name, s.timestamp(), b)) mac := createMac(hmac.New(s.hashFunc, s.hashKey), b[:len(b)-1]) // Append mac, remove name. b = append(b, mac...)[len(name)+1:] // 4. Encode to base64. b = encode(b)
這裡將name值拼接進字串是因為在加碼驗證的時候可以對key-value對進行驗證,說明該value是屬於該name值的。 將時間戳拼接進去,主要是為了對cookie的有效期做驗證。在解密後,用當前時間和字串中的時間做比較,就能知道該cookie值是否已經過期了。
最後,將經過hmac的hash值除去name值後再和b進行拼接。拼接完,為了在url中傳輸,所以再做一次base64的編碼。
相關知識:HMAC是金鑰相關的雜湊運算訊息鑑別碼(Hash-based Message Authentication Code)的縮寫,由H.Krawezyk,M.Bellare,R.Canetti於1996年提出的一種基於Hash函數和金鑰進行訊息認證的方法。其能提供兩方面的內容: ① 訊息完整性認證:能夠證明訊息內容在傳送過程沒有被修改。 ② 信源身份認證:因為通訊雙方共用了認證的金鑰,接收方能夠認證傳送該資料的信源與所宣稱的一致,即能夠可靠地確認接收的訊息與傳送的一致。
筆者檢視了常用的web框架echo、gin、beego,發現只有在beego框架中整合了安全的cookie設定。但也只實現了用hmac演演算法對value值和時間戳做加密hash。該實現在Controller的SetSecureCookie函數中,如下:
// SetSecureCookie puts value into cookie after encoded the value. func (c *Controller) SetSecureCookie(Secret, name, value string, others ...interface{}) { c.Ctx.SetSecureCookie(Secret, name, value, others...) } // SetSecureCookie Set Secure cookie for response. func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) { vs := base64.URLEncoding.EncodeToString([]byte(value)) timestamp := strconv.FormatInt(time.Now().UnixNano(), 10) h := hmac.New(sha256.New, []byte(Secret)) fmt.Fprintf(h, "%s%s", vs, timestamp) sig := fmt.Sprintf("%02x", h.Sum(nil)) cookie := strings.Join([]string{vs, timestamp, sig}, "|") ctx.Output.Cookie(name, cookie, others...) }
經過securecookie編碼過的cookie值是不會被偽造的,因為該值是經過hmac進行編碼的。而且還可以對編碼過的值再進行一次對稱加密。如果是敏感資訊的話,建議不要儲存在cookie中。同時,敏感的資訊也一定使用https進行傳輸,以降低洩露的風險。
以上就是Go web中cookie值安全securecookie庫使用原理的詳細內容,更多關於Go web securecookie庫的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45