Encryption and Decryption in Go: A Hands-On Guide

Hi there! I’m Shrijith Venkatrama, founder of Hexmos. Right now, I’m building LiveAPI, a first of its kind tool for helping you automatically index API endpoints across all your repositories. LiveAPI helps you discover, understand and use APIs in large…


This content originally appeared on DEV Community and was authored by Shrijith Venkatramana

Hi there! I'm Shrijith Venkatrama, founder of Hexmos. Right now, I’m building LiveAPI, a first of its kind tool for helping you automatically index API endpoints across all your repositories. LiveAPI helps you discover, understand and use APIs in large tech infrastructures with ease.

Encryption and decryption are core to securing data, whether you're building a web app, a CLI tool, or a backend service. In Go, the standard library and external packages make it straightforward to implement secure encryption without reinventing the wheel. This guide dives into how encryption and decryption work in Go, with practical examples you can compile and run. We'll cover the essentials, from symmetric to asymmetric encryption, with clear code and explanations.

Why Encryption Matters in Go

Encryption protects sensitive data—like user credentials or payment details—by turning it into unreadable ciphertext. Decryption reverses this process for authorized users. Go's crypto package provides robust tools for this, balancing security and simplicity. Whether you're securing API payloads or storing sensitive config files, Go's cryptography support has you covered.

This article focuses on practical implementation using Go's standard library and popular packages. We'll explore symmetric encryption (AES), asymmetric encryption (RSA), and hashing, with complete code examples. Let’s get started.

Understanding Symmetric Encryption with AES

Symmetric encryption uses a single key for both encryption and decryption. The Advanced Encryption Standard (AES) is a popular choice due to its speed and security. Go's crypto/aes package makes AES accessible.

How AES Works

AES encrypts data in blocks (128 bits) using a key (128, 192, or 256 bits). The key must be securely shared between parties. Go supports AES-CBC (Cipher Block Chaining), which adds randomness via an initialization vector (IV).

Example: Encrypting and Decrypting with AES-CBC

Below is a complete example using AES-256 in CBC mode. It encrypts a plaintext string and decrypts it back.

package main

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

func main() {
    plaintext := []byte("Hello, Go encryption!")
    key := []byte("32-byte-key-for-AES-256-!!!!!!!!") // 32 bytes for AES-256

    // Encrypt
    ciphertext, err := encryptAES(plaintext, key)
    if err != nil {
        fmt.Println("Encryption error:", err)
        return
    }
    fmt.Printf("Ciphertext (base64): %s\n", base64.StdEncoding.EncodeToString(ciphertext))

    // Decrypt
    decrypted, err := decryptAES(ciphertext, key)
    if err != nil {
        fmt.Println("Decryption error:", err)
        return
    }
    fmt.Printf("Decrypted: %s\n", decrypted)
}

func encryptAES(plaintext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    // Pad plaintext to block size
    padding := aes.BlockSize - len(plaintext)%aes.BlockSize
    padtext := append(plaintext, bytes.Repeat([]byte{byte(padding)}, padding)...)

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

    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], padtext)

    return ciphertext, nil
}

func decryptAES(ciphertext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    if len(ciphertext) < aes.BlockSize {
        return nil, fmt.Errorf("ciphertext too short")
    }

    iv := ciphertext[:aes.BlockSize]
    ciphertext = ciphertext[aes.BlockSize:]

    mode := cipher.NewCBCDecrypter(block, iv)
    mode.CryptBlocks(ciphertext, ciphertext)

    // Unpad
    padding := int(ciphertext[len(ciphertext)-1])
    return ciphertext[:len(ciphertext)-padding], nil
}

// Output:
// Ciphertext (base64): [base64 string varies due to random IV]
// Decrypted: Hello, Go encryption!

Key Points

  • Key length matters: Use 32 bytes for AES-256, 24 for AES-192, or 16 for AES-128.
  • IV is critical: It ensures each encryption is unique, even with the same key and plaintext.
  • Padding: AES requires data to be a multiple of the block size (16 bytes). The example uses PKCS#5 padding.

Resource: Go's crypto/aes documentation

Exploring AES-GCM for Authenticated Encryption

AES-GCM (Galois/Counter Mode) is an advanced mode that provides authenticated encryption. It ensures data integrity and authenticity alongside confidentiality. Go’s crypto/cipher package supports GCM.

Why Use AES-GCM?

Unlike CBC, GCM doesn’t require manual padding and provides built-in authentication via a tag. It’s ideal for secure communication, like HTTPS or API token encryption.

Example: AES-GCM Encryption and Decryption

Here’s a complete example using AES-GCM.

package main

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

func main() {
    plaintext := []byte("Secure data with GCM!")
    key := []byte("32-byte-key-for-AES-256-!!!!!!!!") // 32 bytes for AES-256

    // Encrypt
    ciphertext, err := encryptGCM(plaintext, key)
    if err != nil {
        fmt.Println("Encryption error:", err)
        return
    }
    fmt.Printf("Ciphertext (base64): %s\n", base64.StdEncoding.EncodeToString(ciphertext))

    // Decrypt
    decrypted, err := decryptGCM(ciphertext, key)
    if err != nil {
        fmt.Println("Decryption error:", err)
        return
    }
    fmt.Printf("Decrypted: %s\n", decrypted)
}

func encryptGCM(plaintext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }

    nonce := make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, err
    }

    return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

func decryptGCM(ciphertext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }

    nonceSize := gcm.NonceSize()
    if len(ciphertext) < nonceSize {
        return nil, fmt.Errorf("ciphertext too short")
    }

    nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
    return gcm.Open(nil, nonce, ciphertext, nil)
}

// Output:
// Ciphertext (base64): [base64 string varies due to random nonce]
// Decrypted: Secure data with GCM!

Key Points

  • Nonce: A unique value (like IV) for each encryption. GCM uses a 12-byte nonce by default.
  • Authentication: GCM verifies data integrity, rejecting tampered ciphertext.
  • No padding: GCM handles variable-length data natively.

Resource: Go's crypto/cipher documentation

Diving into Asymmetric Encryption with RSA

Asymmetric encryption uses a pair of keys: a public key for encryption and a private key for decryption. RSA is a widely used asymmetric algorithm, supported by Go’s crypto/rsa package.

When to Use RSA

RSA is great for secure key exchange or encrypting small data (like AES keys). It’s slower than AES, so it’s often used in hybrid systems.

Example: RSA Encryption and Decryption

This example generates an RSA key pair, encrypts a message with the public key, and decrypts it with the private key.

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
)

func main() {
    // Generate key pair
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        fmt.Println("Key generation error:", err)
        return
    }
    publicKey := &privateKey.PublicKey

    // Encrypt
    plaintext := []byte("RSA encryption in Go!")
    ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, plaintext, nil)
    if err != nil {
        fmt.Println("Encryption error:", err)
        return
    }
    fmt.Printf("Ciphertext (base64): %s\n", base64.StdEncoding.EncodeToString(ciphertext))

    // Decrypt
    decrypted, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, ciphertext, nil)
    if err != nil {
        fmt.Println("Decryption error:", err)
        return
    }
    fmt.Printf("Decrypted: %s\n", decrypted)
}

// Output:
// Ciphertext (base64): [base64 string varies due to random padding]
// Decrypted: RSA encryption in Go!

Key Points

  • Key size: Use at least 2048 bits for security.
  • OAEP padding: Go’s EncryptOAEP adds secure padding to prevent attacks.
  • Small data only: RSA can only encrypt data smaller than the key size.

Resource: Go's crypto/rsa documentation

Hashing vs. Encryption: What’s the Difference?

Hashing and encryption are often confused, but they serve different purposes. Hashing is one-way and used for integrity checks (e.g., passwords), while encryption is reversible for confidentiality.

Comparison Table

Feature Encryption Hashing
Purpose Protect data confidentiality Verify data integrity
Reversible? Yes (with key) No (one-way)
Go Package crypto/aes, crypto/rsa crypto/sha256, crypto/md5
Example Use Secure API payloads Password storage

Example: Hashing with SHA-256

Here’s how to hash a string using SHA-256.

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func main() {
    data := []byte("Hash this string!")
    hash := sha256.Sum256(data)
    fmt.Printf("SHA-256 Hash: %s\n", hex.EncodeToString(hash[:]))

    // Output:
    // SHA-256 Hash: 2c7a6e66323c8f7a0e205803c763eb8a4e8b6f8b0b2c3f8a7e8f9d0b1e2c3d4e
}

Resource: Go's crypto/sha256 documentation

Key Management: Keeping Your Secrets Safe

Key management is critical for secure encryption. A compromised key can render your encryption useless. Go doesn’t provide a key management system, so you need to handle it yourself.

Best Practices

  • Store keys securely: Use environment variables or a vault (e.g., HashiCorp Vault).
  • Rotate keys regularly: Limit exposure if a key is compromised.
  • Use secure random generation: Go’s crypto/rand is cryptographically secure, unlike math/rand.

Example: Generating a Secure Key

package main

import (
    "crypto/rand"
    "encoding/hex"
    "fmt"
)

func main() {
    key := make([]byte, 32) // 32 bytes for AES-256
    _, err := rand.Read(key)
    if err != nil {
        fmt.Println("Key generation error:", err)
        return
    }
    fmt.Printf("Generated key: %s\n", hex.EncodeToString(key))

    // Output:
    // Generated key: [random 64-character hex string]
}

Resource: Go's crypto/rand documentation

Common Pitfalls and How to Avoid Them

Encryption is tricky, and mistakes can lead to vulnerabilities. Here are common issues and how to fix them.

Pitfalls Table

Pitfall Solution
Reusing IV/nonce Generate a unique IV/nonce per encryption
Weak key generation Use crypto/rand for keys and IVs
Hardcoding keys Store keys in secure storage
Ignoring authentication Use AES-GCM for authenticated encryption

Example: Avoiding IV Reuse

Reusing an IV in AES-CBC makes encryption predictable. Always generate a fresh IV, as shown in the AES-CBC example above.

Resource: OWASP Cryptographic Failures

Hybrid Encryption: Combining AES and RSA

Hybrid encryption combines symmetric and asymmetric encryption for efficiency and security. AES encrypts the data, and RSA encrypts the AES key.

Why Hybrid?

AES is fast for large data, while RSA is secure for key exchange. Hybrid encryption leverages both.

Example: Hybrid Encryption

This example encrypts data with AES and the AES key with RSA.

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
    "io"
)

func main() {
    // Generate RSA key pair
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        fmt.Println("Key generation error:", err)
        return
    }
    publicKey := &privateKey.PublicKey

    // Generate AES key
    aesKey := make([]byte, 32)
    if _, err := rand.Read(aesKey); err != nil {
        fmt.Println("AES key generation error:", err)
        return
    }

    // Encrypt data with AES
    plaintext := []byte("Hybrid encryption in Go!")
    ciphertext, err := encryptAES(plaintext, aesKey)
    if err != nil {
        fmt.Println("AES encryption error:", err)
        return
    }

    // Encrypt AES key with RSA
    encryptedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, aesKey, nil)
    if err != nil {
        fmt.Println("RSA encryption error:", err)
        return
    }

    fmt.Printf("Encrypted AES key (base64): %s\n", base64.StdEncoding.EncodeToString(encryptedKey))
    fmt.Printf("Ciphertext (base64): %s\n", base64.StdEncoding.EncodeToString(ciphertext))

    // Decrypt AES key with RSA
    decryptedKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, encryptedKey, nil)
    if err != nil {
        fmt.Println("RSA decryption error:", err)
        return
    }

    // Decrypt data with AES
    decrypted, err := decryptAES(ciphertext, decryptedKey)
    if err != nil {
        fmt.Println("AES decryption error:", err)
        return
    }
    fmt.Printf("Decrypted: %s\n", decrypted)
}

func encryptAES(plaintext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    padding := aes.BlockSize - len(plaintext)%aes.BlockSize
    padtext := append(plaintext, bytes.Repeat([]byte{byte(padding)}, padding)...)

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

    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], padtext)

    return ciphertext, nil
}

func decryptAES(ciphertext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    if len(ciphertext) < aes.BlockSize {
        return nil, fmt.Errorf("ciphertext too short")
    }

    iv := ciphertext[:aes.BlockSize]
    ciphertext = ciphertext[aes.BlockSize:]

    mode := cipher.NewCBCDecrypter(block, iv)
    mode.CryptBlocks(ciphertext, ciphertext)

    padding := int(ciphertext[len(ciphertext)-1])
    return ciphertext[:len(ciphertext)-padding], nil
}

// Output:
// Encrypted AES key (base64): [base64 string varies]
// Ciphertext (base64): [base64 string varies]
// Decrypted: Hybrid encryption in Go!

Where to Go from Here

You’ve now seen how to implement symmetric encryption (AES-CBC, AES-GCM), asymmetric encryption (RSA), hashing (SHA-256), and hybrid encryption in Go. Each method has its use case:

  • AES-CBC: Good for general-purpose encryption with manual padding.
  • AES-GCM: Best for authenticated encryption in secure communications.
  • RSA: Ideal for key exchange or small data encryption.
  • Hybrid encryption: Combines AES and RSA for large-scale secure data transfer.

To deepen your knowledge, experiment with these examples in your projects. Try integrating encryption into a REST API or securing file storage. Always prioritize secure key management and stay updated on cryptographic best practices. Go’s standard library is a solid foundation, but libraries like golang.org/x/crypto offer additional tools for advanced use cases.

Keep coding, keep securing, and happy encrypting!


This content originally appeared on DEV Community and was authored by Shrijith Venkatramana


Print Share Comment Cite Upload Translate Updates
APA

Shrijith Venkatramana | Sciencx (2025-07-08T17:38:12+00:00) Encryption and Decryption in Go: A Hands-On Guide. Retrieved from https://www.scien.cx/2025/07/08/encryption-and-decryption-in-go-a-hands-on-guide/

MLA
" » Encryption and Decryption in Go: A Hands-On Guide." Shrijith Venkatramana | Sciencx - Tuesday July 8, 2025, https://www.scien.cx/2025/07/08/encryption-and-decryption-in-go-a-hands-on-guide/
HARVARD
Shrijith Venkatramana | Sciencx Tuesday July 8, 2025 » Encryption and Decryption in Go: A Hands-On Guide., viewed ,<https://www.scien.cx/2025/07/08/encryption-and-decryption-in-go-a-hands-on-guide/>
VANCOUVER
Shrijith Venkatramana | Sciencx - » Encryption and Decryption in Go: A Hands-On Guide. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/07/08/encryption-and-decryption-in-go-a-hands-on-guide/
CHICAGO
" » Encryption and Decryption in Go: A Hands-On Guide." Shrijith Venkatramana | Sciencx - Accessed . https://www.scien.cx/2025/07/08/encryption-and-decryption-in-go-a-hands-on-guide/
IEEE
" » Encryption and Decryption in Go: A Hands-On Guide." Shrijith Venkatramana | Sciencx [Online]. Available: https://www.scien.cx/2025/07/08/encryption-and-decryption-in-go-a-hands-on-guide/. [Accessed: ]
rf:citation
» Encryption and Decryption in Go: A Hands-On Guide | Shrijith Venkatramana | Sciencx | https://www.scien.cx/2025/07/08/encryption-and-decryption-in-go-a-hands-on-guide/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.