Security

安全

Introduction

简介

Although the internet was originally designed as a system to withstand atacks by hostile agents, it developed in a co-operative environment of relatively trusted entities. Alas, those days are long gone. Spam mail, denial of service attacks, phishing attempts and so on are indicative that anyone using the internet does so at their own risk.

尽管互联网最初被设计为可以承受敌对代理攻击的系统,但它过去一直是在一个可信的实体和合作的环境中发展起来的。可惜现在已经时过境迁。垃圾邮件,拒绝服务攻击,网络钓鱼这些东西使得每一个上网者都需要自行承担风险。

Applications have to be built to work correctly in hostile situations. "correctly" no longer means just getting the functional aspects of the program correct, but also means ensuring privacy and integrity of data transferred, access only to legitimate users and other issues.

应用程序应当在复杂的互联网环境中仍然可以正确工作。“正确”不光意味着程序功能的正确,同时还意味着要确保数据传输过程中的保密性和完整性,甚至只允许合法用户进行访问和其它问题。

This of course makes your programs much more complex. There are difficult and subtle computing problems involved in making applications secure. Attempts to do it yourself (such as making up your own encryption libraries) are usually doomed to failure. Instead, you need to make use of libraries designed by security professionals

这自然使得编程更加复杂。在构建安全应用程序的过程中,会出现很复杂微妙的问题。如果你想自己这样做(如实现一个自有安全库),通常都会以失败而告终。相反,你需要使用安全专家设计的安全库。

ISO security architecture

ISO 安全架构

The ISO OSI (open systems interconnect) seven-layer model of distributed systems is well known and is repeated in this figure:

ISO OSI(开放系统互连)七层模型分布式系统是众所周知的,在此重复如下图:

What is less well known is that ISO built a whole series of documents upon this architecture. For our purposes here, the most important is the ISO Security Architecture model, ISO 7498-2.

少为人知的是,ISO在此架构的基础上建立了一系列完整的文档。而我们这里最重要的是ISO安全体系结构模型(ISO Security Architecture model)ISO 7498-2。

Functions and levels

功能层次

The principal functions required of a security system are

主要的安全系统功能

These are required at the following levels of the OSI stack:

必须的OSI协议栈

Mechanisms

机制

Data integrity

数据完整性

Ensuring data integrity means supplying a means of testing that the data has not been tampered with. Usually this is done by forming a simple number out of the bytes in the data. This process is called hashing and the resulting number is called a hash or hash value.

确保数据的完整性意味着要提供一个数据未被篡改的测试方法。通常是通过字节数据生成一个简单的数字。这个操作被称为hashing,结果数字成为hash或者hash值

A naive hashing algorithm is just to sum up all the bytes in the data. However, this still allows almost any amount of changing the data around and still preserving the hash values. For example, an attacker could just swap two bytes. This preserves the hash value, but could end up with you owing someone $65,536 instead of $256.

有一个幼稚的hash算法是将数据所有的字节进行总和。然而,这却仍然允许数据在保留hash值不变的情况下对数据进行任意改变。例如,攻击者只需要交换两个字节。这样hash值没有改变,但结果可能是你本来欠别人256美元却变成了65535美元。

Hashing algorithms used for security purposes have to be "strong", so that it is very difficult for an attacker to find a different sequence of bytes with the same hash value. This makes it hard to modify the data to the attacker's purposes. Security researchers are constantly testing hash algorithms to see if they can break them - that is, find a simple way of coming up with byte sequences to match a hash value. They have devised a series of cryptographic hashing algorithms which are believed to be strong.

用于安全目的的hash算法必须很“强”,这样攻击者才很难在保留相同的hash值时找到一个不同的字节序列。这样攻击者才很难修改数据以达到目的。安全研究人员不断测试是否能攻破hash算法 - 寻找一个简单方法,得到一个字节序列来匹配某个hash值。他们设计了一系列被认为很强的加密hash算法.

Go has support for several hashing algorithms, including MD4, MD5, RIPEMD-160, SHA1, SHA224, SHA256, SHA384 and SHA512. They all follow the same pattern as far as the Go programmer is concerned: a function New (or similar) in the appropriate package returns a Hash object from the hash package.

Go支持几个hash算法,包括MD4, MD5, RIPEMD-160, SHA1, SHA224, SHA256, SHA384 and SHA512。它们都尽可能按照Go程序员关注的,遵循相同的模式:在适当的包中定义New或类似的方法,返回一个hash包中的Hash 对象。

A Hash has an io.Writer, and you write the data to be hashed to this writer. You can query the number of bytes in the hash value by Size and the hash value by Sum.

一个Hash结构体拥有一个io.Writer接口,你可以通过writer方法写入被hash的数据.你可以通过Size方法获取hash值的长度,Sum方法返回hash值。

A typical case is MD5 hashing. This uses the md5 package. The hash value is a 16 byte array. This is typically printed out in ASCII form as four hexadecimal numbers, each made of 4 bytes. A simple program is

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()
}

which prints "b1946ac9 2492d234 7c6235b4 d2611184"

输出 "b1946ac9 2492d234 7c6235b4 d2611184"

A variation on this is the HMAC (Keyed-Hash Message Authentication Code) which adds a key to the hash algorithm. There is little change in using this. To use MD5 hashing along with a key, replace the call to New by

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

func NewMD5(key []byte) hash.Hash
    

Symmetric key encryption

key对称加密

There are two major mechanisms used for encrypting data. The first uses a single key that is the same for both encryption and decryption. This key needs to be known to both the encrypting and the decrypting agents. How this key is transmitted between the agents is not discussed.

数据加密有两种机制。第一种方式是在加密和解密时都使用同一个key。加密方和解密方都需要知道这个key。此处如何在这两者之间传输这个key。

As with hashing, there are many encryption algorithms. Many are now known to have weaknesses, and in general algorithms become weaker over time as computers get faster. Go has support for several symmetric key algorithms such as Blowfish and DES.

目前有很多使用hash算法的加密算法。其中很多都有弱点,而且随着时间的推移,计算机越来越快,通用hash算法变得越来越弱。Go已经支持好几个对称加密算法,如Blowfish和DES。

The algorithms are block algorithms. That is they work on blocks of data. If you data is not aligned to the block size, then you will have to pad it with extra blanks at the end.

这些算法都是block算法。因为它们必须基于数据块(block)。如果你的数据不匹配block的大小,那就必须在最后使用空格来填充多余的空间。

Each algorith is represented by a Cipher object. This is created by NewCipher in the appropriate package, and takes the symmetric key as parameter.

每个算法都被表示为一个Cipher对象。可通过在相应的包中使用对称key作为参数调用NewCipher方法来创建该对象。

Once you have a cipher, you can use it to encrypt and decrypt blocks of data. The blocks have to be 8-bit blocks for Blowfish. A program to illustrate this is

创建cipher对象后,就能通过它加密和解密数据块。Blowfish需要8位的block,详见以下程序


/* Blowfish
 */

package main

import (
	"bytes"
	"code.google.com/p/go.crypto/blowfish"
	"fmt"
)

func main() {
	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 is not in the Go 1 distribution. Instead it is on the http://code.google.com/p/ site. You have to install it by running "go get" in a directory where you have source that needs to use it.

Blowfish不在GO 1中,而是在http://code.google.com/p/中。你可以在需要使用它的源码目录下运行“go get”进行安装。

Public key encryption

公钥加密

Public key encryption and decryption requires two keys: one to encrypt and a second one to decrypt. The encryption key is usually made public in some way so that anyone can encrypt messages to you. The decryption key must stay private, otherwise everyon would be able to decrypt those messages! Public key systems aer asymmetric, with different keys for different uses.

公钥加密和解密需要两个key:一个用来加密,另一个用来解密。加密key通常是公开的,这样任何人都可以给你发送加密数据。解密key必须保密,否则任何人都可以解密数据。公钥系统是非对称的,不同的key有不同的用途。

There are many public key encryption systems supported by Go. A typical one is the RSA scheme.

Go支持很多公钥加密系统,RSA就是一个典型的例子。

A program generating RSA private and public keys is

下面是一个生成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)
	}
}

The program also saves the certificates using gob serialisation. They can be read back by this program:

程序通过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 certificates

X.509 证书

A Public Key Infrastructure (PKI) is a framework for a collection of public keys, along with additional information such as owner name and location, and links between them giving some sort of approval mechanism.

公钥基础架构(PKI)是一个公钥集合框架,它连同附加信息,如所有者名称和位置,以及它们之间的联系提供了一些审批机制。

The principal PKI in use today is based on X.509 certificates. For example, web browsers use them to verify the identity of web sites.

目前主要使用的PKI是就是基于X.509证书的。例如浏览器使用它验证站点的身份。

An example program to generate a self-signed X.509 certificate for my web site and store it in a .cer file is

下面的程序是为自己的站点生成自签名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)
	}
}

This can then be read back in by

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


/* 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
	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

Encryption/decryption schemes are of limited use if you have to do all the heavy lifting yourself. The most popular mechanism on the internet to give support for encrypted message passing is currently TLS (Transport Layer Security) which was formerly SSL (Secure Sockets Layer).

如果自己实现所有的细节,加解密的方案在使用上是有限制的。当前互联网上最流行的加密消息传输方案是TLS(Transport Layer Security安全传输层),其前身为SSL(Secure Sockets Layer安全套接字层)。

In TLS, a client and a server negotiate identity using X.509 certificates. One this is complete, a secret key is invented between them, and all encryption/decryption is done using this key. The negotiation is relatively slow, but once complete a faster private key mechanism is used.

在TLS中,客户端和服务器之间使用X.509证书进行身份验证。身份验证完成后,两者之间会生成一个密钥,所有的加密和解密过程都使用这个密钥。虽然客户端和服务端协商的过程相对较慢,但一旦完成就会使用一个较快的私钥机制。

A server is

服务器端程序


/* TLSEchoServer
 */
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 := "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)
	}
}

The server works with the following client:

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


/* 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
		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)
	}
}

Conclusion

结论

Security is a huge area in itself, and in this chapter we have barely touched on it. However, the major concepts have been covered. What has not been stressed is how much security needs to be built into the design phase: security as an afterthought is nearly always a failure.

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

Copyright Jan Newmarch, jan@newmarch.name

If you like this book, please contribute using Flattr
or donate using PayPal