Security

Introduction

虽然互联网在设计时系统是可以抵挡代理人攻击, 它在相对信任的实体环境中成长。可是,这样安全的环境已经一去不复返。垃圾邮件(spam mail),拒绝服务攻击, 网络钓鱼等等,都表示使用互联网不在安全。

应用程序必须建立在恶劣的情况下也能"正常"工作, "正常"不在仅仅是程序功能的正确。必须保证隐私和数据的完整性。仅能是合法的用户才能访问数据。

这就使用你的程序变得复杂。这涉及到复杂和精细的计算问题。如果你自己尝写一个加密的库,这注定会失败。所以你需使用安全专家设计的库。

ISO security architecture

ISO OSI(open system interconnect) 它是众所周知的七层模型描述了分布式系统。如下所示

整个ISO体系结构有很多的文档,我们在这里,只关注ISO 的安全结构模型。ISO 7498-2

Functions and levels

一个安全系统的主要功能要求是:

  • Authentication - 身份证明
  • Data integrity - 数据的完整性,没有被篡改
  • Confidentiality - 保密性,数据不能向它人透露
  • Notarization/signature - 证书和签名
  • Access controll
  • Assurance/availability - 保证/可用性

分别对应OSI协议栈的不同级别

  • Peer entity authentication - 对等实体鉴别(3,4,7)
  • Data origin authentication - 数据源认证(3, 4, 7)
  • Access control service(3, 4, 7)
  • Connection confidentiality -连接的保密性(1,2,3,4,6,7)
  • Connectionless confidentiality (1, 2, 3, 4, 6, 7)
  • Selective field confidentiality - 选择字段加密(6,7)
  • Traffic flow confidentiality - 通信流加密(1, 3, 7)
  • Connection integrity with recovery - 恢复连接完整性(4, 7)
  • Connection integrity without recovery - 不恢复连接完整性 (4, 7)
  • Connection integrity selective field (7)
  • Connectionless integrity selective field (7)
  • Non-repudiation at origin -不可抵赖性源 (7)
  • Non-repudiation of receipt - 不可抵赖接收(7)

Mechanisms

  • Peer entity authentication
    • encryption
    • digital signature
    • authentication exchange
  • Data origin authentication
    • encryption
    • digital signature
  • Access control service
    • access control lists
    • passwords
    • capabilities lists
    • labels
  • Connection confidentiality
    • ecryption
    • routing control
  • Connectionless confidelity
    • encryption
    • routing control
  • Selective field confidelity
    • encryption
  • Traffic flow confidelity
    • encryption
    • traffic padding
    • routing control
  • Connection integrity with recovery
    • encryption
    • data integrity
  • Connection integrity without recovery
    • encryption
    • data integrity
  • Connection integrity selective field
    • encryption
    • data integrity
  • Connectionless integrity
    • encryption
    • digital signature
    • data integrity
  • Connectionless integrity selective field
    • encryption
    • digital signature
    • data integrity
  • Non-repudiation at origin
    • digital signature
    • data integrity
    • notarisation
  • Non-repudiation of receipt
    • digital signature
    • data integrity
    • notarisation

Data integrity

为了保证数据的完整性,意味着我们需要测试数据是否被篡改过. 通常是根据数据中的字节数,生成一个简单的数据。这个处理可以称为hashing(散列法), 生成的结果称为hash or hash value(散列值)

一个最天真的散列算法是数据中所有的字节的总和。可是,它允许改变数据内部,而依旧保持散列值。比如,一个攻击者交换了两个字节的位置。散列值没有被改变,但$65,536变成了$256

所以散列算法用于安全的目的时必须强键。所以它必须增加攻击者找到不同字节序列却有相同hash value的难度。这就使得攻击者难以修改数据。安全研究人员会经常测试hash算法,看它们是否被攻破这些算法——寻找到一种简单的方式来找到跟散列值相匹配的字节序列。他们已经设计出一种cryptographic(加密杂凑)的散列算法,这种算法被人为更加强键。

Go支持多种散列算法,包括MD4, MD5, RIPEMD-160, SHA1, SHA224, SHA256, SHA384以及SHA512. 它们都遵循相同的模式: 即调用相应hash package中的New()函数,返回一个Hash对像。

一个Hash是一个io.Writer, 你写入的数据会被散列到这个writer.你可以通过Size获得散列值的字节数,对过Sum获得散列值。

典型的是MD5散列。它使用md5 package. 它的散列值是一个16字节的数组。通过以16进制ASCII进行打印。每一个为4字节。以下是一个简单的程序

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()
    fmt.Printf("%T\n", hashValue)
    fmt.Printf("%x \n", hashValue)
    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()
}

Output

[]uint8
b1946ac92492d2347c6235b4d2611184 
b1946ac9 2492d234 7c6235b4 d2611184

有一个升级的版本,称为HMAC(Keyed-Hash Message Authentication Code 密钥散列消息认证码). 它可以给散列算法添加密钥。在使用时有一点不同,如下所示

func NewMD5(key []byte) hash.Hash

Symmetric key encryption对称密钥加密

对于数据的加密有两种主要的机制。第一种是在加密和解密时使用单个的key. 在这个加密和解密的代理之间也需要知道这个key. 如何在这些代理之间传输key我们就不在此讨论了。

与散列一样,同样存在主多的加密算法。许多的算法存在弱点,并且随着计算机越来越块,许多算法就变得超来超容易破解。Go支持多个对称密钥加密,比如Bolwfish and DES.

这些算法都是块算法(block). 即它们加密的数据都是块数据,如果你的数据没有对齐块大小block 8字节。则你需要在末尾填充格外的空白。

每一个算法都是创建一个Cipher对像,这通过每一个包的NewCipher方法,这个方法的参数为对称密钥.

一旦你有了cipher对像,你可以使用它来加密和解密块数据。对于blowfish来说,它们是8-bit的块。程序如下

package main

import (
    "bytes"
    "fmt"
    "golang.org/x/crypto/blowfish"
)

func main() {
    key := []byte("my key")
    cipher, err := blowfish.NewCipher(key)

    if err != nil {
        fmt.Println(err.Error())
    }
    src := []byte("hello\n\n\n\n")

    var enc [512]byte
    cipher.Encrypt(enc[0:], src)
    fmt.Println(enc)
    var decrypt [8]byte
    cipher.Decrypt(decrypt[0:], enc[0:])
    result := bytes.NewBuffer(nil)
    result.Write(decrypt[0:8])
    fmt.Println(string(result.Bytes()))
}

Public key encryption

公钥加密和解密需要有两个密钥(key):一个用来加密,另一个用来解密。加密的密钥通常是公开的,任何人都可以使用它来加密信息,然后发送给你。解密密钥必须为私有的,否则其它人就可以解密你的数据.公钥系统是非对称的,不同的key用于不同的阶段。

在Go中支持多种公钥加密系统。一个典型的是RSA模式

以下程序生成一个RSA私钥和公钥

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 modules", 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)
    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.Fprintf(os.Stderr, "Fatal error:%s", err.Error())
        os.Exit(1)
    }
}

在上面的程序中,我们使用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 modules", 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证书

公钥基础设施(public key infrastructure PKI)是一个框架,它集合了所有的公开密钥,以及额外的信息,比如所有者的名字,地址,和它们之间的审批机制。

当前PKI使用的是X.509认证. 比如, web浏览器使用它们来识别网站.

以下是一个例子,用来生成我们网站的X.509证书,并且存储为.cer文件

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: "RAS 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)
    checkErrorh(err)
    inFile.Close()
}

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

读取一个证书

package main

import (
    "crypto/x509"
    "fmt"
    "os"
)

func main() {
    certCerFile, err := os.Open("jan.newmarch.name.cer")
    checkError(err)

    derBytes := make([]byte, 1000)
    count, err := certCerFile.Read(derBytes)
    checkError(err)
    certCerFile.Close()

    //trim the bytes 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 Socket Layer)

在TLS中,客户端和服务器先商议使用X.509来标识身份。当这一步完成后,会在它们之间创建一个安全密钥。所有的加密和解密都使用这个密钥。这个商议的过程非常慢,但一旦创建,一个快速的私人密钥机制就可以使用了

package main

import (
    "crypto/rand"
    "crypto/tls"
    "fmt"
    "net"
    "os"
    "time"
)

func main() {
    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 := ":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)
    }
}
package main

import (
    "crypto/tls"
    "fmt"
    "os"
)

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
        n, 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)
    }
}

此程序会出现: Fatal error x509: certificate signed by unknow authority