Sling Academy
Home/Golang/Encrypting Streams with `io.Reader` and `io.Writer` in Go

Encrypting Streams with `io.Reader` and `io.Writer` in Go

Last updated: November 27, 2024

In this article, we will explore how to encrypt and decrypt data streams using the io.Reader and io.Writer interfaces in Go. This approach is beneficial when dealing with large data, as it allows for processing the data in chunks rather than loading it all into memory.

Introduction to io.Reader and io.Writer

The io.Reader and io.Writer interfaces represent data streams that can be read from or written to, respectively. Here are their definitions:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

Many of Go's most popular packages, such as os for files, net for networking, and http for web requests, use these interfaces extensively, allowing for powerful and flexible data manipulation capabilities.

Setting Up Encryption

In this section, we'll demonstrate how to wrap an io.Reader and an io.Writer with an encryption layer using the standard library.

Generating a Key

First, we need to generate a symmetric key for encryption. In our example, we will use the AES algorithm to encrypt the data, which requires a key size of 16, 24, or 32 bytes.

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "fmt"
    "io"
)

func generateKey() ([]byte, error) {
    key := make([]byte, 32) // generates a 256-bit key
    if _, err := io.ReadFull(rand.Reader, key); err != nil {
        return nil, err
    }
    return key, nil
}

Encrypting Data Streams

Let's see how to perform the actual encryption on the streams:

func encryptStream(key []byte, plaintext io.Reader, ciphertext io.Writer) error {
    block, err := aes.NewCipher(key)
    if err != nil {
        return err
    }

    iv := make([]byte, aes.BlockSize)
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return err
    }

    stream := cipher.NewCFBEncrypter(block, iv)
    writer := &cipher.StreamWriter{S: stream, W: ciphertext}

    if _, err := io.Copy(writer, plaintext); err != nil {
        return err
    }

    return nil
}

In the above code, an initialization vector (IV) is created and random bytes are filled in. We use cipher.StreamWriter to wrap our writer with a CFB (Cipher Feedback) encrypter.

Decrypting Data Streams

To decrypt the data, we would set up a similar function:

func decryptStream(key []byte, ciphertext io.Reader, plaintext io.Writer) error {
    block, err := aes.NewCipher(key)
    if err != nil {
        return err
    }

    iv := make([]byte, aes.BlockSize)
    if _, err := io.ReadFull(ciphertext, iv); err != nil {
        return err
    }

    stream := cipher.NewCFBDecrypter(block, iv)
    reader := &cipher.StreamReader{S: stream, R: ciphertext}

    if _, err := io.Copy(plaintext, reader); err != nil {
        return err
    }

    return nil
}

The decryption setup is very similar to encryption, with the use of cipher.StreamReader for wrapping the reader with a CFB decrypter.

Putting It All Together

Finally, here’s a simple demonstration to tie everything together:

func main() {
    key, _ := generateKey()

    message := "Hello, this is a secret message!"
    fmt.Println("Original: ", message)

    var ciphertext, decrypted bytes.Buffer
    encryptStream(key, strings.NewReader(message), &ciphertext)
    decryptStream(key, &ciphertext, &decrypted)

    fmt.Println("Decrypted: ", decrypted.String())
}

This code example reads a secret message, encrypts it to the ciphertext buffer, and then decrypts it back to verify the process. The output should display the original message followed by the correctly decrypted message.

By leveraging the io.Reader and io.Writer interfaces, you can effectively manage large data, encrypting and decrypting seamlessly without over-consuming memory.

Next Article: Understanding Diffie-Hellman Key Exchange in Go

Previous Article: Securely Storing Secrets with Environment Variables in Go

Series: Cryptography and Security in Go

Golang

Related Articles

You May Also Like

  • How to remove HTML tags in a string in Go
  • How to remove special characters in a string in Go
  • How to remove consecutive whitespace in a string in Go
  • How to count words and characters in a string in Go
  • Relative imports in Go: Tutorial & Examples
  • How to run Python code with Go
  • How to generate slug from title in Go
  • How to create an XML sitemap in Go
  • How to redirect in Go (301, 302, etc)
  • Using Go with MongoDB: CRUD example
  • Auto deploy Go apps with CI/ CD and GitHub Actions
  • Fixing Go error: method redeclared with different receiver type
  • Fixing Go error: copy argument must have slice type
  • Fixing Go error: attempted to use nil slice
  • Fixing Go error: assignment to constant variable
  • Fixing Go error: cannot compare X (type Y) with Z (type W)
  • Fixing Go error: method has pointer receiver, not called with pointer
  • Fixing Go error: assignment mismatch: X variables but Y values
  • Fixing Go error: array index must be non-negative integer constant