<meta charset="utf-8" emacsmode="-*- markdown -*-">
**ipcipher: encrypting IP addresses**
STATUS: This standard is open for discussion. We hope to finalize it quickly - bert.hubert@powerdns.com / @PowerDNS_Bert.
This page documents a simple way to encrypt IPv4 and IPv6 addresses such that any address encrypts to a valid address. This enables existing tools to be used on encrypted IPv4 and IPv6 addresses.
There are many ways to do this, especially for IPv6, but the method described here is simple and interoperable. This page:
- Describes the algorithms used to encrypt/decrypt IP addresses
- Specifies how to derive the key from a password
- Links to reference implementations in various languages
- Provides a set of published test vectors to test interoperabilty
In order to enhance interoperability, implementations that want to encrypt IP addresses are encouraged to do so using this 'ipcipher' standard.
Known implementations:
- In Go, by Silke Hofstra
- PowerDNS
Discussion on how and when to use ipcipher
can be found in the
meta document.
Silke Hofstra built the first interoperable implementation and found many
mistakes in the specification and test vectors. Jean-Philippe Aumasson
supplied the ipcrypt
algorithm & guidance on key derivation. Further thanks to:
Frank Denis for providing the C implementation of ipcrypt
and general
advice, Edwin van Vliet for noting the risk of checksums providing hint of
old IP address.
Frequently, privacy concerns and regulations get in the way of security analysis. Privacy is important, but so is security. Compromised systems eventually also harm privacy.
Per-customer/subscriber traces are extremely useful for researching the security of networks. However, privacy officers rightly object the unbridled sharing of which IP address did what.
One potential solution is to encrypt IP addresses in log files or PCAPs with a secret key. Crucially, this can be done in a way that the IP addresses still look like IP addresses, and can be stored 'in place'.
The encryption key is held by the privacy officer, or their department, and if based on encrypted IP addresses something interesting is found, the address can be decrypted for further action.
The needs and merits of IP encryption are further explored in 'On IP address encryption: security analysis with respect for privacy'. Importantly, this also touches on inherent limitations of encrypting IP addresses for privacy.
Guidance on how to use ipcipher
can be found here.
Both IPv4 and IPv6 encryption use a 128-bit key. To derive this key from the passphrase, use PBKDF2 as follows:
DK = PBKDF2(SHA1, Password, "ipcipheripcipher", 50000, 16)
Or in words, RFC 2898 with SHA1 as hashing function, ipcipheripcipher
as
salt, 50000 iterations, 16 bytes of key DK
. In OpenSSL this
corresponds to:
static const char salt[]="ipcipheripcipher";
unsigned char out[16];
PKCS5_PBKDF2_HMAC_SHA1(passwordptr, passwordlen, (const unsigned char*)salt, sizeof(salt)-1, 50000, sizeof(out), out);
The key derivation step is not optional. The ipcrypt
algorithm used for
IPv4 requires a fully randomized key and is not secure without it. In
addition, PBKDF2 protects against brute forcing of the passphrase.
Some test vectors for key derivation, where first entry is an empty string:
- "" -> bb 8d cd 7b e9 a6 f4 3b 33 04 c6 40 d7 d7 10 3c
- "3.141592653589793" -> 37 05 bd 6c 0e 26 a1 a8 39 89 8f 1f a0 16 a3 74
- "crypto is not a coin" -> 06 c4 ba d2 3a 38 b9 e0 ad 9d 05 90 b0 a3 d9 3a
Take care not to process a possible trailing 0 in the password (or salt).
Note: it is of course also possible to use a fully random 128-bit key that is not derived from a passphrase. This offers some security advantages too, as the full 128-bit keyspace is used. Implementations are encouraged to make it possible to either provide a passphrase or a 128-bit string, but be careful that it is not possible to disambiguate between these two automatically!
An IPv4 address is a 32 bit value, and to encrypt it to another IPv4 address we need a block cipher that is 32 bit native. A modern and suitable algorithm is 'ipcrypt' by Jean-Philippe Aumasson. ipcrypt was inspired by SipHash (which was invented by Aumasson and Dan J. Bernstein).
ipcrypt uses a 128 bit key, there is no padding, no cipher modes or anything else.
Implementations:
- C by Frank Denis
- Go by Jean-Philippe Aumasson
- Python by Jean-Philippe Aumasson
- Rust by Stefan Bühler
Note that the (combined) Python and Go repository also includes command line tools.
Test vectors using the derived key "some 16-byte key" (minus the quotes):
- 127.0.0.1 -> 114.62.227.59
- 8.8.8.8 -> 46.48.51.50
- 1.2.3.4 -> 171.238.15.199
Using the following key in hex: 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
- Start with IP address 192.168.69.42 and encrypt it 100 million times -> 93.155.197.186 (so keep on encrypting the encrypted address)
Using the password "crypto is not a coin":
- 198.41.0.4 -> 139.111.117.167
- 130.161.180.1 -> 66.235.221.231
- 0.0.0.0 -> 203.253.152.187
Note that this password needs to be used to derive the actual key first.
IPv6 addresses are 128 bits, and there is a wealth of suitable algorithms available. AES-128 is robust and widely available, and more than good enough.
AES is typically deployed in a mode like Cipher Block Chaining, but no such mode is required to encrypt IP addresses. A straight AES operation is used, with no further XORing, as in Electronic Code Book "mode".
AES is almost always already available. To get a raw AES-128 encryption operation out of OpenSSL or its variants:
AES_KEY wctx;
AES_set_encrypt_key(key, 128, &wctx);
AES_encrypt((const unsigned char*)&ca.sin6.sin6_addr.s6_addr,
(unsigned char*)&ret.sin6.sin6_addr.s6_addr, &wctx);
Decryption is the same, with the obvious s/encrypt/decrypt/ change.
There is as yet no command line tool that performs these operations,
although PowerDNS pdnsutil
will feature this in the 4.2 release.
Test vectors using the key "some 16-byte key":
- ::1 -> 3718:8853:1723:6c88:7e5f:2e60:c79a:2bf
- 2001:503:ba3e::2:30 -> 64d2:883d:ffb5:dd79:24b:943c:22aa:4ae7
- 2001:DB8:: -> ce7e:7e39:d282:e7b1:1d6d:5ca1:d4de:246f
Using the password "crypto is not a coin":
- ::1 -> a551:9cb0:c9b:f6e1:6112:58a:af29:3a6c
- 2001:503:ba3e::2:30 -> 6e60:2674:2fac:d383:f9d5:dcfe:fc53:328e
- 2001:DB8:: -> a8f5:16c8:e2ea:23b9:748d:67a2:4107:9d2e
Note that this password needs to be used to derive the key first.
<script>window.markdeepOptions={}; window.markdeepOptions.tocStyle="short";</script> <style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style><script src="markdeep.min.js"></script><script src="https://casual-effects.com/markdeep/latest/markdeep.min.js"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>