go网络编程学习笔记6(安全)

GeminiGirl0604 · · 652 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

简介

尽管互联网最初被设计为可以承受敌对代理攻击的系统,但它过去一直是在一个可信的实体和合作的环境中发展起来的。可惜现在已经时过境迁。垃圾邮件,拒绝服务攻击,网络钓鱼这些东西使得每一个上网者都需要自行承担风险。
应用程序应当在复杂的互联网环境中仍然可以正确工作。“正确”不光意味着程序功能的正确,同时还意味着要确保数据传输过程中的保密性和完整性,甚至只允许合法用户进行访问和其它问题
这自然使得编程更加复杂。在构建安全应用程序的过程中,会出现很复杂和微妙的问题。如果你想自己这样做(如实现一个自有安全库),通常都会以失败而告终。相反,你需要使用安全专家设计的安全库

ISO 安全架构

功能层次

主要的安全系统功能

  • 认证 - 身份校验文件
  • 数据完整性 - 数据不被篡改
  • 保密 - 数据不能暴露给他人
  • 公证/签名
  • 访问控制
  • 保证/可用性

必须的 OSI 协议栈

  • 对等实体认证 (3, 4, 7)
  • 数据源认证 (3, 4, 7)
  • 访问控制服务 (3, 4, 7)
  • 连接保密 (1, 2, 3, 4, 6, 7)
  • 无连接的保密 (1, 2, 3, 4, 6, 7)
  • 选择性字段的保密 (6, 7)
  • 传输保密 (1, 3, 7)
  • 恢复连接的完整性 (4, 7)
  • 不可恢复连接的完整性 (4, 7)
  • 选择字段连接完整性 (7)
  • 选择字段的无连接完整性 (7)
  • 接受源 (7)
  • 接受回执 (7)

机制

  • 对等实体认证
    o 加密
    o 数字签名
    o 交换验证
  • 数据源认证
    o 加密
    o 数字签名
  • 访问控制服务
    o 访问控制列表
    o 密码
    o 范围列表
    o 等级
  • 连接保密
    o 密码
    o 路由控制
  • 无连接的保密
    o 密码
    o 路由控制
  • 选择性字段的保密
    o 密码
  • 传输保密
    o 密码
    o 传输填充
    o 路由控制
  • 恢复连接的完整性
    o 密码
    o 数据完整性
  • 不可恢复连接的完整性
    o 密码
    o 数据完整性
  • 选择字段连接完整性
    o 密码
    o 数据完整性
  • 无连接完整性
    o 密码
    o 数字签名
    o 数据完整性
  • 选择字段的无连接完整性
    o 密码
    o 数字签名
    o 数据完整性
  • 接受源
    o 数字签名
    o 数据完整性
    o 公正
  • 接受回执
    o 数字签名
    o 数据完整性
    o 公正

数据完整性

确保数据的完整性意味着要提供一个数据未被篡改的测试方法。通常是通过字节数据生成一个简单的数字。这个操作被称为hashing,结果数字称为 hash 或者 hash 值
有一个幼稚的 hash 算法是将数据所有的字节进行总和。然而,这却仍然允许数据在保留 hash值不变的情况下对数据进行任意改变。例如,攻击者只需要交换两个字节。这样 hash 值没有改变,但结果可能是你本来欠别人 256 美元却变成了 65535 美元。
用于安全目的的 hash 算法必须很“强”,这样攻击者才很难在保留相同的 hash 值时找到一个不同的字节序列。这样攻击者才很难修改数据以达到目的。安全研究人员不断测试是否能攻破 hash 算法 - 寻找一个简单方法,得到一个字节序列来匹配某个 hash 值。他们设计了一系列被认为很强的加密 hash 算法.
Go 支持几个 hash 算法,包括 MD4, MD5, RIPEMD-160, SHA1, SHA224, SHA256, SHA384 andSHA512。它们都尽可能按照 Go 程序员关注的,遵循相同的模式:在适当的包中定义 New或类似的方法,返回一个 hash 包中的 Hash 对象
一个 Hash 结构体拥有一个io.Writer 接口,你可以通过 writer 方法写入被 hash 的数据.你可以通过 Size方法获取 hash 值的长度Sum 方法返回 hash 值
MD5 算法是个典型的例子。使用md5 包,hash 值是一个 16 位的数组。通常以 ASCII 形式输出四个由 4 字节组成的十六进制数。程序如下

/* MD5Hash
*/

package main

import (
    "crypto/md5"
    "fmt"
)
 
func main() {
    hash := md5.New()
    bytes := []byte("hello\n")
    hash.Write(bytes)
    hashValue := hash.Sum(nil)
    hashSize := hash.Size()
    for n := 0; n < hashSize; n += 4 {
        var val uint32
        val = uint32(hashValue[n])<<24
            uint32(hashValue[n+1])<<16 +
            uint32(hashValue[n+2])<<8 +
            uint32(hashValue[n+3])
        fmt.Printf("%x ", val)
    }
    fmt.Println()
}

输出 b1946ac9 2492d234 7c6235b4 d2611184
在此基础上的一个变化是 HMAC(Keyed-Hash Message Authentication Code),它给 hash 算法增加了一个 key。使用时略有不同。要和 key 一起使用 MD5 算法时,可以通过以下形式替换New

func NewMD5(key []byte) hash.Hash 

key 对称加密

数据加密有两种机制。第一种方式是在加密和解密时都使用同一个 key。加密方和解密方都需要知道这个 key。此处如何在这两者之间传输这个 key。
目前有很多使用 hash 算法的加密算法。其中很多都有弱点,而且随着时间的推移,计算机越来越快,通用 hash 算法变得越来越弱。Go 已经支持好几个对称加密算法,如 BlowfishDES
这些算法都是 block 算法。因为它们必须基于数据块(block)。如果你的数据不匹配 block 的大小,那就必须在最后使用空格来填充多余的空间
每个算法都被表示为一个 Cipher 对象。可通过在相应的包中使用对称 key 作为参数调用NewCipher 方法来创建该对象
创建 cipher 对象后,就能通过它加密和解密数据块。Blowfish 需要 8 位的 block,详见以下程序

/* Blowfish
*/

package main

import ( 
    "bytes"
    "golang.org/x/crypto/blowfish"
    "fmt"
)
 
func main() {
    //创建Cipher对象
    key := []byte("my key")
    cipher, err := blowfish.NewCipher(key)
    if err != nil {
        fmt.Println(err.Error())
    }

    src := []byte("hello\n\n\n")
      
    //加密
    var enc [512]byte
    cipher.Encrypt(enc[0:], src)
 
    //解密
    var decrypt [8]byte
    cipher.Decrypt(decrypt[0:], enc[0:])
      
    result := bytes.NewBuffer(nil)
    result.Write(decrypt[0:8])
    fmt.Println(string(result.Bytes()))
 } 

Blowfish 不在 GO 1 中,而是在golang.org/x/中。你可以在需要使用它的源码目录下运行“go get”进行安装。
这里跟书上不一致,书上那个路径无法安装

公钥加密

公钥加密和解密需要两个 key:一个用来加密,另一个用来解密。加密 key 通常是公开的,这样任何人都可以给你发送加密数据。解密 key 必须保密,否则任何人都可以解密数据。公钥系统是非对称,不同的 key 有不同的用途。
Go 支持很多公钥加密系统,RSA就是一个典型的例子。
下面是一个生成 RSA 公钥和私钥的程序

/* GenRSAKeys
*/

package main

import (
   "crypto/rand"
   "crypto/rsa"
   "crypto/x509"
   "encoding/gob"
   "encoding/pem"
   "fmt" 
   "os"
)

func main() {
   reader := rand.Reader
   bitSize := 512
   key, err := rsa.GenerateKey(reader, bitSize)
   checkError(err)

   fmt.Println("Private key primes", key.Primes[0].String(), key.Primes[1].String())
   fmt.Println("Private key exponent", key.D.String())

   publicKey := key.PublicKey
   fmt.Println("Public key modulus", publicKey.N.String())
   fmt.Println("Public key exponent", publicKey.E)

   saveGobKey("private.key", key)
   saveGobKey("public.key", publicKey)

   savePEMKey("private.pem", key)
}

func saveGobKey(fileName string, key interface{}) {
   outFile, err := os.Create(fileName)
   checkError(err)
   encoder:= gob.NewEncoder(outFile)
   err = encoder.Encode(key)
   checkError(err)
   outFile.Close() 
}

func savePEMKey(fileName string, key *rsa.PrivateKey) {
   outFile, err := os.Create(fileName)
   checkError(err)

   var privateKey = &pem.Block{Type: "RSA PRIVATE KEY",
       Bytes: x509.MarshalPKCS1PrivateKey(key)}

   pem.Encode(outFile, privateKey)
   outFile.Close()
}

func checkError(err error) {
   if err != nil {
       fmt.Println("Fatal error ", err.Error())
       os.Exit(1)
   }
} 

结果:

Private key primes 110447466565545308568164154050664723270540238389548895971109016198476127576859 98755224833317938999382105964603507460774905292171139250410857315173921679329
Private key exponent 459510764742620794664451847126349205280233225231873265875659066190282785510957283637157196363689167630079521868520347867735685374968042398806722557092545
Public key modulus 10907264392950792835901550418369991983502587787765765384169890699352612428117415829901964302612687784931700029077242737891886577847347776552469164799047611
Public key exponent 65537

程序通过 gob 序列化保存证书。也可以取回证书:

/* LoadRSAKeys
*/

package main 

import (
    "crypto/rsa"
    "encoding/gob"
    "fmt"
    "os"
)
 
func main() {
    var key rsa.PrivateKey
    loadKey("private.key", &key)
 
    fmt.Println("Private key primes", key.Primes[0].String(), key.Primes[1].String())
    fmt.Println("Private key exponent", key.D.String())
 
    var publicKey rsa.PublicKey
    loadKey("public.key", &publicKey)
 
    fmt.Println("Public key modulus", publicKey.N.String())
    fmt.Println("Public key exponent", publicKey.E)
 }
 
func loadKey(fileName string, key interface{}) {
    inFile, err := os.Open(fileName)
    checkError(err)
    decoder := gob.NewDecoder(inFile)
    err = decoder.Decode(key)
    checkError(err)
    inFile.Close() 
}
 
func checkError(err error) {
    if err != nil {
    fmt.Println("Fatal error ", err.Error())
    os.Exit(1)
    }
}

X.509 证书

公钥基础架构(PKI)是一个公钥集合框架,它连同附加信息,如所有者名称和位置,以及它们之间的联系提供了一些审批机制。
目前主要使用的 PKI 是就是基于 X.509 证书的。例如浏览器使用它验证站点的身份。
下面的程序是为自己的站点生成自签名 X.509 证书并保存到一个.cer 文件中

/* GenX509Cert
*/ 

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "crypto/x509/pkix"
    "encoding/gob"
    "encoding/pem"
    "fmt"
    "math/big"
    "os"
    "time"
)
 
func main() {
    random := rand.Reader
 
    var key rsa.PrivateKey
    loadKey("private.key", &key)
 
    now := time.Now()
    then := now.Add(60 * 60 * 24 * 365 * 1000 * 1000 * 1000) // one year
    template := x509.Certificate{
        SerialNumber: big.NewInt(1),
        Subject: pkix.Name{
            CommonName: "jan.newmarch.name",
            Organization: []string{"Jan Newmarch"}, 
        },
        // NotBefore: time.Unix(now, 0).UTC(),
        // NotAfter: time.Unix(now+60*60*24*365, 0).UTC(),
        NotBefore: now,
        NotAfter: then,
 
        SubjectKeyId: []byte{1, 2, 3, 4},
        KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
        BasicConstraintsValid: true,
        IsCA: true,
        DNSNames: []string{"jan.newmarch.name", "localhost"},
    }
    derBytes, err := x509.CreateCertificate(random, &template,
        &template, &key.PublicKey, &key)
    checkError(err)
 
    certCerFile, err := os.Create("jan.newmarch.name.cer")
    checkError(err)
    certCerFile.Write(derBytes)
    certCerFile.Close()
 
    certPEMFile, err := os.Create("jan.newmarch.name.pem")
    checkError(err)
    pem.Encode(certPEMFile, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
    certPEMFile.Close()
 
    keyPEMFile, err := os.Create("private.pem") 
    checkError(err)
    pem.Encode(keyPEMFile, &pem.Block{Type: "RSA PRIVATE KEY",
        Bytes: x509.MarshalPKCS1PrivateKey(&key)})
    keyPEMFile.Close()
 }
 
func loadKey(fileName string, key interface{}) {
    inFile, err := os.Open(fileName)
    checkError(err)
    decoder := gob.NewDecoder(inFile)
    err = decoder.Decode(key)
    checkError(err)
    inFile.Close()
 }
 
func checkError(err error) {
    if err != nil {
    fmt.Println("Fatal error ", err.Error())
    os.Exit(1)
    }
} 

下面这个程序可以读取这个证书

/* GenX509Cert
*/

package main 

import (
    "crypto/x509"
    "fmt"
    "os"
)
 
func main() {
    certCerFile, err := os.Open("jan.newmarch.name.cer")
    checkError(err)
    derBytes := make([]byte, 1000) // bigger than the file
    count, err := certCerFile.Read(derBytes)
    checkError(err)
    certCerFile.Close()
 
    // trim the bytes to actual length in call to actual length in call
    cert, err := x509.ParseCertificate(derBytes[0:count])
    checkError(err)
 
    fmt.Printf("Name %s\n", cert.Subject.CommonName)
    fmt.Printf("Not before %s\n", cert.NotBefore.String())
    fmt.Printf("Not after %s\n", cert.NotAfter.String())
 }
 
func checkError(err error) {
    if err != nil {
    fmt.Println("Fatal error ", err.Error())
    os.Exit(1) 
    }
}

TLS

如果自己实现所有的细节,加解密的方案在使用上是有限制的。当前互联网上最流行的加密消息传输方案是 TLS(Transport Layer Security 安全传输层),其前身为 SSL(Secure Sockets Layer安全套接字层)
在 TLS 中,客户端和服务器之间使用 X.509 证书进行身份验证。身份验证完成后,两者之间会生成一个密钥,所有的加密和解密过程都使用这个密钥。虽然客户端和服务端协商的过程相对较慢,但一旦完成就会使用一个较快的私钥机制
服务器端程序

/* TLSEchoServer
*/
package main

import ( 
    "crypto/rand"
    "crypto/tls"
    "fmt"
    "net"
    "os"
    "time"
)
 
func main() {
    //加载X509秘钥对
    cert, err := tls.LoadX509KeyPair("jan.newmarch.name.pem", "private.pem")
    checkError(err)
    config := tls.Config{Certificates: []tls.Certificate{cert}}
 
    now := time.Now()
    config.Time = func() time.Time{ return now }
    config.Rand = rand.Reader
 
    service := "0.0.0.0:1200"
 
    listener, err := tls.Listen("tcp", service, &config)
    checkError(err)
    fmt.Println("Listening")
    for{
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println(err.Error())
            continue
        } 
        fmt.Println("Accepted")
        go handleClient(conn)
    }
}
 
func handleClient(conn net.Conn) {
    defer conn.Close()
 
    var buf [512]byte
    for{
        fmt.Println("Trying to read")
        n, err := conn.Read(buf[0:])
        if err != nil {
            fmt.Println(err)
        }
        _, err2 := conn.Write(buf[0:n])
        if err2 != nil {
            return
        }
    }
}
 
func checkError(err error) {
    if err != nil {
        fmt.Println("Fatal error ", err.Error())
        os.Exit(1)
    }
} 

与服务器端程序对应的客户端程序:

/* TLSEchoClient
*/
package main

import (
    "fmt"
    "os"
    "crypto/tls"
)
 
func main() {
    if len(os.Args) != 2 {
        fmt.Println("Usage: ", os.Args[0], "host:port")
        os.Exit(1)
    }
    service := os.Args[1]
 
    conn, err := tls.Dial("tcp", service, nil)
    checkError(err)
 
    for n := 0; n < 10; n++ {
        fmt.Println("Writing...")
        conn.Write([]byte("Hello "+ string(n+48)))
 
        var buf [512]byte
        err := conn.Read(buf[0:])
        checkError(err) 
 
        fmt.Println(string(buf[0:n]))
    }
    os.Exit(0)
}
 
func checkError(err error) {
    if err != nil {
        fmt.Println("Fatal error ", err.Error())
        os.Exit(1)
    }
} 

结论

安全本身是一个巨大的领域,在本章中,我们几乎没有触及,但已经覆盖了主要的概念。尚未强调的是,需要在设计阶段如何考虑安全构建:亡羊补牢几乎是没有意义的。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:GeminiGirl0604

查看原文:go网络编程学习笔记6(安全)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

652 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传