├── .gitignore ├── CITATION.cff ├── .github └── workflows │ ├── tests.yml │ └── visual-studio.yml ├── LICENSE ├── Makefile ├── .clang-format ├── src ├── include │ └── ipcrypt2.h ├── softaes │ └── untrinsics.h ├── test │ └── main.zig └── ipcrypt2.c └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .zig-cache 3 | *~ 4 | zig-out 5 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: ipcrypt2 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: Frank 9 | family-names: Denis 10 | orcid: 'https://orcid.org/0009-0008-4417-1713' 11 | repository-code: 'https://github.com/ipcrypt-std/ipcrypt2' 12 | abstract: 'A library to encrypt IP addresses' 13 | keywords: 14 | - cryptography 15 | - library 16 | license: ISC 17 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | os: 14 | - ubuntu-latest 15 | - ubuntu-24.04-arm 16 | - windows-latest 17 | 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install Zig 24 | uses: mlugg/setup-zig@v2 25 | 26 | - name: Unit tests 27 | run: | 28 | zig build test 29 | zig build test -Doptimize=ReleaseSafe 30 | zig build test -Doptimize=ReleaseFast 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * ISC License 3 | * 4 | * Copyright (c) 2025 5 | * Frank Denis 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for building libipcrypt.a 2 | # This Makefile is designed to work with both GNU Make and BSD Make 3 | 4 | # Compiler settings 5 | CC ?= cc 6 | AR ?= ar 7 | RANLIB ?= ranlib 8 | CFLAGS ?= -O2 9 | CFLAGS += -I./src/include 10 | 11 | # Installation settings 12 | PREFIX ?= /usr/local 13 | LIBDIR = $(PREFIX)/lib 14 | INCLUDEDIR = $(PREFIX)/include 15 | INSTALL ?= install 16 | INSTALL_DATA ?= $(INSTALL) -m 644 17 | INSTALL_DIR ?= $(INSTALL) -d -m 755 18 | RM ?= rm -f 19 | 20 | # Source files 21 | SRC_DIR = src 22 | SRCS = $(SRC_DIR)/ipcrypt2.c 23 | OBJS = $(SRCS:.c=.o) 24 | 25 | # Library name 26 | LIBNAME = libipcrypt2.a 27 | 28 | # Default target 29 | all: $(LIBNAME) 30 | 31 | # Build the static library 32 | $(LIBNAME): $(OBJS) 33 | $(AR) rcs $@ $(OBJS) 34 | $(RANLIB) $@ 35 | 36 | # Compile source files 37 | .c.o: 38 | $(CC) $(CFLAGS) -c $< -o $@ 39 | 40 | # Install the library and header files 41 | install: $(LIBNAME) 42 | $(INSTALL_DIR) $(DESTDIR)$(LIBDIR) 43 | $(INSTALL_DIR) $(DESTDIR)$(INCLUDEDIR) 44 | $(INSTALL_DATA) $(LIBNAME) $(DESTDIR)$(LIBDIR)/ 45 | $(INSTALL_DATA) $(SRC_DIR)/include/ipcrypt2.h $(DESTDIR)$(INCLUDEDIR)/ 46 | 47 | # Uninstall the library and header files 48 | uninstall: 49 | $(RM) $(DESTDIR)$(LIBDIR)/$(LIBNAME) 50 | $(RM) $(DESTDIR)$(INCLUDEDIR)/ipcrypt2.h 51 | 52 | # Clean up 53 | clean: 54 | $(RM) $(OBJS) $(LIBNAME) 55 | 56 | # Test target 57 | test check: 58 | @if command -v zig >/dev/null 2>&1; then \ 59 | zig build test; \ 60 | else \ 61 | echo "zig not found - skipping tests"; \ 62 | exit 0; \ 63 | fi 64 | 65 | # Phony targets 66 | .PHONY: all clean install uninstall test check 67 | -------------------------------------------------------------------------------- /.github/workflows/visual-studio.yml: -------------------------------------------------------------------------------- 1 | name: Visual Studio Build 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | platform: [x64, x86, ARM64] 14 | configuration: [Debug, Release] 15 | include: 16 | - platform: x64 17 | vc_platform: x64 18 | arch: /arch:AVX 19 | - platform: x86 20 | vc_platform: x86 21 | arch: /arch:AVX 22 | - platform: ARM64 23 | vc_platform: ARM64 24 | arch: 25 | 26 | runs-on: windows-latest 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - name: Setup MSVC 32 | uses: ilammy/msvc-dev-cmd@v1 33 | 34 | - name: Create output directories 35 | run: | 36 | mkdir -p obj\${{ matrix.configuration }}\${{ matrix.platform }} 37 | mkdir -p bin\${{ matrix.configuration }}\${{ matrix.platform }} 38 | 39 | - name: Build Library 40 | run: | 41 | cl.exe /nologo /W4 /WX /${{ matrix.configuration }} ${{ matrix.arch }} /Iinclude /c src/ipcrypt2.c /Fo:obj\${{ matrix.configuration }}\${{ matrix.platform }}\ipcrypt2.obj 42 | lib.exe /nologo /OUT:bin\${{ matrix.configuration }}\${{ matrix.platform }}\ipcrypt2.lib obj\${{ matrix.configuration }}\${{ matrix.platform }}\ipcrypt2.obj 43 | 44 | - name: Upload Artifacts 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: ipcrypt2-${{ matrix.platform }}-${{ matrix.configuration }} 48 | path: | 49 | bin/${{ matrix.configuration }}/${{ matrix.platform }}/*.lib 50 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveMacros: true 6 | AlignConsecutiveAssignments: true 7 | AlignConsecutiveBitFields: true 8 | AlignConsecutiveDeclarations: true 9 | AlignEscapedNewlines: true 10 | AlignOperands: true 11 | AlignTrailingComments: false 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortEnumsOnASingleLine: true 16 | AllowShortBlocksOnASingleLine: false 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: false 19 | AllowShortLambdasOnASingleLine: All 20 | AllowShortIfStatementsOnASingleLine: Never 21 | AllowShortLoopsOnASingleLine: false 22 | AlwaysBreakAfterDefinitionReturnType: None 23 | AlwaysBreakAfterReturnType: TopLevelDefinitions 24 | AlwaysBreakBeforeMultilineStrings: true 25 | AlwaysBreakTemplateDeclarations: MultiLine 26 | AttributeMacros: 27 | - __capability 28 | BinPackArguments: true 29 | BinPackParameters: true 30 | BraceWrapping: 31 | AfterCaseLabel: false 32 | AfterClass: false 33 | AfterControlStatement: Never 34 | AfterEnum: false 35 | AfterFunction: true 36 | AfterNamespace: false 37 | AfterObjCDeclaration: false 38 | AfterStruct: false 39 | AfterUnion: false 40 | AfterExternBlock: false 41 | BeforeCatch: false 42 | BeforeElse: false 43 | BeforeLambdaBody: false 44 | BeforeWhile: false 45 | IndentBraces: false 46 | SplitEmptyFunction: true 47 | SplitEmptyRecord: true 48 | SplitEmptyNamespace: true 49 | BreakBeforeBinaryOperators: None 50 | BreakBeforeConceptDeclarations: true 51 | BreakBeforeBraces: WebKit 52 | BreakBeforeInheritanceComma: true 53 | BreakInheritanceList: BeforeColon 54 | BreakBeforeTernaryOperators: true 55 | BreakConstructorInitializersBeforeComma: false 56 | BreakConstructorInitializers: BeforeComma 57 | BreakAfterJavaFieldAnnotations: false 58 | BreakStringLiterals: true 59 | ColumnLimit: 100 60 | CommentPragmas: "^ IWYU pragma:" 61 | CompactNamespaces: false 62 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 63 | ConstructorInitializerIndentWidth: 4 64 | ContinuationIndentWidth: 4 65 | Cpp11BracedListStyle: false 66 | DeriveLineEnding: true 67 | DerivePointerAlignment: true 68 | DisableFormat: false 69 | EmptyLineBeforeAccessModifier: LogicalBlock 70 | ExperimentalAutoDetectBinPacking: true 71 | FixNamespaceComments: false 72 | ForEachMacros: 73 | - foreach 74 | - Q_FOREACH 75 | - BOOST_FOREACH 76 | StatementAttributeLikeMacros: 77 | - Q_EMIT 78 | IncludeBlocks: Preserve 79 | IncludeCategories: 80 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 81 | Priority: 2 82 | SortPriority: 0 83 | CaseSensitive: false 84 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 85 | Priority: 3 86 | SortPriority: 0 87 | CaseSensitive: false 88 | - Regex: ".*" 89 | Priority: 1 90 | SortPriority: 0 91 | CaseSensitive: false 92 | IncludeIsMainRegex: "(Test)?$" 93 | IncludeIsMainSourceRegex: "" 94 | IndentCaseLabels: false 95 | IndentCaseBlocks: false 96 | IndentGotoLabels: true 97 | IndentPPDirectives: AfterHash 98 | IndentExternBlock: AfterExternBlock 99 | IndentRequires: false 100 | IndentWidth: 4 101 | IndentWrappedFunctionNames: false 102 | InsertTrailingCommas: None 103 | JavaScriptQuotes: Leave 104 | JavaScriptWrapImports: true 105 | KeepEmptyLinesAtTheStartOfBlocks: true 106 | MacroBlockBegin: "" 107 | MacroBlockEnd: "" 108 | MaxEmptyLinesToKeep: 1 109 | NamespaceIndentation: Inner 110 | ObjCBinPackProtocolList: Auto 111 | ObjCBlockIndentWidth: 4 112 | ObjCBreakBeforeNestedBlockParam: true 113 | ObjCSpaceAfterProperty: true 114 | ObjCSpaceBeforeProtocolList: true 115 | PenaltyBreakAssignment: 2 116 | PenaltyBreakBeforeFirstCallParameter: 19 117 | PenaltyBreakComment: 300 118 | PenaltyBreakFirstLessLess: 120 119 | PenaltyBreakString: 1000 120 | PenaltyBreakTemplateDeclaration: 10 121 | PenaltyExcessCharacter: 1000000 122 | PenaltyReturnTypeOnItsOwnLine: 60 123 | PenaltyIndentedWhitespace: 0 124 | PointerAlignment: Left 125 | ReflowComments: true 126 | SortIncludes: true 127 | SortJavaStaticImport: Before 128 | SortUsingDeclarations: true 129 | SpaceAfterCStyleCast: true 130 | SpaceAfterLogicalNot: false 131 | SpaceAfterTemplateKeyword: true 132 | SpaceBeforeAssignmentOperators: true 133 | SpaceBeforeCaseColon: false 134 | SpaceBeforeCpp11BracedList: true 135 | SpaceBeforeCtorInitializerColon: true 136 | SpaceBeforeInheritanceColon: true 137 | SpaceBeforeParens: ControlStatements 138 | SpaceAroundPointerQualifiers: Default 139 | SpaceBeforeRangeBasedForLoopColon: true 140 | SpaceInEmptyBlock: true 141 | SpaceInEmptyParentheses: false 142 | SpacesBeforeTrailingComments: 1 143 | SpacesInAngles: false 144 | SpacesInConditionalStatement: false 145 | SpacesInContainerLiterals: true 146 | SpacesInCStyleCastParentheses: false 147 | SpacesInParentheses: false 148 | SpacesInSquareBrackets: false 149 | SpaceBeforeSquareBrackets: false 150 | BitFieldColonSpacing: Both 151 | Standard: Latest 152 | StatementMacros: 153 | - Q_UNUSED 154 | - QT_REQUIRE_VERSION 155 | TabWidth: 4 156 | UseCRLF: false 157 | UseTab: Never 158 | WhitespaceSensitiveMacros: 159 | - STRINGIZE 160 | - PP_STRINGIZE 161 | - BOOST_PP_STRINGIZE 162 | - NS_SWIFT_NAME 163 | - CF_SWIFT_NAME 164 | --- 165 | 166 | -------------------------------------------------------------------------------- /src/include/ipcrypt2.h: -------------------------------------------------------------------------------- 1 | #ifndef ipcrypt2_H 2 | #define ipcrypt2_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | /* System headers for socket structures */ 12 | #if defined(_WIN32) 13 | # include 14 | #else 15 | # include 16 | /* must be included before */ 17 | # include 18 | #endif 19 | 20 | /** Size of the AES encryption key, in bytes (128 bits). */ 21 | #define IPCRYPT_KEYBYTES 16U 22 | 23 | /** Size of the encryption tweak, in bytes (64 bits). */ 24 | #define IPCRYPT_TWEAKBYTES 8U 25 | 26 | /** Maximum length of an IP address string, including the null terminator. */ 27 | #define IPCRYPT_MAX_IP_STR_BYTES 46U 28 | 29 | /** Size of the binary output for non-deterministic encryption. */ 30 | #define IPCRYPT_NDIP_BYTES 24U 31 | 32 | /** Size of the hexadecimal output for non-deterministic encryption, including null terminator. */ 33 | #define IPCRYPT_NDIP_STR_BYTES (48U + 1U) 34 | 35 | /** Size of the NDX encryption key, in bytes (256 bits). */ 36 | #define IPCRYPT_NDX_KEYBYTES 32U 37 | 38 | /** Size of the NDX cryption tweak, in bytes (128 bits). */ 39 | #define IPCRYPT_NDX_TWEAKBYTES 16U 40 | 41 | /** Size of the binary output for NDX encryption. */ 42 | #define IPCRYPT_NDX_NDIP_BYTES 32U 43 | 44 | /** Size of the hexadecimal output for NDX encryption, including null terminator. */ 45 | #define IPCRYPT_NDX_NDIP_STR_BYTES (64U + 1U) 46 | 47 | /** Size of the PFX encryption key, in bytes (256 bits). */ 48 | #define IPCRYPT_PFX_KEYBYTES 32U 49 | 50 | /* -------- Utility functions -------- */ 51 | 52 | /** 53 | * Convert an IP address string (IPv4 or IPv6) to a 16-byte binary representation. 54 | */ 55 | int ipcrypt_str_to_ip16(uint8_t ip16[16], const char *ip_str); 56 | 57 | /** 58 | * Convert a 16-byte binary IP address into a string. 59 | * 60 | * Returns the length of the resulting string on success, or 0 on error. 61 | */ 62 | size_t ipcrypt_ip16_to_str(char ip_str[IPCRYPT_MAX_IP_STR_BYTES], const uint8_t ip16[16]); 63 | 64 | /** 65 | * Convert a socket address structure to a 16-byte binary IP representation. 66 | * 67 | * Supports both IPv4 (AF_INET) and IPv6 (AF_INET6) socket addresses. 68 | * For IPv4 addresses, they are converted to IPv4-mapped IPv6 format. 69 | * 70 | * Returns 0 on success, or -1 if the address family is not supported. 71 | */ 72 | int ipcrypt_sockaddr_to_ip16(uint8_t ip16[16], const struct sockaddr *sa); 73 | 74 | /** 75 | * Convert a 16-byte binary IP address to a socket address structure. 76 | * 77 | * The socket address structure is populated based on the IP format: 78 | * - For IPv4-mapped IPv6 addresses, an IPv4 socket address is created 79 | * - For other IPv6 addresses, an IPv6 socket address is created 80 | * 81 | * The provided sockaddr_storage structure is guaranteed to be large enough 82 | * to hold any socket address type. 83 | */ 84 | void ipcrypt_ip16_to_sockaddr(struct sockaddr_storage *sa, const uint8_t ip16[16]); 85 | 86 | /** 87 | * Convert a hexadecimal string to a secret key. 88 | * 89 | * The input string must be exactly 32 or 64 characters long (IPCRYPT_KEYBYTES or 90 | * IPCRYPT_NDX_KEYBYTES bytes in hex). Returns 0 on success, or -1 if the input string is invalid or 91 | * conversion fails. 92 | */ 93 | int ipcrypt_key_from_hex(uint8_t *key, size_t key_len, const char *hex, size_t hex_len); 94 | 95 | /** 96 | * Convert a hexadecimal string to an ipcrypt-nd ciphertext. 97 | * 98 | * The input string must be exactly 48 characters long (IPCRYPT_NDIP_BYTES bytes in hex). 99 | * Returns 0 on success, or -1 if the input string is invalid or conversion fails. 100 | */ 101 | int ipcrypt_ndip_from_hex(uint8_t ndip[IPCRYPT_NDIP_BYTES], const char *hex, size_t hex_len); 102 | 103 | /** 104 | * Convert a hexadecimal string to an ipcrypt-ndx ciphertext. 105 | * 106 | * The input string must be exactly 64 characters long (IPCRYPT_NDX_NDIP_BYTES bytes in hex). 107 | * Returns 0 on success, or -1 if the input string is invalid or conversion fails. 108 | */ 109 | int ipcrypt_ndx_ndip_from_hex(uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES], const char *hex, 110 | size_t hex_len); 111 | 112 | /* -------- IP encryption -------- */ 113 | 114 | /** 115 | * Encryption context structure. 116 | * Must be initialized with ipcrypt_init() before use. 117 | */ 118 | typedef struct IPCrypt { 119 | uint8_t opaque[16U * 11]; 120 | } IPCrypt; 121 | 122 | /** 123 | * Initialize the IPCrypt context with a 16-byte secret key. 124 | * 125 | * The key must: 126 | * - Be exactly IPCRYPT_KEYBYTES bytes. 127 | * - Be secret and randomly generated. 128 | */ 129 | void ipcrypt_init(IPCrypt *ipcrypt, const uint8_t key[IPCRYPT_KEYBYTES]); 130 | 131 | /** 132 | * Securely clear and deinitialize the IPCrypt context. 133 | * 134 | * Optional: No heap allocations are used, but this ensures secrets are wiped from memory. 135 | */ 136 | void ipcrypt_deinit(IPCrypt *ipcrypt); 137 | 138 | /** 139 | * Encrypt a 16-byte IP address in-place (format-preserving). 140 | */ 141 | void ipcrypt_encrypt_ip16(const IPCrypt *ipcrypt, uint8_t ip16[16]); 142 | 143 | /** 144 | * Decrypt a 16-byte IP address in-place (format-preserving). 145 | */ 146 | void ipcrypt_decrypt_ip16(const IPCrypt *ipcrypt, uint8_t ip16[16]); 147 | 148 | /** 149 | * Encrypt an IP address string (IPv4 or IPv6). 150 | * 151 | * Output is a format-preserving string written to encrypted_ip_str. 152 | * Returns the output length on success, or 0 on error. 153 | */ 154 | size_t ipcrypt_encrypt_ip_str(const IPCrypt *ipcrypt, 155 | char encrypted_ip_str[IPCRYPT_MAX_IP_STR_BYTES], const char *ip_str); 156 | 157 | /** 158 | * Decrypt a previously encrypted IP address string. 159 | * 160 | * Output is written to ip_str. Returns the output length on success, or 0 on error. 161 | */ 162 | size_t ipcrypt_decrypt_ip_str(const IPCrypt *ipcrypt, 163 | char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 164 | const char *encrypted_ip_str); 165 | 166 | /** 167 | * Non-deterministically encrypt a 16-byte IP address using an 8-byte tweak. 168 | * 169 | * Output is written to ndip. `random` must be set to a secure 8-byte random value. 170 | */ 171 | void ipcrypt_nd_encrypt_ip16(const IPCrypt *ipcrypt, uint8_t ndip[IPCRYPT_NDIP_BYTES], 172 | const uint8_t ip16[16], const uint8_t random[IPCRYPT_TWEAKBYTES]); 173 | 174 | /** 175 | * Decrypt a non-deterministically encrypted 16-byte IP address. 176 | * 177 | * Input is ndip, and output is written to ip16. 178 | */ 179 | void ipcrypt_nd_decrypt_ip16(const IPCrypt *ipcrypt, uint8_t ip16[16], 180 | const uint8_t ndip[IPCRYPT_NDIP_BYTES]); 181 | 182 | /** 183 | * Encrypt an IP address string non-deterministically. 184 | * 185 | * Output is a hex-encoded zero-terminated string written to encrypted_ip_str. 186 | *`random` must be an 8-byte random value. 187 | * 188 | * Returns the output length, without the null terminator. 189 | */ 190 | size_t ipcrypt_nd_encrypt_ip_str(const IPCrypt *ipcrypt, 191 | char encrypted_ip_str[IPCRYPT_NDIP_STR_BYTES], 192 | const char *ip_str, 193 | const uint8_t random[IPCRYPT_TWEAKBYTES]); 194 | 195 | /** 196 | * Decrypt a hex-encoded IP address string from non-deterministic mode. 197 | * 198 | * Output is written to ip_str. Returns the output length on success, or 0 on error. 199 | */ 200 | size_t ipcrypt_nd_decrypt_ip_str(const IPCrypt *ipcrypt, 201 | char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 202 | const char *encrypted_ip_str); 203 | 204 | /* -------- Prefix-preserving IP encryption -------- */ 205 | 206 | /** 207 | * Encryption context structure for prefix-preserving IP encryption. 208 | * Must be initialized with ipcrypt_pfx_init() before use. 209 | */ 210 | typedef struct IPCryptPFX { 211 | uint8_t opaque[16U * 11 * 2]; 212 | } IPCryptPFX; 213 | 214 | /** 215 | * Initialize the IPCryptPFX context with a 32-byte secret key. 216 | * 217 | * The key must: 218 | * - Be exactly IPCRYPT_PFX_KEYBYTES bytes. 219 | * - Be secret and randomly generated. 220 | * 221 | * Returns 0 on success. 222 | */ 223 | int ipcrypt_pfx_init(IPCryptPFX *ipcrypt, const uint8_t key[IPCRYPT_PFX_KEYBYTES]); 224 | 225 | /** 226 | * Securely clear and deinitialize the IPCryptPFX context. 227 | * 228 | * Optional: No heap allocations are used, but this ensures secrets are wiped from memory. 229 | */ 230 | void ipcrypt_pfx_deinit(IPCryptPFX *ipcrypt); 231 | 232 | /** 233 | * Encrypt a 16-byte IP address in-place with prefix preservation. 234 | * 235 | * IP addresses with the same prefix produce encrypted IP addresses with the same prefix. 236 | * The prefix can be of any length. For IPv4 addresses (stored as IPv4-mapped IPv6), 237 | * preserves the IPv4 prefix structure. 238 | */ 239 | void ipcrypt_pfx_encrypt_ip16(const IPCryptPFX *ipcrypt, uint8_t ip16[16]); 240 | 241 | /** 242 | * Decrypt a 16-byte IP address in-place with prefix preservation. 243 | * 244 | * Reverses the encryption performed by ipcrypt_pfx_encrypt_ip16(). 245 | */ 246 | void ipcrypt_pfx_decrypt_ip16(const IPCryptPFX *ipcrypt, uint8_t ip16[16]); 247 | 248 | /** 249 | * Encrypt an IP address string (IPv4 or IPv6) with prefix preservation. 250 | * 251 | * Output is a format-preserving string written to encrypted_ip_str. 252 | * Returns the output length on success, or 0 on error. 253 | */ 254 | size_t ipcrypt_pfx_encrypt_ip_str(const IPCryptPFX *ipcrypt, 255 | char encrypted_ip_str[IPCRYPT_MAX_IP_STR_BYTES], 256 | const char *ip_str); 257 | 258 | /** 259 | * Decrypt a previously encrypted IP address string with prefix preservation. 260 | * 261 | * Output is written to ip_str. Returns the output length on success, or 0 on error. 262 | */ 263 | size_t ipcrypt_pfx_decrypt_ip_str(const IPCryptPFX *ipcrypt, 264 | char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 265 | const char *encrypted_ip_str); 266 | 267 | /* -------- IP non-deterministic encryption with a 16-byte tweak -------- */ 268 | 269 | /** 270 | * Encryption context structure for NDX mode (non-deterministic encryption with 16 bytes of 271 | * tweak and a 32-byte secret key). 272 | * 273 | * Must be initialized with ipcrypt_ndx_init() before use. 274 | */ 275 | typedef struct IPCryptNDX { 276 | uint8_t opaque[16U * 11 * 2]; 277 | } IPCryptNDX; 278 | 279 | /** 280 | * Initialize the IPCryptNDX context with a 32-byte secret key. 281 | * 282 | * The key must: 283 | * - Be exactly IPCRYPT_NDX_KEYBYTES bytes. 284 | * - Be secret and randomly generated. 285 | * 286 | * Returns 0 on success. 287 | */ 288 | int ipcrypt_ndx_init(IPCryptNDX *ipcrypt, const uint8_t key[IPCRYPT_NDX_KEYBYTES]); 289 | 290 | /** 291 | * Securely clear and deinitialize the IPCryptNDX context. 292 | * 293 | * Optional: No heap allocations are used, but this ensures secrets are wiped from memory. 294 | */ 295 | void ipcrypt_ndx_deinit(IPCryptNDX *ipcrypt); 296 | 297 | /** 298 | * Non-deterministically encrypt a 16-byte IP address using an 16-byte tweak. 299 | * 300 | * Output is written to ndip. `random` must be set to a secure 16-byte random value. 301 | */ 302 | void ipcrypt_ndx_encrypt_ip16(const IPCryptNDX *ipcrypt, uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES], 303 | const uint8_t ip16[16], const uint8_t random[IPCRYPT_NDX_TWEAKBYTES]); 304 | 305 | /** 306 | * Decrypt a non-deterministically encrypted 16-byte IP address, previously encrypted with 307 | * `ipcrypt_ndx_encrypt_ip16`. 308 | * 309 | * Input is ndip, and output is written to ip16. 310 | */ 311 | void ipcrypt_ndx_decrypt_ip16(const IPCryptNDX *ipcrypt, uint8_t ip16[16], 312 | const uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES]); 313 | 314 | /** 315 | * Encrypt an IP address string non-deterministically. 316 | * 317 | * Output is a hex-encoded zero-terminated string written to encrypted_ip_str. 318 | *`random` must be an 16-byte random value. 319 | * 320 | * Returns the output length, without the null terminator. 321 | */ 322 | size_t ipcrypt_ndx_encrypt_ip_str(const IPCryptNDX *ipcrypt, 323 | char encrypted_ip_str[IPCRYPT_NDX_NDIP_STR_BYTES], 324 | const char *ip_str, const uint8_t random[IPCRYPT_NDX_TWEAKBYTES]); 325 | 326 | /** 327 | * Decrypt a hex-encoded IP address string from non-deterministic mode. 328 | * 329 | * Output is written to ip_str. Returns the output length on success, or 0 on error. 330 | */ 331 | size_t ipcrypt_ndx_decrypt_ip_str(const IPCryptNDX *ipcrypt, char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 332 | const char *encrypted_ip_str); 333 | 334 | #ifdef __cplusplus 335 | } 336 | #endif 337 | 338 | #endif 339 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `ipcrypt2` 2 | 3 | A lightweight, self-contained C implementation of the [Methods for IP Address Encryption and Obfuscation](https://ipcrypt-std.github.io/draft-denis-ipcrypt/draft-denis-ipcrypt.html) draft to encrypt (or "obfuscate") IP addresses for privacy, compliance and security purposes. 4 | 5 | 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. 6 | 7 | ## Features 8 | 9 | - **IPv4 and IPv6 support** 10 | Works seamlessly with both IP address formats. 11 | 12 | - **Format-Preserving Encryption (FPE)** 13 | 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. 14 | 15 | - **Prefix-Preserving Encryption (PFX)** 16 | 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. 17 | 18 | - **Non-Deterministic Encryption** 19 | 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. 20 | 21 | - **Fast and Minimal** 22 | 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. 23 | 24 | - **Convenient APIs** 25 | 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`). 26 | 27 | - **No Extra Heap Allocations** 28 | Simple usage and easy to integrate into existing projects. Just compile and link. 29 | 30 | ## Table of Contents 31 | 32 | - [`ipcrypt2`](#ipcrypt2) 33 | - [Features](#features) 34 | - [Table of Contents](#table-of-contents) 35 | - [Getting Started](#getting-started) 36 | - [Building as a Static Library with Make](#building-as-a-static-library-with-make) 37 | - [Building as a Static Library with Zig](#building-as-a-static-library-with-zig) 38 | - [API Overview](#api-overview) 39 | - [1. `IPCrypt` Context](#1-ipcrypt-context) 40 | - [2. Initialization and Deinitialization](#2-initialization-and-deinitialization) 41 | - [3. Format-Preserving Encryption / Decryption](#3-format-preserving-encryption--decryption) 42 | - [4. Prefix-Preserving Encryption / Decryption](#4-prefix-preserving-encryption--decryption) 43 | - [5. Non-Deterministic Encryption / Decryption](#5-non-deterministic-encryption--decryption) 44 | - [With 8 Byte Tweaks (ND Mode)](#with-8-byte-tweaks-nd-mode) 45 | - [With 16 Byte Tweaks (NDX Mode)](#with-16-byte-tweaks-ndx-mode) 46 | - [6. Helper Functions](#6-helper-functions) 47 | - [Examples](#examples) 48 | - [Format-Preserving Example](#format-preserving-example) 49 | - [Prefix-Preserving Example](#prefix-preserving-example) 50 | - [Non-Deterministic Example](#non-deterministic-example) 51 | - [Security Considerations](#security-considerations) 52 | - [Limitations and Assumptions](#limitations-and-assumptions) 53 | - [Bindings and Other Implementations](#bindings-and-other-implementations) 54 | 55 | ## Getting Started 56 | 57 | `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. 58 | 59 | 1. Download/Clone this repository. 60 | 2. Copy `src/ipcrypt2.c` and `src/include/ipcrypt2.h` directly to your project. 61 | 3. Build and link them with your application. 62 | 63 | If you are cross-compiling for ARM, make sure your toolchain targets AES-enabled ARM CPUs and sets the appropriate flags. 64 | 65 | 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. 66 | 67 | Alternatively, you can build `ipcrypt2` as a static library. This is useful when you want to: 68 | 69 | - Use the library across multiple projects without copying source files 70 | - Manage dependencies more cleanly in larger codebases 71 | - Integrate with build systems that prefer library dependencies 72 | 73 | To force usage of `explicit_bzero` to zero-out secrets on de-initialization, define `HAVE_EXPLICIT_BZERO` in your build system. 74 | 75 | ## Building as a Static Library with Make 76 | 77 | Set the appropriate `CFLAGS` if necessary and type: 78 | 79 | ```sh 80 | make 81 | ``` 82 | 83 | The resulting library is called `libipcrypt2.a`. The header file will be installed from `src/include/ipcrypt2.h`. 84 | 85 | ## Building as a Static Library with Zig 86 | 87 | Zig can compile and link C code. You can typically build the project by running: 88 | 89 | ```sh 90 | zig build -Doptimize=ReleaseFast 91 | ``` 92 | 93 | or 94 | 95 | ```sh 96 | zig build -Doptimize=ReleaseSmall 97 | ``` 98 | 99 | The resulting library and headers will be placed into the `zig-out` directory. 100 | 101 | ## API Overview 102 | 103 | All user-facing declarations are in `src/include/ipcrypt2.h`. Here are the key structures and functions: 104 | 105 | ### 1. `IPCrypt` Context 106 | 107 | ```c 108 | typedef struct IPCrypt { ... } IPCrypt; 109 | ``` 110 | 111 | - Must be initialized via `ipcrypt_init()` with a 16-byte key. 112 | - Optionally, call `ipcrypt_deinit()` to zero out secrets in memory once done. 113 | 114 | ### 2. Initialization and Deinitialization 115 | 116 | ```c 117 | void ipcrypt_init(IPCrypt *ipcrypt, const uint8_t key[IPCRYPT_KEYBYTES]); 118 | void ipcrypt_deinit(IPCrypt *ipcrypt); 119 | ``` 120 | 121 | - **Initialization** loads the user-provided AES key and prepares the context. 122 | - **Deinitialization** scrubs sensitive data from memory. 123 | 124 | ### 3. Format-Preserving Encryption / Decryption 125 | 126 | ```c 127 | // For 16-byte (binary) representation of IP addresses: 128 | void ipcrypt_encrypt_ip16(const IPCrypt *ipcrypt, uint8_t ip16[16]); 129 | void ipcrypt_decrypt_ip16(const IPCrypt *ipcrypt, uint8_t ip16[16]); 130 | 131 | // For string-based IP addresses: 132 | size_t ipcrypt_encrypt_ip_str(const IPCrypt *ipcrypt, 133 | char encrypted_ip_str[IPCRYPT_MAX_IP_STR_BYTES], 134 | const char *ip_str); 135 | 136 | size_t ipcrypt_decrypt_ip_str(const IPCrypt *ipcrypt, 137 | char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 138 | const char *encrypted_ip_str); 139 | ``` 140 | 141 | - **`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. 142 | - **`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. 143 | 144 | ### 4. Prefix-Preserving Encryption / Decryption 145 | 146 | ```c 147 | typedef struct IPCryptPFX { ... } IPCryptPFX; 148 | 149 | int ipcrypt_pfx_init(IPCryptPFX *ipcrypt, const uint8_t key[IPCRYPT_PFX_KEYBYTES]); 150 | void ipcrypt_pfx_deinit(IPCryptPFX *ipcrypt); 151 | 152 | // For 16-byte (binary) representation of IP addresses: 153 | void ipcrypt_pfx_encrypt_ip16(const IPCryptPFX *ipcrypt, uint8_t ip16[16]); 154 | void ipcrypt_pfx_decrypt_ip16(const IPCryptPFX *ipcrypt, uint8_t ip16[16]); 155 | 156 | // For string-based IP addresses: 157 | size_t ipcrypt_pfx_encrypt_ip_str(const IPCryptPFX *ipcrypt, 158 | char encrypted_ip_str[IPCRYPT_MAX_IP_STR_BYTES], 159 | const char *ip_str); 160 | 161 | size_t ipcrypt_pfx_decrypt_ip_str(const IPCryptPFX *ipcrypt, 162 | char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 163 | const char *encrypted_ip_str); 164 | ``` 165 | 166 | - **Prefix-preserving** mode ensures that IP addresses with the same prefix produce encrypted IP addresses with the same prefix. 167 | - The prefix can be of any length - the encryption preserves the common prefix structure. 168 | - Requires a 32-byte key (`IPCRYPT_PFX_KEYBYTES`). 169 | - Returns 0 on success. 170 | - The output is still a valid IP address, maintaining network topology information. 171 | - Useful for scenarios where you need to anonymize individual hosts while preserving network structure for analysis. 172 | 173 | ### 5. Non-Deterministic Encryption / Decryption 174 | 175 | #### With 8 Byte Tweaks (ND Mode) 176 | 177 | ```c 178 | void ipcrypt_nd_encrypt_ip16(const IPCrypt *ipcrypt, 179 | uint8_t ndip[IPCRYPT_NDIP_BYTES], 180 | const uint8_t ip16[16], 181 | const uint8_t random[IPCRYPT_TWEAKBYTES]); 182 | 183 | void ipcrypt_nd_decrypt_ip16(const IPCrypt *ipcrypt, 184 | uint8_t ip16[16], 185 | const uint8_t ndip[IPCRYPT_NDIP_BYTES]); 186 | 187 | void ipcrypt_nd_encrypt_ip_str(const IPCrypt *ipcrypt, 188 | char encrypted_ip_str[IPCRYPT_NDIP_STR_BYTES], 189 | const char *ip_str, 190 | const uint8_t random[IPCRYPT_TWEAKBYTES]); 191 | 192 | size_t ipcrypt_nd_decrypt_ip_str(const IPCrypt *ipcrypt, 193 | char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 194 | const char *encrypted_ip_str); 195 | ``` 196 | 197 | - **Non-deterministic** mode takes a random 8-byte tweak (`random[IPCRYPT_TWEAKBYTES]`). 198 | - 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. 199 | - This mode is _not_ format-preserving: the output is 24 bytes (or 48 hex characters). 200 | 201 | #### With 16 Byte Tweaks (NDX Mode) 202 | 203 | ```c 204 | typedef struct IPCryptNDX { ... } IPCryptNDX; 205 | 206 | int ipcrypt_ndx_init(IPCryptNDX *ipcrypt, 207 | const uint8_t key[IPCRYPT_NDX_KEYBYTES]); 208 | 209 | void ipcrypt_ndx_deinit(IPCryptNDX *ipcrypt); 210 | 211 | void ipcrypt_ndx_encrypt_ip16(const IPCryptNDX *ipcrypt, 212 | uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES], 213 | const uint8_t ip16[16], 214 | const uint8_t random[IPCRYPT_NDX_TWEAKBYTES]); 215 | 216 | void ipcrypt_ndx_decrypt_ip16(const IPCryptNDX *ipcrypt, 217 | uint8_t ip16[16], 218 | const uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES]); 219 | 220 | void ipcrypt_ndx_encrypt_ip_str(const IPCryptNDX *ipcrypt, 221 | char encrypted_ip_str[IPCRYPT_NDX_NDIP_STR_BYTES], 222 | const char *ip_str, 223 | const uint8_t random[IPCRYPT_NDX_TWEAKBYTES]); 224 | 225 | size_t ipcrypt_ndx_decrypt_ip_str(const IPCryptNDX *ipcrypt, 226 | char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 227 | const char *encrypted_ip_str); 228 | ``` 229 | 230 | - The **NDX non-deterministic** mode takes a random 16-byte tweak (`random[IPCRYPT_NDX_TWEAKBYTES]`) and a 32-byte key (`IPCRYPT_NDX_KEYBYTES`). 231 | - Returns 0 on success. 232 | - 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. 233 | - This mode is _not_ format-preserving: the output is 32 bytes (or 64 hex characters). 234 | 235 | 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. 236 | 237 | ### 6. Helper Functions 238 | 239 | ```c 240 | int ipcrypt_str_to_ip16(uint8_t ip16[16], const char *ip_str); 241 | size_t ipcrypt_ip16_to_str(char ip_str[IPCRYPT_MAX_IP_STR_BYTES], const uint8_t ip16[16]); 242 | int ipcrypt_sockaddr_to_ip16(uint8_t ip16[16], const struct sockaddr *sa); 243 | void ipcrypt_ip16_to_sockaddr(struct sockaddr_storage *sa, const uint8_t ip16[16]); 244 | int ipcrypt_key_from_hex(uint8_t *key, size_t key_len, const char *hex, size_t hex_len); 245 | int ipcrypt_ndip_from_hex(uint8_t ndip[24], size_t key_len, const char *hex, size_t hex_len); 246 | int ipcrypt_ndx_ndip_from_hex(uint8_t ndip[32], size_t key_len, const char *hex, size_t hex_len); 247 | ``` 248 | 249 | - **`ipcrypt_str_to_ip16`** / **`ipcrypt_ip16_to_str`**: Convert between string IP addresses and their 16-byte representation. 250 | - **`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. Returns `0` on success, or `-1` if the address family is not supported. 251 | - **`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 provided `sockaddr_storage` structure is guaranteed to be large enough to hold any socket address type. 252 | - **`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). Returns `0` on success, or `-1` if the input string is invalid or conversion fails. 253 | 254 | ## Examples 255 | 256 | Below are two illustrative examples of using `ipcrypt2` in C. 257 | 258 | ### Format-Preserving Example 259 | 260 | ```c 261 | #include 262 | #include 263 | #include "ipcrypt2.h" 264 | 265 | int main(void) { 266 | // A 16-byte AES key (for demonstration only; keep yours secret!) 267 | const uint8_t key[IPCRYPT_KEYBYTES] = { 268 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 269 | 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F 270 | }; 271 | 272 | // Example IP (could be IPv4 or IPv6) 273 | const char *original_ip = "192.168.0.100"; // or "::1" 274 | 275 | IPCrypt ctx; 276 | ipcrypt_init(&ctx, key); 277 | 278 | // Encrypt 279 | char encrypted_ip[IPCRYPT_MAX_IP_STR_BYTES]; 280 | ipcrypt_encrypt_ip_str(&ctx, encrypted_ip, original_ip); 281 | 282 | // Decrypt 283 | char decrypted_ip[IPCRYPT_MAX_IP_STR_BYTES]; 284 | ipcrypt_decrypt_ip_str(&ctx, decrypted_ip, encrypted_ip); 285 | 286 | // Print results 287 | printf("Original IP : %s\n", original_ip); 288 | printf("Encrypted IP: %s\n", encrypted_ip); 289 | printf("Decrypted IP: %s\n", decrypted_ip); 290 | 291 | // Clean up 292 | ipcrypt_deinit(&ctx); 293 | return 0; 294 | } 295 | ``` 296 | 297 | ### Prefix-Preserving Example 298 | 299 | ```c 300 | #include 301 | #include 302 | #include "ipcrypt2.h" 303 | 304 | int main(void) { 305 | // A 32-byte AES key for PFX mode 306 | const uint8_t key[IPCRYPT_PFX_KEYBYTES] = { 307 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 308 | 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 309 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 310 | 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F 311 | }; 312 | 313 | // Example IPv6 addresses 314 | const char *ip1 = "2001:db8:abcd:1234:5678:90ab:cdef:0123"; 315 | const char *ip2 = "2001:db8:abcd:1234:aaaa:bbbb:cccc:dddd"; 316 | const char *ip3 = "2001:db8:9999:5555:1111:2222:3333:4444"; 317 | 318 | IPCryptPFX ctx; 319 | ipcrypt_pfx_init(&ctx, key); 320 | 321 | // Encrypt multiple IPs 322 | char encrypted_ip1[IPCRYPT_MAX_IP_STR_BYTES]; 323 | char encrypted_ip2[IPCRYPT_MAX_IP_STR_BYTES]; 324 | char encrypted_ip3[IPCRYPT_MAX_IP_STR_BYTES]; 325 | 326 | ipcrypt_pfx_encrypt_ip_str(&ctx, encrypted_ip1, ip1); 327 | ipcrypt_pfx_encrypt_ip_str(&ctx, encrypted_ip2, ip2); 328 | ipcrypt_pfx_encrypt_ip_str(&ctx, encrypted_ip3, ip3); 329 | 330 | // Print results 331 | printf("Original IP1: %s\n", ip1); 332 | printf("Encrypted : %s\n\n", encrypted_ip1); 333 | 334 | printf("Original IP2: %s\n", ip2); 335 | printf("Encrypted : %s\n\n", encrypted_ip2); 336 | 337 | printf("Original IP3: %s\n", ip3); 338 | printf("Encrypted : %s\n\n", encrypted_ip3); 339 | 340 | // Note: ip1 and ip2 share the same prefix (2001:db8:abcd:1234) 341 | // so their encrypted versions will also share the same prefix 342 | 343 | // Clean up 344 | ipcrypt_pfx_deinit(&ctx); 345 | return 0; 346 | } 347 | ``` 348 | 349 | ### Non-Deterministic Example 350 | 351 | ```c 352 | #include 353 | #include 354 | #include 355 | #include 356 | #include "ipcrypt2.h" 357 | 358 | int main(void) { 359 | // A 16-byte AES key 360 | const uint8_t key[IPCRYPT_KEYBYTES] = { 361 | 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 362 | 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00 363 | }; 364 | IPCrypt ctx; 365 | ipcrypt_init(&ctx, key); 366 | 367 | // We'll generate a random 8-byte tweak 368 | uint8_t random_tweak[IPCRYPT_TWEAKBYTES]; 369 | arc4random_buf(random_tweak, sizeof IPCRYPT_TWEAKBYTES); 370 | 371 | // Input IP 372 | const char *original_ip = "2607:f8b0:4005:805::200e"; // example IPv6 373 | 374 | // Encrypt string in non-deterministic mode 375 | char nd_encrypted_str[IPCRYPT_NDIP_STR_BYTES]; 376 | ipcrypt_nd_encrypt_ip_str(&ctx, nd_encrypted_str, original_ip, random_tweak); 377 | 378 | // Decrypt 379 | char decrypted_ip[IPCRYPT_MAX_IP_STR_BYTES]; 380 | ipcrypt_nd_decrypt_ip_str(&ctx, decrypted_ip, nd_encrypted_str); 381 | 382 | printf("Original IP : %s\n", original_ip); 383 | printf("ND-Encrypted: %s\n", nd_encrypted_str); 384 | printf("Decrypted IP: %s\n", decrypted_ip); 385 | 386 | ipcrypt_deinit(&ctx); 387 | return 0; 388 | } 389 | ``` 390 | 391 | ## Security Considerations 392 | 393 | 1. **Key Management** 394 | 395 | - Standard and ND modes require a secure 16-byte AES key. 396 | - PFX and NDX modes require a secure 32-byte AES key. 397 | - Protect keys and ensure they remain secret. 398 | - Keys should be frequently rotated. 399 | 400 | 2. **Tweak Randomness** (for non-deterministic modes) 401 | 402 | - **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. 403 | - **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. 404 | 405 | 3. **IP Format Preservation** 406 | 407 | - 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. 408 | 409 | 4. **Not a General Purpose Encryption Library** 410 | 411 | - This library is specialized for IP address encryption and may not be suitable for arbitrary data encryption. 412 | 413 | ## Limitations and Assumptions 414 | 415 | - **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. 416 | - **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. 417 | 418 | ## Bindings and Other Implementations 419 | 420 | - [Python (reference implementation)](https://github.com/jedisct1/draft-denis-ipcrypt/tree/main/implementations/python) 421 | - [Rust - Native implementation](https://docs.rs/ipcrypt_rs) 422 | - [Rust - C bindings](https://docs.rs/ipcrypt2) 423 | - [JavaScript (Browser and Node.js)](https://github.com/jedisct1/ipcrypt-js) 424 | - [Go](https://github.com/jedisct1/go-ipcrypt) 425 | - [Java](https://github.com/jedisct1/ipcrypt-java) 426 | - [Lua](https://github.com/jedisct1/ipcrypt-lua) 427 | - [Swift](https://github.com/jedisct1/ipcrypt-swift) 428 | - [Elixir](https://github.com/jedisct1/ipcrypt-elixir) 429 | - [Ruby](https://github.com/jedisct1/ipcrypt-ruby) 430 | - [Kotlin](https://github.com/jedisct1/ipcrypt-kotlin) 431 | - [AWK](https://github.com/jedisct1/ipcrypt.awk) 432 | - [Dart](https://github.com/elliotwutingfeng/ipcrypt) 433 | 434 | --- 435 | 436 | **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. 437 | -------------------------------------------------------------------------------- /src/softaes/untrinsics.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Untrinsics - Header-only portable implementations of common Intel intrinsics 3 | * for cryptographic implementations. 4 | * https://github.com/jedisct1/untrinsics 5 | * (C) 2025 Frank Denis - Public Domain. 6 | */ 7 | 8 | #ifndef untrinsics_H 9 | #define untrinsics_H 10 | 11 | #define __untrinsics__ 1 12 | 13 | #include 14 | #include 15 | 16 | #ifndef __has_attribute 17 | # define __has_attribute(x) 0 18 | #endif 19 | #if !(__has_attribute(aligned) || defined(__GNUC__) || defined(__clang__) || defined(__attribute__)) 20 | # define __attribute__(x) 21 | #endif 22 | 23 | typedef union { 24 | uint8_t b[16]; 25 | uint32_t w[4]; 26 | uint64_t q[2]; 27 | } __m128i __attribute__((aligned(16))); 28 | 29 | /* clang-format off */ 30 | 31 | static const uint8_t UNTRINSICS_SBOX[256] __attribute__((aligned(64))) = { 32 | 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 33 | 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 34 | 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 35 | 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 36 | 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 37 | 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 38 | 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 39 | 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 40 | 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 41 | 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 42 | 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 43 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 44 | 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 45 | 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 46 | 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 47 | 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 48 | }; 49 | 50 | static const uint8_t UNTRINSICS_INV_SBOX[256] __attribute__((aligned(64))) = { 51 | 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 52 | 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 53 | 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 54 | 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 55 | 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 56 | 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 57 | 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 58 | 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 59 | 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 60 | 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 61 | 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 62 | 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 63 | 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 64 | 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 65 | 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 66 | 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d 67 | }; 68 | 69 | /* clang-format on */ 70 | 71 | static volatile uint8_t untrinsics_optblocker_u8; 72 | static volatile uint32_t untrinsics_optblocker_u32; 73 | static volatile uint64_t untrinsics_optblocker_u64; 74 | 75 | #ifdef UNTRINSICS_MITIGATE 76 | static inline uint8_t 77 | untrinsics_sbox(const uint8_t x) 78 | { 79 | uint32_t optblocker_u32 = untrinsics_optblocker_u32; 80 | uint8_t result = 0; 81 | 82 | for (int i = 0; i < 256; i++) { 83 | uint32_t diff = (uint32_t) (i ^ x); 84 | uint32_t mask = (((diff - 1) >> 29) ^ optblocker_u32) >> 2; 85 | result |= UNTRINSICS_SBOX[i] & -(uint8_t) mask; 86 | } 87 | return result; 88 | } 89 | 90 | static inline uint8_t 91 | untrinsics_inv_sbox(const uint8_t x) 92 | { 93 | uint32_t optblocker_u32 = untrinsics_optblocker_u32; 94 | uint8_t result = 0; 95 | 96 | for (int i = 0; i < 256; i++) { 97 | uint32_t diff = (uint32_t) (i ^ x); 98 | uint32_t mask = (((diff - 1) >> 29) ^ optblocker_u32) >> 2; 99 | result |= UNTRINSICS_INV_SBOX[i] & -(uint8_t) mask; 100 | } 101 | return result; 102 | } 103 | #else 104 | # define untrinsics_sbox(x) UNTRINSICS_SBOX[x] 105 | # define untrinsics_inv_sbox(x) UNTRINSICS_INV_SBOX[x] 106 | #endif 107 | 108 | /* Multiply by x in GF(2^8) using the AES polynomial (usually compiled to branchless code) */ 109 | static inline uint8_t 110 | untrinsics_xtime(uint8_t x) 111 | { 112 | return (uint8_t) ((x << 1) ^ ((x & 0x80) ? 0x1B : 0)); 113 | } 114 | 115 | /* Multiply by 2 (MixColumns) */ 116 | static inline uint8_t 117 | untrinsics_mul2(uint8_t x) 118 | { 119 | return untrinsics_xtime(x); 120 | } 121 | 122 | /* Multiply by 3 (MixColumns) */ 123 | static inline uint8_t 124 | untrinsics_mul3(uint8_t x) 125 | { 126 | return (uint8_t) (untrinsics_xtime(x) ^ x); 127 | } 128 | 129 | /* Multiply by 9 (InvMixColumns) */ 130 | static inline uint8_t 131 | untrinsics_mul9(uint8_t x) 132 | { 133 | uint8_t t2 = untrinsics_xtime(x); 134 | uint8_t t4 = untrinsics_xtime(t2); 135 | uint8_t t8 = untrinsics_xtime(t4); 136 | return (uint8_t) (t8 ^ x); 137 | } 138 | 139 | /* Multiply by 0x0B (InvMixColumns) */ 140 | static inline uint8_t 141 | untrinsics_mul0b(uint8_t x) 142 | { 143 | uint8_t t2 = untrinsics_xtime(x); 144 | uint8_t t4 = untrinsics_xtime(t2); 145 | uint8_t t8 = untrinsics_xtime(t4); 146 | return (uint8_t) (t8 ^ t2 ^ x); 147 | } 148 | 149 | /* Multiply by 0x0D (InvMixColumns) */ 150 | static inline uint8_t 151 | untrinsics_mul0d(uint8_t x) 152 | { 153 | uint8_t t2 = untrinsics_xtime(x); 154 | uint8_t t4 = untrinsics_xtime(t2); 155 | uint8_t t8 = untrinsics_xtime(t4); 156 | return (uint8_t) (t8 ^ t4 ^ x); 157 | } 158 | 159 | /* Multiply by 0x0E (InvMixColumns) */ 160 | static inline uint8_t 161 | untrinsics_mul0e(uint8_t x) 162 | { 163 | uint8_t t2 = untrinsics_xtime(x); 164 | uint8_t t4 = untrinsics_xtime(t2); 165 | uint8_t t8 = untrinsics_xtime(t4); 166 | return (uint8_t) (t8 ^ t4 ^ t2); 167 | } 168 | 169 | /* Combine SubBytes and ShiftRows (forward) */ 170 | static inline void 171 | untrinsics_sub_shiftrows(uint8_t s[16]) 172 | { 173 | uint8_t tmp[16]; 174 | tmp[0] = untrinsics_sbox(s[0]); 175 | tmp[1] = untrinsics_sbox(s[5]); 176 | tmp[2] = untrinsics_sbox(s[10]); 177 | tmp[3] = untrinsics_sbox(s[15]); 178 | tmp[4] = untrinsics_sbox(s[4]); 179 | tmp[5] = untrinsics_sbox(s[9]); 180 | tmp[6] = untrinsics_sbox(s[14]); 181 | tmp[7] = untrinsics_sbox(s[3]); 182 | tmp[8] = untrinsics_sbox(s[8]); 183 | tmp[9] = untrinsics_sbox(s[13]); 184 | tmp[10] = untrinsics_sbox(s[2]); 185 | tmp[11] = untrinsics_sbox(s[7]); 186 | tmp[12] = untrinsics_sbox(s[12]); 187 | tmp[13] = untrinsics_sbox(s[1]); 188 | tmp[14] = untrinsics_sbox(s[6]); 189 | tmp[15] = untrinsics_sbox(s[11]); 190 | memcpy(s, tmp, 16); 191 | } 192 | 193 | /* Combine InvSubBytes and InvShiftRows */ 194 | static inline void 195 | untrinsics_invsub_shiftrows(uint8_t s[16]) 196 | { 197 | uint8_t tmp[16]; 198 | tmp[0] = untrinsics_inv_sbox(s[0]); 199 | tmp[1] = untrinsics_inv_sbox(s[13]); 200 | tmp[2] = untrinsics_inv_sbox(s[10]); 201 | tmp[3] = untrinsics_inv_sbox(s[7]); 202 | tmp[4] = untrinsics_inv_sbox(s[4]); 203 | tmp[5] = untrinsics_inv_sbox(s[1]); 204 | tmp[6] = untrinsics_inv_sbox(s[14]); 205 | tmp[7] = untrinsics_inv_sbox(s[11]); 206 | tmp[8] = untrinsics_inv_sbox(s[8]); 207 | tmp[9] = untrinsics_inv_sbox(s[5]); 208 | tmp[10] = untrinsics_inv_sbox(s[2]); 209 | tmp[11] = untrinsics_inv_sbox(s[15]); 210 | tmp[12] = untrinsics_inv_sbox(s[12]); 211 | tmp[13] = untrinsics_inv_sbox(s[9]); 212 | tmp[14] = untrinsics_inv_sbox(s[6]); 213 | tmp[15] = untrinsics_inv_sbox(s[3]); 214 | memcpy(s, tmp, 16); 215 | } 216 | 217 | /* MixColumns transformation (forward) */ 218 | static inline void 219 | untrinsics_mixcolumns(uint8_t s[16]) 220 | { 221 | for (int c = 0; c < 4; c++) { 222 | int i = 4 * c; 223 | uint8_t a0 = s[i], a1 = s[i + 1], a2 = s[i + 2], a3 = s[i + 3]; 224 | s[i] = (uint8_t) (untrinsics_mul2(a0) ^ untrinsics_mul3(a1) ^ a2 ^ a3); 225 | s[i + 1] = (uint8_t) (a0 ^ untrinsics_mul2(a1) ^ untrinsics_mul3(a2) ^ a3); 226 | s[i + 2] = (uint8_t) (a0 ^ a1 ^ untrinsics_mul2(a2) ^ untrinsics_mul3(a3)); 227 | s[i + 3] = (uint8_t) (untrinsics_mul3(a0) ^ a1 ^ a2 ^ untrinsics_mul2(a3)); 228 | } 229 | } 230 | 231 | /* InvMixColumns transformation */ 232 | static inline void 233 | untrinsics_inv_mixcolumns(uint8_t s[16]) 234 | { 235 | for (int c = 0; c < 4; c++) { 236 | int i = 4 * c; 237 | uint8_t a0 = s[i], a1 = s[i + 1], a2 = s[i + 2], a3 = s[i + 3]; 238 | s[i] = (uint8_t) (untrinsics_mul0e(a0) ^ untrinsics_mul0b(a1) ^ untrinsics_mul0d(a2) ^ 239 | untrinsics_mul9(a3)); 240 | s[i + 1] = (uint8_t) (untrinsics_mul9(a0) ^ untrinsics_mul0e(a1) ^ untrinsics_mul0b(a2) ^ 241 | untrinsics_mul0d(a3)); 242 | s[i + 2] = (uint8_t) (untrinsics_mul0d(a0) ^ untrinsics_mul9(a1) ^ untrinsics_mul0e(a2) ^ 243 | untrinsics_mul0b(a3)); 244 | s[i + 3] = (uint8_t) (untrinsics_mul0b(a0) ^ untrinsics_mul0d(a1) ^ untrinsics_mul9(a2) ^ 245 | untrinsics_mul0e(a3)); 246 | } 247 | } 248 | 249 | /* Rotate a 32-bit word right by 8 bits */ 250 | static inline uint32_t 251 | untrinsics_rot_word(const uint32_t x) 252 | { 253 | return (x >> 8) | (x << 24); 254 | } 255 | 256 | /* Apply S-box to each byte in a 32-bit word */ 257 | static inline uint32_t 258 | untrinsics_sub_word(const uint32_t x) 259 | { 260 | return ((uint32_t) untrinsics_sbox((x >> 24) & 0xff) << 24) | 261 | ((uint32_t) untrinsics_sbox((x >> 16) & 0xff) << 16) | 262 | ((uint32_t) untrinsics_sbox((x >> 8) & 0xff) << 8) | 263 | ((uint32_t) untrinsics_sbox(x & 0xff)); 264 | } 265 | 266 | /* Copy __m128i value */ 267 | static inline __m128i 268 | untrinsics_copy(const __m128i a) 269 | { 270 | __m128i r; 271 | memcpy(r.b, a.b, 16); 272 | return r; 273 | } 274 | 275 | /* AES encryption round */ 276 | static inline __m128i 277 | _mm_aesenc_si128(const __m128i a_, const __m128i rk) 278 | { 279 | __m128i a = untrinsics_copy(a_); 280 | untrinsics_sub_shiftrows(a.b); 281 | untrinsics_mixcolumns(a.b); 282 | for (int i = 0; i < 16; i++) 283 | a.b[i] ^= rk.b[i]; 284 | return a; 285 | } 286 | 287 | /* Final AES encryption round */ 288 | static inline __m128i 289 | _mm_aesenclast_si128(const __m128i a_, const __m128i rk) 290 | { 291 | __m128i a = untrinsics_copy(a_); 292 | untrinsics_sub_shiftrows(a.b); 293 | for (int i = 0; i < 16; i++) 294 | a.b[i] ^= rk.b[i]; 295 | return a; 296 | } 297 | 298 | /* AES decryption round */ 299 | static inline __m128i 300 | _mm_aesdec_si128(const __m128i a_, const __m128i rk) 301 | { 302 | __m128i a = untrinsics_copy(a_); 303 | untrinsics_invsub_shiftrows(a.b); 304 | untrinsics_inv_mixcolumns(a.b); 305 | for (int i = 0; i < 16; i++) 306 | a.b[i] ^= rk.b[i]; 307 | return a; 308 | } 309 | 310 | /* Final AES decryption round */ 311 | static inline __m128i 312 | _mm_aesdeclast_si128(const __m128i a_, const __m128i rk) 313 | { 314 | __m128i a = untrinsics_copy(a_); 315 | untrinsics_invsub_shiftrows(a.b); 316 | for (int i = 0; i < 16; i++) 317 | a.b[i] ^= rk.b[i]; 318 | return a; 319 | } 320 | 321 | /* Transform encryption round key to decryption key */ 322 | static inline __m128i 323 | _mm_aesimc_si128(const __m128i a_) 324 | { 325 | __m128i a = untrinsics_copy(a_); 326 | untrinsics_inv_mixcolumns(a.b); 327 | return a; 328 | } 329 | 330 | /* Key expansion assist */ 331 | static inline __m128i 332 | _mm_aeskeygenassist_si128(const __m128i a, const uint8_t rcon) 333 | { 334 | __m128i dst; 335 | const uint32_t x1 = a.w[1]; 336 | const uint32_t x3 = a.w[3]; 337 | const uint32_t sx1 = untrinsics_sub_word(x1); 338 | const uint32_t sx3 = untrinsics_sub_word(x3); 339 | 340 | dst.w[0] = sx1; 341 | dst.w[1] = untrinsics_rot_word(sx1) ^ rcon; 342 | dst.w[2] = sx3; 343 | dst.w[3] = untrinsics_rot_word(sx3) ^ rcon; 344 | return dst; 345 | } 346 | 347 | /* Carry-less multiplication of selected 64-bit lanes. 348 | imm: bit 0x01 selects lane from a, 0x10 from b. 349 | */ 350 | static inline __m128i 351 | _mm_clmulepi64_si128(const __m128i a, const __m128i b, const int imm) 352 | { 353 | __m128i r; 354 | uint64_t x = (imm & 1) ? a.q[1] : a.q[0]; 355 | uint64_t y = (imm & 0x10) ? b.q[1] : b.q[0]; 356 | uint64_t r_lo = 0, r_hi = 0; 357 | { 358 | uint64_t bit = y & 1ULL; 359 | uint64_t mask = 0ULL - bit; 360 | r_lo ^= x & mask; 361 | } 362 | for (int i = 1; i < 64; i++) { 363 | uint64_t bit = (y >> i) & 1ULL; 364 | uint64_t mask = 0ULL - bit; 365 | r_lo ^= (x << i) & mask; 366 | r_hi ^= (x >> (64 - i)) & mask; 367 | } 368 | r.q[0] = r_lo; 369 | r.q[1] = r_hi; 370 | return r; 371 | } 372 | 373 | /* Load 128 bits from unaligned memory */ 374 | static inline __m128i 375 | _mm_loadu_si128(const void* const p) 376 | { 377 | __m128i r; 378 | memcpy(r.b, p, 16); 379 | return r; 380 | } 381 | 382 | /* Store 128 bits to unaligned memory */ 383 | static inline void 384 | _mm_storeu_si128(void* const p, const __m128i a) 385 | { 386 | memcpy(p, a.b, 16); 387 | } 388 | 389 | /* Bitwise XOR of 128-bit values */ 390 | static inline __m128i 391 | _mm_xor_si128(const __m128i a, const __m128i b) 392 | { 393 | __m128i r; 394 | for (int i = 0; i < 16; i++) 395 | r.b[i] = (uint8_t) (a.b[i] ^ b.b[i]); 396 | return r; 397 | } 398 | 399 | /* Bitwise OR of 128-bit values */ 400 | static inline __m128i 401 | _mm_or_si128(const __m128i a, const __m128i b) 402 | { 403 | __m128i r; 404 | for (int i = 0; i < 16; i++) 405 | r.b[i] = (uint8_t) (a.b[i] | b.b[i]); 406 | return r; 407 | } 408 | 409 | /* Bitwise AND of 128-bit values */ 410 | static inline __m128i 411 | _mm_and_si128(const __m128i a, const __m128i b) 412 | { 413 | __m128i r; 414 | for (int i = 0; i < 16; i++) 415 | r.b[i] = (uint8_t) (a.b[i] & b.b[i]); 416 | return r; 417 | } 418 | 419 | /* Set __m128i from two 64-bit integers (high, low) */ 420 | static inline __m128i 421 | _mm_set_epi64x(const long long high, const long long low) 422 | { 423 | __m128i r; 424 | r.q[0] = (uint64_t) low; 425 | r.q[1] = (uint64_t) high; 426 | return r; 427 | } 428 | 429 | /* Shift left by imm bytes (zero-fill) */ 430 | static inline __m128i 431 | _mm_slli_si128(const __m128i a, const int imm) 432 | { 433 | __m128i r; 434 | if (imm <= 0) 435 | return a; 436 | if (imm >= 16) { 437 | memset(r.b, 0, 16); 438 | return r; 439 | } 440 | memset(r.b, 0, imm); 441 | memcpy(r.b + imm, a.b, 16 - imm); 442 | return r; 443 | } 444 | 445 | /* Shift right by imm bytes (zero-fill) */ 446 | static inline __m128i 447 | _mm_srli_si128(const __m128i a, const int imm) 448 | { 449 | __m128i r; 450 | if (imm <= 0) 451 | return a; 452 | if (imm >= 16) { 453 | memset(r.b, 0, 16); 454 | return r; 455 | } 456 | memcpy(r.b, a.b + imm, 16 - imm); 457 | memset(r.b + (16 - imm), 0, imm); 458 | return r; 459 | } 460 | 461 | #ifndef _MM_SHUFFLE 462 | # define _MM_SHUFFLE(z, y, x, w) (((z & 3) << 6) | ((y & 3) << 4) | ((x & 3) << 2) | (w & 3)) 463 | #endif 464 | 465 | /* Shuffle 32-bit words */ 466 | static inline __m128i 467 | _mm_shuffle_epi32(const __m128i a, const int imm) 468 | { 469 | __m128i r; 470 | int w0 = imm & 0x3; 471 | int w1 = (imm >> 2) & 0x3; 472 | int w2 = (imm >> 4) & 0x3; 473 | int w3 = (imm >> 6) & 0x3; 474 | r.w[0] = a.w[w0]; 475 | r.w[1] = a.w[w1]; 476 | r.w[2] = a.w[w2]; 477 | r.w[3] = a.w[w3]; 478 | return r; 479 | } 480 | 481 | /* Shuffle bytes using a mask; if mask bit 7 is set, output zero */ 482 | static inline __m128i 483 | _mm_shuffle_epi8(const __m128i a, const __m128i b) 484 | { 485 | __m128i r; 486 | for (int i = 0; i < 16; i++) { 487 | uint8_t index = b.b[i] & 0x0F; 488 | uint8_t mask = b.b[i] & 0x80; 489 | r.b[i] = mask ? 0 : a.b[index]; 490 | } 491 | return r; 492 | } 493 | 494 | /* Load 64 bits from unaligned memory; zero upper half */ 495 | static inline __m128i 496 | _mm_loadu_si64(const void* const mem_addr) 497 | { 498 | __m128i r; 499 | uint64_t tmp; 500 | memcpy(&tmp, mem_addr, 8); 501 | r.q[0] = tmp; 502 | r.q[1] = 0; 503 | return r; 504 | } 505 | 506 | /* Set __m128i from 16 int8_t values */ 507 | static inline __m128i 508 | _mm_setr_epi8(const int8_t b0, const int8_t b1, const int8_t b2, const int8_t b3, const int8_t b4, 509 | const int8_t b5, const int8_t b6, const int8_t b7, const int8_t b8, const int8_t b9, 510 | const int8_t b10, const int8_t b11, const int8_t b12, const int8_t b13, 511 | const int8_t b14, const int8_t b15) 512 | { 513 | __m128i r; 514 | r.b[0] = b0; 515 | r.b[1] = b1; 516 | r.b[2] = b2; 517 | r.b[3] = b3; 518 | r.b[4] = b4; 519 | r.b[5] = b5; 520 | r.b[6] = b6; 521 | r.b[7] = b7; 522 | r.b[8] = b8; 523 | r.b[9] = b9; 524 | r.b[10] = b10; 525 | r.b[11] = b11; 526 | r.b[12] = b12; 527 | r.b[13] = b13; 528 | r.b[14] = b14; 529 | r.b[15] = b15; 530 | return r; 531 | } 532 | 533 | /* Set __m128i from 16 int values */ 534 | static inline __m128i 535 | _mm_setr_epi32(const int e0, const int e1, const int e2, const int e3) 536 | { 537 | __m128i v; 538 | v.w[0] = (uint32_t) e0; 539 | v.w[1] = (uint32_t) e1; 540 | v.w[2] = (uint32_t) e2; 541 | v.w[3] = (uint32_t) e3; 542 | return v; 543 | } 544 | 545 | /* Logical right shift each 32-bit lane by imm8 */ 546 | static inline __m128i 547 | _mm_srli_epi32(const __m128i v, const int imm8) 548 | { 549 | __m128i r; 550 | r.w[0] = v.w[0] >> imm8; 551 | r.w[1] = v.w[1] >> imm8; 552 | r.w[2] = v.w[2] >> imm8; 553 | r.w[3] = v.w[3] >> imm8; 554 | return r; 555 | } 556 | 557 | /* Logical left shift each 32-bit lane by imm8 */ 558 | static inline __m128i 559 | _mm_slli_epi32(const __m128i v, const int imm8) 560 | { 561 | __m128i r; 562 | r.w[0] = v.w[0] << imm8; 563 | r.w[1] = v.w[1] << imm8; 564 | r.w[2] = v.w[2] << imm8; 565 | r.w[3] = v.w[3] << imm8; 566 | return r; 567 | } 568 | 569 | /* Logical right shift each 16-bit lane by imm8 */ 570 | static inline __m128i 571 | _mm_srli_epi16(const __m128i v, const int imm8) 572 | { 573 | __m128i r; 574 | for (int i = 0; i < 8; i++) { 575 | uint16_t val = (uint16_t)v.b[i * 2] | ((uint16_t)v.b[i * 2 + 1] << 8); 576 | val >>= imm8; 577 | r.b[i * 2] = (uint8_t)(val & 0xff); 578 | r.b[i * 2 + 1] = (uint8_t)(val >> 8); 579 | } 580 | return r; 581 | } 582 | 583 | /* Logical left shift each 16-bit lane by imm8 */ 584 | static inline __m128i 585 | _mm_slli_epi16(const __m128i v, const int imm8) 586 | { 587 | __m128i r; 588 | for (int i = 0; i < 8; i++) { 589 | uint16_t val = (uint16_t)v.b[i * 2] | ((uint16_t)v.b[i * 2 + 1] << 8); 590 | val <<= imm8; 591 | r.b[i * 2] = (uint8_t)(val & 0xff); 592 | r.b[i * 2 + 1] = (uint8_t)(val >> 8); 593 | } 594 | return r; 595 | } 596 | 597 | /* Logical right shift each 64-bit lane by imm8 */ 598 | static inline __m128i 599 | _mm_srli_epi64(const __m128i v, const int imm8) 600 | { 601 | __m128i r; 602 | r.q[0] = v.q[0] >> imm8; 603 | r.q[1] = v.q[1] >> imm8; 604 | return r; 605 | } 606 | 607 | /* Logical left shift each 64-bit lane by imm8 */ 608 | static inline __m128i 609 | _mm_slli_epi64(const __m128i v, const int imm8) 610 | { 611 | __m128i r; 612 | r.q[0] = v.q[0] << imm8; 613 | r.q[1] = v.q[1] << imm8; 614 | return r; 615 | } 616 | 617 | /* Set __m128i to zero */ 618 | static inline __m128i 619 | _mm_setzero_si128(void) 620 | { 621 | __m128i r; 622 | memset(r.b, 0, 16); 623 | return r; 624 | } 625 | 626 | /* Set all 16 bytes to the same 8-bit value */ 627 | static inline __m128i 628 | _mm_set1_epi8(const int8_t a) 629 | { 630 | __m128i r; 631 | for (int i = 0; i < 16; i++) 632 | r.b[i] = (uint8_t) a; 633 | return r; 634 | } 635 | 636 | /* Add 8-bit integers in two __m128i values */ 637 | static inline __m128i 638 | _mm_add_epi8(const __m128i a, const __m128i b) 639 | { 640 | __m128i r; 641 | for (int i = 0; i < 16; i++) 642 | r.b[i] = (uint8_t) (a.b[i] + b.b[i]); 643 | return r; 644 | } 645 | 646 | /* Subtract 8-bit integers in two __m128i values */ 647 | static inline __m128i 648 | _mm_sub_epi8(const __m128i a, const __m128i b) 649 | { 650 | __m128i r; 651 | for (int i = 0; i < 16; i++) 652 | r.b[i] = (uint8_t) (a.b[i] - b.b[i]); 653 | return r; 654 | } 655 | 656 | /* Add 64-bit integers in two __m128i values */ 657 | static inline __m128i 658 | _mm_add_epi64(const __m128i a, const __m128i b) 659 | { 660 | __m128i r; 661 | r.q[0] = a.q[0] + b.q[0]; 662 | r.q[1] = a.q[1] + b.q[1]; 663 | return r; 664 | } 665 | 666 | /* Subtract 64-bit integers in two __m128i values */ 667 | static inline __m128i 668 | _mm_sub_epi64(const __m128i a, const __m128i b) 669 | { 670 | __m128i r; 671 | r.q[0] = a.q[0] - b.q[0]; 672 | r.q[1] = a.q[1] - b.q[1]; 673 | return r; 674 | } 675 | 676 | /* Compare 16 bytes for equality; result byte is 0xFF if equal, else 0x00 */ 677 | static inline __m128i 678 | _mm_cmpeq_epi8(const __m128i a, const __m128i b) 679 | { 680 | __m128i r; 681 | uint64_t optblocker_u8 = untrinsics_optblocker_u8; 682 | for (int i = 0; i < 16; i++) { 683 | uint8_t diff = a.b[i] ^ b.b[i]; 684 | uint8_t t = ((diff | (uint8_t) (-diff)) >> 5 ^ optblocker_u8) >> 2; 685 | r.b[i] = -(t ^ 1); 686 | } 687 | return r; 688 | } 689 | 690 | /* Compare 16 bytes for less than; result byte is 0xFF if a < b, else 0x00 */ 691 | #define _mm_test_all_zeros(M, V) _mm_testz_si128((M), (V)) 692 | 693 | /* _mm_testz_si128: Returns 1 if (a & b) is all zeros, 0 otherwise. */ 694 | static inline int 695 | _mm_testz_si128(const __m128i a, const __m128i b) 696 | { 697 | uint64_t optblocker_u64 = untrinsics_optblocker_u64; 698 | uint64_t x = (a.q[0] & b.q[0]) | (a.q[1] & b.q[1]); 699 | return (int) (((((x | (optblocker_u64 ^ -x)) >> 61) ^ optblocker_u64) >> 2) ^ 1); 700 | } 701 | 702 | /* _mm_test_all_ones: Returns 1 if all bits of a are 1, 0 otherwise. */ 703 | static inline int 704 | _mm_test_all_ones(const __m128i a) 705 | { 706 | uint64_t optblocker_u64 = untrinsics_optblocker_u64; 707 | uint64_t t = (a.q[0] ^ ~0ULL) | (a.q[1] ^ ~0ULL); 708 | return (int) (((((t | (optblocker_u64 ^ -t)) >> 61) ^ optblocker_u64) >> 2) ^ 1); 709 | } 710 | 711 | #endif /* UNTRINSICS_H */ 712 | -------------------------------------------------------------------------------- /src/test/main.zig: -------------------------------------------------------------------------------- 1 | const ipcrypt = @cImport(@cInclude("ipcrypt2.h")); 2 | 3 | const std = @import("std"); 4 | const testing = std.testing; 5 | 6 | test "ip string encryption and decryption" { 7 | const key = "0123456789abcdef"; 8 | var st: ipcrypt.IPCrypt = undefined; 9 | ipcrypt.ipcrypt_init(&st, key); 10 | defer ipcrypt.ipcrypt_deinit(&st); 11 | 12 | const ip_str = "1.2.3.4"; 13 | 14 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 15 | const encrypted_ip_len = ipcrypt.ipcrypt_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 16 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 17 | 18 | const expected_encrypted_ip = "9f4:e6e1:c77e:ffe8:49ac:6a6a:9f11:620f"; 19 | try testing.expectEqualSlices(u8, expected_encrypted_ip, encrypted_ip); 20 | 21 | var decrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 22 | const decrypted_ip_len = ipcrypt.ipcrypt_decrypt_ip_str(&st, &decrypted_ip_buf, encrypted_ip.ptr); 23 | const decrypted_ip_str = decrypted_ip_buf[0..decrypted_ip_len]; 24 | try testing.expectEqualSlices(u8, ip_str, decrypted_ip_str); 25 | } 26 | 27 | test "ip string non-deterministic encryption and decryption" { 28 | const key = "0123456789abcdef"; 29 | var st: ipcrypt.IPCrypt = undefined; 30 | ipcrypt.ipcrypt_init(&st, key); 31 | defer ipcrypt.ipcrypt_deinit(&st); 32 | 33 | const ip_str = "1.2.3.4"; 34 | const tweak: [8]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8 }; 35 | 36 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_NDIP_STR_BYTES:0]u8 = undefined; 37 | const encrypted_ip_len = ipcrypt.ipcrypt_nd_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str, &tweak); 38 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 39 | 40 | const expected_encrypted_ip = "01020304050607085f8ec3223eaa68378ba06d3bc3df0209"; 41 | try testing.expectEqualSlices(u8, expected_encrypted_ip, encrypted_ip); 42 | 43 | var decrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 44 | const decrypted_ip_len = ipcrypt.ipcrypt_nd_decrypt_ip_str(&st, &decrypted_ip_buf, encrypted_ip.ptr); 45 | const decrypted_ip_str = decrypted_ip_buf[0..decrypted_ip_len]; 46 | try testing.expectEqualSlices(u8, ip_str, decrypted_ip_str); 47 | } 48 | 49 | test "binary ip deterministic encryption and decryption" { 50 | const expected_ip: [16]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 51 | const key = "0123456789abcdef"; 52 | var st: ipcrypt.IPCrypt = undefined; 53 | ipcrypt.ipcrypt_init(&st, key); 54 | defer ipcrypt.ipcrypt_deinit(&st); 55 | 56 | var ip: [16]u8 = expected_ip; 57 | ipcrypt.ipcrypt_encrypt_ip16(&st, &ip); 58 | ipcrypt.ipcrypt_decrypt_ip16(&st, &ip); 59 | try testing.expectEqualSlices(u8, &expected_ip, &ip); 60 | } 61 | 62 | test "binary ip non-deterministic encryption and decryption" { 63 | const ip: [16]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 64 | const key = "0123456789abcdef"; 65 | const tweak: [8]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8 }; 66 | var st: ipcrypt.IPCrypt = undefined; 67 | ipcrypt.ipcrypt_init(&st, key); 68 | defer ipcrypt.ipcrypt_deinit(&st); 69 | 70 | var encrypted_ip: [ipcrypt.IPCRYPT_NDIP_BYTES]u8 = undefined; 71 | ipcrypt.ipcrypt_nd_encrypt_ip16(&st, &encrypted_ip, &ip, &tweak); 72 | var decrypted_ip: [16]u8 = undefined; 73 | ipcrypt.ipcrypt_nd_decrypt_ip16(&st, &decrypted_ip, &encrypted_ip); 74 | try testing.expectEqualSlices(u8, &ip, &decrypted_ip); 75 | } 76 | 77 | test "equivalence between AES and KIASU-BC with tweak=0*" { 78 | const ip: [16]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 79 | const key = "0123456789abcdef"; 80 | const tweak: [8]u8 = .{ 0, 0, 0, 0, 0, 0, 0, 0 }; 81 | 82 | var st: ipcrypt.IPCrypt = undefined; 83 | ipcrypt.ipcrypt_init(&st, key); 84 | defer ipcrypt.ipcrypt_deinit(&st); 85 | 86 | var encrypted_ip: [ipcrypt.IPCRYPT_NDIP_BYTES]u8 = undefined; 87 | ipcrypt.ipcrypt_nd_encrypt_ip16(&st, &encrypted_ip, &ip, &tweak); 88 | 89 | var encrypted_ip2 = ip; 90 | ipcrypt.ipcrypt_encrypt_ip16(&st, &encrypted_ip2); 91 | 92 | try testing.expectEqualSlices(u8, encrypted_ip[ipcrypt.IPCRYPT_TWEAKBYTES..], &encrypted_ip2); 93 | } 94 | 95 | test "binary ip NDX encryption and decryption" { 96 | const ip: [16]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 97 | const key = "0123456789abcdef1032547698badcfe"; 98 | const tweak: [16]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 99 | var st: ipcrypt.IPCryptNDX = undefined; 100 | _ = ipcrypt.ipcrypt_ndx_init(&st, key); 101 | defer ipcrypt.ipcrypt_ndx_deinit(&st); 102 | var encrypted_ip: [ipcrypt.IPCRYPT_NDX_NDIP_BYTES]u8 = undefined; 103 | ipcrypt.ipcrypt_ndx_encrypt_ip16(&st, &encrypted_ip, &ip, &tweak); 104 | var decrypted_ip: [16]u8 = undefined; 105 | ipcrypt.ipcrypt_ndx_decrypt_ip16(&st, &decrypted_ip, &encrypted_ip); 106 | try testing.expectEqualSlices(u8, &ip, &decrypted_ip); 107 | } 108 | 109 | test "ip string NDX encryption and decryption" { 110 | const key = "0123456789abcdef1032547698badcfe"; 111 | var st: ipcrypt.IPCryptNDX = undefined; 112 | _ = ipcrypt.ipcrypt_ndx_init(&st, key); 113 | defer ipcrypt.ipcrypt_ndx_deinit(&st); 114 | 115 | const ip_str = "1.2.3.4"; 116 | const tweak: [16]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 117 | 118 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_NDX_NDIP_STR_BYTES:0]u8 = undefined; 119 | const encrypted_ip_len = ipcrypt.ipcrypt_ndx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str, &tweak); 120 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 121 | 122 | const expected_encrypted_ip = "0102030405060708090a0b0c0d0e0f10a472dd736f82eb599b85141580b21c40"; 123 | try testing.expectEqualSlices(u8, expected_encrypted_ip, encrypted_ip); 124 | 125 | var decrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 126 | const decrypted_ip_len = ipcrypt.ipcrypt_ndx_decrypt_ip_str(&st, &decrypted_ip_buf, encrypted_ip.ptr); 127 | const decrypted_ip_str = decrypted_ip_buf[0..decrypted_ip_len]; 128 | try testing.expectEqualSlices(u8, ip_str, decrypted_ip_str); 129 | } 130 | 131 | test "test vector for ipcrypt-deterministic" { 132 | const key_hex = "0123456789abcdeffedcba9876543210"; 133 | const ip_str = "0.0.0.0"; 134 | const expected = "bde9:6789:d353:824c:d7c6:f58a:6bd2:26eb"; 135 | var key: [16]u8 = undefined; 136 | _ = try std.fmt.hexToBytes(&key, key_hex); 137 | var st: ipcrypt.IPCrypt = undefined; 138 | ipcrypt.ipcrypt_init(&st, &key); 139 | defer ipcrypt.ipcrypt_deinit(&st); 140 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 141 | const encrypted_ip_len = ipcrypt.ipcrypt_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 142 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 143 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 144 | } 145 | 146 | test "test vector 1 for ipcrypt-nd" { 147 | const key_hex = "0123456789abcdeffedcba9876543210"; 148 | const ip_str = "0.0.0.0"; 149 | const expected = "08e0c289bff23b7cb349aadfe3bcef56221c384c7c217b16"; 150 | var key: [16]u8 = undefined; 151 | _ = try std.fmt.hexToBytes(&key, key_hex); 152 | const tweak_hex = "08e0c289bff23b7c"; 153 | var tweak: [16]u8 = undefined; 154 | _ = try std.fmt.hexToBytes(&tweak, tweak_hex); 155 | var st: ipcrypt.IPCrypt = undefined; 156 | ipcrypt.ipcrypt_init(&st, &key); 157 | defer ipcrypt.ipcrypt_deinit(&st); 158 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_NDIP_STR_BYTES:0]u8 = undefined; 159 | const encrypted_ip_len = ipcrypt.ipcrypt_nd_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str, &tweak); 160 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 161 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 162 | } 163 | 164 | test "test vector 2 for ipcrypt-nd" { 165 | const key_hex = "1032547698badcfeefcdab8967452301"; 166 | const ip_str = "192.0.2.1"; 167 | const expected = "21bd1834bc088cd2e5e1fe55f95876e639faae2594a0caad"; 168 | var key: [16]u8 = undefined; 169 | _ = try std.fmt.hexToBytes(&key, key_hex); 170 | const tweak_hex = "21bd1834bc088cd2"; 171 | var tweak: [16]u8 = undefined; 172 | _ = try std.fmt.hexToBytes(&tweak, tweak_hex); 173 | var st: ipcrypt.IPCrypt = undefined; 174 | ipcrypt.ipcrypt_init(&st, &key); 175 | defer ipcrypt.ipcrypt_deinit(&st); 176 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_NDIP_STR_BYTES:0]u8 = undefined; 177 | const encrypted_ip_len = ipcrypt.ipcrypt_nd_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str, &tweak); 178 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 179 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 180 | } 181 | 182 | test "test vector 3 for ipcrypt-nd" { 183 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3c"; 184 | const ip_str = "2001:db8::1"; 185 | const expected = "b4ecbe30b70898d7553ac8974d1b4250eafc4b0aa1f80c96"; 186 | var key: [16]u8 = undefined; 187 | _ = try std.fmt.hexToBytes(&key, key_hex); 188 | const tweak_hex = "b4ecbe30b70898d7"; 189 | var tweak: [16]u8 = undefined; 190 | _ = try std.fmt.hexToBytes(&tweak, tweak_hex); 191 | var st: ipcrypt.IPCrypt = undefined; 192 | ipcrypt.ipcrypt_init(&st, &key); 193 | defer ipcrypt.ipcrypt_deinit(&st); 194 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_NDIP_STR_BYTES:0]u8 = undefined; 195 | const encrypted_ip_len = ipcrypt.ipcrypt_nd_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str, &tweak); 196 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 197 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 198 | } 199 | 200 | test "test vector 1 for ipcrypt-ndx" { 201 | const key_hex = "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301"; 202 | const ip_str = "0.0.0.0"; 203 | const expected = "21bd1834bc088cd2b4ecbe30b70898d782db0d4125fdace61db35b8339f20ee5"; 204 | var key: [32]u8 = undefined; 205 | _ = try std.fmt.hexToBytes(&key, key_hex); 206 | const tweak_hex = "21bd1834bc088cd2b4ecbe30b70898d7"; 207 | var tweak: [16]u8 = undefined; 208 | _ = try std.fmt.hexToBytes(&tweak, tweak_hex); 209 | var st: ipcrypt.IPCryptNDX = undefined; 210 | _ = ipcrypt.ipcrypt_ndx_init(&st, &key); 211 | defer ipcrypt.ipcrypt_ndx_deinit(&st); 212 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_NDX_NDIP_STR_BYTES:0]u8 = undefined; 213 | const encrypted_ip_len = ipcrypt.ipcrypt_ndx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str, &tweak); 214 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 215 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 216 | } 217 | 218 | test "test vector 2 for ipcrypt-ndx" { 219 | const key_hex = "1032547698badcfeefcdab89674523010123456789abcdeffedcba9876543210"; 220 | const ip_str = "192.0.2.1"; 221 | const expected = "08e0c289bff23b7cb4ecbe30b70898d7766a533392a69edf1ad0d3ce362ba98a"; 222 | var key: [32]u8 = undefined; 223 | _ = try std.fmt.hexToBytes(&key, key_hex); 224 | const tweak_hex = "08e0c289bff23b7cb4ecbe30b70898d7"; 225 | var tweak: [16]u8 = undefined; 226 | _ = try std.fmt.hexToBytes(&tweak, tweak_hex); 227 | var st: ipcrypt.IPCryptNDX = undefined; 228 | _ = ipcrypt.ipcrypt_ndx_init(&st, &key); 229 | defer ipcrypt.ipcrypt_ndx_deinit(&st); 230 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_NDX_NDIP_STR_BYTES:0]u8 = undefined; 231 | const encrypted_ip_len = ipcrypt.ipcrypt_ndx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str, &tweak); 232 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 233 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 234 | } 235 | 236 | test "test vector 3 for ipcrypt-ndx" { 237 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3c3c4fcf098815f7aba6d2ae2816157e2b"; 238 | const ip_str = "2001:db8::1"; 239 | const expected = "21bd1834bc088cd2b4ecbe30b70898d76089c7e05ae30c2d10ca149870a263e4"; 240 | var key: [32]u8 = undefined; 241 | _ = try std.fmt.hexToBytes(&key, key_hex); 242 | const tweak_hex = "21bd1834bc088cd2b4ecbe30b70898d7"; 243 | var tweak: [16]u8 = undefined; 244 | _ = try std.fmt.hexToBytes(&tweak, tweak_hex); 245 | var st: ipcrypt.IPCryptNDX = undefined; 246 | _ = ipcrypt.ipcrypt_ndx_init(&st, &key); 247 | defer ipcrypt.ipcrypt_ndx_deinit(&st); 248 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_NDX_NDIP_STR_BYTES:0]u8 = undefined; 249 | const encrypted_ip_len = ipcrypt.ipcrypt_ndx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str, &tweak); 250 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 251 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 252 | } 253 | 254 | test "socket address conversion" { 255 | // Test IPv4-mapped IPv6 address (1.2.3.4) 256 | const ipv4_mapped: [16]u8 = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4 }; 257 | 258 | // Convert to sockaddr_storage (use a byte array of sufficient size) 259 | var sa: [128]u8 = undefined; // 128 bytes is enough for any sockaddr_storage 260 | ipcrypt.ipcrypt_ip16_to_sockaddr(@ptrCast(@alignCast(&sa)), &ipv4_mapped); 261 | 262 | // Convert back to 16-byte IP 263 | var ip16: [16]u8 = undefined; 264 | try testing.expectEqual(0, ipcrypt.ipcrypt_sockaddr_to_ip16(&ip16, @ptrCast(@alignCast(&sa)))); 265 | 266 | // Verify the result matches the original 267 | try testing.expectEqualSlices(u8, &ipv4_mapped, &ip16); 268 | 269 | // Test IPv6 address (2001:db8::1) 270 | const ipv6: [16]u8 = .{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; 271 | 272 | // Convert to sockaddr_storage 273 | ipcrypt.ipcrypt_ip16_to_sockaddr(@ptrCast(@alignCast(&sa)), &ipv6); 274 | 275 | // Convert back to 16-byte IP 276 | try testing.expectEqual(0, ipcrypt.ipcrypt_sockaddr_to_ip16(&ip16, @ptrCast(@alignCast(&sa)))); 277 | 278 | // Verify the result matches the original 279 | try testing.expectEqualSlices(u8, &ipv6, &ip16); 280 | } 281 | 282 | test "key from hex conversion" { 283 | // Test valid 16-byte key 284 | const hex16 = "0123456789abcdef0123456789abcdef"; 285 | var key16: [16]u8 = undefined; 286 | try testing.expectEqual(0, ipcrypt.ipcrypt_key_from_hex(&key16, key16.len, hex16, hex16.len)); 287 | const expected_key16: [16]u8 = .{ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef }; 288 | try testing.expectEqualSlices(u8, &expected_key16, &key16); 289 | 290 | // Test valid 32-byte key 291 | const hex32 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; 292 | var key32: [32]u8 = undefined; 293 | try testing.expectEqual(0, ipcrypt.ipcrypt_key_from_hex(&key32, key32.len, hex32, hex32.len)); 294 | const expected_key32: [32]u8 = .{ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef }; 295 | try testing.expectEqualSlices(u8, &expected_key32, &key32); 296 | 297 | // Test invalid hex length 298 | const invalid_hex = "0123456789abcdef"; 299 | var key: [16]u8 = undefined; 300 | try testing.expectEqual(-1, ipcrypt.ipcrypt_key_from_hex(&key, key.len, invalid_hex, invalid_hex.len)); 301 | 302 | // Test invalid hex characters 303 | const invalid_chars = "0123456789abcdef0123456789abcdeg"; 304 | try testing.expectEqual(-1, ipcrypt.ipcrypt_key_from_hex(&key, key.len, invalid_chars, invalid_chars.len)); 305 | } 306 | 307 | test "ipcrypt-pfx round-trip" { 308 | // Test with 32-byte key for PFX 309 | const key_hex = "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301"; 310 | var key: [32]u8 = undefined; 311 | _ = try std.fmt.hexToBytes(&key, key_hex); 312 | 313 | var st: ipcrypt.IPCryptPFX = undefined; 314 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 315 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 316 | 317 | // Test with IPv4 address string 318 | const ipv4_str = "192.168.1.100"; 319 | var encrypted_ipv4_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 320 | const encrypted_ipv4_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ipv4_buf, ipv4_str); 321 | const encrypted_ipv4 = encrypted_ipv4_buf[0..encrypted_ipv4_len]; 322 | 323 | var decrypted_ipv4_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 324 | const decrypted_ipv4_len = ipcrypt.ipcrypt_pfx_decrypt_ip_str(&st, &decrypted_ipv4_buf, encrypted_ipv4.ptr); 325 | const decrypted_ipv4 = decrypted_ipv4_buf[0..decrypted_ipv4_len]; 326 | 327 | try testing.expectEqualSlices(u8, ipv4_str, decrypted_ipv4); 328 | 329 | // Test with IPv6 address string 330 | const ipv6_str = "2001:db8:85a3::8a2e:370:7334"; 331 | var encrypted_ipv6_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 332 | const encrypted_ipv6_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ipv6_buf, ipv6_str); 333 | const encrypted_ipv6 = encrypted_ipv6_buf[0..encrypted_ipv6_len]; 334 | 335 | var decrypted_ipv6_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 336 | const decrypted_ipv6_len = ipcrypt.ipcrypt_pfx_decrypt_ip_str(&st, &decrypted_ipv6_buf, encrypted_ipv6.ptr); 337 | const decrypted_ipv6 = decrypted_ipv6_buf[0..decrypted_ipv6_len]; 338 | 339 | try testing.expectEqualSlices(u8, ipv6_str, decrypted_ipv6); 340 | 341 | // Test with binary IP16 format for IPv4 342 | var ipv4_binary: [16]u8 = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 100 }; 343 | const original_ipv4_binary = ipv4_binary; 344 | 345 | ipcrypt.ipcrypt_pfx_encrypt_ip16(&st, &ipv4_binary); 346 | ipcrypt.ipcrypt_pfx_decrypt_ip16(&st, &ipv4_binary); 347 | 348 | try testing.expectEqualSlices(u8, &original_ipv4_binary, &ipv4_binary); 349 | 350 | // Test with binary IP16 format for IPv6 351 | var ipv6_binary: [16]u8 = .{ 0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0, 0, 0, 0, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34 }; 352 | const original_ipv6_binary = ipv6_binary; 353 | 354 | ipcrypt.ipcrypt_pfx_encrypt_ip16(&st, &ipv6_binary); 355 | ipcrypt.ipcrypt_pfx_decrypt_ip16(&st, &ipv6_binary); 356 | 357 | try testing.expectEqualSlices(u8, &original_ipv6_binary, &ipv6_binary); 358 | } 359 | 360 | test "ipcrypt-pfx test vectors from python reference" { 361 | // Test vector 1: key="0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip="0.0.0.0", encrypted="151.82.155.134" 362 | { 363 | const key_hex = "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301"; 364 | var key: [32]u8 = undefined; 365 | _ = try std.fmt.hexToBytes(&key, key_hex); 366 | 367 | var st: ipcrypt.IPCryptPFX = undefined; 368 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 369 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 370 | 371 | const ip_str = "0.0.0.0"; 372 | const expected = "151.82.155.134"; 373 | 374 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 375 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 376 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 377 | 378 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 379 | } 380 | 381 | // Test vector 2: key="0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip="255.255.255.255", encrypted="94.185.169.89" 382 | { 383 | const key_hex = "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301"; 384 | var key: [32]u8 = undefined; 385 | _ = try std.fmt.hexToBytes(&key, key_hex); 386 | 387 | var st: ipcrypt.IPCryptPFX = undefined; 388 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 389 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 390 | 391 | const ip_str = "255.255.255.255"; 392 | const expected = "94.185.169.89"; 393 | 394 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 395 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 396 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 397 | 398 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 399 | } 400 | 401 | // Test vector 3: key="0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip="192.0.2.1", encrypted="100.115.72.131" 402 | { 403 | const key_hex = "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301"; 404 | var key: [32]u8 = undefined; 405 | _ = try std.fmt.hexToBytes(&key, key_hex); 406 | 407 | var st: ipcrypt.IPCryptPFX = undefined; 408 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 409 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 410 | 411 | const ip_str = "192.0.2.1"; 412 | const expected = "100.115.72.131"; 413 | 414 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 415 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 416 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 417 | 418 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 419 | } 420 | 421 | // Test vector 4: key="0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301", ip="2001:db8::1", encrypted="c180:5dd4:2587:3524:30ab:fa65:6ab6:f88" 422 | { 423 | const key_hex = "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301"; 424 | var key: [32]u8 = undefined; 425 | _ = try std.fmt.hexToBytes(&key, key_hex); 426 | 427 | var st: ipcrypt.IPCryptPFX = undefined; 428 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 429 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 430 | 431 | const ip_str = "2001:db8::1"; 432 | const expected = "c180:5dd4:2587:3524:30ab:fa65:6ab6:f88"; 433 | 434 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 435 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 436 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 437 | 438 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 439 | } 440 | 441 | // Test vectors with different key: "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a" 442 | // Test vector 5: ip="10.0.0.47", encrypted="19.214.210.244" 443 | { 444 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 445 | var key: [32]u8 = undefined; 446 | _ = try std.fmt.hexToBytes(&key, key_hex); 447 | 448 | var st: ipcrypt.IPCryptPFX = undefined; 449 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 450 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 451 | 452 | const ip_str = "10.0.0.47"; 453 | const expected = "19.214.210.244"; 454 | 455 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 456 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 457 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 458 | 459 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 460 | } 461 | 462 | // Test vector 6: ip="10.0.0.129", encrypted="19.214.210.80" 463 | { 464 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 465 | var key: [32]u8 = undefined; 466 | _ = try std.fmt.hexToBytes(&key, key_hex); 467 | 468 | var st: ipcrypt.IPCryptPFX = undefined; 469 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 470 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 471 | 472 | const ip_str = "10.0.0.129"; 473 | const expected = "19.214.210.80"; 474 | 475 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 476 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 477 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 478 | 479 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 480 | } 481 | 482 | // Test vector 7: ip="10.0.0.234", encrypted="19.214.210.30" 483 | { 484 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 485 | var key: [32]u8 = undefined; 486 | _ = try std.fmt.hexToBytes(&key, key_hex); 487 | 488 | var st: ipcrypt.IPCryptPFX = undefined; 489 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 490 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 491 | 492 | const ip_str = "10.0.0.234"; 493 | const expected = "19.214.210.30"; 494 | 495 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 496 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 497 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 498 | 499 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 500 | } 501 | 502 | // Test vector 8: ip="172.16.5.193", encrypted="210.78.229.136" 503 | { 504 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 505 | var key: [32]u8 = undefined; 506 | _ = try std.fmt.hexToBytes(&key, key_hex); 507 | 508 | var st: ipcrypt.IPCryptPFX = undefined; 509 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 510 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 511 | 512 | const ip_str = "172.16.5.193"; 513 | const expected = "210.78.229.136"; 514 | 515 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 516 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 517 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 518 | 519 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 520 | } 521 | 522 | // Test vector 9: ip="172.16.97.42", encrypted="210.78.179.241" 523 | { 524 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 525 | var key: [32]u8 = undefined; 526 | _ = try std.fmt.hexToBytes(&key, key_hex); 527 | 528 | var st: ipcrypt.IPCryptPFX = undefined; 529 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 530 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 531 | 532 | const ip_str = "172.16.97.42"; 533 | const expected = "210.78.179.241"; 534 | 535 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 536 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 537 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 538 | 539 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 540 | } 541 | 542 | // Test vector 10: ip="172.16.248.177", encrypted="210.78.121.215" 543 | { 544 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 545 | var key: [32]u8 = undefined; 546 | _ = try std.fmt.hexToBytes(&key, key_hex); 547 | 548 | var st: ipcrypt.IPCryptPFX = undefined; 549 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 550 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 551 | 552 | const ip_str = "172.16.248.177"; 553 | const expected = "210.78.121.215"; 554 | 555 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 556 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 557 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 558 | 559 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 560 | } 561 | 562 | // IPv6 test vectors with key "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a" 563 | // Test vector 11: ip="2001:db8::a5c9:4e2f:bb91:5a7d", encrypted="7cec:702c:1243:f70:1956:125:b9bd:1aba" 564 | { 565 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 566 | var key: [32]u8 = undefined; 567 | _ = try std.fmt.hexToBytes(&key, key_hex); 568 | 569 | var st: ipcrypt.IPCryptPFX = undefined; 570 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 571 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 572 | 573 | const ip_str = "2001:db8::a5c9:4e2f:bb91:5a7d"; 574 | const expected = "7cec:702c:1243:f70:1956:125:b9bd:1aba"; 575 | 576 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 577 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 578 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 579 | 580 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 581 | } 582 | 583 | // Test vector 12: ip="2001:db8::7234:d8f1:3c6e:9a52", encrypted="7cec:702c:1243:f70:a3ef:c8e:95c1:cd0d" 584 | { 585 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 586 | var key: [32]u8 = undefined; 587 | _ = try std.fmt.hexToBytes(&key, key_hex); 588 | 589 | var st: ipcrypt.IPCryptPFX = undefined; 590 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 591 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 592 | 593 | const ip_str = "2001:db8::7234:d8f1:3c6e:9a52"; 594 | const expected = "7cec:702c:1243:f70:a3ef:c8e:95c1:cd0d"; 595 | 596 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 597 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 598 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 599 | 600 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 601 | } 602 | 603 | // Test vector 13: ip="2001:db8::f1e0:937b:26d4:8c1a", encrypted="7cec:702c:1243:f70:443c:c8e:6a62:b64d" 604 | { 605 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 606 | var key: [32]u8 = undefined; 607 | _ = try std.fmt.hexToBytes(&key, key_hex); 608 | 609 | var st: ipcrypt.IPCryptPFX = undefined; 610 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 611 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 612 | 613 | const ip_str = "2001:db8::f1e0:937b:26d4:8c1a"; 614 | const expected = "7cec:702c:1243:f70:443c:c8e:6a62:b64d"; 615 | 616 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 617 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 618 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 619 | 620 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 621 | } 622 | 623 | // Test vector 14: ip="2001:db8:3a5c::e7d1:4b9f:2c8a:f673", encrypted="7cec:702c:3503:bef:e616:96bd:be33:a9b9" 624 | { 625 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 626 | var key: [32]u8 = undefined; 627 | _ = try std.fmt.hexToBytes(&key, key_hex); 628 | 629 | var st: ipcrypt.IPCryptPFX = undefined; 630 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 631 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 632 | 633 | const ip_str = "2001:db8:3a5c::e7d1:4b9f:2c8a:f673"; 634 | const expected = "7cec:702c:3503:bef:e616:96bd:be33:a9b9"; 635 | 636 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 637 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 638 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 639 | 640 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 641 | } 642 | 643 | // Test vector 15: ip="2001:db8:9f27::b4e2:7a3d:5f91:c8e6", encrypted="7cec:702c:a504:b74e:194a:3d90:b047:2d1a" 644 | { 645 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 646 | var key: [32]u8 = undefined; 647 | _ = try std.fmt.hexToBytes(&key, key_hex); 648 | 649 | var st: ipcrypt.IPCryptPFX = undefined; 650 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 651 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 652 | 653 | const ip_str = "2001:db8:9f27::b4e2:7a3d:5f91:c8e6"; 654 | const expected = "7cec:702c:a504:b74e:194a:3d90:b047:2d1a"; 655 | 656 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 657 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 658 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 659 | 660 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 661 | } 662 | 663 | // Test vector 16: ip="2001:db8:d8b4::193c:a5e7:8b2f:46d1", encrypted="7cec:702c:f840:aa67:1b8:e84f:ac9d:77fb" 664 | { 665 | const key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a"; 666 | var key: [32]u8 = undefined; 667 | _ = try std.fmt.hexToBytes(&key, key_hex); 668 | 669 | var st: ipcrypt.IPCryptPFX = undefined; 670 | _ = ipcrypt.ipcrypt_pfx_init(&st, &key); 671 | defer ipcrypt.ipcrypt_pfx_deinit(&st); 672 | 673 | const ip_str = "2001:db8:d8b4::193c:a5e7:8b2f:46d1"; 674 | const expected = "7cec:702c:f840:aa67:1b8:e84f:ac9d:77fb"; 675 | 676 | var encrypted_ip_buf: [ipcrypt.IPCRYPT_MAX_IP_STR_BYTES:0]u8 = undefined; 677 | const encrypted_ip_len = ipcrypt.ipcrypt_pfx_encrypt_ip_str(&st, &encrypted_ip_buf, ip_str); 678 | const encrypted_ip = encrypted_ip_buf[0..encrypted_ip_len]; 679 | 680 | try testing.expectEqualSlices(u8, expected, encrypted_ip); 681 | } 682 | } 683 | -------------------------------------------------------------------------------- /src/ipcrypt2.c: -------------------------------------------------------------------------------- 1 | /** 2 | * IPCrypt2: Lightweight IP Address Encryption Library 3 | * 4 | * IPCrypt2 provides simple and efficient encryption and decryption of IP addresses (IPv4 & IPv6). 5 | * Designed for privacy-preserving network applications, it supports four encryption modes: 6 | * 7 | * 1. **Format-Preserving AES Encryption** 8 | * - Transforms an IP address into another valid IP address of the same size. 9 | * - Useful for logs or systems that expect syntactically correct IPs. 10 | * 11 | * 2. **Prefix-Preserving AES Encryption (PFX)** 12 | * - IP addresses with the same prefix produce encrypted IP addresses with the same prefix. 13 | * - The prefix can be of any length. 14 | * - Useful for maintaining network structure while anonymizing individual hosts. 15 | * - Format-preserving: output remains a valid IP address. 16 | * 17 | * 3. **Non-Deterministic AES Encryption (KIASU-BC)** 18 | * - Introduces a 64-bit tweak, producing different ciphertexts for the same IP. 19 | * - Useful when repeated IPs must remain unlinkable. This mode is not format-preserving. 20 | * 21 | * 4. **NDX Mode: Non-Deterministic AES Encryption with Extended Tweaks (AES-XTX)** 22 | * - Introduces a 128-bit tweak, producing different ciphertexts for the same IP. 23 | * - Useful when repeated IPs must remain unlinkable. This mode is not format-preserving. 24 | * - Higher usage limits than KIASU-BC, but half the performance and larger ciphertexts. 25 | * 26 | * Additional Features: 27 | * - Built-in string/binary IP conversion helpers. 28 | * - Optimized for x86_64 and ARM (aarch64) with AES hardware acceleration. 29 | * - Minimal external dependencies; just compile and link. 30 | * 31 | * Limitations: 32 | * - Not intended for general-purpose encryption — IP address only. 33 | * - Ensure keys are secret and tweak values are random or unique per encryption. 34 | */ 35 | 36 | #include 37 | #include 38 | 39 | #include 40 | #ifdef _WIN32 41 | # include 42 | #else 43 | # include 44 | # include 45 | # include 46 | #endif 47 | 48 | #include "include/ipcrypt2.h" 49 | 50 | /** Number of AES rounds. For AES-128, this is 10. */ 51 | #define ROUNDS 10 52 | 53 | #define COMPILER_ASSERT(X) (void) sizeof(char[(X) ? 1 : -1]) 54 | 55 | #if !defined(_MSC_VER) || _MSC_VER < 1800 56 | # define __vectorcall 57 | #endif 58 | 59 | #ifndef HAVE_EXPLICIT_BZERO 60 | # if (defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ 61 | defined(__DragonFly__)) || \ 62 | (defined(__sun) && defined(__illumos__)) 63 | # define HAVE_EXPLICIT_BZERO 1 64 | # elif defined(__GLIBC__) && defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE) 65 | # if __GLIBC_PREREQ(2, 25) 66 | # define HAVE_EXPLICIT_BZERO 1 67 | # endif 68 | # endif 69 | #endif 70 | 71 | #ifdef __aarch64__ 72 | # ifndef __ARM_FEATURE_CRYPTO 73 | # define __ARM_FEATURE_CRYPTO 1 74 | # endif 75 | # ifndef __ARM_FEATURE_AES 76 | # define __ARM_FEATURE_AES 1 77 | # endif 78 | 79 | # if defined(_MSC_VER) && defined(_M_ARM64) 80 | # include 81 | # else 82 | # include 83 | # endif 84 | 85 | # ifdef __clang__ 86 | /** 87 | * Enable AES instructions when compiling with Clang. 88 | */ 89 | # pragma clang attribute push(__attribute__((target("neon,crypto,aes"))), \ 90 | apply_to = function) 91 | # elif defined(__GNUC__) 92 | /** 93 | * Enable AES and crypto instructions when compiling with GCC. 94 | */ 95 | # pragma GCC target("+simd+crypto") 96 | # endif 97 | 98 | /** 99 | * For AArch64, we represent AES blocks using a 128-bit NEON register (uint64x2_t). 100 | */ 101 | typedef uint64x2_t BlockVec; 102 | 103 | /** 104 | * Load 16 bytes from memory into a NEON register. 105 | */ 106 | # define LOAD128(a) vld1q_u64((const uint64_t *) (const void *) (a)) 107 | /** 108 | * Store 16 bytes from a NEON register into memory. 109 | */ 110 | # define STORE128(a, b) vst1q_u64((uint64_t *) (void *) (a), (b)) 111 | /** 112 | * Perform one round of AES encryption (no final round) on block_vec with rkey. 113 | */ 114 | # define AES_XENCRYPT(block_vec, rkey) \ 115 | vreinterpretq_u64_u8(vaesmcq_u8(vaeseq_u8(rkey, vreinterpretq_u8_u64(block_vec)))) 116 | /** 117 | * Perform the final AES encryption round on block_vec with rkey. 118 | * The final round excludes the MixColumns step. 119 | */ 120 | # define AES_XENCRYPTLAST(block_vec, rkey) \ 121 | vreinterpretq_u64_u8(vaeseq_u8(rkey, vreinterpretq_u8_u64(block_vec))) 122 | /** 123 | * Perform one round of AES decryption (no final round) on block_vec with rkey. 124 | */ 125 | # define AES_XDECRYPT(block_vec, rkey) \ 126 | vreinterpretq_u64_u8(vaesimcq_u8(vaesdq_u8(rkey, vreinterpretq_u8_u64(block_vec)))) 127 | /** 128 | * Perform the final AES decryption round on block_vec with rkey. 129 | * The final round excludes the InverseMixColumns step. 130 | */ 131 | # define AES_XDECRYPTLAST(block_vec, rkey) \ 132 | vreinterpretq_u64_u8(vaesdq_u8(rkey, vreinterpretq_u8_u64(block_vec))) 133 | /** 134 | * XOR two 128-bit blocks. 135 | */ 136 | # define XOR128(a, b) veorq_u64((a), (b)) 137 | /** 138 | * XOR three 128-bit blocks. 139 | */ 140 | # define XOR128_3(a, b, c) veorq_u64(veorq_u64((a), (b)), c) 141 | /** 142 | * Create a 128-bit register by combining two 64-bit values. 143 | */ 144 | # define SET64x2(a, b) vsetq_lane_u64((uint64_t) (a), vmovq_n_u64((uint64_t) (b)), 1) 145 | /** 146 | * Shift left a 128-bit register by b bytes (zero-filling from the right). 147 | */ 148 | # define BYTESHL128(a, b) vreinterpretq_u64_u8(vextq_s8(vdupq_n_s8(0), (uint8x16_t) a, 16 - (b))) 149 | /** 150 | * Reorder 32-bit lanes in a 128-bit register according to the indices (a, b, c, d). 151 | */ 152 | # define SHUFFLE32x4(x, a, b, c, d) \ 153 | vreinterpretq_u64_u32(__builtin_shufflevector( \ 154 | vreinterpretq_u32_u64(x), vreinterpretq_u32_u64(x), (a), (b), (c), (d))) 155 | /** 156 | * Invert an AES round key for decryption. 157 | */ 158 | # define RKINVERT(rkey) vaesimcq_u8(rkey) 159 | /** 160 | * Expand the 8-byte tweak into a 128-bit NEON register. 161 | */ 162 | # define TWEAK_EXPAND(tweak) \ 163 | vreinterpretq_u8_u32(vmovl_u16(vld1_u16((const uint16_t *) (tweak)))); 164 | 165 | /** 166 | * Shift an entire 128-bit block left by 1 bit. 167 | */ 168 | static inline BlockVec 169 | SHL1_128(const BlockVec a) 170 | { 171 | const BlockVec shl = vshlq_n_u8(a, 1); 172 | const BlockVec msb = vshrq_n_u8(a, 7); 173 | const BlockVec zero = vdupq_n_u8(0); 174 | const BlockVec carries = vextq_u8(msb, zero, 1); 175 | return vorrq_u8(shl, carries); 176 | } 177 | 178 | /** 179 | * Internal function for deriving a subkey using AES key generation instructions. 180 | * block_vec: the current AES round key block. 181 | * rc: the round constant. 182 | */ 183 | static inline BlockVec 184 | AES_KEYGEN(BlockVec block_vec, const int rc) 185 | { 186 | // Perform an AES single round encryption on block_vec with a zero key. 187 | // This extracts the needed transformation for generating a new round key. 188 | uint8x16_t a = vaeseq_u8(vreinterpretq_u8_u64(block_vec), vmovq_n_u8(0)); 189 | // Shuffle for the key expansion rotation. 190 | const uint8x16_t b = 191 | __builtin_shufflevector(a, a, 4, 1, 14, 11, 1, 14, 11, 4, 12, 9, 6, 3, 9, 6, 3, 12); 192 | // Combine with round constant. 193 | const uint64x2_t c = SET64x2((uint64_t) rc << 32, (uint64_t) rc << 32); 194 | return XOR128(b, c); 195 | } 196 | 197 | #else 198 | 199 | # if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86) 200 | 201 | # ifdef __clang__ 202 | /** 203 | * Enable AES/SSE4.1 instructions when compiling with Clang. 204 | */ 205 | # pragma clang attribute push(__attribute__((target("aes,sse4.1"))), apply_to = function) 206 | # elif defined(__GNUC__) 207 | /** 208 | * Enable AES/SSE4.1 instructions when compiling with GCC. 209 | */ 210 | # pragma GCC target("aes,sse4.1") 211 | # elif defined(_MSC_VER) 212 | # include 213 | # pragma intrinsic(_mm_aesenc_si128) 214 | # pragma intrinsic(_mm_aesenclast_si128) 215 | # pragma intrinsic(_mm_aesdec_si128) 216 | # pragma intrinsic(_mm_aesdeclast_si128) 217 | # pragma intrinsic(_mm_aesimc_si128) 218 | # pragma intrinsic(_mm_aeskeygenassist_si128) 219 | # endif 220 | 221 | # include 222 | # include 223 | # include 224 | 225 | # else 226 | # ifdef __clang__ 227 | # pragma clang attribute push(__attribute__((target(""))), apply_to = function) 228 | # elif defined(__GNUC__) 229 | # pragma GCC target("") 230 | # endif 231 | # include "softaes/untrinsics.h" 232 | # endif 233 | 234 | /** 235 | * On x86_64, we represent AES blocks using __m128i. 236 | */ 237 | typedef __m128i BlockVec; 238 | 239 | /** 240 | * Load 16 bytes from memory into an __m128i. 241 | */ 242 | # define LOAD128(a) _mm_loadu_si128((const BlockVec *) (a)) 243 | /** 244 | * Store 16 bytes from an __m128i into memory. 245 | */ 246 | # define STORE128(a, b) _mm_storeu_si128((BlockVec *) (a), (b)) 247 | /** 248 | * Perform a standard AES round (no final) on block_vec with rkey. 249 | */ 250 | # define AES_ENCRYPT(block_vec, rkey) _mm_aesenc_si128((block_vec), (rkey)) 251 | /** 252 | * Perform the final AES round (excludes MixColumns) on block_vec with rkey. 253 | */ 254 | # define AES_ENCRYPTLAST(block_vec, rkey) _mm_aesenclast_si128((block_vec), (rkey)) 255 | /** 256 | * Perform a standard AES decryption round on block_vec with rkey. 257 | */ 258 | # define AES_DECRYPT(block_vec, rkey) _mm_aesdec_si128((block_vec), (rkey)) 259 | /** 260 | * Perform the final AES decryption round (excludes InverseMixColumns) on block_vec with rkey. 261 | */ 262 | # define AES_DECRYPTLAST(block_vec, rkey) _mm_aesdeclast_si128((block_vec), (rkey)) 263 | /** 264 | * Generate an AES subkey for key expansion. 265 | */ 266 | # define AES_KEYGEN(block_vec, rc) _mm_aeskeygenassist_si128((block_vec), (rc)) 267 | /** 268 | * XOR two 128-bit blocks. 269 | */ 270 | # define XOR128(a, b) _mm_xor_si128((a), (b)) 271 | /** 272 | * XOR three 128-bit blocks. 273 | */ 274 | # define XOR128_3(a, b, c) _mm_xor_si128(_mm_xor_si128((a), (b)), (c)) 275 | /** 276 | * Construct a 128-bit block from two 64-bit values. 277 | */ 278 | # define SET64x2(a, b) _mm_set_epi64x((uint64_t) (a), (uint64_t) (b)) 279 | /** 280 | * Shift a 128-bit block left by b bytes. 281 | */ 282 | # define BYTESHL128(a, b) _mm_slli_si128(a, b) 283 | /** 284 | * Reorder 32-bit lanes in a 128-bit block. 285 | */ 286 | # define SHUFFLE32x4(x, a, b, c, d) _mm_shuffle_epi32((x), _MM_SHUFFLE((d), (c), (b), (a))) 287 | /** 288 | * Invert an AES round key for decryption. 289 | */ 290 | # define RKINVERT(rkey) _mm_aesimc_si128(rkey) 291 | /** 292 | * Expand an 8-byte tweak into a 128-bit register. 293 | */ 294 | # define TWEAK_EXPAND(tweak) \ 295 | _mm_shuffle_epi8(_mm_loadu_si64((const void *) tweak), \ 296 | _mm_setr_epi8(0x00, 0x01, 0x80, 0x80, 0x02, 0x03, 0x80, 0x80, 0x04, 0x05, \ 297 | 0x80, 0x80, 0x06, 0x07, 0x80, 0x80)) 298 | 299 | /** 300 | * Shift an entire 128-bit block left by 1 bit. 301 | */ 302 | static inline BlockVec 303 | SHL1_128(const BlockVec a) 304 | { 305 | const BlockVec shl = _mm_add_epi8(a, a); 306 | const BlockVec msb = _mm_and_si128(_mm_srli_epi16(a, 7), _mm_set1_epi8(0x01)); 307 | const BlockVec carries = _mm_srli_si128(msb, 1); 308 | return _mm_or_si128(shl, carries); 309 | } 310 | #endif 311 | 312 | /** 313 | * KeySchedule is an array of 1 + ROUNDS 128-bit blocks. 314 | * The first block is the initial round key, followed by ROUNDS subkeys. 315 | */ 316 | typedef BlockVec KeySchedule[1 + ROUNDS]; 317 | 318 | /** 319 | * Inverse key schedule for decryption. 320 | */ 321 | typedef BlockVec InvKeySchedule[ROUNDS - 1]; 322 | 323 | /** 324 | * AesState holds the expanded round keys for encryption/decryption. 325 | */ 326 | typedef struct AesState { 327 | KeySchedule rkeys; 328 | } AesState; 329 | 330 | /** 331 | * NDXState holds the expanded tweak round keys and encryption round keys for encryption/decryption. 332 | */ 333 | typedef struct NDXState { 334 | KeySchedule tkeys; 335 | KeySchedule rkeys; 336 | } NDXState; 337 | 338 | /** 339 | * PFXState holds the expanded tweak round keys and encryption round keys for encryption/decryption. 340 | */ 341 | typedef struct PFXState { 342 | KeySchedule k1keys; 343 | KeySchedule k2keys; 344 | } PFXState; 345 | 346 | /** 347 | * expand_key expands a 16-byte AES key into a full set of round keys. 348 | * st: the AesState structure to be populated. 349 | * key: a 16-byte AES key. 350 | */ 351 | static void __vectorcall expand_key(KeySchedule rkeys, const unsigned char key[IPCRYPT_KEYBYTES]) 352 | { 353 | BlockVec t, s; 354 | size_t i = 0; 355 | 356 | #define EXPAND_KEY(RC) \ 357 | rkeys[i++] = t; \ 358 | s = AES_KEYGEN(t, RC); \ 359 | t = XOR128(t, BYTESHL128(t, 4)); \ 360 | t = XOR128(t, BYTESHL128(t, 8)); \ 361 | t = XOR128(t, SHUFFLE32x4(s, 3, 3, 3, 3)); 362 | 363 | // Load the initial 128-bit key from memory. 364 | t = LOAD128(key); 365 | // Repeatedly generate the next round key. 366 | EXPAND_KEY(0x01); 367 | EXPAND_KEY(0x02); 368 | EXPAND_KEY(0x04); 369 | EXPAND_KEY(0x08); 370 | EXPAND_KEY(0x10); 371 | EXPAND_KEY(0x20); 372 | EXPAND_KEY(0x40); 373 | EXPAND_KEY(0x80); 374 | EXPAND_KEY(0x1b); 375 | EXPAND_KEY(0x36); 376 | // Store the final key. 377 | rkeys[i++] = t; 378 | } 379 | 380 | /** 381 | * aes_encrypt encrypts a 16-byte block x in-place using the expanded keys in st. 382 | */ 383 | static void 384 | aes_encrypt(uint8_t x[16], const AesState *st) 385 | { 386 | const BlockVec *rkeys = st->rkeys; 387 | BlockVec t; 388 | size_t i; 389 | 390 | #ifdef AES_XENCRYPT 391 | // For AArch64 with AES_XENCRYPT macros. 392 | t = AES_XENCRYPT(LOAD128(x), rkeys[0]); 393 | for (i = 1; i < ROUNDS - 1; i++) { 394 | t = AES_XENCRYPT(t, rkeys[i]); 395 | } 396 | t = AES_XENCRYPTLAST(t, rkeys[i]); 397 | t = XOR128(t, rkeys[ROUNDS]); 398 | #else 399 | // For x86_64 or a fallback. 400 | t = XOR128(LOAD128(x), rkeys[0]); 401 | for (i = 1; i < ROUNDS; i++) { 402 | t = AES_ENCRYPT(t, rkeys[i]); 403 | } 404 | t = AES_ENCRYPTLAST(t, rkeys[ROUNDS]); 405 | #endif 406 | STORE128(x, t); 407 | } 408 | 409 | /** 410 | * aes_decrypt decrypts a 16-byte block x in-place using the expanded keys in st. 411 | */ 412 | static void 413 | aes_decrypt(uint8_t x[16], const AesState *st) 414 | { 415 | const BlockVec *rkeys = st->rkeys; 416 | InvKeySchedule rkeys_inv; 417 | BlockVec t; 418 | size_t i; 419 | 420 | // Given the purpose of this library, we assume that decryption is not a frequent operation. 421 | for (i = 0; i < ROUNDS - 1; i++) { 422 | rkeys_inv[i] = RKINVERT(rkeys[ROUNDS - 1 - i]); 423 | } 424 | #ifdef AES_XENCRYPT 425 | // AArch64 path with AES_XDECRYPT. 426 | t = AES_XDECRYPT(LOAD128(x), rkeys[ROUNDS]); 427 | for (i = 0; i < ROUNDS - 2; i++) { 428 | t = AES_XDECRYPT(t, rkeys_inv[i]); 429 | } 430 | t = AES_XDECRYPTLAST(t, rkeys_inv[i]); 431 | t = XOR128(t, rkeys[0]); 432 | #else 433 | // x86_64 path using AES_DECRYPT. 434 | t = XOR128(LOAD128(x), rkeys[ROUNDS]); 435 | for (i = 0; i < ROUNDS - 1; i++) { 436 | t = AES_DECRYPT(t, rkeys_inv[i]); 437 | } 438 | t = AES_DECRYPTLAST(t, rkeys[0]); 439 | #endif 440 | STORE128(x, t); 441 | } 442 | 443 | /** 444 | * aes_encrypt_with_tweak encrypts a 16-byte block x with an additional 8-byte tweak. 445 | * The tweak is XORed with each round key. 446 | */ 447 | static void 448 | aes_encrypt_with_tweak(uint8_t x[16], const AesState *st, const uint8_t tweak[IPCRYPT_TWEAKBYTES]) 449 | { 450 | const BlockVec *rkeys = st->rkeys; 451 | const BlockVec tweak_block = TWEAK_EXPAND(tweak); 452 | BlockVec t; 453 | size_t i; 454 | 455 | #ifdef AES_XENCRYPT 456 | // AArch64 path. 457 | t = AES_XENCRYPT(LOAD128(x), XOR128(tweak_block, rkeys[0])); 458 | for (i = 1; i < ROUNDS - 1; i++) { 459 | t = AES_XENCRYPT(t, XOR128(tweak_block, rkeys[i])); 460 | } 461 | t = AES_XENCRYPTLAST(t, XOR128(tweak_block, rkeys[i])); 462 | t = XOR128(t, XOR128(tweak_block, rkeys[ROUNDS])); 463 | #else 464 | // x86_64 path. 465 | t = XOR128_3(LOAD128(x), tweak_block, rkeys[0]); 466 | for (i = 1; i < ROUNDS; i++) { 467 | t = AES_ENCRYPT(t, XOR128(tweak_block, rkeys[i])); 468 | } 469 | t = AES_ENCRYPTLAST(t, XOR128(tweak_block, rkeys[ROUNDS])); 470 | #endif 471 | STORE128(x, t); 472 | } 473 | 474 | /** 475 | * aes_decrypt_with_tweak decrypts a 16-byte block x with an additional 8-byte tweak. 476 | * The same tweak used during encryption must be provided. 477 | */ 478 | static void 479 | aes_decrypt_with_tweak(uint8_t x[16], const AesState *st, const uint8_t tweak[IPCRYPT_TWEAKBYTES]) 480 | { 481 | const BlockVec *rkeys = st->rkeys; 482 | InvKeySchedule rkeys_inv; 483 | const BlockVec tweak_block = TWEAK_EXPAND(tweak); 484 | const BlockVec tweak_block_inv = RKINVERT(tweak_block); 485 | BlockVec t; 486 | size_t i; 487 | 488 | // Given the purpose of this library, we assume that decryption is not a frequent operation. 489 | for (i = 0; i < ROUNDS - 1; i++) { 490 | rkeys_inv[i] = RKINVERT(rkeys[ROUNDS - 1 - i]); 491 | } 492 | #ifdef AES_XENCRYPT 493 | t = AES_XDECRYPT(LOAD128(x), XOR128(tweak_block, rkeys[ROUNDS])); 494 | for (i = 0; i < ROUNDS - 2; i++) { 495 | t = AES_XDECRYPT(t, XOR128(tweak_block_inv, rkeys_inv[i])); 496 | } 497 | t = AES_XDECRYPTLAST(t, XOR128(tweak_block_inv, rkeys_inv[i])); 498 | t = XOR128(t, XOR128(tweak_block, rkeys[0])); 499 | #else 500 | t = XOR128_3(LOAD128(x), tweak_block, rkeys[ROUNDS]); 501 | for (i = 0; i < ROUNDS - 1; i++) { 502 | t = AES_DECRYPT(t, XOR128(tweak_block_inv, rkeys_inv[i])); 503 | } 504 | t = AES_DECRYPTLAST(t, XOR128(tweak_block, rkeys[0])); 505 | #endif 506 | STORE128(x, t); 507 | } 508 | 509 | static BlockVec 510 | aes_xex_tweak(const NDXState *st, const uint8_t tweak[IPCRYPT_NDX_TWEAKBYTES]) 511 | { 512 | const BlockVec *tkeys = st->tkeys; 513 | BlockVec tt; 514 | size_t i; 515 | 516 | COMPILER_ASSERT(IPCRYPT_NDX_TWEAKBYTES == 16); 517 | 518 | #ifdef AES_XENCRYPT 519 | // AArch64 path. 520 | tt = AES_XENCRYPT(LOAD128(tweak), tkeys[0]); 521 | for (i = 1; i < ROUNDS - 1; i++) { 522 | tt = AES_XENCRYPT(tt, tkeys[i]); 523 | } 524 | tt = AES_XENCRYPTLAST(tt, tkeys[i]); 525 | tt = XOR128(tt, tkeys[ROUNDS]); 526 | #else 527 | // x86_64 path. 528 | tt = XOR128(LOAD128(tweak), tkeys[0]); 529 | for (i = 1; i < ROUNDS; i++) { 530 | tt = AES_ENCRYPT(tt, tkeys[i]); 531 | } 532 | tt = AES_ENCRYPTLAST(tt, tkeys[ROUNDS]); 533 | #endif 534 | return tt; 535 | } 536 | 537 | static void 538 | aes_xex_encrypt(uint8_t x[16], const NDXState *st, const uint8_t tweak[IPCRYPT_NDX_TWEAKBYTES]) 539 | { 540 | const BlockVec tt = aes_xex_tweak(st, tweak); 541 | const BlockVec *rkeys = st->rkeys; 542 | BlockVec t; 543 | size_t i; 544 | 545 | COMPILER_ASSERT(IPCRYPT_NDX_TWEAKBYTES == 16); 546 | 547 | #ifdef AES_XENCRYPT 548 | // For AArch64 with AES_XENCRYPT macros. 549 | t = AES_XENCRYPT(XOR128(LOAD128(x), tt), rkeys[0]); 550 | for (i = 1; i < ROUNDS - 1; i++) { 551 | t = AES_XENCRYPT(t, rkeys[i]); 552 | } 553 | t = AES_XENCRYPTLAST(t, rkeys[i]); 554 | t = XOR128_3(t, rkeys[ROUNDS], tt); 555 | #else 556 | // For x86_64 or a fallback. 557 | t = XOR128(XOR128(LOAD128(x), tt), rkeys[0]); 558 | for (i = 1; i < ROUNDS; i++) { 559 | t = AES_ENCRYPT(t, rkeys[i]); 560 | } 561 | t = AES_ENCRYPTLAST(t, XOR128(rkeys[ROUNDS], tt)); 562 | #endif 563 | STORE128(x, t); 564 | } 565 | 566 | static void 567 | aes_ndx_decrypt(uint8_t x[16], const NDXState *st, const uint8_t tweak[IPCRYPT_NDX_TWEAKBYTES]) 568 | { 569 | 570 | const BlockVec tt = aes_xex_tweak(st, tweak); 571 | const BlockVec *rkeys = st->rkeys; 572 | BlockVec t; 573 | size_t i; 574 | 575 | #ifdef AES_XENCRYPT 576 | // AArch64 path with AES_XDECRYPT. 577 | t = AES_XDECRYPT(XOR128(LOAD128(x), tt), rkeys[ROUNDS]); 578 | for (i = ROUNDS - 1; i > 1; i--) { 579 | t = AES_XDECRYPT(t, RKINVERT(rkeys[i])); 580 | } 581 | t = AES_XDECRYPTLAST(t, RKINVERT(rkeys[1])); 582 | t = XOR128_3(t, rkeys[0], tt); 583 | #else 584 | // x86_64 path using AES_DECRYPT. 585 | t = XOR128(XOR128(LOAD128(x), tt), rkeys[ROUNDS]); 586 | for (i = ROUNDS - 1; i > 0; i--) { 587 | t = AES_DECRYPT(t, RKINVERT(rkeys[i])); 588 | } 589 | t = AES_DECRYPTLAST(t, XOR128(rkeys[0], tt)); 590 | #endif 591 | STORE128(x, t); 592 | } 593 | 594 | /** 595 | * bin2hex converts a binary buffer into a lowercase hex string. 596 | * hex: the destination buffer. 597 | * hex_maxlen: maximum capacity of hex. 598 | * bin: source buffer. 599 | * bin_len: length of bin. 600 | * Returns NULL on error, or hex on success. 601 | */ 602 | static char * 603 | bin2hex(char *hex, size_t hex_maxlen, const uint8_t *bin, size_t bin_len) 604 | { 605 | size_t i = (size_t) 0U; 606 | unsigned int x; 607 | int b; 608 | int c; 609 | 610 | // Check buffer limits. 611 | if (bin_len >= SIZE_MAX / 2 || hex_maxlen <= bin_len * 2U) { 612 | return NULL; 613 | } 614 | // Convert each byte to two hex characters. 615 | while (i < bin_len) { 616 | c = bin[i] & 0xf; 617 | b = bin[i] >> 4; 618 | x = (unsigned char) (87U + c + (((c - 10U) >> 8) & ~38U)) << 8 | 619 | (unsigned char) (87U + b + (((b - 10U) >> 8) & ~38U)); 620 | hex[i * 2U] = (char) x; 621 | x >>= 8; 622 | hex[i * 2U + 1U] = (char) x; 623 | i++; 624 | } 625 | // Null-terminate the string. 626 | hex[i * 2U] = 0U; 627 | 628 | return hex; 629 | } 630 | 631 | /** 632 | * hex2bin converts a hex string into a binary buffer. 633 | * bin: destination buffer. 634 | * bin_maxlen: capacity of bin. 635 | * hex: source string. 636 | * hex_len: length of the hex string. 637 | * Returns the number of bytes written on success, or 0 on error. 638 | */ 639 | static size_t 640 | hex2bin(uint8_t *bin, size_t bin_maxlen, const char *hex, size_t hex_len) 641 | { 642 | const size_t bin_len = hex_len / 2U; 643 | size_t i; 644 | 645 | // Must have an even length and fit the destination. 646 | if (hex_len % 2U != 0 || bin_len > bin_maxlen) { 647 | return 0U; 648 | } 649 | for (i = 0; i < bin_len; i++) { 650 | unsigned char c = (unsigned char) hex[i * 2U]; 651 | unsigned char b = (unsigned char) hex[i * 2U + 1U]; 652 | // Convert from ASCII to nibble. 653 | if (c >= '0' && c <= '9') { 654 | c -= '0'; 655 | } else if (c >= 'a' && c <= 'f') { 656 | c -= 'a' - 10; 657 | } else { 658 | return 0U; 659 | } 660 | if (b >= '0' && b <= '9') { 661 | b -= '0'; 662 | } else if (b >= 'a' && b <= 'f') { 663 | b -= 'a' - 10; 664 | } else { 665 | return 0U; 666 | } 667 | bin[i] = ((uint8_t) c << 4) | b; 668 | } 669 | return bin_len; 670 | } 671 | 672 | /** 673 | * ipcrypt_zeroize securely zeroes a memory region of length len starting at pnt. 674 | * This function attempts to prevent the compiler from optimizing away the zeroing. 675 | */ 676 | static void 677 | ipcrypt_zeroize(void *pnt, size_t len) 678 | { 679 | #ifdef HAVE_EXPLICIT_BZERO 680 | explicit_bzero(pnt, len); 681 | #elif defined(_MSC_VER) 682 | SecureZeroMemory(pnt, len); 683 | #elif defined(__STDC_LIB_EXT1__) 684 | memset_s(pnt, len, 0, len); 685 | #elif defined(__GNUC__) || defined(__clang__) 686 | memset(pnt, 0, len); 687 | // Compiler barrier to prevent optimizations from removing memset. 688 | __asm__ __volatile__("" : : "r"(pnt) : "memory"); 689 | #else 690 | volatile unsigned char *volatile pnt_ = (volatile unsigned char *volatile) pnt; 691 | size_t i = (size_t) 0U; 692 | while (i < len) { 693 | pnt_[i++] = 0U; 694 | } 695 | #endif 696 | } 697 | 698 | /** 699 | * Convert a hexadecimal string to a secret key. 700 | * 701 | * The input string must be exactly 32 or 64 characters long (IPCRYPT_KEYBYTES or 702 | * IPCRYPT_NDX_KEYBYTES bytes in hex). Returns 0 on success, or -1 if the input string is invalid or 703 | * conversion fails. 704 | */ 705 | int 706 | ipcrypt_key_from_hex(uint8_t *key, size_t key_len, const char *hex, size_t hex_len) 707 | { 708 | if (hex_len != 2 * IPCRYPT_KEYBYTES && hex_len != 2 * IPCRYPT_NDX_KEYBYTES) { 709 | return -1; 710 | } 711 | if (hex2bin(key, key_len, hex, hex_len) != key_len) { 712 | return -1; 713 | } 714 | return 0; 715 | } 716 | 717 | /** 718 | * Convert a hexadecimal string to an ipcrypt-nd ciphertext. 719 | * 720 | * The input string must be exactly 48 characters long (IPCRYPT_NDIP_BYTES bytes in hex). 721 | * Returns 0 on success, or -1 if the input string is invalid or conversion fails. 722 | */ 723 | int 724 | ipcrypt_ndip_from_hex(uint8_t ndip[IPCRYPT_NDIP_BYTES], const char *hex, size_t hex_len) 725 | { 726 | if (hex_len != 2 * IPCRYPT_NDIP_BYTES) { 727 | return -1; 728 | } 729 | if (hex2bin(ndip, IPCRYPT_NDIP_BYTES, hex, hex_len) != IPCRYPT_NDIP_BYTES) { 730 | return -1; 731 | } 732 | return 0; 733 | } 734 | 735 | /** 736 | * Convert a hexadecimal string to an ipcrypt-ndx ciphertext. 737 | * 738 | * The input string must be exactly 64 characters long (IPCRYPT_NDX_NDIP_BYTES bytes in hex). 739 | * Returns 0 on success, or -1 if the input string is invalid or conversion fails. 740 | */ 741 | int 742 | ipcrypt_ndx_ndip_from_hex(uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES], const char *hex, size_t hex_len) 743 | { 744 | if (hex_len != 2 * IPCRYPT_NDX_NDIP_BYTES) { 745 | return -1; 746 | } 747 | if (hex2bin(ndip, IPCRYPT_NDX_NDIP_BYTES, hex, hex_len) != IPCRYPT_NDX_NDIP_BYTES) { 748 | return -1; 749 | } 750 | return 0; 751 | } 752 | 753 | /** 754 | * ipcrypt_str_to_ip16 parses an IP address string (IPv4 or IPv6) into a 16-byte buffer ip16. 755 | * If it detects an IPv4 address, it is stored as an IPv4-mapped IPv6 address. 756 | * Returns 0 on success, or -1 on failure. 757 | */ 758 | int 759 | ipcrypt_str_to_ip16(uint8_t ip16[16], const char *ip_str) 760 | { 761 | struct in6_addr addr6; 762 | struct in_addr addr4; 763 | 764 | // Try parsing as IPv6. 765 | if (inet_pton(AF_INET6, ip_str, &addr6) == 1) { 766 | memcpy(ip16, &addr6, 16); 767 | return 0; 768 | } 769 | // Try parsing as IPv4. 770 | if (inet_pton(AF_INET, ip_str, &addr4) == 1) { 771 | memset(ip16, 0, 16); 772 | ip16[10] = 0xff; 773 | ip16[11] = 0xff; 774 | memcpy(ip16 + 12, &addr4, 4); 775 | return 0; 776 | } 777 | return -1; // Parsing failed. 778 | } 779 | 780 | /** 781 | * ipcrypt_ip16_to_str converts a 16-byte buffer ip16 into its string representation (IPv4 or IPv6). 782 | * If the buffer holds an IPv4-mapped address, it returns an IPv4 string. 783 | * Returns the length of the resulting string on success, or 0 on error. 784 | */ 785 | size_t 786 | ipcrypt_ip16_to_str(char ip_str[IPCRYPT_MAX_IP_STR_BYTES], const uint8_t ip16[16]) 787 | { 788 | int is_ipv4_mapped = 1; 789 | size_t i; 790 | 791 | COMPILER_ASSERT(IPCRYPT_MAX_IP_STR_BYTES >= 46U); 792 | 793 | // Check whether it's an IPv4-mapped IPv6 address (::ffff:x.x.x.x). 794 | for (i = 0; i < 10; i++) { 795 | if (ip16[i] != 0) { 796 | is_ipv4_mapped = 0; 797 | break; 798 | } 799 | } 800 | if (is_ipv4_mapped && (ip16[10] != 0xff || ip16[11] != 0xff)) { 801 | is_ipv4_mapped = 0; 802 | } 803 | // If IPv4-mapped, convert to IPv4 string. 804 | if (is_ipv4_mapped) { 805 | struct in_addr addr4; 806 | memcpy(&addr4, ip16 + 12, 4); 807 | if (inet_ntop(AF_INET, &addr4, ip_str, INET_ADDRSTRLEN) == NULL) { 808 | return 0; 809 | } 810 | } else { 811 | // Otherwise, treat as IPv6. 812 | struct in6_addr addr6; 813 | memcpy(&addr6, ip16, 16); 814 | if (inet_ntop(AF_INET6, &addr6, ip_str, INET6_ADDRSTRLEN) == NULL) { 815 | return 0; 816 | } 817 | } 818 | return strlen(ip_str); 819 | } 820 | 821 | /** 822 | * Convert a socket address structure to a 16-byte binary IP representation. 823 | * 824 | * Supports both IPv4 (AF_INET) and IPv6 (AF_INET6) socket addresses. 825 | * For IPv4 addresses, they are converted to IPv4-mapped IPv6 format. 826 | * 827 | * Returns 0 on success, or -1 if the address family is not supported. 828 | */ 829 | int 830 | ipcrypt_sockaddr_to_ip16(uint8_t ip16[16], const struct sockaddr *sa) 831 | { 832 | if (sa->sa_family == AF_INET) { 833 | const struct sockaddr_in *s = (const struct sockaddr_in *) sa; 834 | memset(ip16, 0, 10); 835 | ip16[10] = 0xff; 836 | ip16[11] = 0xff; 837 | memcpy(ip16 + 12, &s->sin_addr, 4); 838 | return 0; 839 | } else if (sa->sa_family == AF_INET6) { 840 | const struct sockaddr_in6 *s = (const struct sockaddr_in6 *) sa; 841 | memcpy(ip16, &s->sin6_addr, 16); 842 | return 0; 843 | } 844 | return -1; 845 | } 846 | 847 | /** 848 | * Convert a 16-byte binary IP address to a socket address structure. 849 | * 850 | * The socket address structure is populated based on the IP format: 851 | * - For IPv4-mapped IPv6 addresses, an IPv4 socket address is created 852 | * - For other IPv6 addresses, an IPv6 socket address is created 853 | * 854 | * The provided sockaddr_storage structure is guaranteed to be large enough 855 | * to hold any socket address type. 856 | */ 857 | void 858 | ipcrypt_ip16_to_sockaddr(struct sockaddr_storage *sa, const uint8_t ip16[16]) 859 | { 860 | const uint8_t ipv4_mapped[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; 861 | 862 | memset(sa, 0, sizeof *sa); 863 | if (memcmp(ip16, ipv4_mapped, sizeof ipv4_mapped) == 0) { 864 | struct sockaddr_in *s = (struct sockaddr_in *) sa; 865 | s->sin_family = AF_INET; 866 | memcpy(&s->sin_addr, ip16 + 12, 4); 867 | #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ 868 | defined(__DragonFly__) 869 | s->sin_len = sizeof *s; 870 | #endif 871 | } else { 872 | struct sockaddr_in6 *s = (struct sockaddr_in6 *) sa; 873 | s->sin6_family = AF_INET6; 874 | memcpy(&s->sin6_addr, ip16, 16); 875 | #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ 876 | defined(__DragonFly__) 877 | s->sin6_len = sizeof *s; 878 | #endif 879 | } 880 | } 881 | 882 | /** 883 | * ipcrypt_init initializes an IPCrypt context with a 16-byte key. 884 | * Expands the key into round keys and stores them in ipcrypt->opaque. 885 | */ 886 | void 887 | ipcrypt_init(IPCrypt *ipcrypt, const uint8_t key[IPCRYPT_KEYBYTES]) 888 | { 889 | AesState st; 890 | 891 | expand_key(st.rkeys, key); 892 | COMPILER_ASSERT(sizeof ipcrypt->opaque >= sizeof st); 893 | memcpy(ipcrypt->opaque, &st, sizeof st); 894 | } 895 | 896 | /** 897 | * ipcrypt_deinit clears the IPCrypt context to wipe sensitive data from memory. 898 | */ 899 | void 900 | ipcrypt_deinit(IPCrypt *ipcrypt) 901 | { 902 | ipcrypt_zeroize(ipcrypt, sizeof *ipcrypt); 903 | } 904 | 905 | /** 906 | * ipcrypt_pfx_init initializes the IPCryptPFX context with a 32-byte secret key. 907 | * This prepares the context for prefix-preserving IP address encryption operations. 908 | * Returns 0 on success. 909 | */ 910 | int 911 | ipcrypt_pfx_init(IPCryptPFX *ipcrypt, const uint8_t key[IPCRYPT_PFX_KEYBYTES]) 912 | { 913 | PFXState st; 914 | uint8_t diff[16]; 915 | size_t i; 916 | uint8_t d; 917 | 918 | expand_key(st.k1keys, key); 919 | expand_key(st.k2keys, key + 16); 920 | 921 | /** 922 | * Ensure the two keys differ in case of misuse. 923 | */ 924 | STORE128(diff, XOR128(st.k1keys[ROUNDS / 2], st.k2keys[ROUNDS / 2])); 925 | d = 0; 926 | for (i = 0; i < 16; i++) { 927 | d |= diff[i]; 928 | } 929 | if (d == 0) { 930 | for (i = 0; i < 16; i++) { 931 | diff[i] = key[i] ^ 0x5a; 932 | } 933 | expand_key(st.k2keys, diff); 934 | } 935 | 936 | COMPILER_ASSERT(sizeof ipcrypt->opaque >= sizeof st); 937 | memcpy(ipcrypt->opaque, &st, sizeof st); 938 | 939 | return -(d == 0); 940 | } 941 | 942 | /** 943 | * ipcrypt_pfx_deinit securely clears and deinitializes the IPCryptPFX context. 944 | * This ensures that secret key material is wiped from memory. 945 | */ 946 | void 947 | ipcrypt_pfx_deinit(IPCryptPFX *ipcrypt) 948 | { 949 | ipcrypt_zeroize(ipcrypt, sizeof *ipcrypt); 950 | } 951 | 952 | static int 953 | ipcrypt_is_mapped_ipv4(const uint8_t ip16[16]) 954 | { 955 | static const uint8_t ipv4_mapped[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; 956 | return memcmp(ip16, ipv4_mapped, sizeof ipv4_mapped) == 0; 957 | } 958 | 959 | static void 960 | ipcrypt_pfx_pad_prefix(uint8_t padded_prefix[16], unsigned int prefix_len_bits) 961 | { 962 | memset(padded_prefix, 0, 16); 963 | if (prefix_len_bits == 0) { 964 | padded_prefix[15] = 0x01; 965 | } else { 966 | padded_prefix[3] = 0x01; 967 | padded_prefix[14] = 0xff; 968 | padded_prefix[15] = 0xff; 969 | } 970 | } 971 | 972 | static uint8_t 973 | ipcrypt_pfx_get_bit(const uint8_t ip16[16], const unsigned int bit_index) 974 | { 975 | return (ip16[15 - bit_index / 8] >> (bit_index % 8)) & 1; 976 | } 977 | 978 | static void 979 | ipcrypt_pfx_set_bit(uint8_t ip16[16], const unsigned int bit_index, const uint8_t bit_value) 980 | { 981 | if (bit_value) { 982 | ip16[15 - bit_index / 8] |= (1 << (bit_index % 8)); 983 | } else { 984 | ip16[15 - bit_index / 8] &= ~(1 << (bit_index % 8)); 985 | } 986 | } 987 | 988 | static void 989 | ipcrypt_pfx_shift_left(uint8_t ip16[16]) 990 | { 991 | #ifdef STANDARD 992 | size_t i; 993 | 994 | for (i = 0; i < 15; i++) { 995 | ip16[i] = (ip16[i] << 1) | (ip16[i + 1] >> 7); 996 | } 997 | ip16[15] <<= 1; 998 | #else 999 | BlockVec v = LOAD128(ip16); 1000 | v = SHL1_128(v); 1001 | STORE128(ip16, v); 1002 | #endif 1003 | } 1004 | 1005 | /** 1006 | * ipcrypt_pfx_encrypt_ip16 encrypts a 16-byte IP address in-place with prefix preservation. 1007 | * IP addresses with the same prefix produce encrypted IP addresses with the same prefix. 1008 | * The prefix can be of any length. For IPv4 addresses (stored as IPv4-mapped IPv6), 1009 | * this preserves the IPv4 prefix structure. 1010 | */ 1011 | void 1012 | ipcrypt_pfx_encrypt_ip16(const IPCryptPFX *ipcrypt, uint8_t ip16[16]) 1013 | { 1014 | PFXState st; 1015 | BlockVec e1_0, e2_0, e_0, e1_1, e2_1, e_1; 1016 | uint8_t encrypted_ip[16]; 1017 | uint8_t padded_prefix_0[16], padded_prefix_1[16]; 1018 | uint8_t t_0[16], t_1[16]; 1019 | size_t i; 1020 | unsigned int bit_pos_0, bit_pos_1; 1021 | unsigned int prefix_start = 0; 1022 | unsigned int prefix_len_bits; 1023 | uint8_t cipher_bit_0, cipher_bit_1; 1024 | uint8_t original_bit_0, original_bit_1; 1025 | 1026 | memcpy(&st, ipcrypt->opaque, sizeof st); 1027 | if (ipcrypt_is_mapped_ipv4(ip16)) { 1028 | prefix_start = 96; 1029 | } 1030 | 1031 | ipcrypt_pfx_pad_prefix(padded_prefix_0, prefix_start); 1032 | 1033 | memset(encrypted_ip, 0, 16); 1034 | if (prefix_start == 96) { 1035 | encrypted_ip[10] = 0xff; 1036 | encrypted_ip[11] = 0xff; 1037 | } 1038 | 1039 | // Process two bits per iteration for better parallelism 1040 | for (prefix_len_bits = prefix_start; prefix_len_bits < 128; prefix_len_bits += 2) { 1041 | // Prepare padded_prefix_1 for the second iteration 1042 | memcpy(padded_prefix_1, padded_prefix_0, 16); 1043 | bit_pos_0 = 127 - prefix_len_bits; 1044 | original_bit_0 = ipcrypt_pfx_get_bit(ip16, bit_pos_0); 1045 | ipcrypt_pfx_shift_left(padded_prefix_1); 1046 | ipcrypt_pfx_set_bit(padded_prefix_1, 0, original_bit_0); 1047 | 1048 | #ifdef AES_XENCRYPT 1049 | // For AArch64 with AES_XENCRYPT macros - process two encryptions in parallel 1050 | e1_0 = AES_XENCRYPT(LOAD128(padded_prefix_0), st.k1keys[0]); 1051 | e2_0 = AES_XENCRYPT(LOAD128(padded_prefix_0), st.k2keys[0]); 1052 | e1_1 = AES_XENCRYPT(LOAD128(padded_prefix_1), st.k1keys[0]); 1053 | e2_1 = AES_XENCRYPT(LOAD128(padded_prefix_1), st.k2keys[0]); 1054 | 1055 | for (i = 1; i < ROUNDS - 1; i++) { 1056 | e1_0 = AES_XENCRYPT(e1_0, st.k1keys[i]); 1057 | e2_0 = AES_XENCRYPT(e2_0, st.k2keys[i]); 1058 | e1_1 = AES_XENCRYPT(e1_1, st.k1keys[i]); 1059 | e2_1 = AES_XENCRYPT(e2_1, st.k2keys[i]); 1060 | } 1061 | 1062 | e1_0 = AES_XENCRYPTLAST(e1_0, st.k1keys[i]); 1063 | e2_0 = AES_XENCRYPTLAST(e2_0, st.k2keys[i]); 1064 | e1_1 = AES_XENCRYPTLAST(e1_1, st.k1keys[i]); 1065 | e2_1 = AES_XENCRYPTLAST(e2_1, st.k2keys[i]); 1066 | 1067 | e1_0 = XOR128(e1_0, st.k1keys[ROUNDS]); 1068 | e2_0 = XOR128(e2_0, st.k2keys[ROUNDS]); 1069 | e1_1 = XOR128(e1_1, st.k1keys[ROUNDS]); 1070 | e2_1 = XOR128(e2_1, st.k2keys[ROUNDS]); 1071 | #else 1072 | // For x86_64 or a fallback - process two encryptions in parallel 1073 | e1_0 = XOR128(LOAD128(padded_prefix_0), st.k1keys[0]); 1074 | e2_0 = XOR128(LOAD128(padded_prefix_0), st.k2keys[0]); 1075 | e1_1 = XOR128(LOAD128(padded_prefix_1), st.k1keys[0]); 1076 | e2_1 = XOR128(LOAD128(padded_prefix_1), st.k2keys[0]); 1077 | 1078 | for (i = 1; i < ROUNDS; i++) { 1079 | e1_0 = AES_ENCRYPT(e1_0, st.k1keys[i]); 1080 | e2_0 = AES_ENCRYPT(e2_0, st.k2keys[i]); 1081 | e1_1 = AES_ENCRYPT(e1_1, st.k1keys[i]); 1082 | e2_1 = AES_ENCRYPT(e2_1, st.k2keys[i]); 1083 | } 1084 | 1085 | e1_0 = AES_ENCRYPTLAST(e1_0, st.k1keys[ROUNDS]); 1086 | e2_0 = AES_ENCRYPTLAST(e2_0, st.k2keys[ROUNDS]); 1087 | e1_1 = AES_ENCRYPTLAST(e1_1, st.k1keys[ROUNDS]); 1088 | e2_1 = AES_ENCRYPTLAST(e2_1, st.k2keys[ROUNDS]); 1089 | #endif 1090 | 1091 | // Process results for first bit 1092 | e_0 = XOR128(e1_0, e2_0); 1093 | STORE128(t_0, e_0); 1094 | cipher_bit_0 = t_0[15] & 1; 1095 | 1096 | // Process results for second bit 1097 | e_1 = XOR128(e1_1, e2_1); 1098 | STORE128(t_1, e_1); 1099 | cipher_bit_1 = t_1[15] & 1; 1100 | bit_pos_1 = bit_pos_0 - 1; 1101 | original_bit_1 = ipcrypt_pfx_get_bit(ip16, bit_pos_1); 1102 | 1103 | ipcrypt_pfx_set_bit(encrypted_ip, bit_pos_0, original_bit_0 ^ cipher_bit_0); 1104 | ipcrypt_pfx_set_bit(encrypted_ip, bit_pos_1, original_bit_1 ^ cipher_bit_1); 1105 | 1106 | // Update padded_prefix_0 for next iteration 1107 | ipcrypt_pfx_shift_left(padded_prefix_1); 1108 | ipcrypt_pfx_set_bit(padded_prefix_1, 0, original_bit_1); 1109 | memcpy(padded_prefix_0, padded_prefix_1, 16); 1110 | } 1111 | memcpy(ip16, encrypted_ip, 16); 1112 | } 1113 | 1114 | /** 1115 | * ipcrypt_pfx_decrypt_ip16 decrypts a 16-byte IP address in-place with prefix preservation. 1116 | * This reverses the encryption performed by ipcrypt_pfx_encrypt_ip16, recovering the original IP 1117 | * address. 1118 | */ 1119 | void 1120 | ipcrypt_pfx_decrypt_ip16(const IPCryptPFX *ipcrypt, uint8_t ip16[16]) 1121 | { 1122 | PFXState st; 1123 | BlockVec e1, e2, e; 1124 | uint8_t original_ip[16]; 1125 | uint8_t padded_prefix[16]; 1126 | uint8_t t[16]; 1127 | size_t i; 1128 | unsigned int bit_pos; 1129 | unsigned int prefix_start = 0; 1130 | unsigned int prefix_len_bits; 1131 | uint8_t cipher_bit; 1132 | uint8_t encrypted_bit; 1133 | uint8_t original_bit; 1134 | 1135 | memcpy(&st, ipcrypt->opaque, sizeof st); 1136 | if (ipcrypt_is_mapped_ipv4(ip16)) { 1137 | prefix_start = 96; 1138 | } 1139 | 1140 | ipcrypt_pfx_pad_prefix(padded_prefix, prefix_start); 1141 | 1142 | memset(original_ip, 0, 16); 1143 | if (prefix_start == 96) { 1144 | original_ip[10] = 0xff; 1145 | original_ip[11] = 0xff; 1146 | } 1147 | 1148 | for (prefix_len_bits = prefix_start; prefix_len_bits < 128; prefix_len_bits++) { 1149 | #ifdef AES_XENCRYPT 1150 | // For AArch64 with AES_XENCRYPT macros. 1151 | e1 = AES_XENCRYPT(LOAD128(padded_prefix), st.k1keys[0]); 1152 | e2 = AES_XENCRYPT(LOAD128(padded_prefix), st.k2keys[0]); 1153 | for (i = 1; i < ROUNDS - 1; i++) { 1154 | e1 = AES_XENCRYPT(e1, st.k1keys[i]); 1155 | e2 = AES_XENCRYPT(e2, st.k2keys[i]); 1156 | } 1157 | e1 = AES_XENCRYPTLAST(e1, st.k1keys[i]); 1158 | e2 = AES_XENCRYPTLAST(e2, st.k2keys[i]); 1159 | e1 = XOR128(e1, st.k1keys[ROUNDS]); 1160 | e2 = XOR128(e2, st.k2keys[ROUNDS]); 1161 | #else 1162 | // For x86_64 or a fallback. 1163 | e1 = XOR128(LOAD128(padded_prefix), st.k1keys[0]); 1164 | e2 = XOR128(LOAD128(padded_prefix), st.k2keys[0]); 1165 | for (i = 1; i < ROUNDS; i++) { 1166 | e1 = AES_ENCRYPT(e1, st.k1keys[i]); 1167 | e2 = AES_ENCRYPT(e2, st.k2keys[i]); 1168 | } 1169 | e1 = AES_ENCRYPTLAST(e1, st.k1keys[ROUNDS]); 1170 | e2 = AES_ENCRYPTLAST(e2, st.k2keys[ROUNDS]); 1171 | #endif 1172 | e = XOR128(e1, e2); 1173 | STORE128(t, e); 1174 | cipher_bit = t[15] & 1; 1175 | bit_pos = 127 - prefix_len_bits; 1176 | encrypted_bit = ipcrypt_pfx_get_bit(ip16, bit_pos); 1177 | original_bit = encrypted_bit ^ cipher_bit; 1178 | ipcrypt_pfx_set_bit(original_ip, bit_pos, original_bit); 1179 | ipcrypt_pfx_shift_left(padded_prefix); 1180 | ipcrypt_pfx_set_bit(padded_prefix, 0, original_bit); 1181 | } 1182 | memcpy(ip16, original_ip, 16); 1183 | } 1184 | 1185 | /** 1186 | * ipcrypt_pfx_encrypt_ip_str encrypts an IP address string (IPv4 or IPv6) with prefix preservation. 1187 | * The result is another valid IP address string. 1188 | * Returns the length of the encrypted string or 0 on error. 1189 | */ 1190 | size_t 1191 | ipcrypt_pfx_encrypt_ip_str(const IPCryptPFX *ipcrypt, 1192 | char encrypted_ip_str[IPCRYPT_MAX_IP_STR_BYTES], 1193 | const char *ip_str) 1194 | { 1195 | uint8_t ip16[16]; 1196 | 1197 | if (ipcrypt_str_to_ip16(ip16, ip_str) != 0) { 1198 | return 0; 1199 | } 1200 | ipcrypt_pfx_encrypt_ip16(ipcrypt, ip16); 1201 | return ipcrypt_ip16_to_str(encrypted_ip_str, ip16); 1202 | } 1203 | 1204 | /** 1205 | * ipcrypt_pfx_decrypt_ip_str decrypts an encrypted IP address string with prefix preservation. 1206 | * Returns the length of the decrypted string or 0 on error. 1207 | */ 1208 | size_t 1209 | ipcrypt_pfx_decrypt_ip_str(const IPCryptPFX *ipcrypt, 1210 | char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 1211 | const char *encrypted_ip_str) 1212 | { 1213 | uint8_t ip16[16]; 1214 | 1215 | memset(ip_str, 0, IPCRYPT_MAX_IP_STR_BYTES); 1216 | if (ipcrypt_str_to_ip16(ip16, encrypted_ip_str) != 0) { 1217 | return 0; 1218 | } 1219 | ipcrypt_pfx_decrypt_ip16(ipcrypt, ip16); 1220 | return ipcrypt_ip16_to_str(ip_str, ip16); 1221 | } 1222 | 1223 | /** 1224 | * ipcrypt_init initializes an IPCrypt context with a 16-byte key. 1225 | * Expands the key into round keys and stores them in ipcrypt->opaque. 1226 | * Returns 0 on success. 1227 | */ 1228 | int 1229 | ipcrypt_ndx_init(IPCryptNDX *ipcrypt, const uint8_t key[IPCRYPT_NDX_KEYBYTES]) 1230 | { 1231 | NDXState st; 1232 | uint8_t diff[16]; 1233 | size_t i; 1234 | uint8_t d; 1235 | 1236 | expand_key(st.tkeys, key + 16); 1237 | expand_key(st.rkeys, key); 1238 | 1239 | /** 1240 | * Ensure the two keys differ in case of misuse. 1241 | */ 1242 | STORE128(diff, XOR128(st.tkeys[ROUNDS / 2], st.rkeys[ROUNDS / 2])); 1243 | d = 0; 1244 | for (i = 0; i < 16; i++) { 1245 | d |= diff[i]; 1246 | } 1247 | if (d == 0) { 1248 | for (i = 0; i < 16; i++) { 1249 | diff[i] = key[i] ^ 0x5a; 1250 | } 1251 | expand_key(st.rkeys, diff); 1252 | } 1253 | 1254 | COMPILER_ASSERT(sizeof ipcrypt->opaque >= sizeof st); 1255 | memcpy(ipcrypt->opaque, &st, sizeof st); 1256 | 1257 | return -(d == 0); 1258 | } 1259 | 1260 | /** 1261 | * ipcrypt_deinit clears the IPCrypt context to wipe sensitive data from memory. 1262 | */ 1263 | void 1264 | ipcrypt_ndx_deinit(IPCryptNDX *ipcrypt) 1265 | { 1266 | ipcrypt_zeroize(ipcrypt, sizeof *ipcrypt); 1267 | } 1268 | 1269 | /** 1270 | * ipcrypt_encrypt_ip16 performs format-preserving encryption on a 16-byte IP buffer. 1271 | * Encrypted data is stored in-place. 1272 | */ 1273 | void 1274 | ipcrypt_encrypt_ip16(const IPCrypt *ipcrypt, uint8_t ip16[16]) 1275 | { 1276 | AesState st; 1277 | memcpy(&st, ipcrypt->opaque, sizeof st); 1278 | aes_encrypt(ip16, &st); 1279 | } 1280 | 1281 | /** 1282 | * ipcrypt_decrypt_ip16 performs format-preserving decryption on a 16-byte IP buffer. 1283 | * Decrypted data is stored in-place. 1284 | */ 1285 | void 1286 | ipcrypt_decrypt_ip16(const IPCrypt *ipcrypt, uint8_t ip16[16]) 1287 | { 1288 | AesState st; 1289 | memcpy(&st, ipcrypt->opaque, sizeof st); 1290 | aes_decrypt(ip16, &st); 1291 | } 1292 | 1293 | /** 1294 | * ipcrypt_encrypt_ip_str encrypts an IP address string (IPv4 or IPv6) in a format-preserving way. 1295 | * The result is another valid IP address string. 1296 | * Returns the length of the encrypted string or 0 on error. 1297 | */ 1298 | size_t 1299 | ipcrypt_encrypt_ip_str(const IPCrypt *ipcrypt, char encrypted_ip_str[IPCRYPT_MAX_IP_STR_BYTES], 1300 | const char *ip_str) 1301 | { 1302 | uint8_t ip16[16]; 1303 | 1304 | if (ipcrypt_str_to_ip16(ip16, ip_str) != 0) { 1305 | return 0; 1306 | } 1307 | ipcrypt_encrypt_ip16(ipcrypt, ip16); 1308 | return ipcrypt_ip16_to_str(encrypted_ip_str, ip16); 1309 | } 1310 | 1311 | /** 1312 | * ipcrypt_decrypt_ip_str decrypts an encrypted IP address string and restores the original address. 1313 | * Returns the length of the decrypted string or 0 on error. 1314 | */ 1315 | size_t 1316 | ipcrypt_decrypt_ip_str(const IPCrypt *ipcrypt, char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 1317 | const char *encrypted_ip_str) 1318 | { 1319 | uint8_t ip16[16]; 1320 | 1321 | memset(ip_str, 0, IPCRYPT_MAX_IP_STR_BYTES); 1322 | if (ipcrypt_str_to_ip16(ip16, encrypted_ip_str) != 0) { 1323 | return 0; 1324 | } 1325 | ipcrypt_decrypt_ip16(ipcrypt, ip16); 1326 | return ipcrypt_ip16_to_str(ip_str, ip16); 1327 | } 1328 | 1329 | /** 1330 | * ipcrypt_nd_encrypt_ip16 performs non-deterministic encryption of a 16-byte IP. 1331 | * A random 8-byte tweak (random) must be provided. 1332 | * Output is 24 bytes: the tweak + the encrypted IP. 1333 | */ 1334 | void 1335 | ipcrypt_nd_encrypt_ip16(const IPCrypt *ipcrypt, uint8_t ndip[IPCRYPT_NDIP_BYTES], 1336 | const uint8_t ip16[16], const uint8_t random[IPCRYPT_TWEAKBYTES]) 1337 | { 1338 | AesState st; 1339 | 1340 | COMPILER_ASSERT(IPCRYPT_NDIP_BYTES == 16 + IPCRYPT_TWEAKBYTES); 1341 | memcpy(&st, ipcrypt->opaque, sizeof st); 1342 | // Copy the tweak into the first 8 bytes. 1343 | memcpy(ndip, random, IPCRYPT_TWEAKBYTES); 1344 | // Copy the IP into the next 16 bytes. 1345 | memcpy(ndip + IPCRYPT_TWEAKBYTES, ip16, 16); 1346 | // Encrypt the IP portion with the tweak. 1347 | aes_encrypt_with_tweak(ndip + IPCRYPT_TWEAKBYTES, &st, random); 1348 | } 1349 | 1350 | /** 1351 | * ipcrypt_nd_decrypt_ip16 decrypts a 24-byte (tweak + IP) buffer produced by 1352 | * ipcrypt_nd_encrypt_ip16. The original IP is restored in ip16. 1353 | */ 1354 | void 1355 | ipcrypt_nd_decrypt_ip16(const IPCrypt *ipcrypt, uint8_t ip16[16], 1356 | const uint8_t ndip[IPCRYPT_NDIP_BYTES]) 1357 | { 1358 | AesState st; 1359 | 1360 | COMPILER_ASSERT(IPCRYPT_NDIP_BYTES == 16 + IPCRYPT_TWEAKBYTES); 1361 | memcpy(&st, ipcrypt->opaque, sizeof st); 1362 | // Copy the IP portion from ndip. 1363 | memcpy(ip16, ndip + IPCRYPT_TWEAKBYTES, 16); 1364 | // Decrypt using the tweak from the first 8 bytes. 1365 | aes_decrypt_with_tweak(ip16, &st, ndip); 1366 | } 1367 | 1368 | /** 1369 | * ipcrypt_nd_encrypt_ip_str encrypts an IP address string in non-deterministic mode. 1370 | * The output is a hex-encoded string of length IPCRYPT_NDIP_STR_BYTES (48 hex chars + null 1371 | * terminator). random must be an 8-byte random value. 1372 | */ 1373 | size_t 1374 | ipcrypt_nd_encrypt_ip_str(const IPCrypt *ipcrypt, char encrypted_ip_str[IPCRYPT_NDIP_STR_BYTES], 1375 | const char *ip_str, const uint8_t random[IPCRYPT_TWEAKBYTES]) 1376 | { 1377 | uint8_t ip16[16]; 1378 | uint8_t ndip[IPCRYPT_NDIP_BYTES]; 1379 | 1380 | COMPILER_ASSERT(IPCRYPT_NDIP_STR_BYTES == IPCRYPT_NDIP_BYTES * 2 + 1); 1381 | // Convert to 16-byte IP. 1382 | ipcrypt_str_to_ip16(ip16, ip_str); 1383 | // Perform non-deterministic encryption. 1384 | ipcrypt_nd_encrypt_ip16(ipcrypt, ndip, ip16, random); 1385 | // Convert the 24-byte ndip to a hex string. 1386 | bin2hex(encrypted_ip_str, IPCRYPT_NDIP_STR_BYTES, ndip, IPCRYPT_NDIP_BYTES); 1387 | 1388 | return IPCRYPT_NDIP_STR_BYTES - 1; 1389 | } 1390 | 1391 | /** 1392 | * ipcrypt_nd_decrypt_ip_str decrypts a hex-encoded string produced by ipcrypt_nd_encrypt_ip_str. 1393 | * The original IP address string is written to ip_str. 1394 | * Returns the length of the resulting IP string on success, or 0 on error. 1395 | */ 1396 | size_t 1397 | ipcrypt_nd_decrypt_ip_str(const IPCrypt *ipcrypt, char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 1398 | const char *encrypted_ip_str) 1399 | { 1400 | uint8_t ip16[16]; 1401 | uint8_t ndip[IPCRYPT_NDIP_BYTES]; 1402 | memset(ip_str, 0, IPCRYPT_MAX_IP_STR_BYTES); 1403 | // Convert the hex string back to a 24-byte buffer. 1404 | if (hex2bin(ndip, sizeof ndip, encrypted_ip_str, strlen(encrypted_ip_str)) != sizeof ndip) { 1405 | return 0; 1406 | } 1407 | // Decrypt the IP. 1408 | ipcrypt_nd_decrypt_ip16(ipcrypt, ip16, ndip); 1409 | // Convert binary IP to string. 1410 | return ipcrypt_ip16_to_str(ip_str, ip16); 1411 | } 1412 | 1413 | /** 1414 | * ipcrypt_ndx_encrypt_ip16 performs non-deterministic encryption of a 16-byte IP. 1415 | * A random 16-byte tweak (random) must be provided. 1416 | * Output is 32 bytes: the tweak + the encrypted IP. 1417 | */ 1418 | void 1419 | ipcrypt_ndx_encrypt_ip16(const IPCryptNDX *ipcrypt, uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES], 1420 | const uint8_t ip16[16], const uint8_t random[IPCRYPT_NDX_TWEAKBYTES]) 1421 | { 1422 | NDXState st; 1423 | 1424 | COMPILER_ASSERT(IPCRYPT_NDX_NDIP_BYTES == 16 + IPCRYPT_NDX_TWEAKBYTES); 1425 | memcpy(&st, ipcrypt->opaque, sizeof st); 1426 | // Copy the tweak into the first 8 bytes. 1427 | memcpy(ndip, random, IPCRYPT_NDX_TWEAKBYTES); 1428 | // Copy the IP into the next 16 bytes. 1429 | memcpy(ndip + IPCRYPT_NDX_TWEAKBYTES, ip16, 16); 1430 | // Encrypt the IP portion with the tweak. 1431 | aes_xex_encrypt(ndip + IPCRYPT_NDX_TWEAKBYTES, &st, random); 1432 | } 1433 | 1434 | /** 1435 | * ipcrypt_ndx_decrypt_ip16 decrypts a 32 byte (tweak + IP) buffer produced by 1436 | * ipcrypt_ndx_encrypt_ip16. The original IP is restored in ip16. 1437 | */ 1438 | void 1439 | ipcrypt_ndx_decrypt_ip16(const IPCryptNDX *ipcrypt, uint8_t ip16[16], 1440 | const uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES]) 1441 | { 1442 | NDXState st; 1443 | 1444 | COMPILER_ASSERT(IPCRYPT_NDX_NDIP_BYTES == 16 + IPCRYPT_NDX_TWEAKBYTES); 1445 | memcpy(&st, ipcrypt->opaque, sizeof st); 1446 | // Copy the IP portion from ndip. 1447 | memcpy(ip16, ndip + IPCRYPT_NDX_TWEAKBYTES, 16); 1448 | // Decrypt using the tweak from the first 16 bytes. 1449 | aes_ndx_decrypt(ip16, &st, ndip); 1450 | } 1451 | 1452 | /** 1453 | * ipcrypt_ndx_encrypt_ip_str encrypts an IP address string in NDX mode. 1454 | * The output is a hex-encoded string of length IPCRYPT_NDIP_STR_BYTES (64 hex chars + null 1455 | * terminator). random must be an 8-byte random value. 1456 | */ 1457 | size_t 1458 | ipcrypt_ndx_encrypt_ip_str(const IPCryptNDX *ipcrypt, 1459 | char encrypted_ip_str[IPCRYPT_NDX_NDIP_STR_BYTES], const char *ip_str, 1460 | const uint8_t random[IPCRYPT_NDX_TWEAKBYTES]) 1461 | { 1462 | uint8_t ip16[16]; 1463 | uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES]; 1464 | 1465 | COMPILER_ASSERT(IPCRYPT_NDX_NDIP_STR_BYTES == IPCRYPT_NDX_NDIP_BYTES * 2 + 1); 1466 | // Convert to 16-byte IP. 1467 | ipcrypt_str_to_ip16(ip16, ip_str); 1468 | // Perform non-deterministic encryption. 1469 | ipcrypt_ndx_encrypt_ip16(ipcrypt, ndip, ip16, random); 1470 | // Convert the 32-byte ndip to a hex string. 1471 | bin2hex(encrypted_ip_str, IPCRYPT_NDX_NDIP_STR_BYTES, ndip, IPCRYPT_NDX_NDIP_BYTES); 1472 | 1473 | return IPCRYPT_NDX_NDIP_STR_BYTES - 1; 1474 | } 1475 | 1476 | /** 1477 | * ipcrypt_ndx_decrypt_ip_str decrypts a hex-encoded string produced by ipcrypt_ndx_encrypt_ip_str. 1478 | * The original IP address string is written to ip_str. 1479 | * Returns the length of the resulting IP string on success, or 0 on error. 1480 | */ 1481 | size_t 1482 | ipcrypt_ndx_decrypt_ip_str(const IPCryptNDX *ipcrypt, char ip_str[IPCRYPT_MAX_IP_STR_BYTES], 1483 | const char *encrypted_ip_str) 1484 | { 1485 | uint8_t ip16[16]; 1486 | uint8_t ndip[IPCRYPT_NDX_NDIP_BYTES]; 1487 | memset(ip_str, 0, IPCRYPT_MAX_IP_STR_BYTES); 1488 | // Convert the hex string back to a 32-byte buffer. 1489 | if (hex2bin(ndip, sizeof ndip, encrypted_ip_str, strlen(encrypted_ip_str)) != sizeof ndip) { 1490 | return 0; 1491 | } 1492 | // Decrypt the IP. 1493 | ipcrypt_ndx_decrypt_ip16(ipcrypt, ip16, ndip); 1494 | // Convert binary IP to string. 1495 | return ipcrypt_ip16_to_str(ip_str, ip16); 1496 | } 1497 | 1498 | #ifdef __clang__ 1499 | # pragma clang attribute pop 1500 | #endif 1501 | --------------------------------------------------------------------------------