A complete, idiomatic Kotlin implementation of the IPCrypt specification for IP address encryption and obfuscation.
This library implements three variants of IP address encryption as defined in draft-denis-ipcrypt
:
- ipcrypt-deterministic: Deterministic, format-preserving encryption using AES-128
- ipcrypt-nd: Non-deterministic encryption using KIASU-BC with 8-byte tweaks
- ipcrypt-ndx: Non-deterministic encryption using AES-XTS with 16-byte tweaks
- ✅ Full specification compliance - Passes all official test vectors
- 🎯 Type-safe API - Leverages Kotlin's type system for safety
- 🔧 DSL builder pattern - Fluent configuration API
- 🚀 Extension functions - Convenient utilities for common operations
- 📦 Zero dependencies - Uses only Java standard library
- 🧪 Comprehensive testing - Full test coverage with JUnit 5
- 🔒 Thread-safe - All implementations are thread-safe
dependencies {
implementation("org.ipcrypt:ipcrypt-kotlin:1.0.0")
}
dependencies {
implementation 'org.ipcrypt:ipcrypt-kotlin:1.0.0'
}
<dependency>
<groupId>org.ipcrypt</groupId>
<artifactId>ipcrypt-kotlin</artifactId>
<version>1.0.0</version>
</dependency>
import org.ipcrypt.*
fun main() {
// Create encryptor using DSL
val (crypto, key) = ipCrypt {
randomKey()
nd() // Non-deterministic mode
}
// Encrypt IP addresses
val encrypted = crypto.encryptNonDeterministic("192.168.1.1")
println("Encrypted: ${encrypted.toHexString()}")
// Decrypt back
val original = crypto.decryptNonDeterministic(encrypted)
println("Original: $original")
}
Use for consistent anonymization where the same IP always produces the same encrypted result:
// Using hex key (recommended)
val key = "0123456789abcdeffedcba9876543210".hexToByteArray()
val crypto = IpCrypt.deterministic(key)
val ip = "192.168.1.1"
val encrypted = crypto.encryptDeterministic(ip)
// encrypted is an IPv6-formatted string like "bde9:6789:d353:824c:..."
val decrypted = crypto.decryptDeterministic(encrypted)
assert(decrypted == ip)
// Or generate a random key
val randomKey = IpCrypt.generateKey(IpCrypt.Mode.DETERMINISTIC)
val crypto2 = IpCrypt.deterministic(randomKey)
Use for preventing correlation attacks with 8-byte tweaks:
val crypto = IpCrypt.nd(IpCrypt.generateKey(IpCrypt.Mode.ND))
val ip = "10.0.0.1"
val encrypted1 = crypto.encryptNonDeterministic(ip)
val encrypted2 = crypto.encryptNonDeterministic(ip)
// Different outputs each time
assert(!encrypted1.contentEquals(encrypted2))
assert(crypto.decryptNonDeterministic(encrypted1) == ip)
assert(crypto.decryptNonDeterministic(encrypted2) == ip)
Highest collision resistance with 16-byte tweaks:
val crypto = IpCrypt.ndx(IpCrypt.generateKey(IpCrypt.Mode.NDX))
val ipv6 = "2001:db8::1"
val encrypted = crypto.encryptNonDeterministic(ipv6)
// Returns 32-byte ByteArray (16-byte tweak + 16-byte ciphertext)
val decrypted = crypto.decryptNonDeterministic(encrypted)
assert(decrypted == ipv6)
Fluent API for configuration:
// With random key
val (crypto, key) = ipCrypt {
randomKey()
nd()
}
// With hex key
val (crypto2, _) = ipCrypt {
key("0123456789abcdeffedcba9876543210")
deterministic()
}
// With byte array key
val (crypto3, _) = ipCrypt {
key(myKeyBytes)
ndx()
}
Convenient utilities for common operations:
// Check if string is valid IP
"192.168.1.1".isIPv4 // true
"2001:db8::1".isIPv6 // true
"not.an.ip".isIPAddress // false
// Hex conversions (supports various formats)
val bytes1 = "deadbeef".hexToByteArray()
val bytes2 = "DE:AD:BE:EF".hexToByteArray() // With colons
val bytes3 = "DE AD BE EF".hexToByteArray() // With spaces
val hex = bytes1.toHexString() // Returns lowercase hex
Process multiple IPs efficiently:
val crypto = IpCrypt.deterministic(key)
// Process a list of IPs
val ips = listOf("192.168.1.1", "10.0.0.1", "172.16.0.1")
val encrypted = ips.map { crypto.encryptDeterministic(it) }
// Decrypt all
val decrypted = encrypted.map { crypto.decryptDeterministic(it) }
// Anonymize log entries
val logs = listOf(
"User 192.168.1.100 logged in",
"Request from 192.168.1.101"
)
val ipPattern = """(\d{1,3}\.){3}\d{1,3}""".toRegex()
val anonymized = logs.map { log ->
ipPattern.replace(log) { match ->
crypto.encryptDeterministic(match.value)
}
}
Main encryption/decryption interface:
class IpCrypt {
// Factory methods
companion object {
fun deterministic(key: ByteArray): IpCrypt
fun nd(key: ByteArray): IpCrypt
fun ndx(key: ByteArray): IpCrypt
fun withMode(key: ByteArray, mode: Mode): IpCrypt
fun generateKey(mode: Mode): ByteArray
}
// Generic methods
fun encrypt(ipAddress: String, tweak: ByteArray? = null): Any
fun decrypt(encryptedData: Any): String
// Type-safe methods
fun encryptDeterministic(ipAddress: String): String
fun decryptDeterministic(encryptedIp: String): String
fun encryptNonDeterministic(ipAddress: String, tweak: ByteArray? = null): ByteArray
fun decryptNonDeterministic(encryptedData: ByteArray): String
}
- DETERMINISTIC: 16 bytes (128 bits)
- ND: 16 bytes (128 bits)
- NDX: 32 bytes (256 bits)
- DETERMINISTIC: IPv6-formatted string
- ND: 24-byte ByteArray (8-byte tweak + 16-byte ciphertext)
- NDX: 32-byte ByteArray (16-byte tweak + 16-byte ciphertext)
- Generate keys using a cryptographically secure random number generator
- Store keys securely using appropriate key management systems
- Rotate keys regularly based on usage volume
- Never hardcode keys in source code
- Deterministic: No usage limits but reveals patterns
- ND: ~4 billion operations per key before collision concerns
- NDX: ~18 quintillion operations per key before collision concerns
Log anonymization while preserving structure:
data class LogEntry(val timestamp: Long, val ip: String, val action: String)
fun anonymizeLogs(logs: List<LogEntry>, key: ByteArray): List<LogEntry> {
val crypto = IpCrypt.deterministic(key)
return logs.map { entry ->
entry.copy(
ip = try {
crypto.encryptDeterministic(entry.ip)
} catch (e: Exception) {
entry.ip // Keep original if not a valid IP
}
)
}
}
// Usage
val key = IpCrypt.generateKey(IpCrypt.Mode.DETERMINISTIC)
val anonymizedLogs = anonymizeLogs(originalLogs, key)
# Run all tests
./gradlew test
# Run with coverage
./gradlew test jacocoTestReport
# Run specific test class
./gradlew test --tests IpCryptTest
# Verify against specification vectors
kotlin -cp build/classes org.ipcrypt.VerifySpecKt
# Clean build
./gradlew clean build
# Create JAR
./gradlew jar
# Generate documentation
./gradlew dokkaHtml
The project uses ktlint for code formatting and detekt for static analysis:
# Check formatting
./gradlew ktlintCheck
./lint.sh check
# Auto-format code
./gradlew ktlintFormat
./lint.sh format
# Run static analysis
./gradlew detekt
# Generate detailed reports
./gradlew detektGenerateBaseline
./lint.sh report
Install the pre-commit hook to ensure code quality:
./lint.sh install-hook
This will automatically check formatting before each commit.
This implementation fully complies with the IPCrypt specification and passes all official test vectors:
- ✅ ipcrypt-deterministic (AES-128 ECB)
- ✅ ipcrypt-nd (KIASU-BC with 8-byte tweak)
- ✅ ipcrypt-ndx (AES-XTS with 16-byte tweak)
- Requires Java 8 or higher
- Kotlin 1.9.20 or higher
- No external dependencies beyond JDK