This episode opens up cryptography for C applications: the basics of symmetric and asymmetric encryption, the OpenSSL and libsodium libraries, hashing, HMAC, and secure random number generation, as well as secure key handling and the management of sensitive data in memory.

Data that crosses a network without protection can be read by anyone. Episode 14 discusses cryptography in C applications: how to protect data with encryption, ensure integrity with hashing and HMAC, and generate cryptographically secure random numbers.
An important rule to understand from the start: never write your own cryptography. Always use libraries tested by the world's security community, such as OpenSSL and libsodium. Self-written cryptography almost certainly contains fatal flaws.
This episode focuses on using libraries correctly: the right algorithms, safe configuration, and habits for keeping keys and sensitive data safe in memory.
Symmetric encryption uses one key to encrypt and decrypt, is fast, and suits large data. A modern example: AES-256-GCM. Asymmetric encryption uses a public-private key pair and is used for key exchange and signatures. In real practice, both are combined: a random symmetric key is wrapped by asymmetric cryptography.
C programs talk to cryptography libraries through an API that provides context objects, initialization functions, and processing functions. Most usage errors aren't in the algorithm's math but in key management, padding, and memory cleanup. The quality of a cryptography implementation is determined by these details.
Cryptography provides two different guarantees that are often confused: confidentiality ensures only key holders can read, while integrity ensures data hasn't been modified. Encryption without authentication like AES-CBC only guarantees confidentiality; an attacker can still tamper with the ciphertext. That's why the AES-GCM mode combines encryption and authentication in one operation. Understand which guarantee you need before choosing an algorithm.
OpenSSL is the most widely used cryptography library in the C ecosystem, forming the foundation of TLS for most of the world's servers. Many operating systems already ship it:
openssl version
pkg-config --modversion libcrypto libsslThe command openssl version shows the installed library version. To link a C program, use pkg-config, which outputs the right flags:
gcc -Wall $(pkg-config --cflags openssl) app.c -o app $(pkg-config --libs openssl)The pattern $(pkg-config --cflags openssl) inserts the header paths, and the libs part links libcrypto and libssl. Always use a supported OpenSSL version — outdated versions are vulnerable to CVEs.
libsodium is a modern wrapper over recommended algorithms, with an API that's far harder to misuse. Its development focuses on usability: default operations already use safe settings, and key lengths are always explicit. For new projects that prioritize simplicity, libsodium is a very strong choice.
A one-way hash such as SHA-256 produces a fixed-length fingerprint of data. Hashes suit integrity checks and password storage, but for passwords use specialized functions like Argon2 that are deliberately slow. HMAC uses a hash with a secret key, producing a signature that proves a message's authenticity and integrity:
echo -n "pesan rahasia" | openssl dgst -sha256
openssl rand -hex 32The command openssl dgst -sha256 computes the SHA-256 hash of the input. openssl rand -hex 32 generates 32 secure random bytes that can be used as a key. This is the key difference: ordinary random generators for statistics, cryptographic random generators for keys — don't mix them up.
The standard rand generator is not secure for cryptography. Use getrandom on Linux or the randombytes function in libsodium. Keys must be generated from the kernel's entropy source, not from system time or a guessable seed.
Keys in memory can leak through core dumps, swap, or process snapshots. Some recommended practices: wipe keys with memset after use, prevent the compiler from optimizing away the cleanup with volatile, and hold memory out of swap with mlock:
#include <string.h>
#include <sys/mman.h>
void bersihkan(unsigned char *kunci, size_t n) {
mlock(kunci, n);
volatile unsigned char *p = kunci;
while (n--) {
*p++ = 0;
}
munlock(kunci, n);
}mlock(kunci, n) prevents the key's memory pages from being swapped out, and the wipe through a volatile pointer ensures zeros are really written without compiler optimization. Read keys from an env or a vault, not from arguments visible in the process list.
Sensitive data like passwords and tokens must be wiped as soon as it's no longer needed, never printed to logs, and never sent as plaintext without TLS. Also restrict who can read key files with strict file permissions. Data security is a chain; one weak link weakens everything.
Warning
Rolling your own cryptography almost certainly produces an insecure system. Use tested libraries, follow their security updates, and run tools like sodium_init and audit dependencies regularly.
Key takeaways:
In the next episode 15 we will discuss performance optimization — profiling with perf, gprof, and Valgrind, optimization with loop unrolling, inline, and compiler flags, memory locality and cache friendliness, up to the tradeoff between readability and performance.