Skip to content

Latest commit

 

History

History
1484 lines (1179 loc) · 51.6 KB

06.md

File metadata and controls

1484 lines (1179 loc) · 51.6 KB

六、密码学

密码学是一种确保通信安全的实践,即使第三方可以查看这些通信。有双向对称和非对称加密方法,以及单向散列算法。

加密是现代互联网的重要组成部分。通过LetsEncrypt.com等服务,每个人都可以访问受信任的 SSL 证书。我们的整个基础设施依赖并信任加密,以确保所有机密数据的机密性。正确地加密和散列数据非常重要,而且很容易错误配置服务,使其易受攻击或暴露。

本章介绍以下方面的示例和用例:

  • 对称和非对称加密
  • 签名和验证消息
  • 散列
  • 安全地存储密码
  • 生成安全随机数
  • Creating and using TLS/SSL certificates

散列

哈希是将可变长度的消息转换为唯一的固定长度字母数字字符串。有各种可用的散列算法,如 MD5 和 SHA1。散列是单向且不可逆的,与 AES 等对称加密函数不同,AES 可以在拥有密钥的情况下恢复原始消息。因为散列无法反转,所以大多数散列都是用蛮力破解的。破解者将用几个 GPU 构建吸能装备,对每个可能的角色组合进行散列,直到找到匹配的散列。它们还将生成彩虹表或文件,其中包含为快速查找而生成的所有哈希输出。

因为这个原因,在你的散列中加盐是很重要的。Salting 是在用户提供的密码末尾添加一个随机字符串的过程,以添加更多的随机性或熵。考虑一个存储用户登录信息和散列密码进行身份验证的应用。如果两个用户拥有相同的密码,那么他们的散列输出将是相同的。如果没有盐,破解者可能会找到多个使用同一密码的人,并且只需要破解一次哈希。通过向每个用户的密码添加唯一的 salt,可以确保每个用户都有唯一的哈希值。盐渍会降低彩虹表的有效性,因为即使他们知道每个散列中包含的盐,他们也必须为每个盐生成一个彩虹,这非常耗时。

哈希通常用于验证密码。另一个常见用途是文件完整性。大型下载通常附带文件的 MD5 或 SHA1 散列。下载后,可以对文件进行散列,以确保它与预期值匹配。如果不匹配,则下载内容会以某种方式进行修改。散列也被用作记录妥协或 IOC 指标的一种方式。已知恶意或危险的文件将被散列,散列存储在目录中。这些文件通常是公开的,这样人们就可以根据已知的风险检查可疑文件。与整个文件相比,存储和比较散列要高效得多。

散列小文件

If a file is small enough to be contained in memory, the ReadFile() method works quickly. It loads the whole file into memory and then digests the data. The sum will be calculated with multiple different hash algorithms for demonstration:

package main

import (
   "crypto/md5"
   "crypto/sha1"
   "crypto/sha256"
   "crypto/sha512"
   "fmt"
   "io/ioutil"
   "log"
   "os"
)

func printUsage() {
   fmt.Println("Usage: " + os.Args[0] + " <filepath>")
   fmt.Println("Example: " + os.Args[0] + " document.txt")
}

func checkArgs() string {
   if len(os.Args) < 2 {
      printUsage()
      os.Exit(1)
   }
   return os.Args[1]
}

func main() {
   filename := checkArgs()

   // Get bytes from file
   data, err := ioutil.ReadFile(filename)
   if err != nil {
      log.Fatal(err)
   }

   // Hash the file and output results
   fmt.Printf("Md5: %x\n\n", md5.Sum(data))
   fmt.Printf("Sha1: %x\n\n", sha1.Sum(data))
   fmt.Printf("Sha256: %x\n\n", sha256.Sum256(data))
   fmt.Printf("Sha512: %x\n\n", sha512.Sum512(data))
}

散列大文件

在前面的散列示例中,要散列的整个文件在散列之前加载到内存中。当文件达到一定大小时,这是不实际的,甚至是不可能的。物理内存限制将发挥作用。由于散列是作为分组密码实现的,因此它将一次对一个块进行操作,而无需立即将整个文件加载到内存中:

package main

import (
   "crypto/md5"
   "fmt"
   "io"
   "log"
   "os"
)

func printUsage() {
   fmt.Println("Usage: " + os.Args[0] + " <filename>")
   fmt.Println("Example: " + os.Args[0] + " diskimage.iso")
}

func checkArgs() string {
   if len(os.Args) < 2 {
      printUsage()
      os.Exit(1)
   }
   return os.Args[1]
}

func main() {
   filename := checkArgs()

   // Open file for reading
   file, err := os.Open(filename)
   if err != nil {
      log.Fatal(err)
   }
   defer file.Close()

   // Create new hasher, which is a writer interface
   hasher := md5.New()

   // Default buffer size for copying is 32*1024 or 32kb per copy
   // Use io.CopyBuffer() if you want to specify the buffer to use
   // It will write 32kb at a time to the digest/hash until EOF
   // The hasher implements a Write() function making it satisfy
   // the writer interface. The Write() function performs the digest
   // at the time the data is copied/written to it. It digests
   // and processes the hash one chunk at a time as it is received.
   _, err = io.Copy(hasher, file)
   if err != nil {
      log.Fatal(err)
   }

   // Now get the final sum or checksum.
   // We pass nil to the Sum() function because
   // we already copied the bytes via the Copy to the
   // writer interface and don't need to pass any new bytes
   checksum := hasher.Sum(nil)

   fmt.Printf("Md5 checksum: %x\n", checksum)
}

安全地存储密码

既然我们知道了如何散列,我们就可以讨论安全地存储密码了。哈希是保护密码的一个重要因素。其他重要因素包括使用加密强散列函数的 salt,以及可选使用基于散列的消息认证码(HMAC),所有这些都在散列算法中添加了额外的密钥。

HMAC is an added layer that uses a secret key; so, even if an attacker got your database of hashed passwords with the salts, they would still have a difficult time cracking them without the secret key. The secret key should be stored in a separate location such as an environment variable rather than in the database with the hashed passwords and salts.

这个示例应用的用途是有限的。将其用作您自己应用的参考

package main

import (
   "crypto/hmac"
   "crypto/rand"
   "crypto/sha256"
   "encoding/base64"
   "encoding/hex"
   "fmt"
   "io"
   "os"
)

func printUsage() {
   fmt.Println("Usage: " + os.Args[0] + " <password>")
   fmt.Println("Example: " + os.Args[0] + " Password1!")
}

func checkArgs() string {
   if len(os.Args) < 2 {
      printUsage()
      os.Exit(1)
   }
   return os.Args[1]
}

// secretKey should be unique, protected, private,
// and not hard-coded like this. Store in environment var
// or in a secure configuration file.
// This is an arbitrary key that should only be used 
// for example purposes.
var secretKey = "neictr98y85klfgneghre"

// Create a salt string with 32 bytes of crypto/rand data
func generateSalt() string {
   randomBytes := make([]byte, 32)
   _, err := rand.Read(randomBytes)
   if err != nil {
      return ""
   }
   return base64.URLEncoding.EncodeToString(randomBytes)
}

// Hash a password with the salt
func hashPassword(plainText string, salt string) string {
   hash := hmac.New(sha256.New, []byte(secretKey))
   io.WriteString(hash, plainText+salt)
   hashedValue := hash.Sum(nil)
   return hex.EncodeToString(hashedValue)
}

func main() {
   // Get the password from command line argument
   password := checkArgs()
   salt := generateSalt()
   hashedPassword := hashPassword(password, salt)
   fmt.Println("Password: " + password)
   fmt.Println("Salt: " + salt)
   fmt.Println("Hashed password: " + hashedPassword)
}

加密

Encryption is different from hashing because it is reversible and the original message can be recovered. There are symmetric encryption methods that use a password or a shared key to encrypt and decrypt. There are also asymmetric encryption algorithms that operate with a public and private key pair. AES is an example of symmetric encryption, and it is used to encrypt ZIP files, PDF files, or an entire filesystem. RSA is an example of asymmetric encryption and is used for SSL, SSH keys, and PGP.

加密安全伪随机数生成器(CSPRNG)

mathrand包提供的随机性与crypto/rand包提供的随机性不同。请勿将math/rand用于加密应用。

阅读更多关于 Gocrypto/rand套餐的信息 https://golang.org/pkg/crypto/rand/

以下示例将演示如何生成随机字节、随机整数或任何其他有符号或无符号类型的整数:

package main

import (
   "crypto/rand"
   "encoding/binary"
   "fmt"
   "log"
   "math"
   "math/big"
)

func main() {
   // Generate a random int
   limit := int64(math.MaxInt64) // Highest random number allowed
   randInt, err := rand.Int(rand.Reader, big.NewInt(limit))
   if err != nil {
      log.Fatal(err)
   }
   fmt.Println("Random int value: ", randInt)

   // Alternatively, you could generate the random bytes
   // and turn them into the specific data type needed.
   // binary.Read() will only read enough bytes to fill the data type
   var number uint32
   err = binary.Read(rand.Reader, binary.BigEndian, &number)
   if err != nil {
      log.Fatal(err)
   }
   fmt.Println("Random uint32 value: ", number)

   // Or just generate a random byte slice
   numBytes := 4
   randomBytes := make([]byte, numBytes)
   rand.Read(randomBytes)
   fmt.Println("Random byte values: ", randomBytes)
}

Symmetric encryption

对称加密是指使用相同的密钥或密码对数据进行加密和解密。高级加密标准,也称为 AES 或 Rijndael,是 NIST 于 2001 年制定的标准对称加密算法。

数据加密标准(DES)是另一种对称加密算法,它比 AES 更古老、更不安全。除非有具体要求或规范,否则不得在 AES 上使用。Go 标准库包括 AES 和 DES 包。

AES

该程序将使用密钥加密和解密文件,密钥基本上是 32 字节(256 位)密码。

生成密钥、加密或解密时,输出通常发送到STDOUT或终端。您可以使用>操作符轻松地将输出重定向到文件或其他程序。有关示例,请参阅使用模式。如果需要将密钥或加密数据存储为 ASCII 编码字符串,请使用 base64 编码。

在本例中的某个时刻,您将看到消息被分成两部分:IV 和密码文本。初始化向量或 IV 是一个随机值,在实际加密消息之前加上前缀。每次使用 AES 加密消息时,都会生成一个随机值,并将其用作加密的一部分。随机值称为 nonce,这意味着仅使用一次的数字。

为什么要创建这些一次性值?特别是,如果它们不是保密的,并且被放在加密消息的正前方,那么它有什么用途呢?随机 IV 的使用方式与盐相似。它主要用于重复加密同一消息时,密码文本每次都不同。

要使用伽罗瓦/计数器模式GCM代替 CFB,请更改加密和解密方法。GCM 具有更好的性能和效率,因为它允许并行处理。更多关于 GCM 的信息,请访问https://en.wikipedia.org/wiki/Galois/Counter_Mode

以 AES 密码开始并调用cipher.NewCFBEncrypter(block, iv)。然后,根据您是否需要加密或解密,您可以使用生成的 nonce 调用.Seal(),或者调用.Open()并将分离的 nonce 和密码文本传递给它:

package main

import (
   "crypto/aes"
   "crypto/cipher"
   "crypto/rand"
   "fmt"
   "io"
   "io/ioutil"
   "os"
   "log"
)

func printUsage() {
   fmt.Printf(os.Args[0] + `

Encrypt or decrypt a file using AES with a 256-bit key file.
This program can also generate 256-bit keys.

Usage:
  ` + os.Args[0] + ` [-h|--help]
  ` + os.Args[0] + ` [-g|--genkey]
  ` + os.Args[0] + ` <keyFile> <file> [-d|--decrypt]

Examples:
  # Generate a 32-byte (256-bit) key
  ` + os.Args[0] + ` --genkey

  # Encrypt with secret key. Output to STDOUT
  ` + os.Args[0] + ` --genkey > secret.key

  # Encrypt message using secret key. Output to ciphertext.dat
  ` + os.Args[0] + ` secret.key message.txt > ciphertext.dat

  # Decrypt message using secret key. Output to STDOUT
  ` + os.Args[0] + ` secret.key ciphertext.dat -d

  # Decrypt message using secret key. Output to message.txt
  ` + os.Args[0] + ` secret.key ciphertext.dat -d > cleartext.txt
`)
}

// Check command-line arguments.
// If the help or generate key functions are chosen
// they are run and then the program exits
// otherwise it returns keyFile, file, decryptFlag.
func checkArgs() (string, string, bool) {
   if len(os.Args) < 2  || len(os.Args) > 4 {
      printUsage()
      os.Exit(1)
   }

   // One arg provided
   if len(os.Args) == 2 {
      // Only -h, --help and --genkey are valid one-argument uses
      if os.Args[1] == "-h" || os.Args[1] == "--help" {
         printUsage() // Print help text
         os.Exit(0) // Exit gracefully no error
      }
      if os.Args[1] == "-g" || os.Args[1] == "--genkey" {
         // Generate a key and print to STDOUT
         // User should redirect output to a file if needed
         key := generateKey()
         fmt.Printf(string(key[:])) // No newline
         os.Exit(0) // Exit gracefully
      }
   }

   // The only use options left is
   // encrypt <keyFile> <file> [-d|--decrypt]
   // If there are only 2 args provided, they must be the
   // keyFile and file without a decrypt flag.
   if len(os.Args) == 3 {
      // keyFile, file, decryptFlag
      return os.Args[1], os.Args[2], false 
   }
   // If 3 args are provided,
   // check that the last one is -d or --decrypt
   if len(os.Args) == 4 {
      if os.Args[3] != "-d" && os.Args[3] != "--decrypt" {
         fmt.Println("Error: Unknown usage.")
         printUsage()
         os.Exit(1) // Exit with error code
      }
      return os.Args[1], os.Args[2], true
   }
    return "", "", false // Default blank return
}

func generateKey() []byte {
   randomBytes := make([]byte, 32) // 32 bytes, 256 bit
   numBytesRead, err := rand.Read(randomBytes)
   if err != nil {
      log.Fatal("Error generating random key.", err)
   }
   if numBytesRead != 32 {
      log.Fatal("Error generating 32 random bytes for key.")
   }
   return randomBytes
}

// AES encryption
func encrypt(key, message []byte) ([]byte, error) {
   // Initialize block cipher
   block, err := aes.NewCipher(key)
   if err != nil {
      return nil, err
   }

   // Create the byte slice that will hold encrypted message
   cipherText := make([]byte, aes.BlockSize+len(message))

   // Generate the Initialization Vector (IV) nonce
   // which is stored at the beginning of the byte slice
   // The IV is the same length as the AES blocksize
   iv := cipherText[:aes.BlockSize]
   _, err = io.ReadFull(rand.Reader, iv)
   if err != nil {
      return nil, err
   }

   // Choose the block cipher mode of operation
   // Using the cipher feedback (CFB) mode here.
   // CBCEncrypter also available.
   cfb := cipher.NewCFBEncrypter(block, iv)
   // Generate the encrypted message and store it
   // in the remaining bytes after the IV nonce
   cfb.XORKeyStream(cipherText[aes.BlockSize:], message)

   return cipherText, nil
}

// AES decryption
func decrypt(key, cipherText []byte) ([]byte, error) {
   // Initialize block cipher
   block, err := aes.NewCipher(key)
   if err != nil {
      return nil, err
   }

   // Separate the IV nonce from the encrypted message bytes
   iv := cipherText[:aes.BlockSize]
   cipherText = cipherText[aes.BlockSize:]

   // Decrypt the message using the CFB block mode
   cfb := cipher.NewCFBDecrypter(block, iv)
   cfb.XORKeyStream(cipherText, cipherText)

   return cipherText, nil
}

func main() {
   // if generate key flag, just output a key to stdout and exit
   keyFile, file, decryptFlag := checkArgs()

   // Load key from file
   keyFileData, err := ioutil.ReadFile(keyFile)
   if err != nil {
      log.Fatal("Unable to read key file contents.", err)
   }

   // Load file to be encrypted or decrypted
   fileData, err := ioutil.ReadFile(file)
   if err != nil {
      log.Fatal("Unable to read key file contents.", err)
   }

   // Perform encryption unless the decryptFlag was provided
   // Outputs to STDOUT. User can redirect output to file.
   if decryptFlag {
      message, err := decrypt(keyFileData, fileData)
      if err != nil {
         log.Fatal("Error decrypting. ", err)
      }
      fmt.Printf("%s", message)
   } else {
      cipherText, err := encrypt(keyFileData, fileData)
      if err != nil {
         log.Fatal("Error encrypting. ", err)
      }
      fmt.Printf("%s", cipherText)
   }
}

非对称加密

不对称是指每一方都有两个密钥。每一方都需要一对公钥和私钥。非对称加密算法包括 RSA、DSA 和 ECDSA。Go 标准库包含用于 RSA、DSA 和 ECDSA 的软件包。一些使用非对称加密的应用包括安全外壳SSH)、安全套接字层SSL)和相当好的隐私PGP)。

SSL 是最初由 Netscape 开发的安全套接字层,第 2 版于 1995 年公开发布。它用于加密服务器和客户端之间的通信,提供机密性、完整性和身份验证。TLS传输层安全是 SSL 的新版本,1.2 在 2008 年被定义为 RFC 5246。TLS 的 Go 包没有完全实现规范,但它实现了主要部分。在阅读更多关于 Gocrypto/tls套餐的信息 https://golang.org/pkg/crypto/tls/

您只能加密小于密钥大小(通常为 2048 位)的内容。由于这种大小限制,非对称 RSA 加密不适用于加密整个文档,因为整个文档很容易超过 2048 位或 256 字节。另一方面,AES 等对称加密可以加密大型文档,但它需要双方共享密钥。TLS/SSL 结合使用非对称和对称加密。初始连接和握手是使用不对称加密和各方的公钥和私钥完成的。一旦建立了连接,就会生成并共享一个共享密钥。一旦双方都知道共享密钥,就会放弃非对称加密,剩下的通信将使用对称加密完成,例如使用共享密钥的 AES。

这里的示例将使用 RSA 密钥。我们将介绍如何生成您自己的公钥和私钥,并将其保存为 PEM 编码文件、对消息进行数字签名和验证签名。在下一节中,我们将使用密钥创建自签名证书并建立安全的 TLS 连接。

生成公钥和私钥对

在使用非对称加密之前,需要公钥和私钥对。私钥必须保持安全,不得与任何人共享。公钥应该与其他人共享。

Go 标准库中提供了RSA****Rivest Shamir AdlemanECDSA****椭圆曲线数字签名算法算法。ECDSA 被认为更安全,但 RSA 是 SSL 证书中最常用的算法。

您可以选择密码保护您的私钥。您不需要这样做,但这是一个额外的安全层。由于私钥非常敏感,建议您使用密码保护。

如果要使用对称加密算法(如 AES)对私钥文件进行密码保护,可以使用一些标准库函数。您需要的主要功能是x509.EncryptPEMBlock()x509.DecryptPEMBlock()x509.IsEncryptedPEMBlock()

要使用 OpenSSL 执行生成私钥和公钥文件的等效操作,请使用以下命令:

# Generate the private key  
openssl genrsa -out priv.pem 2048 
# Extract the public key from the private key 
openssl rsa -in priv.pem -pubout -out public.pem 

You can learn more about PEM encoding with Go at https://golang.org/pkg/encoding/pem/. Refer to the following code:

package main

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

func printUsage() {
   fmt.Printf(os.Args[0] + `

Generate a private and public RSA keypair and save as PEM files.
If no key size is provided, a default of 2048 is used.

Usage:
  ` + os.Args[0] + ` <private_key_filename> <public_key_filename>       [keysize]

Examples:
  # Store generated private and public key in privkey.pem and   pubkey.pem
  ` + os.Args[0] + ` priv.pem pub.pem
  ` + os.Args[0] + ` priv.pem pub.pem 4096`)
}

func checkArgs() (string, string, int) {
   // Too many or too few arguments
   if len(os.Args) < 3 || len(os.Args) > 4 {
      printUsage()
      os.Exit(1)
   }

   defaultKeySize := 2048

   // If there are 2 args provided, privkey and pubkey filenames
   if len(os.Args) == 3 {
      return os.Args[1], os.Args[2], defaultKeySize
   }

   // If 3 args provided, privkey, pubkey, keysize
   if len(os.Args) == 4 {
      keySize, err := strconv.Atoi(os.Args[3])
      if err != nil {
         printUsage()
         fmt.Println("Invalid keysize. Try 1024 or 2048.")
         os.Exit(1)
      }
      return os.Args[1], os.Args[2], keySize
   }

   return "", "", 0 // Default blank return catch-all
}

// Encode the private key as a PEM file
// PEM is a base-64 encoding of the key
func getPrivatePemFromKey(privateKey *rsa.PrivateKey) *pem.Block {
   encodedPrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
   var privatePem = &pem.Block {
      Type: "RSA PRIVATE KEY",
      Bytes: encodedPrivateKey,
   }
   return privatePem
}

// Encode the public key as a PEM file
func generatePublicPemFromKey(publicKey rsa.PublicKey) *pem.Block {
   encodedPubKey, err := x509.MarshalPKIXPublicKey(&publicKey)
   if err != nil {
      log.Fatal("Error marshaling PKIX pubkey. ", err)
   }

   // Create a public PEM structure with the data
   var publicPem = &pem.Block{
      Type:  "PUBLIC KEY",
      Bytes: encodedPubKey,
   }
   return publicPem
}

func savePemToFile(pemBlock *pem.Block, filename string) {
   // Save public pem to file
   publicPemOutputFile, err := os.Create(filename)
   if err != nil {
      log.Fatal("Error opening pubkey output file. ", err)
   }
   defer publicPemOutputFile.Close()

   err = pem.Encode(publicPemOutputFile, pemBlock)
   if err != nil {
      log.Fatal("Error encoding public PEM. ", err)
   }
}

// Generate a public and private RSA key in PEM format
func main() {
   privatePemFilename, publicPemFilename, keySize := checkArgs()

   // Generate private key
   privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
   if err != nil {
      log.Fatal("Error generating private key. ", err)
   }

   // Encode keys to PEM format
   privatePem := getPrivatePemFromKey(privateKey)
   publicPem := generatePublicPemFromKey(privateKey.PublicKey)

   // Save the PEM output to files
   savePemToFile(privatePem, privatePemFilename)
   savePemToFile(publicPem, publicPemFilename)

   // Print the public key to STDOUT for convenience
   fmt.Printf("%s", pem.EncodeToMemory(publicPem))
}

对邮件进行数字签名

签名邮件的目的是让收件人知道邮件来自正确的人。要对消息进行签名,首先生成消息的哈希,然后使用私钥对哈希进行加密。加密的散列是您的签名。

收件人将解密您的签名以获得您提供的原始散列,然后他们将散列消息本身,并查看他们从消息中生成的散列是否与签名的解密值匹配。如果它们匹配,则收件人知道签名有效并且来自正确的发件人。

请注意,对消息进行签名实际上并不加密消息。如果需要,您仍然需要在发送消息之前对其进行加密。如果要公开发布消息,则可能不希望对消息本身进行加密。其他人仍然可以使用签名来验证谁发布了消息。

只能对小于 RSA 密钥大小的消息进行签名。由于 SHA-256 哈希始终具有相同的输出长度,因此我们可以确保它在可接受的大小限制内。在本例中,我们使用 RSA PKCS#1 v1.5 标准签名和 SHA-256 哈希方法。

Go 编程语言在核心包中附带了处理签名和验证的函数。主要功能是rsa.VerifyPKCS1v5。此函数负责对消息进行散列,然后使用私钥对其进行加密。

以下程序将获取一条消息和一个私钥,并创建一个签名输出到STDOUT

package main

import (
   "crypto"
   "crypto/rand"
   "crypto/rsa"
   "crypto/sha256"
   "crypto/x509"
   "encoding/pem"
   "fmt"
   "io/ioutil"
   "log"
   "os"
)

func printUsage() {
   fmt.Println(os.Args[0] + `

Cryptographically sign a message using a private key.
Private key should be a PEM encoded RSA key.
Signature is generated using SHA256 hash.
Output signature is stored in filename provided.

Usage:
  ` + os.Args[0] + ` <privateKeyFilename> <messageFilename>   <signatureFilename>

Example:
  # Use priv.pem to encrypt msg.txt and output to sig.txt.256
  ` + os.Args[0] + ` priv.pem msg.txt sig.txt.256
`)
}

// Get arguments from command line
func checkArgs() (string, string, string) {
   // Need exactly 3 arguments provided
   if len(os.Args) != 4 {
      printUsage()
      os.Exit(1)
   }

   // Private key file name and message file name
   return os.Args[1], os.Args[2], os.Args[3]
}

// Cryptographically sign a message= creating a digital signature
// of the original message. Uses SHA-256 hashing.
func signMessage(privateKey *rsa.PrivateKey, message []byte) []byte {
   hashed := sha256.Sum256(message)

   signature, err := rsa.SignPKCS1v15(
      rand.Reader,
      privateKey,
      crypto.SHA256,
      hashed[:],
   )
   if err != nil {
      log.Fatal("Error signing message. ", err)
   }

   return signature
}

// Load the message that will be signed from file
func loadMessageFromFile(messageFilename string) []byte {
   fileData, err := ioutil.ReadFile(messageFilename)
   if err != nil {
      log.Fatal(err)
   }
   return fileData
}

// Load the RSA private key from a PEM encoded file
func loadPrivateKeyFromPemFile(privateKeyFilename string) *rsa.PrivateKey {
   // Quick load file to memory
   fileData, err := ioutil.ReadFile(privateKeyFilename)
   if err != nil {
      log.Fatal(err)
   }

   // Get the block data from the PEM encoded file
   block, _ := pem.Decode(fileData)
   if block == nil || block.Type != "RSA PRIVATE KEY" {
      log.Fatal("Unable to load a valid private key.")
   }

   // Parse the bytes and put it in to a proper privateKey struct
   privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
   if err != nil {
      log.Fatal("Error loading private key.", err)
   }

   return privateKey
}

// Save data to file
func writeToFile(filename string, data []byte) error {
   // Open a new file for writing only
   file, err := os.OpenFile(
      filename,
      os.O_WRONLY|os.O_TRUNC|os.O_CREATE,
      0666,
   )
   if err != nil {
      return err
   }
   defer file.Close()

   // Write bytes to file
   _, err = file.Write(data)
   if err != nil {
      return err
   }

   return nil
}

// Sign a message using a private RSA key
func main() {
   // Get arguments from command line
   privateKeyFilename, messageFilename, sigFilename := checkArgs()

   // Load message and private key files from disk
   message := loadMessageFromFile(messageFilename)
   privateKey := loadPrivateKeyFromPemFile(privateKeyFilename)

   // Cryptographically sign the message
   signature := signMessage(privateKey, message)

   // Output to file
   writeToFile(sigFilename, signature)
}

验证签名

在前面的示例中,我们学习了如何创建消息的签名以供收件人验证。现在让我们看看验证签名的过程。

如果收到消息和签名,必须首先使用发件人的公钥解密签名。然后对原始消息进行散列,查看散列是否与解密的签名匹配。如果散列与解密的签名匹配,则可以确保发送方是私钥的所有者,私钥与用于验证的公钥配对。

为了验证签名,我们使用了与创建签名相同的算法(RSA PKCS#1 v1.5 和 SHA-256)。

此示例需要两个命令行参数。第一个参数是创建签名者的公钥,第二个参数是带有签名的文件。要创建签名文件,请使用上一示例中的签名程序,并将输出重定向到文件。

与上一节类似,Go 在标准库中有一个验证签名的函数。我们可以使用rsa.VerifyPKCS1v5()将消息散列与签名的解密值进行比较,看看它们是否匹配:

package main

import (
   "crypto"
   "crypto/rsa"
   "crypto/sha256"
   "crypto/x509"
   "encoding/pem"
   "fmt"
   "io/ioutil"
   "log"
   "os"
)

func printUsage() {
    fmt.Println(os.Args[0] + `

Verify an RSA signature of a message using SHA-256 hashing.
Public key is expected to be a PEM file.

Usage:
  ` + os.Args[0] + ` <publicKeyFilename> <signatureFilename> <messageFilename>

Example:
  ` + os.Args[0] + ` pubkey.pem signature.txt message.txt
`)
}

// Get arguments from command line
func checkArgs() (string, string, string) {
   // Expect 3 arguments: pubkey, signature, message file names
   if len(os.Args) != 4 {
      printUsage()
      os.Exit(1)
   }

   return os.Args[1], os.Args[2], os.Args[3]
}

// Returns bool whether signature was verified
func verifySignature(
   signature []byte,
   message []byte,
   publicKey *rsa.PublicKey) bool {

   hashedMessage := sha256.Sum256(message)

   err := rsa.VerifyPKCS1v15(
      publicKey,
      crypto.SHA256,
      hashedMessage[:],
      signature,
   )

   if err != nil {
      log.Println(err)
      return false
   }
   return true // If no error, match.
}

// Load file to memory
func loadFile(filename string) []byte {
   fileData, err := ioutil.ReadFile(filename)
   if err != nil {
      log.Fatal(err)
   }
   return fileData
}

// Load a public RSA key from a PEM encoded file
func loadPublicKeyFromPemFile(publicKeyFilename string) *rsa.PublicKey {
   // Quick load file to memory
   fileData, err := ioutil.ReadFile(publicKeyFilename)
   if err != nil {
      log.Fatal(err)
   }

   // Get the block data from the PEM encoded file
   block, _ := pem.Decode(fileData)
   if block == nil || block.Type != "PUBLIC KEY" {
      log.Fatal("Unable to load valid public key. ")
   }

   // Parse the bytes and store in a public key format
   publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
   if err != nil {
      log.Fatal("Error loading public key. ", err)
   }

   return publicKey.(*rsa.PublicKey) // Cast interface to PublicKey
}

// Verify a cryptographic signature using RSA PKCS#1 v1.5 with SHA-256
// and a PEM encoded PKIX public key.
func main() {
   // Parse command line arguments
   publicKeyFilename, signatureFilename, messageFilename :=   
      checkArgs()

   // Load all the files from disk
   publicKey := loadPublicKeyFromPemFile(publicKeyFilename)
   signature := loadFile(signatureFilename)
   message := loadFile(messageFilename)

   // Verify signature
   valid := verifySignature(signature, message, publicKey)

   if valid {
      fmt.Println("Signature verified.")
   } else {
      fmt.Println("Signature could not be verified.")
   }
}

TLS

我们通常不使用 RSA 加密整个消息,因为它只能加密小于密钥大小的消息。解决这一问题的方法通常是从使用 RSA 密钥加密的小消息开始通信。当他们建立了一个安全通道后,他们可以安全地交换一个共享密钥,可以使用该密钥对其余消息进行对称加密,而不受大小限制。这是 SSL 和 TLS 用于建立安全通信的方法。握手负责协商在生成和共享对称密钥时将使用哪些加密算法。

生成自签名证书

要使用 Go 创建自签名证书,需要公钥和私钥对。x509 软件包具有创建证书的功能。它需要公钥和私钥以及包含所有信息的模板证书。由于我们是自签名的,所以模板证书也将用作进行签名的父证书。

每个应用都可以对自签名证书进行不同的处理。如果证书是自签名的,有些应用会向您发出警告,有些应用会拒绝接受它,而其他应用则会在不向您发出警告的情况下愉快地使用它。当您编写自己的应用时,您必须决定是要验证证书还是接受自签名证书。

The important function is x509.CreateCertificate(), referenced at https://golang.org/pkg/crypto/x509/#CreateCertificate. Here is the function signature:

func CreateCertificate (rand io.Reader, template, parent *Certificate, pub, 
   priv interface{}) (cert []byte, err error)

本例将获取私钥并生成由其签名的证书。它将以 PEM 格式保存到文件中。创建自签名证书后,可以使用该证书和私钥运行安全的 TLS 套接字侦听器和 web 服务器。

为简洁起见,此示例将证书所有者信息和主机名 IP 硬编码为 localhost。这对于在本地机器上进行测试来说已经足够好了。

修改这些值以满足您的需要,自定义值,通过命令行参数输入值,或使用标准输入从用户动态获取值,如以下代码块所示:

package main

import (
   "crypto/rand"
   "crypto/rsa"
   "crypto/x509/pkix"
   "crypto/x509"
   "encoding/pem"
   "fmt"
   "io/ioutil"
   "log"
   "math/big"
   "net"
   "os"
   "time"
)

func printUsage() {
   fmt.Println(os.Args[0] + ` - Generate a self signed TLS certificate

Usage:
  ` + os.Args[0] + ` <privateKeyFilename> <certOutputFilename> [-ca|--cert-authority]

Example:
  ` + os.Args[0] + ` priv.pem cert.pem
  ` + os.Args[0] + ` priv.pem cacert.pem -ca
`)
}

func checkArgs() (string, string, bool) {
   if len(os.Args) < 3 || len(os.Args) > 4 {
      printUsage()
      os.Exit(1)
   }

   // See if the last cert authority option was passed
   isCA := false // Default
   if len(os.Args) == 4 {
      if os.Args[3] == "-ca" || os.Args[3] == "--cert-authority" {
         isCA = true
      }
   }

   // Private key filename, cert output filename, is cert authority
   return os.Args[1], os.Args[2], isCA
}

func setupCertificateTemplate(isCA bool) x509.Certificate {
   // Set valid time frame to start now and end one year from now
   notBefore := time.Now()
   notAfter := notBefore.Add(time.Hour * 24 * 365) // 1 year/365 days

   // Generate secure random serial number
   serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
   randomNumber, err := rand.Int(rand.Reader, serialNumberLimit)
   if err != nil {
      log.Fatal("Error generating random serial number. ", err)
   }

   nameInfo := pkix.Name{
      Organization: []string{"My Organization"},
      CommonName: "localhost",
      OrganizationalUnit: []string{"My Business Unit"},
      Country:        []string{"US"}, // 2-character ISO code
      Province:       []string{"Texas"}, // State
      Locality:       []string{"Houston"}, // City
   }

   // Create the certificate template
   certTemplate := x509.Certificate{
      SerialNumber: randomNumber,
      Subject: nameInfo,
      EmailAddresses: []string{"test@localhost"},
      NotBefore: notBefore,
      NotAfter: notAfter,
      KeyUsage: x509.KeyUsageKeyEncipherment |   
         x509.KeyUsageDigitalSignature,
      // For ExtKeyUsage, default to any, but can specify to use
      // only as server or client authentication, code signing, etc
      ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
      BasicConstraintsValid: true,
      IsCA: false,
   }

   // To create a certificate authority that can sign cert signing   
   // requests, set these
   if isCA {
      certTemplate.IsCA = true
      certTemplate.KeyUsage = certTemplate.KeyUsage |  
         x509.KeyUsageCertSign
   }

   // Add any IP addresses and hostnames covered by this cert
   // This example only covers localhost
   certTemplate.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
   certTemplate.DNSNames = []string{"localhost", "localhost.local"}

   return certTemplate
}

// Load the RSA private key from a PEM encoded file
func loadPrivateKeyFromPemFile(privateKeyFilename string) *rsa.PrivateKey {
   // Quick load file to memory
   fileData, err := ioutil.ReadFile(privateKeyFilename)
   if err != nil {
      log.Fatal("Error loading private key file. ", err)
   }

   // Get the block data from the PEM encoded file
   block, _ := pem.Decode(fileData)
   if block == nil || block.Type != "RSA PRIVATE KEY" {
      log.Fatal("Unable to load a valid private key.")
   }

   // Parse the bytes and put it in to a proper privateKey struct
   privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
   if err != nil {
      log.Fatal("Error loading private key. ", err)
   }

   return privateKey
}

// Save the certificate as a PEM encoded file
func writeCertToPemFile(outputFilename string, derBytes []byte ) {
   // Create a PEM from the certificate
   certPem := &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}

   // Open file for writing
   certOutfile, err := os.Create(outputFilename)
   if err != nil {
      log.Fatal("Unable to open certificate output file. ", err)
   }
   pem.Encode(certOutfile, certPem)
   certOutfile.Close()
}

// Create a self-signed TLS/SSL certificate for localhost 
// with an RSA private key
func main() {
   privPemFilename, certOutputFilename, isCA := checkArgs()

   // Private key of signer - self signed means signer==signee
   privKey := loadPrivateKeyFromPemFile(privPemFilename)

   // Public key of signee. Self signing means we are the signer and    
   // the signee so we can just pull our public key from our private key
   pubKey := privKey.PublicKey

   // Set up all the certificate info
   certTemplate := setupCertificateTemplate(isCA)

   // Create (and sign with the priv key) the certificate
   certificate, err := x509.CreateCertificate(
      rand.Reader,
      &certTemplate,
      &certTemplate,
      &pubKey,
      privKey,
   )
   if err != nil {
      log.Fatal("Failed to create certificate. ", err)
   }

   // Format the certificate as a PEM and write to file
   writeCertToPemFile(certOutputFilename, certificate)
}

创建证书签名请求

If you don't want to create a self-signed certificate, you have to create a certificate signing request and have it signed by a trusted certificate authority. You create a certificate request by calling x509.CreateCertificateRequest() and passing it an x509.CertificateRequest object with the private key.

The equivalent operation using OpenSSL is as follows:

# Create CSR 
openssl req -new -key priv.pem -out csr.pem 
# View details to verify request was created properly 
openssl req -verify -in csr.pem -text -noout 

此示例演示如何创建证书签名请求:

package main

import (
   "crypto/rand"
   "crypto/rsa"
   "crypto/x509"
   "crypto/x509/pkix"
   "encoding/pem"
   "fmt"
   "io/ioutil"
   "log"
   "net"
   "os"
)

func printUsage() {
   fmt.Println(os.Args[0] + ` - Create a certificate signing request  
   with a private key.

Private key is expected in PEM format. Certificate valid for localhost only.
Certificate signing request is created using the SHA-256 hash.

Usage:
  ` + os.Args[0] + ` <privateKeyFilename> <csrOutputFilename>

Example:
  ` + os.Args[0] + ` priv.pem csr.pem
`)
}

func checkArgs() (string, string) {
   if len(os.Args) != 3 {
      printUsage()
      os.Exit(1)
   }

   // Private key filename, cert signing request output filename
   return os.Args[1], os.Args[2]
}

// Load the RSA private key from a PEM encoded file
func loadPrivateKeyFromPemFile(privateKeyFilename string) *rsa.PrivateKey {
   // Quick load file to memory
   fileData, err := ioutil.ReadFile(privateKeyFilename)
   if err != nil {
      log.Fatal("Error loading private key file. ", err)
   }

   // Get the block data from the PEM encoded file
   block, _ := pem.Decode(fileData)
   if block == nil || block.Type != "RSA PRIVATE KEY" {
      log.Fatal("Unable to load a valid private key.")
   }

   // Parse the bytes and put it in to a proper privateKey struct
   privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
   if err != nil {
      log.Fatal("Error loading private key.", err)
   }

   return privateKey
}

// Create a CSR PEM and save to file
func saveCSRToPemFile(csr []byte, filename string) {
   csrPem := &pem.Block{
      Type:  "CERTIFICATE REQUEST",
      Bytes: csr,
   }
   csrOutfile, err := os.Create(filename)
   if err != nil {
      log.Fatal("Error opening "+filename+" for saving. ", err)
   }
   pem.Encode(csrOutfile, csrPem)
}

// Create a certificate signing request with a private key 
// valid for localhost
func main() {
   // Load parameters
   privKeyFilename, csrOutFilename := checkArgs()
   privKey := loadPrivateKeyFromPemFile(privKeyFilename)

   // Prepare information about organization the cert will belong to
   nameInfo := pkix.Name{
      Organization:       []string{"My Organization Name"},
      CommonName:         "localhost",
      OrganizationalUnit: []string{"Business Unit Name"},
      Country:            []string{"US"}, // 2-character ISO code
      Province:           []string{"Texas"},
      Locality:           []string{"Houston"}, // City
   }

   // Prepare CSR template
   csrTemplate := x509.CertificateRequest{
      Version:            2, // Version 3, zero-indexed values
      SignatureAlgorithm: x509.SHA256WithRSA,
      PublicKeyAlgorithm: x509.RSA,
      PublicKey:          privKey.PublicKey,
      Subject:            nameInfo,

      // Subject Alternate Name values.
      DNSNames:       []string{"Business Unit Name"},
      EmailAddresses: []string{"test@localhost"},
      IPAddresses:    []net.IP{},
   }

   // Create the CSR based off the template
   csr, err := x509.CreateCertificateRequest(rand.Reader,  
      &csrTemplate, privKey)
   if err != nil {
      log.Fatal("Error creating certificate signing request. ", err)
   }
   saveCSRToPemFile(csr, csrOutFilename)
}

签署证书请求

在前面的示例中,在生成自签名证书时,我们已经演示了创建签名证书的过程。在自签名示例中,我们只使用了与签名者和签名者相同的证书模板。因此,没有单独的代码示例。唯一的区别是,进行签名的父证书或要签名的模板应交换到其他证书。

这是x509.CreateCertificate()的功能定义:

func CreateCertificate(rand io.Reader, template, parent *Certificate, pub, 
   priv interface{}) (cert []byte, err error)

在自签名示例中,模板和父证书是同一个对象。要对证书请求进行签名,请创建一个新的证书对象,并使用签名请求中的信息填充字段。将新证书作为模板传递,并使用签名者的证书作为父级。pub参数为签字人的公钥,priv参数为签字人的私钥。签名者是证书颁发机构,签名者是请求者。有关此功能的更多信息,请访问https://golang.org/pkg/crypto/x509/#CreateCertificate

X509.CreateCertificate()参数如下:

  • rand:这是加密安全的伪随机数生成器
  • template:这是使用 CSR 信息填充的证书模板
  • parent:这是签字人的证明
  • pub:这是签字人的公钥
  • priv:这是签名人的私钥

使用 OpenSSL 的等效操作如下所示:

# Create signed certificate using
# the CSR, CA certificate, and private key 
openssl x509 -req -in csr.pem -CA cacert.pem \
-CAkey capriv.pem -CAcreateserial \
-out cert.pem -sha256
# Print info about cert 
openssl x509 -in cert.pem -text -noout  

TLS 服务器

您可以像设置普通套接字连接一样设置侦听器,但需要加密。只需调用 TLSListen()函数,并向其提供您的证书和私钥。使用前面的示例生成的证书和密钥将起作用。

以下程序将创建一个 TLS 服务器并回显接收到的任何数据,然后关闭连接。服务器将不需要或验证客户端证书,但这样做的代码将被注释掉,以供您使用证书对客户端进行身份验证时参考:

package main

import (
   "bufio"
   "crypto/tls"
   "fmt"
   "log"
   "net"
   "os"
)

func printUsage() {
   fmt.Println(os.Args[0] + ` - Start a TLS echo server

Server will echo one message received back to client.
Provide a certificate and private key file in PEM format.
Host string in the format: hostname:port

Usage:
  ` + os.Args[0] + ` <certFilename> <privateKeyFilename> <hostString>

Example:
  ` + os.Args[0] + ` cert.pem priv.pem localhost:9999
`)
}

func checkArgs() (string, string, string) {
  if len(os.Args) != 4 {
     printUsage()
     os.Exit(1)
  }

  return os.Args[1], os.Args[2], os.Args[3]
}

// Create a TLS listener and echo back data received by clients.
func main() {
   certFilename, privKeyFilename, hostString := checkArgs()

   // Load the certificate and private key
   serverCert, err := tls.LoadX509KeyPair(certFilename, privKeyFilename)
   if err != nil {
      log.Fatal("Error loading certificate and private key. ", err)
   }

   // Set up certificates, host/ip, and port
   config := &tls.Config{
      // Specify server certificate
      Certificates: []tls.Certificate{serverCert},

      // By default no client certificate is required.
      // To require and validate client certificates, specify the
      // ClientAuthType to be one of:
      //    NoClientCert, RequestClientCert, RequireAnyClientCert,
      //    VerifyClientCertIfGiven, RequireAndVerifyClientCert)

      // ClientAuth: tls.RequireAndVerifyClientCert

      // Define the list of certificates you will accept as
      // trusted certificate authorities with ClientCAs.

      // ClientCAs: *x509.CertPool
   }

   // Create the TLS socket listener
   listener, err := tls.Listen("tcp", hostString, config)
   if err != nil {
      log.Fatal("Error starting TLS listener. ", err)
   }
   defer listener.Close()

   // Listen forever for connections
   for {
      clientConnection, err := listener.Accept()
      if err != nil {
         log.Println("Error accepting client connection. ", err)
         continue
      }
      // Launch a goroutine(thread)go-1.6 to handle each connection
      go handleConnection(clientConnection)
   }
}

// Function that gets launched in a goroutine to handle client connection
func handleConnection(clientConnection net.Conn) {
   defer clientConnection.Close()
   socketReader := bufio.NewReader(clientConnection)
   for {
      // Read a message from the client
      message, err := socketReader.ReadString('\n')
      if err != nil {
         log.Println("Error reading from client socket. ", err)
         return
      }
      fmt.Println(message)

      // Echo back the data to the client.
      numBytesWritten, err := clientConnection.Write([]byte(message))
      if err != nil {
         log.Println("Error writing data to client socket. ", err)
         return
      }
      fmt.Printf("Wrote %d bytes back to client.\n", numBytesWritten)
   }
}

TLS 客户端

TCP 套接字是通过网络进行通信的一种简单而常见的方式。使用 Go 的标准库,在标准 TCP 套接字上添加 TLS 层非常简单。

客户端像标准套接字一样拨打 TLS 服务器。客户端通常不需要任何类型的密钥或证书,但服务器可以实现客户端身份验证,并且只允许某些用户连接。

此程序将连接到 TLS 服务器,并将 STDIN 的内容发送到远程服务器并读取响应。我们可以使用这个程序来测试在上一节中创建的基本 TLS echo 服务器。

在运行此程序之前,请确保上一节中的 TLS 服务器正在运行,以便您可以连接。

请注意,这是一个原始套接字级别的服务器。它不是 HTTP 服务器。在第 9 章Web 应用中有运行 HTTPS TLS Web 服务器的示例。

默认情况下,客户端验证服务器的证书是否由受信任的机构签名。我们必须重写此默认值,并告诉客户机不要验证证书,因为我们是自己签名的。可信证书颁发机构列表从系统加载,但可以通过在tls.Config中填充 RootCAs 变量来覆盖。此示例不会验证服务器证书,但提供了提供受信任的 rootca 列表的代码,但注释掉以供参考。

You can see how Go is loading the certificate pool for each system by looking through the root_*.go files in https://golang.org/src/crypto/x509/. For example, root_windows.go and root_linux.go load the system's default certificates.

如果您想连接到服务器并检查或存储其证书,您将连接并检查客户端的net.Conn.ConnectionState().PeerCertificates。它有一个标准的x509.Certificate结构。为此,请参阅以下代码块:

package main

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

func printUsage() {
   fmt.Println(os.Args[0] + ` - Send and receive a message to a TLS server

Usage:
  ` + os.Args[0] + ` <hostString>

Example:
  ` + os.Args[0] + ` localhost:9999
`)
}

func checkArgs() string {
   if len(os.Args) != 2 {
      printUsage()
      os.Exit(1)
   }

   // Host string e.g. localhost:9999
   return os.Args[1]
}

// Simple TLS client that sends a message and receives a message
func main() {
   hostString := checkArgs()
   messageToSend := "Hello?\n"

   // Configure TLS settings
   tlsConfig := &tls.Config{
      // Required to accept self-signed certs
      InsecureSkipVerify: true, 
      // Provide your client certificate if necessary
      // Certificates: []Certificate

      // ServerName is used to verify the hostname (unless you are     
      // skipping verification)
      // It is also included in the handshake in case the server uses   
      // virtual hosts Can also just be an IP address 
      // instead of a hostname.
      // ServerName: string,

      // RootCAs that you are willing to accept
      // If RootCAs is nil, the host's default root CAs are used
      // RootCAs: *x509.CertPool
   }

   // Set up dialer and call the server
   connection, err := tls.Dial("tcp", hostString, tlsConfig)
   if err != nil {
      log.Fatal("Error dialing server. ", err)
   }
   defer connection.Close()

   // Write data to socket
   numBytesWritten, err := connection.Write([]byte(messageToSend))
   if err != nil {
      log.Println("Error writing to socket. ", err)
      os.Exit(1)
   }
   fmt.Printf("Wrote %d bytes to the socket.\n", numBytesWritten)

   // Read data from socket and print to STDOUT
   buffer := make([]byte, 100)
   numBytesRead, err := connection.Read(buffer)
   if err != nil {
      log.Println("Error reading from socket. ", err)
      os.Exit(1)
   }
   fmt.Printf("Read %d bytes to the socket.\n", numBytesRead)
   fmt.Printf("Message received:\n%s\n", buffer)
}

其他加密软件包

以下部分没有源代码示例,但值得一提。Go 提供的这些包是基于前面示例中演示的原则构建的。

OpenPGP

PGP 代表相当好的隐私,OpenPGP 是标准的 RFC4880。PGP 是一个方便的套件,用于加密文本、文件、目录和磁盘。所有原则与上一节讨论的 SSL 和 TLS 密钥/证书相同。加密、签名和验证都是相同的。Go 提供了一个 OpenPGP 包。请访问了解更多信息 https://godoc.org/golang.org/x/crypto/openpgp

非记录(OTR)消息传递

非记录OTR消息传递是一种端到端加密形式,用户可以通过使用的任何消息媒体对其通信进行加密。这很方便,因为即使协议本身未加密,也可以在任何协议上实现加密层。例如,OTR 消息在 XMPP、IRC 和许多其他聊天协议上工作。许多聊天客户机,如 Pidgin、Adium 和 Xabber,都通过本机或插件支持 OTR。Go 提供了一个用于实现 OTR 消息传递的包。更多关于 Go OTR 支持的信息,请访问https://godoc.org/golang.org/x/crypto/otr/

Summary

阅读本章后,您应该对 Go 加密包的功能有了很好的理解。使用本章中给出的示例作为参考,您应该能够轻松地执行基本哈希操作、加密、解密、生成密钥和使用密钥。

此外,您应该了解对称和非对称加密之间的区别,以及它与散列的区别。您应该熟悉运行 TLS 服务器和连接 TLS 客户端的基本知识。

记住,目标不是记住每一个细节,而是记住哪些选项是可用的,这样你就可以为工作选择最好的工具。

在下一章中,我们将介绍如何使用 secure shell,也称为 SSH。首先介绍使用公钥和私钥对以及密码进行身份验证,以及如何验证远程主机的密钥。我们还将研究如何在远程服务器上执行命令以及如何创建交互式 shell。Secure shell 使用本章讨论的加密技术。它是加密最常见和最实际的应用之一。继续阅读以了解有关在 Go 中使用 SSH 的更多信息。