Skip to content
All essays
ArchitectureMarch 27, 202412 min

Encryption Basics: Protecting Data in the Digital Age

Learn the fundamentals of encryption, why it matters, and how to implement it correctly in your applications

Ü
Ümit Uz
Mobile & Full Stack Developer

In an era where data breaches and cyber threats are increasingly common, encryption has become one of the most critical tools for protecting sensitive information. Whether you're building a web application, securing communications, or storing user data, understanding encryption fundamentals is essential for every developer.

What is Encryption?

Encryption is the process of converting plaintext data into ciphertext, an unreadable format that can only be deciphered by authorized parties with the correct decryption key. This mathematical process ensures that even if data is intercepted or accessed by unauthorized individuals, it remains meaningless without the key.

The Core Components

Plaintext: The original, readable data that you want to protect

Ciphertext: The encrypted, unreadable version of the data

Encryption Key: A string of data used in conjunction with an algorithm to encrypt and decrypt information

Encryption Algorithm: The mathematical process that transforms plaintext into ciphertext

Why Encryption Matters

Modern applications handle vast amounts of sensitive data: personal information, financial details, health records, authentication credentials, and confidential communications. Encryption provides:

  • Confidentiality: Ensures data remains private
  • Integrity: Verifies data hasn't been tampered with
  • Authentication: Confirms the identity of communicating parties
  • Non-repudiation: Prevents parties from denying their actions

Types of Encryption

Symmetric Encryption

Symmetric encryption uses the same key for both encryption and decryption. It's fast and efficient, making it ideal for encrypting large amounts of data.

typescript
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

// Generate a random key and IV
const key = randomBytes(32); // 256-bit key
const iv = randomBytes(16);  // Initialization vector

// Encrypt data
function encrypt(text: string): string {
  const cipher = createCipheriv('aes-256-cbc', key, iv);
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  return encrypted;
}

// Decrypt data
function decrypt(encryptedText: string): string {
  const decipher = createDecipheriv('aes-256-cbc', key, iv);
  let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  return decrypted;
}

const original = 'Sensitive data to protect';
const encrypted = encrypt(original);
const decrypted = decrypt(encrypted);

console.log('Original:', original);
console.log('Encrypted:', encrypted);
console.log('Decrypted:', decrypted);

Asymmetric Encryption

Asymmetric encryption uses a pair of keys: a public key for encryption and a private key for decryption. This enables secure communication without sharing secret keys.

typescript
import { generateKeyPairSync, publicEncrypt, privateDecrypt } from 'crypto';

// Generate key pair
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
  modulusLength: 2048,
});

// Encrypt with public key
function encryptWithPublicKey(text: string): Buffer {
  const buffer = Buffer.from(text, 'utf8');
  return publicEncrypt(publicKey, buffer);
}

// Decrypt with private key
function decryptWithPrivateKey(encrypted: Buffer): string {
  const decrypted = privateDecrypt(privateKey, encrypted);
  return decrypted.toString('utf8');
}

const message = 'Secret message';
const encrypted = encryptWithPublicKey(message);
const decrypted = decryptWithPrivateKey(encrypted);

console.log('Original:', message);
console.log('Encrypted:', encrypted.toString('hex'));
console.log('Decrypted:', decrypted);

Common Encryption Algorithms

AES (Advanced Encryption Standard)

AES is the most widely used symmetric encryption algorithm. It's fast, secure, and supported by virtually all platforms.

  • Key Sizes: 128, 192, or 256 bits
  • Block Size: 128 bits
  • Use Cases: File encryption, database encryption, secure communications

RSA (Rivest-Shamir-Adleman)

RSA is the most common asymmetric encryption algorithm, based on the mathematical difficulty of factoring large prime numbers.

  • Key Size: Typically 2048 or 4096 bits
  • Use Cases: Key exchange, digital signatures, small data encryption

ChaCha20

A modern stream cipher that's faster than AES in software-only implementations.

  • Key Size: 256 bits
  • Use Cases: Mobile applications, real-time communications

Best Practices for Implementation

1. Never Roll Your Own Crypto

Always use well-established, peer-reviewed cryptographic libraries. Implementing your own encryption is virtually guaranteed to have vulnerabilities.

typescript
// ❌ BAD: Custom "encryption"
function badEncrypt(text: string, key: string): string {
  return text.split('').map((c, i) =>
    String.fromCharCode(c.charCodeAt(0) ^ key.charCodeAt(i % key.length))
  ).join('');
}

// ✅ GOOD: Use established libraries
import { createCipheriv } from 'crypto';
const cipher = createCipheriv('aes-256-gcm', key, iv);

2. Use Authenticated Encryption

Always use authenticated encryption modes like AES-GCM or ChaCha20-Poly1305 that provide both confidentiality and integrity.

typescript
import { createCipheriv, createDecipheriv } from 'crypto';

function encryptWithAuth(text: string, key: Buffer, iv: Buffer) {
  const cipher = createCipheriv('aes-256-gcm', key, iv);

  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  // Get authentication tag
  const authTag = cipher.getAuthTag();

  return { encrypted, authTag: authTag.toString('hex') };
}

3. Manage Keys Securely

Never hardcode keys in your source code or commit them to version control.

typescript
// ❌ BAD: Hardcoded key
const API_KEY = '1234567890abcdef';

// ✅ GOOD: Load from environment
const key = process.env.ENCRYPTION_KEY;

// ✅ BETTER: Use a key management service
import { SecretsManager } from 'aws-sdk';
const secret = await SecretsManager.getSecretValue('production/encryption-key');

4. Use Proper Initialization Vectors

Always use a random, unique IV for each encryption operation. Never reuse IVs.

typescript
import { randomBytes } from 'crypto';

// Generate a new IV for each encryption
const iv = randomBytes(16);

// Store IV alongside ciphertext (it's not secret)
const result = {
  iv: iv.toString('hex'),
  ciphertext: encrypted,
};

Real-World Implementation Scenarios

Encrypting Database Fields

typescript
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto';

class FieldEncryption {
  private key: Buffer;

  constructor(password: string, salt: string) {
    // Derive key from password
    this.key = scryptSync(password, salt, 32);
  }

  encryptField(value: string): { iv: string; data: string } {
    const iv = randomBytes(16);
    const cipher = createCipheriv('aes-256-cbc', this.key, iv);

    let encrypted = cipher.update(value, 'utf8', 'hex');
    encrypted += cipher.final('hex');

    return {
      iv: iv.toString('hex'),
      data: encrypted,
    };
  }

  decryptField(encrypted: { iv: string; data: string }): string {
    const iv = Buffer.from(encrypted.iv, 'hex');
    const decipher = createDecipheriv('aes-256-cbc', this.key, iv);

    let decrypted = decipher.update(encrypted.data, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
  }
}

// Usage
const encryption = new FieldEncryption(
  process.env.DB_ENCRYPTION_KEY!,
  process.env.DB_ENCRYPTION_SALT!
);

// Encrypt before saving
const encryptedSSN = encryption.encryptField(user.ssn);
await database.save({ ...user, ssn: encryptedSSN });

// Decrypt after retrieving
const record = await database.findOne(id);
const ssn = encryption.decryptField(record.ssn);

Secure API Communication

typescript
import { createSign, createVerify } from 'crypto';

// Sign outgoing requests
function signRequest(data: any, privateKey: string): string {
  const sign = createSign('SHA256');
  sign.update(JSON.stringify(data));
  sign.end();
  return sign.sign(privateKey, 'base64');
}

// Verify incoming requests
function verifyRequest(data: any, signature: string, publicKey: string): boolean {
  const verify = createVerify('SHA256');
  verify.update(JSON.stringify(data));
  verify.end();
  return verify.verify(publicKey, signature, 'base64');
}

Common Pitfalls to Avoid

1. Using Weak Key Sizes

typescript
// ❌ BAD: 1024-bit RSA is deprecated
const { publicKey } = generateKeyPairSync('rsa', { modulusLength: 1024 });

// ✅ GOOD: Use at least 2048 bits
const { publicKey: goodKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });

2. Reusing Keys Across Applications

Each application or context should have its own unique keys.

3. Forgetting to Encrypt Data at Rest

Many developers encrypt data in transit but forget to encrypt stored data.

4. Ignoring Key Rotation

Regularly rotate encryption keys to limit the impact of potential compromises.

Testing Your Implementation

Always test encryption implementations thoroughly:

typescript
import { describe, it, expect } from 'vitest';

describe('Encryption', () => {
  it('should encrypt and decrypt correctly', () => {
    const original = 'Test data';
    const encrypted = encrypt(original);
    const decrypted = decrypt(encrypted);

    expect(decrypted).toBe(original);
    expect(encrypted).not.toBe(original);
  });

  it('should produce different ciphertext for same plaintext', () => {
    const plaintext = 'Same data';
    const encrypted1 = encrypt(plaintext);
    const encrypted2 = encrypt(plaintext);

    expect(encrypted1).not.toBe(encrypted2);
    expect(decrypt(encrypted1)).toBe(decrypt(encrypted2));
  });
});

Conclusion

Encryption is a fundamental tool for protecting data in modern applications. By understanding the basics, following best practices, and using established libraries, you can significantly improve the security of your applications.

Remember: encryption is just one part of a comprehensive security strategy. Combine it with other security measures like authentication, authorization, input validation, and secure coding practices to build truly secure applications.

Enjoyed this essay? Share it.

Related essays

Next essay
Hash Functions: The Foundation of Data Integrity