开发者

Go web中cookie值安全securecookie库使用原理

开发者 https://www.devze.com 2022-12-04 10:58 出处:网络 作者: Go学堂
目录引言securecookie小档案一、安装二、使用示例明文的cookie值输出使用securecookie包对cookie值进行编码使用securecookie对value加密对cookie值进行解码三、实现原理序列化加密base64编码使用hMAC做hash四、beego
目录
  • 引言
  • securecookie小档案
  • 一、安装
  • 二、使用示例
    • 明文的cookie值输出
    • 使用securecookie包对cookie值进行编码
    • 使用securecookie对value加密
    • 对cookie值进行解码
  • 三、实现原理
    • 序列化
    • 加密
    • base64编码
    • 使用hMAC做hash
  • 四、beego框架中的cookie安全
    • 五、总结

      引言

      今天给大家推荐的是web应用安全防护方面的另一个包:securecookie。该包给cookie中存储的敏感信息进行编、解码及解密、解密功能,以保证数据的安全。

      securecookie小档案

      securecookie小档案   
      star595used by-
      contributors19作者Gorilla
      功能简介对cookie中存储的敏感信息进行编码、解码以及加密、解www.devze.com密功能,以保证数据不能被伪造。  
      项目地址github.com/gorilla/sec…  
      相关知识web安全、加密解密、HMAC编码解码、base64编码  

      一、安装

       go get github.com/gorilla/securecookie 
      

      二、使用示例

      明文的cookie值输出

      我们先来看下未进行编码或未加密的cookie输出是什么样的。本文以beego框架为例,当然在beego中已经实现了安全的cookie输出,稍后再看其具体的实现。这里主要是来说明cookie中未编码的输出和使用securecookie包后cookie的值输出。

      package main
      import (
      	"github.com/beego/beego"
      )
      func main() {
      	beego.Router("/", &MainController{})
      	beejsgo.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的输出是明文的。如下:

      Go web中cookie值安全securecookie库使用原理

      使用securecookie包对cookie值进行编码

      securecookie包的使用也很简单。首先使用securecookie.New函数实例化一个securecookie实例,在实例化的时候需要传入一个32位或64位的hashkey值。然后调用securecookie实例的Encode对明文值进行编码即可。如下示例:

      package main
      import (
      	"github.com/beego/beego"
      	"github.com/goriwww.devze.comlla/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值输出结果:

      Go web中cookie值安全securecookie库使用原理

      在调用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不止可以对明文值进行编码,而且还可以对编码后的值进一步加密,使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值输出结果:

      Go web中cookie值安全securecookie库使用原理

      在securecookie包中,是否对cookie值进行加密是可选的。在调用New时,如果第二个参数传nil,则cookie值只进行hash,而不加密。如果给第二个参数传了一个值,即秘钥,则该包还会对hash后的值再进行加密处理。这里需要注意,加密秘钥的长度必须是16字节或32字节,否则会加密失败。

      对cookie值进行解码

      有编码就有解码。在收到请求中的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”。

      Go web中cookie值安全securecookie库使用原理

      这里需要注意的是,解码的时候Decode的第一个参数是cookie的name值。第二个参数才是cookie的value值。这是成对出现的。后面在讲编码的实现原理时会详细讲解。

      三、实现原理

      securecookie包Encode函数的实现主要有两点:加密和hash转换。同样Decode的过程与Encode是相反的。

      Encode函数的实现流程如下:

      Go web中cookie值安全securecookie库使用原理

      序列化

      第一步为什么要把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个参数,那么就会对序列化后的数据加密操作。如下:javascript

      	// 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.javascriptXORKeyStream(value, value)
      	// Return iv + ciphertext.
      	return append(iv, value...), nil
      }
      

      该对称加密算法其实还可以应用其他具有敏感信息的传输中,比如价格信息、密码等。

      base64编码

      经过上述编码(或加密)后的数据实际上是一串字节序列。如果转换成字符串大家可以看到会有乱码的出现。这里的乱码实际上是不可见字符。如果想让不可见字符变成可见字符,最常用的就是使用base64编码。 base64编码是将二进制字节转换成文本的一种编码方式。该编码方式是将二进制字节转换成可打印的asc码。就是先预定义一个可见字符的编码表,参考RFC4648文档。然后将原字符串的二进制字节序列以每6位为一组进行分组,然后再将每组转换成十进制对应的数字,在根据该数字从预定义的编码表中找到对应的字符,最终组成的字符串就是经过base64编码的字符串。在base64编码中有4种模式:

      • base64.StdEncoding:标准模式是依据RFC 4648文档实现的,最终转换成的字符由A到Z、a-z、0-9以及+和 / 符号组成的。
      • base64.URLEncoding: URLEncoding模式最终转成的字符是由A到Z、a-z、0-9以及 - 和 _ 组成的。就是把标准模式中的+和/字符替换成了-和/。因为该模式主要应用于URL地址传输中,而在URL中+和/是保留字符,不能出现,所以讲其做了替换。
      • base64.RawEncoding: 该模式使用的字符集和StdEncoding一样。但该模式是按照位数来的,每6bits换为一个base64字符,就没有在尾部补齐到4的倍数字节了。
      • base64.RawURLEncoding: 该模式使用的字符集和URLEncoding模式一样。同样该模式也是按照位数来的,每6bits换为一个base64字符,就没有在尾部补齐到4的倍数字节了。

      base64编码的具体应用和实现原理大家可参考我的另外一篇文章:

      使用hmac做hash

      简单来讲就是对字符串做了加密的hash转换。在上文中我们提到,加密是可选的,hmac才是必需的。如果没有使用加密,那么经过上述序列化、base64编码后的字符串依然是明文的。所以无论有没有加密,都要做一次hash。这里使用的是内建包crypto/hmac。

      做hmac操作时,不是只对value值进行hash,而是经过了字符串的拼接。实际上是对cookie名、日期、value值三部分进行拼接,并用 "|"隔开进行的:

      Go web中cookie值安全securecookie库使用原理

      代码如下:

      	// 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函数和密钥进行消息认证的方法。其能提供两方面的内容: ① 消息完整性认证:能够证明消息内容在传送过程没有被修改。 ② 信源身份认证:因为通信双方共享了认证的密钥,接收方能够认证发送该数据的信源与所宣称的一致,即能够可靠地确认接收的消息与发送的一致。

      四、beego框架中的cookie安全

      笔者查看了常用的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))
      	tim开发者_JAVA教程estamp := 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库的资料请关注我们其它相关文章!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号