Symmetric Cryptography

Symmetric cryptography uses the same key for both encryption and decryption. It’s fast and efficient for encrypting large amounts of data.

Common Symmetric Algorithms

Advanced Encryption Standard (AES)

ChaCha20

Legacy Algorithms

Security Considerations

Implementation Examples

Python: AES-256-GCM

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

def encrypt_aes_gcm(plaintext, key=None):
    if key is None:
        key = os.urandom(32)  # 256-bit key
    aesgcm = AESGCM(key)
    nonce = os.urandom(12)  # 96-bit nonce for GCM
    ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None)
    return key, nonce, ciphertext

def decrypt_aes_gcm(key, nonce, ciphertext):
    aesgcm = AESGCM(key)
    return aesgcm.decrypt(nonce, ciphertext, None).decode()

Go: ChaCha20-Poly1305

package main

import (
    "crypto/cipher"
    "crypto/rand"
    "golang.org/x/crypto/chacha20poly1305"
)

func encryptChaCha20(key, plaintext []byte) ([]byte, error) {
    aead, err := chacha20poly1305.New(key)
    if err != nil {
        return nil, err
    }
    nonce := make([]byte, aead.NonceSize())
    if _, err := rand.Read(nonce); err != nil {
        return nil, err
    }
    return aead.Seal(nonce, nonce, plaintext, nil), nil
}

func decryptChaCha20(key, ciphertext []byte) ([]byte, error) {
    aead, err := chacha20poly1305.New(key)
    if err != nil {
        return nil, err
    }
    nonceSize := aead.NonceSize()
    if len(ciphertext) < nonceSize {
        return nil, errors.New("ciphertext too short")
    }
    nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
    return aead.Open(nil, nonce, ciphertext, nil)
}

C: AES-256-CBC with OpenSSL

#include <openssl/evp.h>
#include <openssl/rand.h>

int encrypt_aes_256_cbc(unsigned char *plaintext, int plaintext_len, unsigned char *key,
                        unsigned char *iv, unsigned char *ciphertext) {
    EVP_CIPHER_CTX *ctx;
    int len;
    int ciphertext_len;

    if(!(ctx = EVP_CIPHER_CTX_new()))
        return -1;

    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
        return -1;

    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        return -1;
    ciphertext_len = len;

    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
        return -1;
    ciphertext_len += len;

    EVP_CIPHER_CTX_free(ctx);
    return ciphertext_len;
}

Rust: XChaCha20-Poly1305

use chacha20poly1305::{
    aead::{Aead, KeyInit},
    XChaCha20Poly1305, XNonce
};
use rand_core::OsRng;

fn encrypt_xchacha20(key: &[u8; 32], plaintext: &[u8]) -> (XNonce, Vec<u8>) {
    let cipher = XChaCha20Poly1305::new_from_slice(key).unwrap();
    let nonce = XNonce::from_slice(b"24-byte nonce for XChaCha20");
    let ciphertext = cipher.encrypt(nonce, plaintext).unwrap();
    (*nonce, ciphertext)
}

fn decrypt_xchacha20(key: &[u8; 32], nonce: &XNonce, ciphertext: &[u8]) -> Result<Vec<u8>, String> {
    let cipher = XChaCha20Poly1305::new_from_slice(key).map_err(|e| e.to_string())?;
    cipher.decrypt(nonce, ciphertext).map_err(|e| e.to_string())
}

Performance Considerations

Authenticated Encryption with Associated Data (AEAD)

AEAD combines encryption and authentication into a single cryptographic scheme, ensuring both confidentiality and integrity of the encrypted data. It’s the recommended approach for modern cryptographic systems.

Why Use AEAD?

Common AEAD Algorithms

  1. AES-GCM (Galois/Counter Mode)

    • Most widely used AEAD
    • 128-bit block cipher
    • 12-byte nonce (96 bits) recommended
    • 16-byte authentication tag (128 bits)
  2. ChaCha20-Poly1305

    • Faster in software than AES on non-AES-NI hardware
    • 256-bit key
    • 12-byte nonce
    • 16-byte authentication tag
  3. AES-CCM

    • Combines CTR mode with CBC-MAC
    • Used in wireless protocols like 802.11i
    • Slower than GCM due to two passes over the data

Implementation Examples

Python: AES-GCM

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

def encrypt_aead(key, plaintext, associated_data):
    # Generate a random 96-bit nonce
    nonce = os.urandom(12)
    
    # Create AES-GCM cipher
    aesgcm = AESGCM(key)
    
    # Encrypt and authenticate
    ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
    
    # Nonce is not secret, but must be unique for each encryption with the same key
    return nonce, ciphertext

def decrypt_aead(key, nonce, ciphertext, associated_data):
    aesgcm = AESGCM(key)
    return aesgcm.decrypt(nonce, ciphertext, associated_data)

Go: ChaCha20-Poly1305

package main

import (
    "crypto/cipher"
    "crypto/rand"
    "golang.org/x/crypto/chacha20poly1305"
    "io"
)

func encryptAEAD(key, plaintext, additionalData []byte) ([]byte, error) {
    // Create new ChaCha20-Poly1305 cipher
    aead, err := chacha20poly1305.New(key)
    if err != nil {
        return nil, err
    }

    // Nonce must be unique for each encryption with this key
    nonce := make([]byte, aead.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, err
    }

    // Encrypt and authenticate
    ciphertext := aead.Seal(nil, nonce, plaintext, additionalData)
    
    // Prepend nonce to ciphertext for storage/transmission
    return append(nonce, ciphertext...), nil
}

func decryptAEAD(key, data, additionalData []byte) ([]byte, error) {
    aead, err := chacha20poly1305.New(key)
    if err != nil {
        return nil, err
    }
    
    // Extract nonce from the beginning of the data
    nonceSize := aead.NonceSize()
    if len(data) < nonceSize {
        return nil, errors.New("ciphertext too short")
    }
    
    nonce := data[:nonceSize]
    ciphertext := data[nonceSize:]
    
    // Decrypt and verify
    return aead.Open(nil, nonce, ciphertext, additionalData)
}

Security Considerations for AEAD

  1. Nonce Management

    • Never reuse a nonce with the same key
    • Use a counter or cryptographically secure random nonce
    • For AES-GCM, 96-bit nonces are recommended
  2. Key Management

    • Use proper key derivation for password-based encryption
    • Rotate keys periodically
    • Store keys securely (HSM, key management service)
  3. Associated Data

    • Authenticate all data that affects message handling
    • Include protocol version numbers and message types
    • Include message sequence numbers to prevent replay attacks

Performance Benchmarks

Algorithm Throughput (MB/s) Latency (μs) Key Size Nonce Size Tag Size
AES-128-GCM 1,200 0.8 16 B 12 B 16 B
AES-256-GCM 1,000 1.0 32 B 12 B 16 B
ChaCha20-Poly1305 900 1.1 32 B 12 B 16 B
AES-CCM 600 1.8 16/24/32 B 13 B 4-16 B

Common Pitfalls

  1. Nonce Reuse

    • Catastrophic security failure in most AEAD schemes
    • Solution: Use a counter or cryptographically secure random nonce
  2. Unverified Plaintext

    • Always verify the authentication tag before decrypting
    • Solution: Use the API correctly - don’t skip verification
  3. Key Derivation

    • Weak keys from passwords can compromise security
    • Solution: Use Argon2, scrypt, or PBKDF2 with sufficient iterations
  4. Timing Attacks

    • Some implementations may be vulnerable to timing attacks
    • Solution: Use constant-time comparison for authentication tags

Best Practices

  1. Always use AEAD for new development when possible
  2. Generate cryptographically secure random keys and nonces
  3. Never reuse the same (key, nonce) pair
  4. Use key derivation functions (like Argon2) for password-based encryption
  5. Keep your cryptographic libraries up to date
  6. Authenticate all data that affects message handling
  7. Use appropriate key sizes (256-bit for long-term security)
  8. Implement proper error handling - don’t leak timing information
  9. Consider hardware acceleration for better performance
  10. Regularly audit your implementation for security vulnerabilities

Further Reading