Comprehensive guide to OpenVPN’s encryption mechanisms, supported ciphers, and cryptographic implementation details
OpenVPN provides robust encryption for securing VPN tunnels through multiple cryptographic layers. Understanding the encryption architecture is essential for configuring secure and performant VPN connections.
OpenVPN is tightly bound to the OpenSSL library and derives much of its crypto capabilities from it. It also supports mbedTLS as an alternative crypto backend.
GCM modes are only supported in TLS mode. In these modes, the IV consists ofthe 32-bit packet counter followed by data from the HMAC key. The HMAC keycan be used as IV, since in GCM and CCM modes the HMAC key is not used forthe HMAC. The packet counter may not roll over within a single TLS sessions.This results in a unique IV for each packet, as required by GCM.The HMAC key data is pre-shared during the connection setup, and thus can beomitted in on-the-wire packets, saving 8 bytes per packet (for GCM and CCM).In GCM mode, P_DATA_V2 headers (the opcode and peer-id) are alsoauthenticated as Additional Data.
P_DATA_V2 GCM format:
[ - opcode/peer-id - ] [ - packet ID - ] [ TAG ] [ * payload * ]
AEAD ciphers provide both encryption and authentication in a single operation, eliminating the need for a separate HMAC and improving performance.
CBC mode ciphers are considered legacy. Use AEAD ciphers (AES-GCM or ChaCha20-Poly1305) for new deployments. CBC mode will be deprecated in future OpenVPN versions.
AES-256-GCM is the recommended cipher for most deployments, offering excellent security and performance on hardware with AES-NI support.
ChaCha20-Poly1305Configuration:
cipher CHACHA20-POLY1305
Characteristics:
Software-based cipher
Excellent performance on devices without AES-NI
Strong security
Required for DCO support
Good for mobile devices
ChaCha20-Poly1305 is ideal for mobile devices and embedded systems that lack hardware AES acceleration, providing better performance than AES-GCM in software.
On Windows, ChaCha20-Poly1305 support is available starting with Windows 11.
/** * Container for unidirectional cipher and HMAC key material. * @ingroup control_processor. This is used as a wire format/file format * key, so it cannot be changed to add fields or change the length of fields */struct key{ uint8_t cipher[MAX_CIPHER_KEY_LENGTH]; /**< Key material for cipher operations. */ uint8_t hmac[MAX_HMAC_KEY_LENGTH]; /**< Key material for HMAC operations. */};
/** * Container for one set of cipher and/or HMAC contexts. * @ingroup control_processor */struct key_ctx{ cipher_ctx_t *cipher; /**< Generic cipher context. */ hmac_ctx_t *hmac; /**< Generic HMAC context. */ /** * This implicit IV will be always XORed with the packet id that is * sent on the wire to get the IV. For the common AEAD ciphers of * AES-GCM and Chacha20-Poly1305, the length of the IV is 12 bytes * (96 bits). */ uint8_t implicit_iv[OPENVPN_MAX_IV_LENGTH]; /**< The implicit part of the IV */ size_t implicit_iv_len; /**< The length of implicit_iv */};
/** * Encrypt and HMAC sign a packet so that it can be sent as a data channel * VPN tunnel packet to a remote OpenVPN peer. * @ingroup data_crypto * * This function handles encryption and HMAC signing of a data channel * packet before it is sent to its remote OpenVPN peer. It receives the * necessary security parameters in the \a opt argument, which should have * been set to the correct values by the \c tls_pre_encrypt() function. */void openvpn_encrypt(struct buffer *buf, struct buffer work, struct crypto_options *opt);
/** * HMAC verify and decrypt a data channel packet received from a remote * OpenVPN peer. * @ingroup data_crypto * * This function handles authenticating and decrypting a data channel * packet received from a remote OpenVPN peer. It receives the necessary * security parameters in the \a opt argument, which should have been set * to the correct values by the \c tls_pre_decrypt() function. */bool openvpn_decrypt(struct buffer *buf, struct buffer work, struct crypto_options *opt, const struct frame *frame, const uint8_t *ad_start);
If an error occurs during decryption or authentication, the buffer is set to empty and the packet is silently dropped to prevent information leakage.
struct packet_id packet_id; /**< Current packet ID state for both * sending and receiving directions. * * This contains the packet id that is * used for replay protection. * * The packet id also used as the IV * for AEAD/OFB/CFG ciphers. */
/** * Check packet ID for replay, and perform replay administration. * * @param opt Crypto options for this packet, contains replay state. * @param pin Packet ID read from packet. * @param epoch Epoch read from packet or 0 when epoch is not used. * @param error_prefix Prefix to use when printing error messages. * @param gc Garbage collector to use. * * @return true if packet ID is validated to be not a replay, * false otherwise. */bool crypto_check_replay(struct crypto_options *opt, const struct packet_id_net *pin, uint16_t epoch, const char *error_prefix, struct gc_arena *gc);
Packet ID replay protection is critical for security. Never disable replay warnings in production unless you fully understand the security implications.
/** * Checks if the usage limit for an AEAD cipher is reached * * This method abstracts the calculation to make the calling function * easier to read. */static inline boolaead_usage_limit_reached(const uint64_t limit, const struct key_ctx *key_ctx, int64_t higest_pid){ /* This is the q + s <= p^(1/2) * 2^(129/2) - 1 calculation where * q is the number of protected messages (highest_pid) * s Total plaintext length in all messages (in blocks) */ return (limit > 0 && key_ctx->plaintext_blocks + (uint64_t)higest_pid > limit);}
AEAD ciphers have cryptographic limits on how much data can be safely encrypted with a single key. OpenVPN automatically manages key rotation to stay within these limits.
/** * Check if the cipher is an AEAD cipher and needs to be limited to a * certain number of number of blocks + packets. Return 0 if ciphername * is not an AEAD cipher or no limit (e.g. Chacha20-Poly1305) is needed. * * For reference see the OpenVPN RFC draft and * https://www.ietf.org/archive/id/draft-irtf-cfrg-aead-limits-08.html */uint64_t cipher_get_aead_limits(const char *ciphername);
/** * Calculate the maximum overhead that our encryption has on a packet. * This does not include needed additional buffer size * * This does NOT include the padding and rounding of CBC size as the * users (mssfix/fragment) of this function need to adjust for this and * add it themselves. * * @param kt Struct with the crypto algorithm to use * @param pkt_id_size Size of the packet id * @param occ if true calculates the overhead for crypto in * the same incorrect way as all previous OpenVPN * versions did, to end up with identical numbers * for OCC compatibility */unsigned int calculate_crypto_overhead(const struct key_type *kt, unsigned int pkt_id_size, bool occ);
--show-tls (Standalone) Show all TLS ciphers supported by the crypto library. OpenVPN uses TLS to secure the control channel, over which the keys that are used to protect the actual VPN traffic are exchanged. The TLS ciphers will be sorted from highest preference (most secure) to lowest.
/* * Show the TLS ciphers that are available for us to use in the SSL * library with headers hinting their usage and warnings about usage. * * @param cipher_list list of allowed TLS cipher, or NULL. * @param cipher_list_tls13 list of allowed TLS 1.3+ cipher, or NULL * @param tls_cert_profile TLS certificate crypto profile name. */void show_available_tls_ciphers(const char *cipher_list, const char *cipher_list_tls13, const char *tls_cert_profile);
# Server offers multiple ciphersdata-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305# Client will negotiate from this listdata-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
Cipher negotiation happens during the TLS handshake, allowing peers to automatically select the best mutually supported cipher without manual configuration matching.