DEV Community

kingyou
kingyou

Posted on

Go Security Guide: Tokens, SHA1, RSA & AES Encryption

Introduction

Security is critical in modern applications. Go provides robust standard libraries for implementing secure authentication and encryption. In this comprehensive guide, we'll explore:

  • Token Generation - Creating secure random tokens
  • SHA1 Hashing - Understanding cryptographic hashing
  • RSA Encryption - Asymmetric encryption for secure data transmission
  • AES Encryption - Symmetric encryption for fast, secure data storage

1. Token Generation

Tokens are used for authentication, session management, and API keys. Let's generate cryptographically secure random tokens.

Basic Random Token

package main

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

// GenerateToken creates a random hex token
func GenerateToken(length int) (string, error) {
    bytes := make([]byte, length)
    if _, err := rand.Read(bytes); err != nil {
        return "", err
    }
    return hex.EncodeToString(bytes), nil
}

func main() {
    token, err := GenerateToken(32)
    if err != nil {
        panic(err)
    }
    fmt.Println("Token:", token)
}
Enter fullscreen mode Exit fullscreen mode

Key Points:

  • Always use crypto/rand for security-critical random generation
  • Never use math/rand for tokens or keys
  • Common token sizes: 16, 32, or 64 bytes

UUID-based Token

import "github.com/google/uuid"

func GenerateUUIDToken() string {
    return uuid.New().String()
}
Enter fullscreen mode Exit fullscreen mode

2. SHA1 Hashing

Note: SHA1 is cryptographically broken for collision resistance. Use SHA256 or SHA512 for new applications. This example is for educational purposes.

package main

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

func HashSHA1(data string) string {
    hash := sha1.New()
    hash.Write([]byte(data))
    return hex.EncodeToString(hash.Sum(nil))
}

func main() {
    data := "Hello, Secure World!"
    hashed := HashSHA1(data)
    fmt.Printf("Original: %s\n", data)
    fmt.Printf("SHA1: %s\n", hashed)
}
Enter fullscreen mode Exit fullscreen mode

Better Alternative: SHA256

import "crypto/sha256"

func HashSHA256(data string) string {
    hash := sha256.Sum256([]byte(data))
    return hex.EncodeToString(hash[:])
}
Enter fullscreen mode Exit fullscreen mode

Use Cases:

  • File integrity verification
  • Data fingerprinting
  • Password hashing (with salt and iterations)

3. RSA Encryption

RSA is an asymmetric encryption algorithm using public/private key pairs.

Generate RSA Keys

package main

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

func GenerateRSAKeys(bits int) (*rsa.PrivateKey, error) {
    privateKey, err := rsa.GenerateKey(rand.Reader, bits)
    if err != nil {
        return nil, err
    }
    return privateKey, nil
}

func SavePrivateKey(filename string, key *rsa.PrivateKey) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    privateKeyBytes := x509.MarshalPKCS1PrivateKey(key)
    privateKeyPEM := &pem.Block{
        Type:  "RSA PRIVATE KEY",
        Bytes: privateKeyBytes,
    }
    return pem.Encode(file, privateKeyPEM)
}

func SavePublicKey(filename string, key *rsa.PublicKey) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    publicKeyBytes, err := x509.MarshalPKIXPublicKey(key)
    if err != nil {
        return err
    }

    publicKeyPEM := &pem.Block{
        Type:  "PUBLIC KEY",
        Bytes: publicKeyBytes,
    }
    return pem.Encode(file, publicKeyPEM)
}
Enter fullscreen mode Exit fullscreen mode

RSA Encryption & Decryption

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
)

func EncryptRSA(publicKey *rsa.PublicKey, message []byte) ([]byte, error) {
    hash := sha256.New()
    ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, publicKey, message, nil)
    if err != nil {
        return nil, err
    }
    return ciphertext, nil
}

func DecryptRSA(privateKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
    hash := sha256.New()
    plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, privateKey, ciphertext, nil)
    if err != nil {
        return nil, err
    }
    return plaintext, nil
}

// Example usage
func main() {
    privateKey, _ := GenerateRSAKeys(2048)
    publicKey := &privateKey.PublicKey

    message := []byte("Secret message")
    encrypted, _ := EncryptRSA(publicKey, message)
    decrypted, _ := DecryptRSA(privateKey, encrypted)

    fmt.Printf("Decrypted: %s\n", decrypted)
}
Enter fullscreen mode Exit fullscreen mode

4. AES Encryption

AES (Advanced Encryption Standard) is a symmetric encryption algorithm - fast and secure.

AES-GCM Mode (Recommended)

package main

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

func EncryptAES(key []byte, plaintext []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
    }

    ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
    return ciphertext, nil
}

func DecryptAES(key []byte, ciphertext []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:]
    plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        return nil, err
    }

    return plaintext, nil
}
Enter fullscreen mode Exit fullscreen mode

Complete AES Example

func main() {
    // AES-256 requires 32-byte key
    key := make([]byte, 32)
    if _, err := rand.Read(key); err != nil {
        panic(err)
    }

    plaintext := []byte("Sensitive data to encrypt")
    fmt.Printf("Original: %s\n", plaintext)

    encrypted, err := EncryptAES(key, plaintext)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Encrypted: %x\n", encrypted)

    decrypted, err := DecryptAES(key, encrypted)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Decrypted: %s\n", decrypted)
}
Enter fullscreen mode Exit fullscreen mode

Key Sizes:

  • AES-128: 16 bytes
  • AES-192: 24 bytes
  • AES-256: 32 bytes (recommended)

Best Practices

Security Checklist

  1. Random Generation

    • Always use crypto/rand, never math/rand
    • Generate keys with sufficient entropy
  2. Key Management

    • Store keys securely (environment variables, key vaults)
    • Never hardcode keys in source code
    • Rotate keys regularly
  3. Algorithm Selection

    • Use SHA256/SHA512 instead of SHA1
    • Prefer AES-GCM for authenticated encryption
    • Use RSA with OAEP padding
  4. Error Handling

    • Never ignore cryptographic errors
    • Use constant-time comparisons for secrets

Common Pitfalls to Avoid

  • Don't use ECB mode for AES
  • Don't reuse nonces in AES-GCM
  • Don't use small RSA key sizes (< 2048 bits)
  • Don't roll your own crypto primitives

Conclusion

Go's crypto package provides production-ready cryptographic primitives:

  • Tokens: Use crypto/rand for secure random generation
  • Hashing: Prefer SHA256+ for integrity and fingerprinting
  • RSA: Ideal for key exchange and digital signatures
  • AES-GCM: Best choice for encrypting data at rest and in transit

Remember: cryptography is complex. When in doubt, consult security experts and use established libraries. Stay secure! 🔐

Top comments (0)