Encryption Basics: AES, RSA, and Modern Cryptography

Introduction

Working in OWASP, penetration testing, cryptography, zero trust, and security audits for the past 15 years, I've seen firsthand how encryption protects sensitive data. Encryption technologies like AES and RSA are vital for safeguarding information transmitted over insecure channels, ensuring privacy and integrity. Without them, sensitive data is at risk of interception and misuse.

This tutorial covers the fundamentals of encryption, focusing on Advanced Encryption Standard (AES) and Rivest–Shamir–Adleman (RSA). You'll learn how AES (symmetric) secures data with shared keys while RSA (asymmetric) enables secure key exchange and digital signatures. This knowledge is crucial for developing applications that handle sensitive information securely.

By the end of this tutorial, you'll be able to implement AES encryption using Java (Java 11+) and Python (Python 3.8+), generate RSA keys in Java and Node.js (Node.js 16+), and apply best practices for key management and future-proofing with post-quantum considerations.

Understanding Symmetric Encryption: The AES Method

What is AES?

AES (Advanced Encryption Standard) is a symmetric block cipher that processes data in 128-bit blocks and supports 128-, 192-, and 256-bit keys. It is optimized for performance in software and hardware, and is widely used for data-at-rest and data-in-transit encryption when both endpoints can share a secret key.

Key characteristics:

  • Block size: 128 bits
  • Key sizes: 128, 192, 256 bits
  • Use cases: disk/file encryption, TLS bulk encryption, secure messaging
AES and RSA combined encryption flow Client encrypts payload with AES; AES key encrypted with RSA for key exchange; server decrypts RSA to retrieve AES key then decrypts payload. Client Generates AES key (K) Encrypt payload AES Encrypt data with K Encrypt K with RSA RSA Protects AES key
Figure: Hybrid encryption flow — AES for bulk data, RSA for key exchange

Why prefer authenticated modes (AES-GCM) over CBC

Authenticated encryption modes like AES-GCM provide confidentiality and integrity in a single primitive. CBC with PKCS#7/PKCS#5 padding provides confidentiality but not integrity — developers must add an HMAC to avoid forgery and padding oracle attacks. AES-GCM (or AES-GCM-SIV where nonce misuse resistance is needed) is widely recommended for new designs because it resists tampering and avoids the complexity of composing encryption and MAC correctly.

Runnable AES-GCM example (Java 11+)

Complete class: AES-GCM encrypt/decrypt with random IV, 128-bit authentication tag, and SecureRandom key generation.

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.SecureRandom;

public class AesGcmExample {
    private static final int AES_KEY_SIZE = 256; // or 128
    private static final int GCM_IV_LENGTH = 12;
    private static final int GCM_TAG_LENGTH = 128; // bits

    public static void main(String[] args) throws Exception {
        byte[] plaintext = "Example plaintext data".getBytes("UTF-8");

        // Generate key
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(AES_KEY_SIZE);
        SecretKey key = keyGen.generateKey();

        // Encrypt
        byte[] iv = new byte[GCM_IV_LENGTH];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);
        byte[] ciphertext = cipher.doFinal(plaintext);

        // Decrypt
        Cipher decipher = Cipher.getInstance("AES/GCM/NoPadding");
        decipher.init(Cipher.DECRYPT_MODE, key, spec);
        byte[] decrypted = decipher.doFinal(ciphertext);

        System.out.println(new String(decrypted, "UTF-8"));
    }
}

Troubleshooting tips (AES)

  • Always use authenticated modes (e.g., AES-GCM). CBC requires a separate MAC and is prone to padding oracle issues if not implemented correctly.
  • Never reuse an IV/nonce with the same key; for GCM use a 96-bit (12-byte) IV generated with secure randomness or a counter-based scheme with careful collision avoidance.
  • Validate ciphertext lengths and handle exceptions carefully to avoid side channels; fail closed on decryption/authentication errors.

The World of Asymmetric Encryption: Exploring RSA

Introduction to RSA

RSA uses a public/private key pair: the public key encrypts (or verifies signatures) and the private key decrypts (or signs). It is commonly used to encrypt small payloads (like session or symmetric keys) or to provide digital signatures. Use OAEP padding (with SHA-256) for encryption and RSA-PSS for signatures when possible.

Runnable RSA key generation and AES key wrapping (Java 11+)

Generate RSA keys, wrap (encrypt) an AES key with RSA-OAEP, and unwrap it on the receiver side.

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.KeyPairGenerator;

public class RsaWrapExample {
    public static void main(String[] args) throws Exception {
        // Generate AES key
        KeyGenerator kg = KeyGenerator.getInstance("AES");
        kg.init(256);
        SecretKey aesKey = kg.generateKey();

        // Generate RSA key pair
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(2048);
        KeyPair kp = kpg.generateKeyPair();

        // Wrap AES key with RSA-OAEP
        Cipher wrapCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        wrapCipher.init(Cipher.ENCRYPT_MODE, kp.getPublic());
        byte[] wrappedKey = wrapCipher.doFinal(aesKey.getEncoded());

        // Unwrap (recover AES key)
        Cipher unwrapCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        unwrapCipher.init(Cipher.DECRYPT_MODE, kp.getPrivate());
        byte[] unwrapped = unwrapCipher.doFinal(wrappedKey);

        System.out.println("Wrapped key length: " + wrappedKey.length);
        // Reconstruct SecretKey if needed
    }
}

Node.js example: AES-GCM encrypt/decrypt and RSA wrapping (Node.js 16+)

Generates an RSA key pair (synchronously for simplicity), performs AES-GCM encryption, then encrypts the AES key with the RSA public key (OAEP/SHA-256).

const crypto = require('crypto');

// Generate RSA key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicExponent: 0x10001,
  publicKeyEncoding: { type: 'spki', format: 'pem' },
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

// Generate AES key and IV
const aesKey = crypto.randomBytes(32); // 256-bit
const iv = crypto.randomBytes(12); // 96-bit recommended for GCM

// AES-GCM encrypt
const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);
const plaintext = Buffer.from('Example plaintext data');
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const tag = cipher.getAuthTag();

// Wrap AES key with RSA-OAEP (SHA-256)
const wrappedKey = crypto.publicEncrypt({
  key: publicKey,
  padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
  oaepHash: 'sha256'
}, aesKey);

// Later: unwrap and decrypt
const unwrappedKey = crypto.privateDecrypt({
  key: privateKey,
  padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
  oaepHash: 'sha256'
}, wrappedKey);

// AES-GCM decrypt
const decipher = crypto.createDecipheriv('aes-256-gcm', unwrappedKey, iv);
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
console.log(decrypted.toString());

Troubleshooting tips (RSA)

  • Use RSA only for small payloads; use hybrid encryption (RSA + AES) for bulk data to avoid performance issues.
  • Persist private keys in an HSM or cloud KMS; avoid storing unprotected private keys on disk.
  • Prefer OAEP for encryption and RSA-PSS for signatures to avoid padding-related attacks from older schemes.

Key Size Guidance and Justification

Choosing key sizes should follow current standards and threat models. For practical guidance, consult standards bodies such as NIST. General recommendations:

  • AES-128 provides robust security for many applications; AES-256 offers an extra security margin and is recommended for high-sensitivity data.
  • RSA 2048-bit is commonly used today and provides acceptable security for many applications; RSA 3072-bit or higher is recommended where longer-term security is required.
  • Elliptic-curve alternatives (e.g., P-256, X25519) provide equivalent security with smaller keys and often better performance.

Practical rule: match the key type and size to your threat model, expected data lifetime, and operational constraints. Refer to authoritative guidance from NIST for algorithm transitions and exact mappings between symmetric key sizes and asymmetric key sizes (nist.gov).

Key Management: Safeguarding Your Encryption Keys

Understanding Key Management

Effective key management covers generation, distribution, storage, rotation, backup, and destruction of keys. Use dedicated key management systems (KMS) or hardware security modules (HSMs) for production secrets. Examples of managed services include cloud provider KMS offerings; for on-premise HSMs, choose FIPS 140-2/3 compliant devices.

CLI example (AWS CLI v2):

aws kms create-key --description 'My key for encryption'

Security best practices for key management:

  • Store keys in a KMS/HSM and avoid embedding keys in application code or configuration files.
  • Implement strict access control and least-privilege policies for key usage.
  • Automate key rotation and document rotation windows; rotate symmetric keys used for long-term storage periodically.
  • Audit key usage logs to detect anomalous access patterns and potential compromise.

Troubleshooting tips (KMS/HSM):

  • If decryption fails, verify that the service principal/role has decrypt permissions and that the correct key alias/ARN is used.
  • Check for clock skew on systems — some KMS operations can rely on timestamps for token validation.
  • When migrating keys between environments, plan asymmetric key escrow and avoid exporting private keys unless strictly necessary and protected.

Modern Cryptography Techniques and Real-World Applications

Exploring Modern Techniques

Beyond AES and RSA, modern cryptography includes elliptic-curve cryptography (ECC), homomorphic encryption, and post-quantum algorithms. ECC (e.g., X25519 or P-256) gives strong security with smaller keys and is widely adopted for TLS, mobile apps, and constrained devices.

Example: ECC key generation in Node.js (Node.js 16+):

const crypto = require('crypto');
const ec = crypto.createECDH('secp256k1');
ec.generateKeys();

Homomorphic encryption — resources and libraries

  • Microsoft SEAL (C++) is a widely-used library for homomorphic encryption research and prototyping.
  • PALISADE and TFHE are additional frameworks for advanced homomorphic and lattice-based operations.
  • For experiment-level Python work, libraries and bindings exist that expose Paillier-style schemes for additive homomorphism (useful for simple aggregate computations).

Implementation guidance:

  • Start with well-maintained libraries and test on representative data sets to measure performance.
  • Homomorphic operations are computationally expensive — profile CPU, memory, and latency to understand trade-offs.
  • Consider hybrid designs where only sensitive aggregates run over encrypted inputs, while bulk processing uses conventional methods.

Hashing and Digital Signatures

Hashing and digital signatures are distinct from encryption but are essential for integrity and authenticity. Hash functions (SHA-256, SHA-3) produce fixed-length digests. HMACs combine a hash with a secret key for fast message authentication. Digital signatures (RSA-PSS, ECDSA) provide non-repudiation and integrity verification.

Quick examples — SHA-256 and HMAC (Node.js)

const crypto = require('crypto');

// SHA-256 digest
const data = 'message to hash';
const digest = crypto.createHash('sha256').update(data).digest('hex');
console.log('SHA-256:', digest);

// HMAC-SHA256
const key = crypto.randomBytes(32);
const hmac = crypto.createHmac('sha256', key).update(data).digest('hex');
console.log('HMAC-SHA256:', hmac);

Digital signature example — RSA-PSS (Node.js)

// sign and verify using RSA-PSS
const { generateKeyPairSync, sign, verify } = require('crypto');
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicExponent: 0x10001,
});

const message = Buffer.from('message to sign');
const signature = sign('sha256', message, {
  key: privateKey,
  padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
  saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
});

const ok = verify('sha256', message, {
  key: publicKey,
  padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
  saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
}, signature);
console.log('Signature valid:', ok);

Security notes:

  • Prefer salted, parameterized signature schemes such as RSA-PSS over older PKCS#1 v1.5 signatures.
  • Use domain-separated keys or context strings with signatures when signing heterogeneous data to avoid cross-protocol replay.

Further Reading

Key Takeaways

  • AES is a symmetric algorithm for bulk encryption (128-bit block size; 128/192/256-bit keys); prefer authenticated modes like AES-GCM.
  • RSA is an asymmetric system used for key exchange and signatures; use appropriate key sizes (e.g., 2048-bit or larger) and modern padding (OAEP for encryption, RSA-PSS for signatures).
  • Use dedicated KMS/HSM for key storage, automate rotation, and audit key usage to reduce risk.
  • Prepare for post-quantum transitions by designing algorithm agility and prototyping with available libraries and standards guidance from NIST and community projects.

Conclusion

Encryption remains a foundational element of secure systems. AES and RSA, when used with best practices (authenticated encryption, secure key storage, rotation), form the backbone of many secure systems. For future-proofing, organizations should monitor standardization efforts and begin prototyping post-quantum and homomorphic techniques where they make sense for their threat model and performance constraints.

To practice, implement AES-based file encryption and an RSA-wrapped key exchange in a small project using Java (Java 11+) or Node.js (Node.js 16+). Use a KMS/HSM in production and consult authoritative guidance at NIST when selecting keys and algorithms.

About the Author

Marcus Johnson

Marcus Johnson is a Cybersecurity Engineer with 15 years of experience specializing in OWASP, penetration testing, cryptography, zero trust, and security audits. His deep expertise in computer architecture and security allows him to identify vulnerabilities at the system level and implement comprehensive security solutions. Marcus has worked on critical infrastructure protection, threat analysis, and security architecture design for organizations handling sensitive data and mission-critical operations.


Published: Dec 20, 2025 | Updated: Jan 07, 2026