A lightweight, self-contained C implementation of the Methods for IP Address Encryption and Obfuscation draft to encrypt (or "obfuscate") IP addresses for privacy, compliance and security purposes.
It supports both IPv4 and IPv6 addresses, and it can optionally preserve the IP format (so an IP address is still recognized as an IP address after encryption). ipcrypt2 also provides prefix-preserving encryption (preserving network structure while encrypting host portions) and non-deterministic encryption modes, where encrypting the same address multiple times will yield different ciphertexts.
IPv4 and IPv6 support Works seamlessly with both IP address formats.
Format-Preserving Encryption (FPE) In "standard" mode, an address is encrypted into another valid IP address. This means that consumers of the data (e.g., logs) still see what appears to be an IP address, but without revealing the original address.
Prefix-Preserving Encryption (PFX) IP addresses with the same prefix produce encrypted IP addresses with the same prefix. The prefix can be of any length. Useful for maintaining network topology information while anonymizing individual hosts.
Non-Deterministic Encryption Supports non-deterministic encryption using the KIASU-BC and AES-XTX tweakable block ciphers, ensuring that repeated encryptions of the same IP produce different outputs.
Fast and Minimal Fast and Minimal: Written in C with no external dependencies. It uses hardware-accelerated AES instructions when available for improved performance, but it also supports a software fallback on any CPU, including WebAssembly environments.
Convenient APIs Functions are provided to encrypt/decrypt in-place (16-byte arrays for addresses) or via string-to-string conversions (e.g.,
x.x.x.x→y.y.y.y).No Extra Heap Allocations Simple usage and easy to integrate into existing projects. Just compile and link.
ipcrypt2
ipcrypt2 is a single C file implementation that can be directly copied into any existing project. Simply include src/ipcrypt2.c and src/include/ipcrypt2.h in your project and you're ready to go.
- Download/Clone this repository.
- Copy
src/ipcrypt2.candsrc/include/ipcrypt2.hdirectly to your project. - Build and link them with your application.
If you are cross-compiling for ARM, make sure your toolchain targets AES-enabled ARM CPUs and sets the appropriate flags.
The untrinsics.h file is only required on target CPUs that lack AES hardware support. On systems with AES-NI (x86_64) or AES instructions (ARM64), this file is unnecessary.
Alternatively, you can build ipcrypt2 as a static library. This is useful when you want to:
- Use the library across multiple projects without copying source files
- Manage dependencies more cleanly in larger codebases
- Integrate with build systems that prefer library dependencies
To force usage of explicit_bzero to zero-out secrets on de-initialization, define HAVE_EXPLICIT_BZERO in your build system.
Set the appropriate CFLAGS if necessary and type:
makeThe resulting library is called libipcrypt2.a. The header file will be installed from src/include/ipcrypt2.h.
Zig can compile and link C code. You can typically build the project by running:
zig build -Doptimize=ReleaseFastor
zig build -Doptimize=ReleaseSmallThe resulting library and headers will be placed into the zig-out directory.
All user-facing declarations are in src/include/ipcrypt2.h. Here are the key structures and functions:
typedefstructIPCrypt { ... } IPCrypt;- Must be initialized via
ipcrypt_init()with a 16-byte key. - Optionally, call
ipcrypt_deinit()to zero out secrets in memory once done.
voidipcrypt_init(IPCrypt*ipcrypt, constuint8_tkey[IPCRYPT_KEYBYTES]);
voidipcrypt_deinit(IPCrypt*ipcrypt);- Initialization loads the user-provided AES key and prepares the context.
- Deinitialization scrubs sensitive data from memory.
// For 16-byte (binary) representation of IP addresses:voidipcrypt_encrypt_ip16(constIPCrypt*ipcrypt, uint8_tip16[16]);
voidipcrypt_decrypt_ip16(constIPCrypt*ipcrypt, uint8_tip16[16]);
// For string-based IP addresses:size_tipcrypt_encrypt_ip_str(constIPCrypt*ipcrypt,
charencrypted_ip_str[IPCRYPT_MAX_IP_STR_BYTES],
constchar*ip_str);
size_tipcrypt_decrypt_ip_str(constIPCrypt*ipcrypt,
charip_str[IPCRYPT_MAX_IP_STR_BYTES],
constchar*encrypted_ip_str);ipcrypt_encrypt_ip16/ipcrypt_decrypt_ip16: In-place encryption/decryption of a 16-byte buffer. An IPv4 address must be placed inside a 16-byte buffer as an IPv4-mapped IPv6.ipcrypt_encrypt_ip_str/ipcrypt_decrypt_ip_str: Takes an IP string (IPv4 or IPv6), encrypts it as a new IP, and returns the encrypted address as a string. Decryption reverses that process.
typedefstructIPCryptPFX { ... } IPCryptPFX;
intipcrypt_pfx_init(IPCryptPFX*ipcrypt, constuint8_tkey[IPCRYPT_PFX_KEYBYTES]);
voidipcrypt_pfx_deinit(IPCryptPFX*ipcrypt);
// For 16-byte (binary) representation of IP addresses:voidipcrypt_pfx_encrypt_ip16(constIPCryptPFX*ipcrypt, uint8_tip16[16]);
voidipcrypt_pfx_decrypt_ip16(constIPCryptPFX*ipcrypt, uint8_tip16[16]);
// For string-based IP addresses:size_tipcrypt_pfx_encrypt_ip_str(constIPCryptPFX*ipcrypt,
charencrypted_ip_str[IPCRYPT_MAX_IP_STR_BYTES],
constchar*ip_str);
size_tipcrypt_pfx_decrypt_ip_str(constIPCryptPFX*ipcrypt,
charip_str[IPCRYPT_MAX_IP_STR_BYTES],
constchar*encrypted_ip_str);- Prefix-preserving mode ensures that IP addresses with the same prefix produce encrypted IP addresses with the same prefix.
- The prefix can be of any length - the encryption preserves the common prefix structure.
- Requires a 32-byte key (
IPCRYPT_PFX_KEYBYTES). - Returns 0 on success.
- The output is still a valid IP address, maintaining network topology information.
- Useful for scenarios where you need to anonymize individual hosts while preserving network structure for analysis.
voidipcrypt_nd_encrypt_ip16(constIPCrypt*ipcrypt,
uint8_tndip[IPCRYPT_NDIP_BYTES],
constuint8_tip16[16],
constuint8_trandom[IPCRYPT_TWEAKBYTES]);
voidipcrypt_nd_decrypt_ip16(constIPCrypt*ipcrypt,
uint8_tip16[16],
constuint8_tndip[IPCRYPT_NDIP_BYTES]);
voidipcrypt_nd_encrypt_ip_str(constIPCrypt*ipcrypt,
charencrypted_ip_str[IPCRYPT_NDIP_STR_BYTES],
constchar*ip_str,
constuint8_trandom[IPCRYPT_TWEAKBYTES]);
size_tipcrypt_nd_decrypt_ip_str(constIPCrypt*ipcrypt,
charip_str[IPCRYPT_MAX_IP_STR_BYTES],
constchar*encrypted_ip_str);- Non-deterministic mode takes a random 8-byte tweak (
random[IPCRYPT_TWEAKBYTES]). - Even if you encrypt the same IP multiple times with the same key, encrypted values will be unique, which helps mitigate traffic analysis or repeated-pattern attacks.
- This mode is not format-preserving: the output is 24 bytes (or 48 hex characters).
typedefstructIPCryptNDX { ... } IPCryptNDX;
intipcrypt_ndx_init(IPCryptNDX*ipcrypt,
constuint8_tkey[IPCRYPT_NDX_KEYBYTES]);
voidipcrypt_ndx_deinit(IPCryptNDX*ipcrypt);
voidipcrypt_ndx_encrypt_ip16(constIPCryptNDX*ipcrypt,
uint8_tndip[IPCRYPT_NDX_NDIP_BYTES],
constuint8_tip16[16],
constuint8_trandom[IPCRYPT_NDX_TWEAKBYTES]);
voidipcrypt_ndx_decrypt_ip16(constIPCryptNDX*ipcrypt,
uint8_tip16[16],
constuint8_tndip[IPCRYPT_NDX_NDIP_BYTES]);
voidipcrypt_ndx_encrypt_ip_str(constIPCryptNDX*ipcrypt,
charencrypted_ip_str[IPCRYPT_NDX_NDIP_STR_BYTES],
constchar*ip_str,
constuint8_trandom[IPCRYPT_NDX_TWEAKBYTES]);
size_tipcrypt_ndx_decrypt_ip_str(constIPCryptNDX*ipcrypt,
charip_str[IPCRYPT_MAX_IP_STR_BYTES],
constchar*encrypted_ip_str);- The NDX non-deterministic mode takes a random 16-byte tweak (
random[IPCRYPT_NDX_TWEAKBYTES]) and a 32-byte key (IPCRYPT_NDX_KEYBYTES). - Returns 0 on success.
- Even if you encrypt the same IP multiple times with the same key, encrypted values will be unique, which helps mitigate traffic analysis or repeated-pattern attacks.
- This mode is not format-preserving: the output is 32 bytes (or 64 hex characters).
The NDX mode is similar to the ND mode, but larger tweaks make it even more difficult to detect repeated IP addresses. The downside is that it runs at half the speed of ND mode and produces larger ciphertexts.
intipcrypt_str_to_ip16(uint8_tip16[16], constchar*ip_str);
size_tipcrypt_ip16_to_str(charip_str[IPCRYPT_MAX_IP_STR_BYTES], constuint8_tip16[16]);
intipcrypt_sockaddr_to_ip16(uint8_tip16[16], conststructsockaddr*sa);
voidipcrypt_ip16_to_sockaddr(structsockaddr_storage*sa, constuint8_tip16[16]);
intipcrypt_key_from_hex(uint8_t*key, size_tkey_len, constchar*hex, size_thex_len);
intipcrypt_ndip_from_hex(uint8_tndip[24], size_tkey_len, constchar*hex, size_thex_len);
intipcrypt_ndx_ndip_from_hex(uint8_tndip[32], size_tkey_len, constchar*hex, size_thex_len);ipcrypt_str_to_ip16/ipcrypt_ip16_to_str: Convert between string IP addresses and their 16-byte representation.ipcrypt_sockaddr_to_ip16: Convert a socket address structure to a 16-byte binary IP representation. Supports both IPv4 (AF_INET) and IPv6 (AF_INET6) socket addresses. For IPv4 addresses, they are converted to IPv4-mapped IPv6 format. Returns0on success, or-1if the address family is not supported.ipcrypt_ip16_to_sockaddr: Convert a 16-byte binary IP address to a socket address structure. The socket address structure is populated based on the IP format: for IPv4-mapped IPv6 addresses, an IPv4 socket address is created; for other IPv6 addresses, an IPv6 socket address is created. The providedsockaddr_storagestructure is guaranteed to be large enough to hold any socket address type.ipcrypt_key_from_hex: Convert a hexadecimal string to a secret key. The input string must be exactly 32 or 64 characters long (16 or 32 bytes in hex). Returns0on success, or-1if the input string is invalid or conversion fails.
Below are two illustrative examples of using ipcrypt2 in C.
#include<stdio.h>#include<string.h>#include"ipcrypt2.h"intmain(void) {
// A 16-byte AES key (for demonstration only; keep yours secret!)constuint8_tkey[IPCRYPT_KEYBYTES] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
// Example IP (could be IPv4 or IPv6)constchar*original_ip="192.168.0.100"; // or "::1"IPCryptctx;
ipcrypt_init(&ctx, key);
// Encryptcharencrypted_ip[IPCRYPT_MAX_IP_STR_BYTES];
ipcrypt_encrypt_ip_str(&ctx, encrypted_ip, original_ip);
// Decryptchardecrypted_ip[IPCRYPT_MAX_IP_STR_BYTES];
ipcrypt_decrypt_ip_str(&ctx, decrypted_ip, encrypted_ip);
// Print resultsprintf("Original IP : %s\n", original_ip);
printf("Encrypted IP: %s\n", encrypted_ip);
printf("Decrypted IP: %s\n", decrypted_ip);
// Clean upipcrypt_deinit(&ctx);
return0;
}#include<stdio.h>#include<string.h>#include"ipcrypt2.h"intmain(void) {
// A 32-byte AES key for PFX modeconstuint8_tkey[IPCRYPT_PFX_KEYBYTES] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
};
// Example IPv6 addressesconstchar*ip1="2001:db8:abcd:1234:5678:90ab:cdef:0123";
constchar*ip2="2001:db8:abcd:1234:aaaa:bbbb:cccc:dddd";
constchar*ip3="2001:db8:9999:5555:1111:2222:3333:4444";
IPCryptPFXctx;
ipcrypt_pfx_init(&ctx, key);
// Encrypt multiple IPscharencrypted_ip1[IPCRYPT_MAX_IP_STR_BYTES];
charencrypted_ip2[IPCRYPT_MAX_IP_STR_BYTES];
charencrypted_ip3[IPCRYPT_MAX_IP_STR_BYTES];
ipcrypt_pfx_encrypt_ip_str(&ctx, encrypted_ip1, ip1);
ipcrypt_pfx_encrypt_ip_str(&ctx, encrypted_ip2, ip2);
ipcrypt_pfx_encrypt_ip_str(&ctx, encrypted_ip3, ip3);
// Print resultsprintf("Original IP1: %s\n", ip1);
printf("Encrypted : %s\n\n", encrypted_ip1);
printf("Original IP2: %s\n", ip2);
printf("Encrypted : %s\n\n", encrypted_ip2);
printf("Original IP3: %s\n", ip3);
printf("Encrypted : %s\n\n", encrypted_ip3);
// Note: ip1 and ip2 share the same prefix (2001:db8:abcd:1234)// so their encrypted versions will also share the same prefix// Clean upipcrypt_pfx_deinit(&ctx);
return0;
}#include<stdio.h>#include<string.h>#include<stdlib.h>#include<time.h>#include"ipcrypt2.h"intmain(void) {
// A 16-byte AES keyconstuint8_tkey[IPCRYPT_KEYBYTES] = {
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22,
0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00
};
IPCryptctx;
ipcrypt_init(&ctx, key);
// We'll generate a random 8-byte tweakuint8_trandom_tweak[IPCRYPT_TWEAKBYTES];
arc4random_buf(random_tweak, sizeofIPCRYPT_TWEAKBYTES);
// Input IPconstchar*original_ip="2607:f8b0:4005:805::200e"; // example IPv6// Encrypt string in non-deterministic modecharnd_encrypted_str[IPCRYPT_NDIP_STR_BYTES];
ipcrypt_nd_encrypt_ip_str(&ctx, nd_encrypted_str, original_ip, random_tweak);
// Decryptchardecrypted_ip[IPCRYPT_MAX_IP_STR_BYTES];
ipcrypt_nd_decrypt_ip_str(&ctx, decrypted_ip, nd_encrypted_str);
printf("Original IP : %s\n", original_ip);
printf("ND-Encrypted: %s\n", nd_encrypted_str);
printf("Decrypted IP: %s\n", decrypted_ip);
ipcrypt_deinit(&ctx);
return0;
}Key Management
- Standard and ND modes require a secure 16-byte AES key.
- PFX and NDX modes require a secure 32-byte AES key.
- Protect keys and ensure they remain secret.
- Keys should be frequently rotated.
Tweak Randomness (for non-deterministic modes)
- ND mode: the 8-byte tweak does not need to be secret; however, it should be random or unique for each encryption to prevent predictable patterns. While collisions may become a statistical concern after approximately 2^32 encryptions of the same IP address with the same key, they do not directly expose the IP address without the key.
- NDX mode: the 16-byte tweak does not need to be secret; however, it should be random or unique for each encryption to prevent predictable patterns. Collisions become a statistical concern after approximately 2^64 encryptions of the same IP address with the same key. They only reveal the fact that an IP address was observed multiple times, but not the IP address itself.
IP Format Preservation
- In "standard" mode, the library encrypts a 16-byte IP buffer into another 16-byte buffer. After encryption, it may become a valid IPv6 address even if the original address was IPv4, or vice versa.
Not a General Purpose Encryption Library
- This library is specialized for IP address encryption and may not be suitable for arbitrary data encryption.
- Architecture: Optimized for x86_64 and ARM (aarch64) with hardware AES, but fully functional on any CPU using a software fallback. WebAssembly is also supported.
- Format-Preserving: Standard encryption is format-preserving at the 16-byte level. However, an original IPv4 may decrypt to an IPv6 format (or vice versa) in string form.
- Python (reference implementation)
- Rust - Native implementation
- Rust - C bindings
- JavaScript (Browser and Node.js)
- Go
- Java
- Lua
- Swift
- Elixir
- Ruby
- Kotlin
- AWK
- Dart
Enjoy using ipcrypt2! Contributions and bug reports are always welcome. Feel free to open issues or submit pull requests on GitHub to help improve the library.