├── NOTICE ├── shadow ├── fpe_go │ ├── go.mod │ ├── go.sum │ ├── errors.go │ ├── fpe_test.go │ ├── fpe_export.h │ ├── fpe.go │ ├── fpe_wrapper.go │ └── fpe_wrapper_test.go ├── common │ ├── common.h │ ├── config.h.in │ └── common.cpp ├── fpe │ ├── fpe_bench.cpp │ ├── fpe_internal.h │ ├── fpe.h │ ├── fpe_test.cpp │ └── fpe.cpp └── fpe_export │ ├── fpe_export.h │ ├── fpe_export_test.cpp │ └── fpe_export.cpp ├── .pre-commit-config.yaml ├── .gitattributes ├── .clang-format ├── cmake ├── EnableDebugFlags.cmake ├── ExternalGTest.cmake ├── ShadowConfig.cmake.in ├── ExternalBenchmark.cmake ├── ShadowCustomMacros.cmake └── ExternalOpenSSL.cmake ├── example ├── CMakeLists.txt ├── fpe_example.cpp └── fpe_go_example │ └── fpe_example.go ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── README.md ├── LICENSE ├── .gitignore └── CMakeLists.txt /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2023 TikTok Pte. Ltd. 2 | -------------------------------------------------------------------------------- /shadow/fpe_go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tiktok-privacy-innovation/shadowgraphy/shadow/fpe_go 2 | 3 | go 1.20 4 | 5 | require github.com/stretchr/testify v1.8.4 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-case-conflict 7 | - id: check-merge-conflict 8 | - id: check-symlinks 9 | - id: check-yaml 10 | - id: end-of-file-fixer 11 | - id: forbid-submodules 12 | - id: mixed-line-ending 13 | - id: trailing-whitespace 14 | - repo: https://github.com/pre-commit/mirrors-clang-format 15 | rev: v16.0.0 16 | hooks: 17 | - id: clang-format 18 | -------------------------------------------------------------------------------- /shadow/fpe_go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 6 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 8 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 9 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 10 | -------------------------------------------------------------------------------- /shadow/common/common.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include 18 | 19 | namespace shadow { 20 | namespace common { 21 | 22 | void get_bytes_from_random_device(std::size_t byte_count, unsigned char* out); 23 | 24 | } // namespace common 25 | } // namespace shadow 26 | -------------------------------------------------------------------------------- /shadow/common/config.h.in: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #define SHADOW_VERSION "@SHADOW_VERSION@" 18 | #define SHADOW_VERSION_MAJOR @SHADOW_VERSION_MAJOR@ 19 | #define SHADOW_VERSION_MINOR @SHADOW_VERSION_MINOR@ 20 | #define SHADOW_VERSION_PATCH @SHADOW_VERSION_PATCH@ 21 | 22 | // Are we in debug mode? 23 | #cmakedefine SHADOW_DEBUG 24 | -------------------------------------------------------------------------------- /shadow/fpe_go/errors.go: -------------------------------------------------------------------------------- 1 | package fpe 2 | 3 | import "fmt" 4 | 5 | const ( 6 | E_Go = iota 7 | S_FALSE 8 | E_POINTER 9 | E_INVALIDARG 10 | E_OUTOFMEMORY 11 | E_UNEXPECTED 12 | COR_E_IO 13 | COR_E_INVALIDOPERATION 14 | ) 15 | 16 | type FPEError struct { 17 | msg string 18 | code int64 19 | } 20 | 21 | // newFPEError creates a new CustomError with the given code. 22 | func newFPEError(msg string, code int64) *FPEError { 23 | return &FPEError{msg: msg, code: code} 24 | } 25 | 26 | // Error implements the error interface. 27 | func (e *FPEError) Error() string { 28 | switch e.code { 29 | case E_Go: 30 | return fmt.Sprintf("fpe error: %v", e.msg) 31 | case E_POINTER: 32 | return fmt.Sprintf("%v, C lib error: empty pointer", e.msg) 33 | case E_INVALIDARG: 34 | return fmt.Sprintf("%v, C lib error: invalid argument", e.msg) 35 | case E_OUTOFMEMORY: 36 | return fmt.Sprintf("%v, C lib error: out of memory", e.msg) 37 | default: 38 | return fmt.Sprintf("unknown error, C lib error code: %d", e.code) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Git Line Endings # 3 | ############################### 4 | # Set default behaviour to automatically normalize line endings. 5 | * text=auto 6 | 7 | ############################### 8 | # C++ Attributes # 9 | ############################### 10 | # Sources 11 | *.c text diff=cpp 12 | *.cc text diff=cpp 13 | *.cxx text diff=cpp 14 | *.cpp text diff=cpp 15 | *.c++ text diff=cpp 16 | *.hpp text diff=cpp 17 | *.h text diff=cpp 18 | *.h++ text diff=cpp 19 | *.hh text diff=cpp 20 | 21 | # Compiled Object files 22 | *.slo binary 23 | *.lo binary 24 | *.o binary 25 | *.obj binary 26 | 27 | # Precompiled Headers 28 | *.gch binary 29 | *.pch binary 30 | 31 | # Compiled Dynamic libraries 32 | *.so binary 33 | *.dylib binary 34 | *.dll binary 35 | 36 | # Compiled Static libraries 37 | *.lai binary 38 | *.la binary 39 | *.a binary 40 | *.lib binary 41 | 42 | # Executables 43 | *.exe binary 44 | *.out binary 45 | *.app binary 46 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | IndentWidth: 4 3 | TabWidth: 4 4 | 5 | Language: Cpp 6 | Standard: Cpp11 7 | BasedOnStyle: Google 8 | # indent 9 | AccessModifierOffset: -4 10 | ContinuationIndentWidth: 8 11 | # align 12 | BreakBeforeTernaryOperators: true 13 | BreakBeforeBinaryOperators: false 14 | AlignAfterOpenBracket: false 15 | ColumnLimit: 120 16 | # constructor 17 | BreakConstructorInitializersBeforeComma: false 18 | ConstructorInitializerIndentWidth: 8 19 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 20 | # short block 21 | AllowShortBlocksOnASingleLine: false 22 | AllowShortFunctionsOnASingleLine: false 23 | AllowShortIfStatementsOnASingleLine: false 24 | AllowShortLoopsOnASingleLine: false 25 | Cpp11BracedListStyle: true 26 | # other 27 | AlwaysBreakTemplateDeclarations: true 28 | DerivePointerAlignment: false 29 | PointerAlignment: Left 30 | 31 | # clang-format 3.9+ 32 | SortIncludes: true 33 | BreakStringLiterals: false 34 | ReflowComments: true 35 | 36 | # custom 37 | IncludeCategories: 38 | # Matches common headers first, but sorts them after project includes 39 | - Regex: '^<.*.h>' 40 | Priority: 1 41 | - Regex: '^<.*.hpp>' 42 | Priority: 2 43 | - Regex: '^<[^/]*>' 44 | Priority: 2 45 | - Regex: '^"duet/.*' 46 | Priority: 5 47 | - Regex: '^"solo/.*' 48 | Priority: 4 49 | - Regex: '^"verse/.*' 50 | Priority: 4 51 | - Regex: '^"network/.*' 52 | Priority: 4 53 | - Regex: '^".*' 54 | Priority: 3 55 | ... 56 | -------------------------------------------------------------------------------- /cmake/EnableDebugFlags.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TikTok Pte. Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | include(CheckCXXCompilerFlag) 16 | function(shadow_add_compiler_flag flag) 17 | string(FIND "${CMAKE_CXX_FLAGS}" "${flag}" flag_already_set) 18 | if(flag_already_set EQUAL -1) 19 | message(STATUS "Adding CXX compiler flag: ${flag} ...") 20 | check_cxx_compiler_flag("${flag}" flag_supported) 21 | if(flag_supported) 22 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}" PARENT_SCOPE) 23 | endif() 24 | unset(flag_supported CACHE) 25 | endif() 26 | endfunction() 27 | if(NOT MSVC AND SETOPS_DEBUG) 28 | shadow_add_compiler_flag("-Wall") 29 | shadow_add_compiler_flag("-Wextra") 30 | shadow_add_compiler_flag("-Wconversion") 31 | shadow_add_compiler_flag("-Wshadow") 32 | shadow_add_compiler_flag("-pedantic") 33 | endif() 34 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TikTok Pte. Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | cmake_minimum_required(VERSION 3.14) 16 | 17 | project(SHADOWExample VERSION 0.1.0 LANGUAGES CXX) 18 | 19 | # If not called from root CMakeLists.txt 20 | if(NOT DEFINED SHADOW_BUILD_EXAMPLE) 21 | set(SHADOW_BUILD_EXAMPLE ON) 22 | 23 | # Import Shadow 24 | find_package(Shadow 0.1.0 EXACT REQUIRED) 25 | 26 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) 27 | endif() 28 | 29 | if(SHADOW_BUILD_EXAMPLE) 30 | set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl -lrt") 31 | 32 | # fpe 33 | add_executable(fpe_example ${CMAKE_CURRENT_LIST_DIR}/fpe_example.cpp) 34 | if(TARGET Shadow::fpe) 35 | target_link_libraries(fpe_example PRIVATE Shadow::fpe) 36 | else() 37 | message(FATAL_ERROR "Cannot find target Shadow::fpe") 38 | endif() 39 | endif() 40 | -------------------------------------------------------------------------------- /cmake/ExternalGTest.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TikTok Pte. Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FetchContent_Declare( 16 | googletest 17 | GIT_REPOSITORY https://github.com/google/googletest.git 18 | GIT_TAG 58d77fa8070e8cec2dc1ed015d66b454c8d78850 # 1.12.1 19 | ) 20 | FetchContent_GetProperties(googletest) 21 | 22 | if(NOT googletest_POPULATED) 23 | FetchContent_Populate(googletest) 24 | 25 | set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) 26 | set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) 27 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 28 | mark_as_advanced(BUILD_GMOCK) 29 | mark_as_advanced(INSTALL_GTEST) 30 | mark_as_advanced(FETCHCONTENT_SOURCE_DIR_GOOGLETEST) 31 | mark_as_advanced(FETCHCONTENT_UPDATES_DISCONNECTED_GOOGLETEST) 32 | 33 | add_subdirectory( 34 | ${googletest_SOURCE_DIR} 35 | ${SHADOW_THIRDPARTY_DIR}/googletest-src 36 | EXCLUDE_FROM_ALL) 37 | endif() 38 | -------------------------------------------------------------------------------- /shadow/common/common.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "shadow/common/common.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace shadow { 23 | namespace common { 24 | 25 | void get_bytes_from_random_device(std::size_t byte_count, unsigned char* out) { 26 | if (byte_count == 0) { 27 | return; 28 | } 29 | if (byte_count != 0 && out == nullptr) { 30 | throw std::invalid_argument("out is nullptr"); 31 | } 32 | std::random_device rd("/dev/urandom"); 33 | while (byte_count >= 4) { 34 | *reinterpret_cast(out) = rd(); 35 | out += 4; 36 | byte_count -= 4; 37 | } 38 | if (byte_count) { 39 | std::uint32_t last = rd(); 40 | std::copy_n(reinterpret_cast(&last), byte_count, out); 41 | } 42 | } 43 | 44 | } // namespace common 45 | } // namespace shadow 46 | -------------------------------------------------------------------------------- /shadow/fpe_go/fpe_test.go: -------------------------------------------------------------------------------- 1 | package fpe 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestKeyGenerate(t *testing.T){ 10 | keyString, err := KeyGenerate() 11 | assert.Nil(t, err) 12 | assert.Equal(t, len(keyString), 16) 13 | } 14 | 15 | func TestEncryption(t *testing.T) { 16 | t.Run("test encrypt", func(t *testing.T) { 17 | alphabet := "zyxwvutsrqponmlkjihgfedcba" + " " 18 | key := "7gHJ4D58F6hj27L1" 19 | tweak := "" 20 | msg := "tell u a secret" 21 | 22 | t.Run("encrypt and decrypt", func(t *testing.T) { 23 | encrypted, err := Encrypt(alphabet, key, tweak, msg) 24 | assert.Nil(t, err) 25 | decrypted, err := Decrypt(alphabet, key, tweak, encrypted) 26 | assert.Nil(t, err) 27 | assert.Equal(t, msg, decrypted) 28 | }) 29 | 30 | t.Run("encrypt and decrypt skip", func(t *testing.T) { 31 | encrypted, err := EncryptSkipUnsupported(alphabet, key, tweak, msg) 32 | assert.Nil(t, err) 33 | decrypted, err := DecryptSkipUnsupported(alphabet, key, tweak, encrypted) 34 | assert.Nil(t, err) 35 | assert.Equal(t, msg, decrypted) 36 | }) 37 | 38 | t.Run("encrypt and decrypt specified", func(t *testing.T) { 39 | msg := "tell u a secret!" 40 | skipAlphabet := "!" 41 | encrypted, err := EncryptSkipSpecified(alphabet, key, tweak, msg, skipAlphabet) 42 | assert.Nil(t, err) 43 | decrypted, err := DecryptSkipSpecified(alphabet, key, tweak, encrypted, skipAlphabet) 44 | assert.Nil(t, err) 45 | assert.Equal(t, msg, decrypted) 46 | }) 47 | }) 48 | } 49 | 50 | func BenchmarkCipher16(b *testing.B) { 51 | benchmarkCipher(b, 16) 52 | } 53 | 54 | func BenchmarkCipher64(b *testing.B) { 55 | benchmarkCipher(b, 64) 56 | } 57 | 58 | func BenchmarkCipher255(b *testing.B) { 59 | benchmarkCipher(b, 255) 60 | } 61 | 62 | func benchmarkCipher(b *testing.B, msgLength int) { 63 | alphabet := "zyxwvutsrqponmlkjihgfedcba" + " " 64 | key := "7gHJ4D58F6hj27L1" 65 | tweak := "" 66 | msg := randomString(msgLength) 67 | 68 | b.ResetTimer() 69 | for i := 0; i < b.N; i++ { 70 | encrypted, _ := Encrypt(alphabet, key, tweak, msg) 71 | Decrypt(alphabet, key, tweak, encrypted) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cmake/ShadowConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TikTok Pte. Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | @PACKAGE_INIT@ 16 | 17 | include(CMakeFindDependencyMacro) 18 | 19 | macro(shadow_find_dependency dep) 20 | find_dependency(${dep}) 21 | if(NOT ${dep}_FOUND) 22 | if(NOT Shadow_FIND_QUIETLY) 23 | message(WARNING "Could not find dependency `${dep}` required by this configuration") 24 | endif() 25 | set(Shadow_FOUND FALSE) 26 | return() 27 | endif() 28 | endmacro() 29 | 30 | set(Shadow_FOUND FALSE) 31 | 32 | set(SHADOW_DEBUG @SHADOW_DEBUG@) 33 | set(SHADOW_VERSION @SHADOW_VERSION@) 34 | set(SHADOW_VERSION_MAJOR @SHADOW_VERSION_MAJOR@) 35 | set(SHADOW_VERSION_MINOR @SHADOW_VERSION_MINOR@) 36 | set(SHADOW_VERSION_PATCH @SHADOW_VERSION_PATCH@) 37 | set(SHADOW_BUILD_TYPE @CMAKE_BUILD_TYPE@) 38 | 39 | set(SHADOW_CARRY_OPENSSL @SHADOW_CARRY_OPENSSL@) 40 | 41 | if (NOT SHADOW_CARRY_OPENSSL) 42 | shadow_find_dependency(OpenSSL) 43 | endif() 44 | 45 | set(CMAKE_THREAD_PREFER_PTHREAD TRUE) 46 | set(THREADS_PREFER_PTHREAD_FLAG TRUE) 47 | shadow_find_dependency(Threads) 48 | 49 | # Add the current directory to the module search path 50 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) 51 | 52 | include(${CMAKE_CURRENT_LIST_DIR}/ShadowTargets.cmake) 53 | 54 | if(TARGET Shadow::fpe) 55 | set(Shadow_FOUND TRUE) 56 | endif() 57 | 58 | if(Shadow_FOUND) 59 | if(NOT Shadow_FIND_QUIETLY) 60 | message(STATUS "Shadow -> Version ${Shadow_VERSION} detected") 61 | endif() 62 | if(SHADOW_DEBUG AND NOT Shadow_FIND_QUIETLY) 63 | message(STATUS "Performance warning: Shadow compiled in debug mode") 64 | endif() 65 | set(Shadow_TARGETS_AVAILABLE "Shadow Targets available: Shadow::fpe") 66 | 67 | if(NOT Shadow_FIND_QUIETLY) 68 | message(STATUS ${Shadow_TARGETS_AVAILABLE}) 69 | endif() 70 | else() 71 | if(NOT Shadow_QUIETLY) 72 | message(STATUS "Shadow -> NOT FOUND") 73 | endif() 74 | endif() 75 | -------------------------------------------------------------------------------- /cmake/ExternalBenchmark.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TikTok Pte. Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FetchContent_Declare( 16 | benchmark 17 | GIT_REPOSITORY https://github.com/google/benchmark.git 18 | GIT_TAG 0d98dba29d66e93259db7daa53a9327df767a415 # 1.6.1 19 | ) 20 | FetchContent_GetProperties(benchmark) 21 | 22 | if(NOT benchmark) 23 | FetchContent_Populate(benchmark) 24 | 25 | set(LLVMAR_EXECUTABLE ${CMAKE_AR}) 26 | set(LLVMNM_EXECUTABLE ${CMAKE_NM}) 27 | set(LLVMRANLIB_EXECUTABLE ${CMAKE_RANLIB}) 28 | set(LLVM_FILECHECK_EXE ${CMAKE_CXX_COMPILER_AR}/../FileCheck) 29 | set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) 30 | set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) 31 | set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) 32 | set(BENCHMARK_ENABLE_LTO OFF CACHE BOOL "" FORCE) 33 | mark_as_advanced(LIBRT) 34 | mark_as_advanced(LLVM_FILECHECK_EXE) 35 | mark_as_advanced(BENCHMARK_BUILD_32_BITS) 36 | mark_as_advanced(BENCHMARK_DOWNLOAD_DEPENDENCIES) 37 | mark_as_advanced(BENCHMARK_ENABLE_ASSEMBLY_TESTS) 38 | mark_as_advanced(BENCHMARK_ENABLE_EXCEPTIONS) 39 | mark_as_advanced(BENCHMARK_ENABLE_GTEST_TESTS) 40 | mark_as_advanced(BENCHMARK_ENABLE_INSTALL) 41 | mark_as_advanced(BENCHMARK_ENABLE_LTO) 42 | mark_as_advanced(BENCHMARK_ENABLE_TESTING) 43 | mark_as_advanced(BENCHMARK_USE_LIBCXX) 44 | mark_as_advanced(FETCHCONTENT_SOURCE_DIR_BENCHMARK) 45 | mark_as_advanced(FETCHCONTENT_UPDATES_DISCONNECTED_BENCHMARK) 46 | 47 | if(NOT WIN32) 48 | # Google Benchmark contains unsafe conversions so force -Wno-conversion temporarily 49 | set(OLD_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) 50 | set(CMAKE_CXX_FLAGS "${OLD_CMAKE_CXX_FLAGS} -Wno-conversion") 51 | endif() 52 | 53 | add_subdirectory( 54 | ${benchmark_SOURCE_DIR} 55 | EXCLUDE_FROM_ALL) 56 | 57 | if(NOT WIN32) 58 | set(CMAKE_CXX_FLAGS ${OLD_CMAKE_CXX_FLAGS}) 59 | endif() 60 | endif() 61 | -------------------------------------------------------------------------------- /cmake/ShadowCustomMacros.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TikTok Pte. Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Manually combine archives, using ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} to keep temporary files. 16 | macro(shadow_combine_archives target dependency) 17 | if(MSVC) 18 | add_custom_command(TARGET ${target} POST_BUILD 19 | COMMAND lib.exe /OUT:$ $ $ 20 | DEPENDS $ $ 21 | WORKING_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) 22 | else() 23 | if(CMAKE_HOST_WIN32) 24 | get_filename_component(CXX_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) 25 | set(AR_CMD_PATH "${CXX_DIR}/llvm-ar.exe") 26 | file(TO_NATIVE_PATH "${AR_CMD_PATH}" AR_CMD_PATH) 27 | set(DEL_CMD "del") 28 | set(DEL_CMD_OPTS "") 29 | else() 30 | set(AR_CMD_PATH "ar") 31 | set(DEL_CMD "rm") 32 | set(DEL_CMD_OPTS "-rf") 33 | endif() 34 | if(EMSCRIPTEN) 35 | set(AR_CMD_PATH "emar") 36 | endif() 37 | add_custom_command(TARGET ${target} POST_BUILD 38 | COMMAND "${AR_CMD_PATH}" x $ 39 | COMMAND "${AR_CMD_PATH}" x $ 40 | COMMAND "${AR_CMD_PATH}" rcs $ *.o 41 | COMMAND ${DEL_CMD} ${DEL_CMD_OPTS} *.o 42 | WORKING_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) 43 | endif() 44 | endmacro() 45 | 46 | # Fetch thirdparty content specified by content_file 47 | macro(shadow_fetch_thirdparty_content content_file) 48 | set(SHADOW_FETCHCONTENT_BASE_DIR_OLD ${FETCHCONTENT_BASE_DIR}) 49 | set(FETCHCONTENT_BASE_DIR ${SHADOW_THIRDPARTY_DIR} CACHE STRING "" FORCE) 50 | include(${content_file}) 51 | set(FETCHCONTENT_BASE_DIR ${SHADOW_FETCHCONTENT_BASE_DIR_OLD} CACHE STRING "" FORCE) 52 | unset(SHADOW_FETCHCONTENT_BASE_DIR_OLD) 53 | endmacro() 54 | -------------------------------------------------------------------------------- /shadow/fpe/fpe_bench.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include "benchmark/benchmark.h" 19 | #include "shadow/common/common.h" 20 | #include "shadow/fpe/fpe.h" 21 | 22 | #define FPE_REG_BENCH(category, name0, name1, func, ...) \ 23 | benchmark::RegisterBenchmark((std::string(#category " / " #name0 " / ") + std::to_string(name1)).c_str(), \ 24 | [=](benchmark::State& st) { func(st, __VA_ARGS__); }) \ 25 | ->Unit(benchmark::kMicrosecond); 26 | 27 | static const std::vector kFPEMessagesLength = { 28 | 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 32, 40, 48, 56, 64, 128, 192, 256, 320}; 29 | 30 | void generate_random_messages(const std::vector& messages_length, const std::string& charset, 31 | std::vector& random_messages) { 32 | unsigned char charset_length = static_cast(charset.length()); 33 | random_messages.clear(); 34 | random_messages.reserve(messages_length.size()); 35 | for (auto msg_len : messages_length) { 36 | std::vector random_bytes(msg_len); 37 | shadow::common::get_bytes_from_random_device(msg_len, random_bytes.data()); 38 | std::string cur_msg = ""; 39 | for (std::size_t i = 0; i < msg_len; ++i) { 40 | cur_msg += charset[random_bytes[i] % charset_length]; 41 | } 42 | random_messages.push_back(cur_msg); 43 | } 44 | } 45 | 46 | void fpe_encrypt_charset_numbers( 47 | benchmark::State& state, const shadow::fpe::Key& key, const shadow::fpe::Tweak& tweak, const std::string& pt) { 48 | shadow::fpe::Alphabet alphabet(shadow::fpe::kCharsetNumbers); 49 | std::string ct; 50 | for (auto _ : state) { 51 | shadow::fpe::encrypt(alphabet, key, tweak, pt, ct); 52 | } 53 | } 54 | 55 | int main(int argc, char** argv) { 56 | // All bench use the same key and tweaks. 57 | shadow::fpe::Key key = shadow::fpe::generate_key(); 58 | 59 | // Benchmark for charset number. 60 | std::vector random_numbers; 61 | generate_random_messages(kFPEMessagesLength, shadow::fpe::kCharsetNumbers, random_numbers); 62 | for (std::size_t j = 0; j < kFPEMessagesLength.size(); ++j) { 63 | FPE_REG_BENCH(FPENumbers, MessageLength, j, fpe_encrypt_charset_numbers, key, shadow::fpe::Tweak(), 64 | random_numbers[j]); 65 | } 66 | 67 | benchmark::Initialize(&argc, argv); 68 | 69 | benchmark::RunSpecifiedBenchmarks(); 70 | } 71 | -------------------------------------------------------------------------------- /shadow/fpe_export/fpe_export.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef SHADOW_FPE_EXPORT_HEADER_FPE_H 16 | #define SHADOW_FPE_EXPORT_HEADER_FPE_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | typedef long FPEStatus; 28 | 29 | #define _FPE_STATUS_TYPEDEF_(status) ((FPEStatus)status) 30 | 31 | #define S_OK _FPE_STATUS_TYPEDEF_(0L) 32 | #define S_FALSE _FPE_STATUS_TYPEDEF_(1L) 33 | #define E_POINTER _FPE_STATUS_TYPEDEF_(2L) 34 | #define E_INVALIDARG _FPE_STATUS_TYPEDEF_(3L) 35 | #define E_OUTOFMEMORY _FPE_STATUS_TYPEDEF_(4L) 36 | #define E_UNEXPECTED _FPE_STATUS_TYPEDEF_(5L) 37 | #define COR_E_IO _FPE_STATUS_TYPEDEF_(6L) 38 | #define COR_E_INVALIDOPERATION _FPE_STATUS_TYPEDEF_(7L) 39 | 40 | typedef struct { 41 | char* data; 42 | size_t len; 43 | } FPEBytes; 44 | 45 | typedef struct FPEAlphabet FPEAlphabet; 46 | 47 | typedef struct FPEKey FPEKey; 48 | 49 | typedef struct FPETweak FPETweak; 50 | 51 | FPEAlphabet* fpe_alphabet_new(FPEBytes* charset, FPEStatus* status); 52 | 53 | FPEStatus fpe_alphabet_free(FPEAlphabet* alphabet); 54 | 55 | FPEStatus fpe_alphabet_size(FPEAlphabet* alphabet, size_t* size); 56 | 57 | FPEStatus fpe_alphabet_are_identical(FPEAlphabet* alphabet_0, FPEAlphabet* alphabet_1, bool* result); 58 | 59 | FPEStatus fpe_alphabet_are_exclusive(FPEAlphabet* alphabet_0, FPEAlphabet* alphabet_1, bool* result); 60 | 61 | FPEKey* fpe_key_new(FPEStatus* status); 62 | 63 | FPEStatus fpe_key_free(FPEKey* key); 64 | 65 | FPEStatus fpe_key_from_bytes(FPEKey* key, FPEBytes* bytes); 66 | 67 | FPEStatus fpe_key_to_bytes(FPEKey* key, FPEBytes* bytes); 68 | 69 | FPEStatus fpe_key_generate(FPEKey* key); 70 | 71 | FPETweak* fpe_tweak_new(FPEStatus* status); 72 | 73 | FPEStatus fpe_tweak_free(FPETweak* tweak); 74 | 75 | FPEStatus fpe_tweak_fill(FPETweak* tweak, FPEBytes* bytes); 76 | 77 | FPEStatus fpe_encrypt(FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 78 | 79 | FPEStatus fpe_decrypt(FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 80 | 81 | FPEStatus fpe_encrypt_skip_unsupported( 82 | FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 83 | 84 | FPEStatus fpe_decrypt_skip_unsupported( 85 | FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 86 | 87 | FPEStatus fpe_encrypt_skip_specified( 88 | FPEAlphabet* alphabet, FPEAlphabet* specification, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 89 | 90 | FPEStatus fpe_decrypt_skip_specified( 91 | FPEAlphabet* alphabet, FPEAlphabet* specification, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 92 | 93 | #ifdef __cplusplus 94 | } 95 | #endif 96 | 97 | #endif // SHADOW_FPE_EXPORT_HEADER_FPE_H 98 | -------------------------------------------------------------------------------- /shadow/fpe_go/fpe_export.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef SHADOW_FPE_EXPORT_HEADER_FPE_H 16 | #define SHADOW_FPE_EXPORT_HEADER_FPE_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | typedef long FPEStatus; 28 | 29 | #define _FPE_STATUS_TYPEDEF_(status) ((FPEStatus)status) 30 | 31 | #define S_OK _FPE_STATUS_TYPEDEF_(0L) 32 | #define S_FALSE _FPE_STATUS_TYPEDEF_(1L) 33 | #define E_POINTER _FPE_STATUS_TYPEDEF_(2L) 34 | #define E_INVALIDARG _FPE_STATUS_TYPEDEF_(3L) 35 | #define E_OUTOFMEMORY _FPE_STATUS_TYPEDEF_(4L) 36 | #define E_UNEXPECTED _FPE_STATUS_TYPEDEF_(5L) 37 | #define COR_E_IO _FPE_STATUS_TYPEDEF_(6L) 38 | #define COR_E_INVALIDOPERATION _FPE_STATUS_TYPEDEF_(7L) 39 | 40 | typedef struct { 41 | char* data; 42 | size_t len; 43 | } FPEBytes; 44 | 45 | typedef struct FPEAlphabet FPEAlphabet; 46 | 47 | typedef struct FPEKey FPEKey; 48 | 49 | typedef struct FPETweak FPETweak; 50 | 51 | FPEAlphabet* fpe_alphabet_new(FPEBytes* charset, FPEStatus* status); 52 | 53 | FPEStatus fpe_alphabet_free(FPEAlphabet* alphabet); 54 | 55 | FPEStatus fpe_alphabet_size(FPEAlphabet* alphabet, size_t* size); 56 | 57 | FPEStatus fpe_alphabet_are_identical(FPEAlphabet* alphabet_0, FPEAlphabet* alphabet_1, bool* result); 58 | 59 | FPEStatus fpe_alphabet_are_exclusive(FPEAlphabet* alphabet_0, FPEAlphabet* alphabet_1, bool* result); 60 | 61 | FPEKey* fpe_key_new(FPEStatus* status); 62 | 63 | FPEStatus fpe_key_free(FPEKey* key); 64 | 65 | FPEStatus fpe_key_from_bytes(FPEKey* key, FPEBytes* bytes); 66 | 67 | FPEStatus fpe_key_to_bytes(FPEKey* key, FPEBytes* bytes); 68 | 69 | FPEStatus fpe_key_generate(FPEKey* key); 70 | 71 | FPETweak* fpe_tweak_new(FPEStatus* status); 72 | 73 | FPEStatus fpe_tweak_free(FPETweak* tweak); 74 | 75 | FPEStatus fpe_tweak_fill(FPETweak* tweak, FPEBytes* bytes); 76 | 77 | FPEStatus fpe_encrypt(FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 78 | 79 | FPEStatus fpe_decrypt(FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 80 | 81 | FPEStatus fpe_encrypt_skip_unsupported( 82 | FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 83 | 84 | FPEStatus fpe_decrypt_skip_unsupported( 85 | FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 86 | 87 | FPEStatus fpe_encrypt_skip_specified( 88 | FPEAlphabet* alphabet, FPEAlphabet* specification, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 89 | 90 | FPEStatus fpe_decrypt_skip_specified( 91 | FPEAlphabet* alphabet, FPEAlphabet* specification, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out); 92 | 93 | #ifdef __cplusplus 94 | } 95 | #endif 96 | 97 | #endif // SHADOW_FPE_EXPORT_HEADER_FPE_H 98 | -------------------------------------------------------------------------------- /cmake/ExternalOpenSSL.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TikTok Pte. Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | include(ExternalProject) 16 | 17 | set(OPENSSL_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/openssl-src) # default path by CMake 18 | set(OPENSSL_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/openssl) 19 | set(OPENSSL_INCLUDE_DIR ${OPENSSL_INSTALL_DIR}/include) 20 | set(OPENSSL_CONFIGURE_COMMAND ${OPENSSL_SOURCE_DIR}/config) 21 | ExternalProject_Add( 22 | openssl 23 | SOURCE_DIR ${OPENSSL_SOURCE_DIR} 24 | GIT_REPOSITORY https://github.com/openssl/openssl.git 25 | GIT_TAG OpenSSL_1_1_1u 26 | USES_TERMINAL_DOWNLOAD TRUE 27 | CONFIGURE_COMMAND 28 | ${OPENSSL_CONFIGURE_COMMAND} 29 | --prefix=${OPENSSL_INSTALL_DIR} 30 | --openssldir=${OPENSSL_INSTALL_DIR} 31 | no-afalgeng 32 | no-aria 33 | no-asan 34 | no-asm 35 | no-async 36 | no-autoalginit 37 | no-autoerrinit 38 | no-autoload-config 39 | no-bf 40 | no-blake2 41 | no-buildtest-c++ 42 | no-camellia 43 | no-capieng 44 | no-cast 45 | no-chacha 46 | no-cmac 47 | no-cms 48 | no-comp 49 | no-crypto-mdebug 50 | no-crypto-mdebug-backtrace 51 | no-ct 52 | no-deprecated 53 | no-des 54 | no-devcryptoeng 55 | no-dgram 56 | no-dh 57 | no-dsa 58 | no-dso 59 | no-dtls 60 | no-dynamic-engine 61 | no-ec 62 | no-ec2m 63 | no-ecdh 64 | no-ecdsa 65 | no-ec_nistp_64_gcc_128 66 | no-egd 67 | no-engine 68 | no-err 69 | no-external-tests 70 | no-filenames 71 | no-fuzz-libfuzzer 72 | no-fuzz-afl 73 | no-gost 74 | no-heartbeats 75 | no-hw 76 | no-idea 77 | no-makedepend 78 | no-md2 79 | no-md4 80 | no-mdc2 81 | no-msan 82 | no-multiblock 83 | no-nextprotoneg 84 | no-pinshared 85 | no-ocb 86 | no-ocsp 87 | no-poly1305 88 | no-posix-io 89 | no-psk 90 | no-rc2 91 | no-rc4 92 | no-rc5 93 | no-rdrand 94 | no-rfc3779 95 | no-rmd160 96 | no-scrypt 97 | no-sctp 98 | no-seed 99 | no-shared 100 | no-siphash 101 | no-sm2 102 | no-sm3 103 | no-sm4 104 | no-sock 105 | no-srp 106 | no-srtp 107 | no-sse2 108 | no-ssl 109 | no-ssl-trace 110 | no-static-engine 111 | no-stdio 112 | no-tests 113 | no-threads 114 | no-tls 115 | no-ts 116 | no-ubsan 117 | no-ui-console 118 | no-unit-test 119 | no-whirlpool 120 | no-weak-ssl-ciphers 121 | no-zlib 122 | no-zlib-dynamic 123 | BUILD_COMMAND make -j 124 | TEST_COMMAND "" 125 | INSTALL_COMMAND make install_sw -j 126 | INSTALL_DIR ${OPENSSL_INSTALL_DIR} 127 | ) 128 | 129 | file(MAKE_DIRECTORY ${OPENSSL_INCLUDE_DIR}) 130 | 131 | add_library(OpenSSL::crypto STATIC IMPORTED GLOBAL) 132 | set_property(TARGET OpenSSL::crypto PROPERTY IMPORTED_LOCATION ${OPENSSL_INSTALL_DIR}/lib/libcrypto.a) 133 | set_property(TARGET OpenSSL::crypto PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${OPENSSL_INCLUDE_DIR}) 134 | add_dependencies(OpenSSL::crypto openssl) 135 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Shadowgraphy 2 | 3 | Thank you for investing your time in contributing to Shadowgraphy! 4 | 5 | Read our [Code of Coduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectable. 6 | 7 | This guide details how to use issues and pull requests to improve Shadowgraphy. 8 | 9 | ## General Guidelines 10 | 11 | ### Pull Requests 12 | 13 | Make sure to keep Pull Requests small and functional to make them easier to review, understand, and look up in commit history. This repository uses "Squash and Commit" to keep our history clean and make it easier to revert changes based on PR. 14 | 15 | Adding the appropriate documentation, unit tests and e2e tests as part of a feature is the responsibility of the feature owner, whether it is done in the same Pull Request or not. 16 | 17 | Pull Requests should follow the "subject: message" format, where the subject describes what part of the code is being modified. 18 | 19 | Refer to the template for more information on what goes into a PR description. 20 | 21 | ### Design Docs 22 | 23 | A contributor proposes a design with a PR on the repository to allow for revisions and discussions. If a design needs to be discussed before formulating a document for it, make use of Google doc and GitHub issue to involve the community on the discussion. 24 | 25 | ### GitHub Issues 26 | 27 | GitHub Issues are used to file bugs, work items, and feature requests with actionable items/issues (Please refer to the "Reporting Bugs/Feature Requests" section below for more information). 28 | 29 | ### Reporting Bugs/Feature Requests 30 | 31 | We welcome you to use the GitHub issue tracker to report bugs or suggest features that have actionable items/issues (as opposed to introducing a feature request on GitHub Discussions). 32 | 33 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 34 | 35 | - A reproducible test case or series of steps 36 | - The version of the code being used 37 | - Any modifications you've made relevant to the bug 38 | - Anything unusual about your environment or deployment 39 | 40 | ## Contributing via Pull Requests 41 | 42 | ### Find interesting issue 43 | 44 | If you spot a problem with the problem, [search if an issue already exists](https://github.com/tiktok-privacy-innovation/Shadowgraphy/issues). If a related issue doesn't exist, you can open a new issue using [issue template](https://github.com/tiktok-privacy-innovation/Shadowgraphy/issues/new/choose). 45 | 46 | ### Solve an issue 47 | 48 | Please check `DEVELOPMENT.md` in sub folder to get familar with running and testing codes. 49 | 50 | ### Open a Pull request. 51 | 52 | When you're done making the changes, open a pull request and fill PR template so we can better review your PR. The template helps reviewers understand your changes and the purpose of your pull request. 53 | 54 | Don't forget to link PR to issue if you are solving one. 55 | 56 | If you run into any merge issues, checkout this [git tutorial](https://lab.github.com/githubtraining/managing-merge-conflicts) to help you resolve merge conflicts and other issues. 57 | 58 | 59 | ## Finding contributions to work on 60 | 61 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' and 'good first issue' issues are a great place to start. 62 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series 85 | of actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or 92 | permanent ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within 112 | the community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, available at 118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 119 | 120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 121 | enforcement ladder](https://github.com/mozilla/diversity). 122 | 123 | [homepage]: https://www.contributor-covenant.org 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | https://www.contributor-covenant.org/faq. Translations are available at 127 | https://www.contributor-covenant.org/translations. 128 | -------------------------------------------------------------------------------- /shadow/fpe/fpe_internal.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include 18 | 19 | #include "shadow/fpe/fpe.h" 20 | 21 | namespace shadow { 22 | namespace fpe { 23 | 24 | class AlphabetInternal : public Alphabet { 25 | public: 26 | /** 27 | * @brief Constructs an alphabet from a given set of characters. 28 | * @param[in] charset A set of characters. 29 | * @throws std::invalid_argument if charset's size is less than SHADOW_FPE_ALPHABET_SIZE_MIN or larger than 30 | * SHADOW_FPE_ALPHABET_SIZE_MAX, or if character set has duplication. 31 | */ 32 | explicit AlphabetInternal(const std::string& charset); 33 | 34 | /** 35 | * @brief Constructs an alphabet from a given set of characters. 36 | * @param[in] charset A set of characters. 37 | * @throws std::invalid_argument if charset's size is less than SHADOW_FPE_ALPHABET_SIZE_MIN or larger than 38 | * SHADOW_FPE_ALPHABET_SIZE_MAX, or if character set has duplication. 39 | */ 40 | explicit AlphabetInternal(const Alphabet& alphabet); 41 | 42 | /** 43 | * @brief Returns the size of this alphabet. 44 | */ 45 | std::uint32_t radix() const; 46 | 47 | /** 48 | * @brief Returns false if the character is unsupported. 49 | * @param[in] in A character. 50 | */ 51 | bool validate(const unsigned char& in) const; 52 | 53 | /** 54 | * @brief Returns false if any character is unsupported. 55 | * @param[in] in A string of characters. 56 | */ 57 | bool validate(const std::string& in) const; 58 | 59 | /** 60 | * @brief Returns an integer digit that maps to the character. 61 | * @param[in] in A character. 62 | * @throws std::invalid_argument if the character is unsupported by this alphabet. 63 | */ 64 | std::uint32_t to_digit(const unsigned char& in) const; 65 | 66 | /** 67 | * @brief Returns an character that maps to the integer digit. 68 | * @param[in] in An integer digit. 69 | * @throws std::invalid_argument if the integer digit is unsupported by this alphabet. 70 | */ 71 | unsigned char to_char(std::uint32_t in) const; 72 | 73 | /** 74 | * @brief Maps a string of characters to a vector of integer digits. 75 | * @param[in] in A string of characters. 76 | * @param[out] out A vector of integer digits. 77 | * @throws std::invalid_argument if any character is unsupported by this alphabet. 78 | */ 79 | void to_digit(const std::string& in, std::vector& out) const; 80 | 81 | /** 82 | * @brief Maps a vector of integer digits to a string of characters. 83 | * @param[in] in A vector of integer digits. 84 | * @param[out] in A string of characters. 85 | * @throws std::invalid_argument if any integer digit is unsupported by this alphabet. 86 | */ 87 | void to_char(const std::vector& in, std::string& out) const; 88 | 89 | /** 90 | * @brief Extracts supported characters from a string to a vector of integer digits; record unsupported characters 91 | * and their indexes in a schema. 92 | * @param[in] in A string of characters. 93 | * @param[out] schema A string of empty and unsupported characters. 94 | * @param[out] out A vector of integer digits. 95 | */ 96 | void to_digit(const std::string& in, std::string& schema, std::vector& out) const; 97 | 98 | /** 99 | * @brief Maps a vector of integer digits to a string of characters formatted by the schema. 100 | * @param[in] in A vector of integer digits. 101 | * @param[in] schema A string of empty and unsupported characters. 102 | * @param[out] in A string of characters. 103 | * @throws std::invalid_argument if any integer digit is unsupported by this alphabet. 104 | */ 105 | void to_char(const std::vector& in, const std::string& schema, std::string& out) const; 106 | }; 107 | 108 | void encrypt_internal(const Alphabet& alphabet, const Key& key, const std::vector& tweak, bool encrypt, 109 | const std::string& in, std::string& out); 110 | 111 | void encrypt_skip_unsupported_internal(const Alphabet& alphabet, const Key& key, 112 | const std::vector& tweak, bool encrypt, const std::string& in, std::string& out); 113 | 114 | void encrypt_skip_specified_internal(const Alphabet& alphabet, const Alphabet& specification, const Key& key, 115 | const std::vector& tweak, bool encrypt, const std::string& in, std::string& out); 116 | 117 | inline void print(const std::string& name, const unsigned char* in, std::size_t len) { 118 | std::cout << name << ": "; 119 | for (std::size_t i = 0; i < len; i++) 120 | std::cout << static_cast(in[i]) << " "; 121 | std::cout << std::endl; 122 | } 123 | 124 | inline void print(const std::string& name, const std::uint32_t* in, std::size_t len) { 125 | std::cout << name << ": "; 126 | for (std::size_t i = 0; i < len; i++) 127 | std::cout << in[i] << " "; 128 | std::cout << std::endl; 129 | } 130 | 131 | } // namespace fpe 132 | } // namespace shadow 133 | -------------------------------------------------------------------------------- /example/fpe_example.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "shadow/fpe/fpe.h" 20 | 21 | int main(int argc, char** argv) { 22 | // Example 1: credit card number 23 | shadow::fpe::Alphabet alphabet(shadow::fpe::kCharsetNumbers); 24 | std::string pt = "4263982640269299"; 25 | std::string ct; 26 | std::string pt_check; 27 | shadow::fpe::Key key( 28 | {0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 29 | shadow::fpe::encrypt(alphabet, key, shadow::fpe::Tweak(), pt, ct); 30 | shadow::fpe::decrypt(alphabet, key, shadow::fpe::Tweak(), ct, pt_check); 31 | std::cout << "Example 1: encrypt credit card numbers" << std::endl; 32 | std::cout << " message : " << pt << std::endl; 33 | std::cout << " encryption : " << ct << std::endl; 34 | std::cout << " decryption : " << pt_check << std::endl; 35 | std::cout << std::endl; 36 | 37 | // Example 2: credit card number with tweaks 38 | // use first 4 digits and last 6 digits as tweak, and encrypt middle 6 digits 39 | std::string pt_middle_six = pt.substr(6, 6); 40 | shadow::fpe::Tweak tweak_remaining_ten(pt.substr(0, 6) + pt.substr(12, 4)); 41 | shadow::fpe::encrypt(alphabet, key, tweak_remaining_ten, pt_middle_six, ct); 42 | shadow::fpe::decrypt(alphabet, key, tweak_remaining_ten, ct, pt_check); 43 | std::cout << "Example 2: encrypt credit card numbers with tweaks" << std::endl; 44 | std::cout << " message : " << pt_middle_six << std::endl; 45 | std::cout << " encryption : " << ct << std::endl; 46 | std::cout << " decryption : " << pt_check << std::endl; 47 | std::cout << std::endl; 48 | 49 | // Example 3: encrypt an email address as a string. 50 | shadow::fpe::Alphabet alphabet_email_1(shadow::fpe::kCharsetNumbers + shadow::fpe::kCharsetLettersLowercase + "@."); 51 | std::string pt_email = "my.personal.email@hotmail.com"; 52 | shadow::fpe::encrypt(alphabet_email_1, key, shadow::fpe::Tweak(), pt_email, ct); 53 | shadow::fpe::decrypt(alphabet_email_1, key, shadow::fpe::Tweak(), ct, pt_check); 54 | std::cout << "Example 3: encrypt email addresses as strings" << std::endl; 55 | std::cout << " message : " << pt_email << std::endl; 56 | std::cout << " encryption : " << ct << std::endl; 57 | std::cout << " decryption : " << pt_check << std::endl; 58 | std::cout << std::endl; 59 | 60 | // Example 4: encrypt email addresses 61 | // encrypt all numbers and characters, but leave '@' and '.' as it is. 62 | shadow::fpe::Alphabet alphabet_email_2(shadow::fpe::kCharsetNumbers + shadow::fpe::kCharsetLettersLowercase); 63 | shadow::fpe::encrypt_skip_unsupported(alphabet_email_2, key, shadow::fpe::Tweak(), pt_email, ct); 64 | shadow::fpe::decrypt_skip_unsupported(alphabet_email_2, key, shadow::fpe::Tweak(), ct, pt_check); 65 | std::cout << "Example 4: encrypt email addresses and preserve email address format" << std::endl; 66 | std::cout << " message : " << pt_email << std::endl; 67 | std::cout << " encryption : " << ct << std::endl; 68 | std::cout << " decryption : " << pt_check << std::endl; 69 | std::cout << std::endl; 70 | 71 | // Example 5: encrypt email addresses 72 | // encrypt only the parts before '@', and use the rest as tweak. 73 | std::string pt_email_prefix = "my.personal.email"; 74 | shadow::fpe::Tweak tweak_email("@hotmail.com"); 75 | shadow::fpe::encrypt_skip_unsupported(alphabet_email_2, key, tweak_email, pt_email_prefix, ct); 76 | shadow::fpe::decrypt_skip_unsupported(alphabet_email_2, key, tweak_email, ct, pt_check); 77 | std::cout << "Example 5: encrypt email addresses with tweaks" << std::endl; 78 | std::cout << " message : " << pt_email_prefix << std::endl; 79 | std::cout << " encryption : " << ct << std::endl; 80 | std::cout << " decryption : " << pt_check << std::endl; 81 | std::cout << std::endl; 82 | 83 | // Example 6: encrypt physical addresses 84 | // Leave the space and comma as it is, and encrypt the rest. 85 | shadow::fpe::Alphabet alphabet_address(shadow::fpe::kCharsetNumbers + shadow::fpe::kCharsetLettersLowercase); 86 | std::string pt_address = "6666 fpe avenue , san jose, ca, 94000"; 87 | shadow::fpe::encrypt_skip_unsupported(alphabet_address, key, shadow::fpe::Tweak(), pt_address, ct); 88 | shadow::fpe::decrypt_skip_unsupported(alphabet_address, key, shadow::fpe::Tweak(), ct, pt_check); 89 | std::cout << "Example 6: encrypt physical addresses" << std::endl; 90 | std::cout << " message : " << pt_address << std::endl; 91 | std::cout << " encryption : " << ct << std::endl; 92 | std::cout << " decryption : " << pt_check << std::endl; 93 | std::cout << std::endl; 94 | 95 | // Example 7: encrypt physical addresses 96 | // the encryptions of digits are still digits, the encryptions of letters remain letters 97 | std::string ct_temp; 98 | shadow::fpe::encrypt_skip_unsupported( 99 | shadow::fpe::Alphabet(shadow::fpe::kCharsetNumbers), key, shadow::fpe::Tweak(), pt_address, ct_temp); 100 | shadow::fpe::encrypt_skip_unsupported( 101 | shadow::fpe::Alphabet(shadow::fpe::kCharsetLettersLowercase), key, shadow::fpe::Tweak(), ct_temp, ct); 102 | std::cout << "Example 7: encrypt physical addresses and preserve the format of street numbers and zip codes" 103 | << std::endl; 104 | std::cout << " message : " << pt_address << std::endl; 105 | std::cout << " encryption : " << ct << std::endl; 106 | 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /shadow/fpe_go/fpe.go: -------------------------------------------------------------------------------- 1 | package fpe 2 | 3 | // KeyGenerate method generate key and return it's string represent. 4 | func KeyGenerate() (string, error) { 5 | cKey, err := newKey() 6 | if err != nil { 7 | return "", err 8 | } 9 | defer freeKey(cKey) 10 | 11 | err = keyGenerate(cKey) 12 | if err != nil { 13 | return "", err 14 | } 15 | bytesOut, err := keyToBytes(cKey) 16 | if err != nil { 17 | return "", err 18 | } 19 | return string(bytesOut), nil 20 | } 21 | 22 | // Encrypt method takes the given input string and encrypts it. The alphabet, key, and tweak 23 | // used in the encryption process are defined in the Cipher struct. These values are converted 24 | // to C strings and then passed to the core encryption function. After the encryption is done, 25 | // the C strings are freed from memory. If the encryption is successful, the encrypted string 26 | // is returned. If an error occurs during the process, the error is returned. 27 | func Encrypt(alphabet, key, tweak, plaintext string) (string, error) { 28 | cAlphabet, cKey, cTweak, err := convertStr(alphabet, key, tweak) 29 | if err != nil { 30 | return "", err 31 | } 32 | defer freeAlphabet(cAlphabet) 33 | defer freeKey(cKey) 34 | defer freeTweak(cTweak) 35 | 36 | out, err := encrypt(cAlphabet, cKey, cTweak, plaintext) 37 | if err != nil { 38 | return "", err 39 | } 40 | 41 | return out, nil 42 | } 43 | 44 | // Decrypt method takes the given encrypted input string and decrypts it. The alphabet, key, and tweak 45 | // used in the decryption process are defined in the Cipher struct. These values are converted 46 | // to C strings and then passed to the core decryption function. After the decryption is done, 47 | // the C strings are freed from memory. If the decryption is successful, the decrypted string 48 | // is returned. If an error occurs during the process, the error is returned. 49 | func Decrypt(alphabet, key, tweak, ciphertext string) (string, error) { 50 | cAlphabet, cKey, cTweak, err := convertStr(alphabet, key, tweak) 51 | if err != nil { 52 | return "", err 53 | } 54 | defer freeAlphabet(cAlphabet) 55 | defer freeKey(cKey) 56 | defer freeTweak(cTweak) 57 | 58 | out, err := decrypt(cAlphabet, cKey, cTweak, ciphertext) 59 | if err != nil { 60 | return "", err 61 | } 62 | 63 | return out, nil 64 | } 65 | 66 | // EncryptSkipUnsupported performs encryption and skips unsupported characters which are not in alphabet. 67 | func EncryptSkipUnsupported(alphabet, key, tweak, plaintext string) (string, error) { 68 | cAlphabet, cKey, cTweak, err := convertStr(alphabet, key, tweak) 69 | if err != nil { 70 | return "", err 71 | } 72 | defer freeAlphabet(cAlphabet) 73 | defer freeKey(cKey) 74 | defer freeTweak(cTweak) 75 | 76 | out, err := encryptSkipUnsupported(cAlphabet, cKey, cTweak, plaintext) 77 | if err != nil { 78 | return "", err 79 | } 80 | 81 | return out, nil 82 | } 83 | 84 | // DecryptSkipUnsupported performs decryption and skips unsupported characters which are not in alphabet. 85 | func DecryptSkipUnsupported(alphabet, key, tweak, ciphertext string) (string, error) { 86 | cAlphabet, cKey, cTweak, err := convertStr(alphabet, key, tweak) 87 | if err != nil { 88 | return "", err 89 | } 90 | defer freeAlphabet(cAlphabet) 91 | defer freeKey(cKey) 92 | defer freeTweak(cTweak) 93 | 94 | out, err := decryptSkipUnsupported(cAlphabet, cKey, cTweak, ciphertext) 95 | if err != nil { 96 | return "", err 97 | } 98 | 99 | return out, nil 100 | } 101 | 102 | // EncryptSkipSpecified performs encryption and skips specified characters which are defined in a skipped alphabet. 103 | func EncryptSkipSpecified(alphabet, key, tweak, plaintext, skipAlphabet string) (string, error) { 104 | cAlphabet, cKey, cTweak, err := convertStr(alphabet, key, tweak) 105 | if err != nil { 106 | return "", err 107 | } 108 | cSkipAlphabet, err := newAlphabet(skipAlphabet) 109 | if err != nil { 110 | return "", err 111 | } 112 | defer freeAlphabet(cAlphabet) 113 | defer freeAlphabet(cSkipAlphabet) 114 | defer freeKey(cKey) 115 | defer freeTweak(cTweak) 116 | 117 | out, err := encryptSkipSpecified(cAlphabet, cSkipAlphabet, cKey, cTweak, plaintext) 118 | if err != nil { 119 | return "", err 120 | } 121 | 122 | return out, nil 123 | } 124 | 125 | // DecryptSkipSpecified performs decryption and skips specified characters which are defined in a skipped alphabet. 126 | func DecryptSkipSpecified(alphabet, key, tweak, ciphertext, skipAlphabet string) (string, error) { 127 | cAlphabet, cKey, cTweak, err := convertStr(alphabet, key, tweak) 128 | if err != nil { 129 | return "", err 130 | } 131 | cSkipAlphabet, err := newAlphabet(skipAlphabet) 132 | if err != nil { 133 | return "", err 134 | } 135 | defer freeAlphabet(cAlphabet) 136 | defer freeAlphabet(cSkipAlphabet) 137 | defer freeKey(cKey) 138 | defer freeTweak(cTweak) 139 | 140 | out, err := decryptSkipSpecified(cAlphabet, cSkipAlphabet, cKey, cTweak, ciphertext) 141 | if err != nil { 142 | return "", err 143 | } 144 | 145 | return out, nil 146 | } 147 | 148 | // BatchEncrypt performs batch encryption. 149 | func BatchEncrypt(alphabet, key, tweak string, plaintext []string) ([]string, error) { 150 | cAlphabet, cKey, cTweak, err := convertStr(alphabet, key, tweak) 151 | if err != nil { 152 | return nil, err 153 | } 154 | defer freeAlphabet(cAlphabet) 155 | defer freeKey(cKey) 156 | defer freeTweak(cTweak) 157 | 158 | out := make([]string, len(plaintext)) 159 | for _, p := range plaintext { 160 | res, err := encrypt(cAlphabet, cKey, cTweak, p) 161 | if err != nil { 162 | return nil, err 163 | } 164 | out = append(out, res) 165 | } 166 | 167 | return out, nil 168 | } 169 | 170 | // BatchDecrypt performs batch decryption. 171 | func BatchDecrypt(alphabet, key, tweak string, plaintext []string) ([]string, error) { 172 | 173 | cAlphabet, cKey, cTweak, err := convertStr(alphabet, key, tweak) 174 | if err != nil { 175 | return nil, err 176 | } 177 | defer freeAlphabet(cAlphabet) 178 | defer freeKey(cKey) 179 | defer freeTweak(cTweak) 180 | 181 | out := make([]string, len(plaintext)) 182 | for _, p := range plaintext { 183 | res, err := decrypt(cAlphabet, cKey, cTweak, p) 184 | if err != nil { 185 | return nil, err 186 | } 187 | out = append(out, res) 188 | } 189 | 190 | return out, nil 191 | } 192 | -------------------------------------------------------------------------------- /example/fpe_go_example/fpe_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | fpe "github.com/tiktok-privacy-innovation/shadowgraphy/shadow/fpe_go" 6 | ) 7 | 8 | func ExampleCreditCardNumber() { 9 | // Example 1: credit card number 10 | pt := "4263982640269299" 11 | // 0-9 12 | alphabet := fpe.KCharsetNumbers 13 | byteKey := []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} 14 | tweak := "" 15 | encrypted, err := fpe.Encrypt(alphabet, string(byteKey), tweak, pt) 16 | if (err != nil) { 17 | fmt.Println("Error!") 18 | } 19 | 20 | ptCheck, err := fpe.Decrypt(alphabet, string(byteKey), tweak, encrypted) 21 | if (err != nil) { 22 | fmt.Println("Error!") 23 | } 24 | 25 | fmt.Println("Example 1: encrypt credit card numbers:") 26 | fmt.Print(" message : ") 27 | fmt.Println(pt) 28 | fmt.Print(" encryption : ") 29 | fmt.Println(encrypted) 30 | fmt.Print(" decryption : ") 31 | fmt.Println(ptCheck) 32 | fmt.Println() 33 | } 34 | 35 | func ExampleCreditCardNumbeWithTweak() { 36 | // Example 2: credit card number with tweaks 37 | // use first 4 digits and last 6 digits as tweak, and encrypt middle 6 digits 38 | pt := "4263982640269299" 39 | ptMiddleSix := pt[6: 12] 40 | // 0-9 41 | alphabet := fpe.KCharsetNumbers 42 | byteKey := []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} 43 | tweak := pt[0: 6] + pt[12: 16] 44 | encrypted, err := fpe.Encrypt(alphabet, string(byteKey), tweak, ptMiddleSix) 45 | if (err != nil) { 46 | fmt.Println("Error!") 47 | } 48 | 49 | ptCheck, err := fpe.Decrypt(alphabet, string(byteKey), tweak, encrypted) 50 | if (err != nil) { 51 | fmt.Println("Error!") 52 | } 53 | 54 | fmt.Println("Example 2: encrypt credit card numbers with tweaks:") 55 | fmt.Print(" message : ") 56 | fmt.Println(ptMiddleSix) 57 | fmt.Print(" encryption : ") 58 | fmt.Println(encrypted) 59 | fmt.Print(" decryption : ") 60 | fmt.Println(ptCheck) 61 | fmt.Println() 62 | } 63 | 64 | func ExampleEmailAddressAll() { 65 | // Example 3: encrypt an email address as a string. 66 | pt := "my.personal.email@hotmail.com" 67 | // 0-9a-z@. 68 | alphabet := fpe.KCharsetNumbers + fpe.KCharsetLettersLowercase + "@." 69 | byteKey := []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} 70 | tweak := "" 71 | encrypted, err := fpe.Encrypt(alphabet, string(byteKey), tweak, pt) 72 | if (err != nil) { 73 | fmt.Println("Error!") 74 | } 75 | 76 | ptCheck, err := fpe.Decrypt(alphabet, string(byteKey), tweak, encrypted) 77 | if (err != nil) { 78 | fmt.Println("Error!") 79 | } 80 | 81 | fmt.Println("Example 3: encrypt email addresses as strings:") 82 | fmt.Print(" message : ") 83 | fmt.Println(pt) 84 | fmt.Print(" encryption : ") 85 | fmt.Println(encrypted) 86 | fmt.Print(" decryption : ") 87 | fmt.Println(ptCheck) 88 | fmt.Println() 89 | } 90 | 91 | func ExampleEmailAddressPart() { 92 | // Example 4: encrypt email addresses 93 | // encrypt all numbers and characters, but leave '@' and '.' as it is. 94 | pt := "my.personal.email@hotmail.com" 95 | // a-z@. 96 | alphabet := fpe.KCharsetNumbers + fpe.KCharsetLettersLowercase 97 | byteKey := []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} 98 | tweak := "" 99 | encrypted, err := fpe.EncryptSkipUnsupported(alphabet, string(byteKey), tweak, pt) 100 | if (err != nil) { 101 | fmt.Println("Error!") 102 | } 103 | 104 | ptCheck, err := fpe.DecryptSkipUnsupported(alphabet, string(byteKey), tweak, encrypted) 105 | if (err != nil) { 106 | fmt.Println("Error!") 107 | } 108 | 109 | fmt.Println("Example 4: encrypt email addresses and preserve email address format:") 110 | fmt.Print(" message : ") 111 | fmt.Println(pt) 112 | fmt.Print(" encryption : ") 113 | fmt.Println(encrypted) 114 | fmt.Print(" decryption : ") 115 | fmt.Println(ptCheck) 116 | fmt.Println() 117 | } 118 | 119 | func ExampleEmailAddressHalf() { 120 | // Example 5: encrypt email addresses 121 | // encrypt only the parts before '@', and use the rest as tweak 122 | pt := "my.personal.email" 123 | // a-z@. 124 | alphabet := fpe.KCharsetNumbers + fpe.KCharsetLettersLowercase 125 | byteKey := []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} 126 | tweak := "@hotmail.com" 127 | encrypted, err := fpe.EncryptSkipUnsupported(alphabet, string(byteKey), tweak, pt) 128 | if (err != nil) { 129 | fmt.Println("Error!") 130 | } 131 | 132 | ptCheck, err := fpe.DecryptSkipUnsupported(alphabet, string(byteKey), tweak, encrypted) 133 | if (err != nil) { 134 | fmt.Println("Error!") 135 | } 136 | 137 | fmt.Println("Example 5: encrypt email addresses with tweaks:") 138 | fmt.Print(" message : ") 139 | fmt.Println(pt) 140 | fmt.Print(" encryption : ") 141 | fmt.Println(encrypted) 142 | fmt.Print(" decryption : ") 143 | fmt.Println(ptCheck) 144 | fmt.Println() 145 | } 146 | 147 | func ExampleResidentialAddress() { 148 | // Example 6: encrypt physical addresses 149 | // Leave the space and comma as it is, and encrypt the rest. 150 | pt := "6666 fpe avenue , san jose, ca, 94000" 151 | // a-z@. 152 | alphabet := fpe.KCharsetNumbers + fpe.KCharsetLettersLowercase 153 | byteKey := []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} 154 | tweak := "" 155 | encrypted, err := fpe.EncryptSkipUnsupported(alphabet, string(byteKey), tweak, pt) 156 | if (err != nil) { 157 | fmt.Println("Error!") 158 | } 159 | 160 | ptCheck, err := fpe.DecryptSkipUnsupported(alphabet, string(byteKey), tweak, encrypted) 161 | if (err != nil) { 162 | fmt.Println("Error!") 163 | } 164 | 165 | fmt.Println("Example 6: encrypt physical addresses:") 166 | fmt.Print(" message : ") 167 | fmt.Println(pt) 168 | fmt.Print(" encryption : ") 169 | fmt.Println(encrypted) 170 | fmt.Print(" decryption : ") 171 | fmt.Println(ptCheck) 172 | fmt.Println() 173 | } 174 | 175 | func ExampleResidentialAddressPart() { 176 | // Example 7: encrypt physical addresses 177 | // the encryptions of digits are still digits, the encryptions of letters remain letters 178 | pt := "6666 fpe avenue , san jose, ca, 94000" 179 | byteKey := []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} 180 | tweak := "" 181 | ctTemp, err := fpe.EncryptSkipUnsupported(fpe.KCharsetNumbers, string(byteKey), tweak, pt) 182 | if (err != nil) { 183 | fmt.Println("Error!") 184 | } 185 | 186 | encrypted, err := fpe.EncryptSkipUnsupported(fpe.KCharsetLettersLowercase, string(byteKey), tweak, ctTemp) 187 | if (err != nil) { 188 | fmt.Println("Error!") 189 | } 190 | 191 | fmt.Println("Example 7: encrypt physical addresses and preserve the format of street numbers and zip codes:") 192 | fmt.Print(" message : ") 193 | fmt.Println(pt) 194 | fmt.Print(" encryption : ") 195 | fmt.Println(encrypted) 196 | } 197 | 198 | func main() { 199 | ExampleCreditCardNumber() 200 | ExampleCreditCardNumbeWithTweak() 201 | ExampleEmailAddressAll() 202 | ExampleEmailAddressPart() 203 | ExampleEmailAddressHalf() 204 | ExampleResidentialAddress() 205 | ExampleResidentialAddressPart() 206 | } 207 | -------------------------------------------------------------------------------- /shadow/fpe_go/fpe_wrapper.go: -------------------------------------------------------------------------------- 1 | package fpe 2 | 3 | // #cgo LDFLAGS: -lshadow_fpe_export 4 | // #include "fpe_export.h" 5 | import "C" 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | // ShadowFPEAlphabetSizeMin Alphabet should have at least two characters. 12 | ShadowFPEAlphabetSizeMin = 2 13 | 14 | // ShadowFPEAlphabetSizeMax Restricts alphabets to 8-bit characters. 15 | ShadowFPEAlphabetSizeMax = 256 16 | 17 | // ShadowFPEMessageLenMin The minimum number of characters in an input or output message. 18 | ShadowFPEMessageLenMin = 0x2 19 | 20 | // ShadowFPEMessageLenMax The maximum number of characters in an input or output message. 21 | ShadowFPEMessageLenMax = 0x7FFFFFFF 22 | 23 | // ShadowFPEKeyByteCount The number of bytes in a key. 24 | ShadowFPEKeyByteCount = 16 25 | 26 | // ShadowFPEKeyBitCount The number of bytes in a key. 27 | ShadowFPEKeyBitCount = ShadowFPEKeyByteCount * 8 28 | 29 | // ShadowFPETweakByteCountMax The maximum number of bytes in a tweak. 30 | ShadowFPETweakByteCountMax = 0x7FFFFFFF 31 | 32 | // ShadowFF1NumRounds The number of rounds in FF1. 33 | ShadowFF1NumRounds = 10 34 | 35 | // KCharsetNumbers Arabic number characters 0-9. 36 | KCharsetNumbers = "0123456789" 37 | 38 | // KCharsetLettersLowercase English lower-case letter characters a-z. 39 | KCharsetLettersLowercase = "abcdefghijklmnopqrstuvwxyz" 40 | 41 | // KCharsetLettersUppercase English upper-case letter characters A-Z. 42 | KCharsetLettersUppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 43 | ) 44 | 45 | // newAlphabet constructs an alphabet from a given set of characters. 46 | // It throws an error if charset's size is empty or larger than ShadowFPEAlphabetSizeMax, or if character set has duplication. 47 | func newAlphabet(charset string) (*C.FPEAlphabet, error) { 48 | cString := C.CString(charset) 49 | defer C.free(unsafe.Pointer(cString)) 50 | 51 | cBytes := C.FPEBytes{} 52 | 53 | cBytes.data = cString 54 | cBytes.len = C.size_t(len(charset)) 55 | var cStatus C.FPEStatus = C.S_FALSE 56 | 57 | var alphabet *C.FPEAlphabet = C.fpe_alphabet_new(&cBytes, &cStatus) 58 | if cStatus != C.S_OK { 59 | return nil, newFPEError("failed to construct alphabet", int64(cStatus)) 60 | } 61 | return alphabet, nil 62 | } 63 | 64 | func freeAlphabet(alphabet *C.FPEAlphabet) { 65 | C.fpe_alphabet_free(alphabet) 66 | } 67 | 68 | // newKey constructs a key pointer. 69 | // Key is a key with 128 bits. 70 | func newKey() (*C.FPEKey, error) { 71 | var cStatus C.FPEStatus = C.S_FALSE 72 | var key *C.FPEKey = C.fpe_key_new(&cStatus) 73 | if cStatus != C.S_OK { 74 | return nil, newFPEError("failed to construct key", int64(cStatus)) 75 | } 76 | return key, nil 77 | } 78 | 79 | func freeKey(key *C.FPEKey) { 80 | C.fpe_key_free(key) 81 | } 82 | 83 | // keyFromBytes constructs a key from an array of 16 bytes. 84 | func keyFromBytes(goBytes []byte, key *C.FPEKey) error { 85 | cString := C.CString(string(goBytes)) 86 | defer C.free(unsafe.Pointer(cString)) 87 | cBytes := C.FPEBytes{} 88 | cBytes.data = cString 89 | cBytes.len = C.size_t(len(goBytes)) 90 | 91 | cStatus := C.fpe_key_from_bytes(key, &cBytes) 92 | if cStatus != C.S_OK { 93 | return newFPEError("failed to key from bytes", int64(cStatus)) 94 | } 95 | return nil 96 | } 97 | 98 | // keyToBytes return the context of a key within an array of 16 bytes. 99 | func keyToBytes(key *C.FPEKey) ([]byte, error) { 100 | cData := C.malloc(C.size_t(ShadowFPEKeyByteCount)) 101 | defer C.free(unsafe.Pointer(cData)) 102 | 103 | cBytes := C.FPEBytes{} 104 | cBytes.data = (*C.char)(cData) 105 | cBytes.len = C.size_t(ShadowFPEKeyByteCount) 106 | 107 | cStatus := C.fpe_key_to_bytes(key, &cBytes) 108 | if cStatus != C.S_OK { 109 | return nil, newFPEError("failed to key to bytes", int64(cStatus)) 110 | } 111 | return C.GoBytes(unsafe.Pointer(cBytes.data), (C.int)(cBytes.len)), nil 112 | } 113 | 114 | // keyGenerate generate random key 115 | func keyGenerate(key *C.FPEKey) error { 116 | cStatus := C.fpe_key_generate(key) 117 | if cStatus != C.S_OK { 118 | return newFPEError("failed to key generate", int64(cStatus)) 119 | } 120 | return nil 121 | } 122 | 123 | // newTweak constructs a tweak pointer. 124 | // It throws an error if tweak is longer than ShadowFPETweakByteCountMax. 125 | func newTweak() (*C.FPETweak, error) { 126 | var cStatus C.FPEStatus = C.S_FALSE 127 | var tweak *C.FPETweak = C.fpe_tweak_new(&cStatus) 128 | if cStatus != C.S_OK { 129 | return nil, newFPEError("failed to construct tweak", int64(cStatus)) 130 | } 131 | return tweak, nil 132 | } 133 | 134 | // tweakFill constructs a tweak from a string. 135 | // It throws an error if tweak is longer than ShadowFPETweakByteCountMax. 136 | func tweakFill(goBytes []byte, tweak *C.FPETweak) error { 137 | cString := C.CString(string(goBytes)) 138 | defer C.free(unsafe.Pointer(cString)) 139 | cBytes := C.FPEBytes{} 140 | cBytes.data = cString 141 | cBytes.len = C.size_t(len(goBytes)) 142 | 143 | cStatus := C.fpe_tweak_fill(tweak, &cBytes) 144 | if cStatus != C.S_OK { 145 | return newFPEError("failed to fill tweak", int64(cStatus)) 146 | } 147 | return nil 148 | } 149 | 150 | func freeTweak(tweak *C.FPETweak) { 151 | C.fpe_tweak_free(tweak) 152 | } 153 | 154 | // encrypt performs encryption and throws if any character is unsupported. 155 | func encrypt(alphabet *C.FPEAlphabet, key *C.FPEKey, tweak *C.FPETweak, in string) (string, error) { 156 | cString := C.CString(in) 157 | defer C.free(unsafe.Pointer(cString)) 158 | cBytesIn := C.FPEBytes{} 159 | cBytesIn.data = cString 160 | cBytesIn.len = C.size_t(len(in)) 161 | 162 | cData := C.malloc(C.size_t(len(in))) 163 | defer C.free(unsafe.Pointer(cData)) 164 | 165 | cBytesOut := C.FPEBytes{} 166 | cBytesOut.data = (*C.char)(cData) 167 | cBytesOut.len = C.size_t(len(in)) 168 | 169 | cStatus := C.fpe_encrypt(alphabet, key, tweak, &cBytesIn, &cBytesOut) 170 | if cStatus != C.S_OK { 171 | return "", newFPEError("failed to encrypt", int64(cStatus)) 172 | } 173 | return C.GoStringN(cBytesOut.data, (C.int)(cBytesOut.len)), nil 174 | } 175 | 176 | // decrypt performs decryption and throws if any character is unsupported. 177 | func decrypt(alphabet *C.FPEAlphabet, key *C.FPEKey, tweak *C.FPETweak, in string) (string, error) { 178 | cString := C.CString(in) 179 | defer C.free(unsafe.Pointer(cString)) 180 | cBytesIn := C.FPEBytes{} 181 | cBytesIn.data = cString 182 | cBytesIn.len = C.size_t(len(in)) 183 | 184 | cData := C.malloc(C.size_t(len(in))) 185 | defer C.free(unsafe.Pointer(cData)) 186 | 187 | cBytesOut := C.FPEBytes{} 188 | cBytesOut.data = (*C.char)(cData) 189 | cBytesOut.len = C.size_t(len(in)) 190 | 191 | cStatus := C.fpe_decrypt(alphabet, key, tweak, &cBytesIn, &cBytesOut) 192 | if cStatus != C.S_OK { 193 | return "", newFPEError("failed to decrypt", int64(cStatus)) 194 | } 195 | return C.GoStringN(cBytesOut.data, (C.int)(cBytesOut.len)), nil 196 | } 197 | 198 | // encryptSkipUnsupported performs encryption and skips unsupported characters. 199 | func encryptSkipUnsupported(alphabet *C.FPEAlphabet, key *C.FPEKey, tweak *C.FPETweak, in string) (string, error) { 200 | cString := C.CString(in) 201 | defer C.free(unsafe.Pointer(cString)) 202 | cBytesIn := C.FPEBytes{} 203 | cBytesIn.data = cString 204 | cBytesIn.len = C.size_t(len(in)) 205 | 206 | cData := C.malloc(C.size_t(len(in))) 207 | defer C.free(unsafe.Pointer(cData)) 208 | 209 | cBytesOut := C.FPEBytes{} 210 | cBytesOut.data = (*C.char)(cData) 211 | cBytesOut.len = C.size_t(len(in)) 212 | 213 | cStatus := C.fpe_encrypt_skip_unsupported(alphabet, key, tweak, &cBytesIn, &cBytesOut) 214 | if cStatus != C.S_OK { 215 | return "", newFPEError("failed to encrypt skip unsupported", int64(cStatus)) 216 | } 217 | return C.GoStringN(cBytesOut.data, (C.int)(cBytesOut.len)), nil 218 | } 219 | 220 | // decryptSkipUnsupported performs decryption and skips unsupported characters. 221 | func decryptSkipUnsupported(alphabet *C.FPEAlphabet, key *C.FPEKey, tweak *C.FPETweak, in string) (string, error) { 222 | cString := C.CString(in) 223 | defer C.free(unsafe.Pointer(cString)) 224 | cBytesIn := C.FPEBytes{} 225 | cBytesIn.data = cString 226 | cBytesIn.len = C.size_t(len(in)) 227 | 228 | cData := C.malloc(C.size_t(len(in))) 229 | defer C.free(unsafe.Pointer(cData)) 230 | 231 | cBytesOut := C.FPEBytes{} 232 | cBytesOut.data = (*C.char)(cData) 233 | cBytesOut.len = C.size_t(len(in)) 234 | 235 | cStatus := C.fpe_decrypt_skip_unsupported(alphabet, key, tweak, &cBytesIn, &cBytesOut) 236 | if cStatus != C.S_OK { 237 | return "", newFPEError("failed to decrypt skip unsupported", int64(cStatus)) 238 | } 239 | return C.GoStringN(cBytesOut.data, (C.int)(cBytesOut.len)), nil 240 | } 241 | 242 | // encryptSkipSpecified performs encryption and skips specified characters. 243 | func encryptSkipSpecified(alphabet *C.FPEAlphabet, alphabetSkip *C.FPEAlphabet, key *C.FPEKey, tweak *C.FPETweak, in string) (string, error) { 244 | cString := C.CString(in) 245 | defer C.free(unsafe.Pointer(cString)) 246 | cBytesIn := C.FPEBytes{} 247 | cBytesIn.data = cString 248 | cBytesIn.len = C.size_t(len(in)) 249 | 250 | cData := C.malloc(C.size_t(len(in))) 251 | defer C.free(unsafe.Pointer(cData)) 252 | 253 | cBytesOut := C.FPEBytes{} 254 | cBytesOut.data = (*C.char)(cData) 255 | cBytesOut.len = C.size_t(len(in)) 256 | 257 | cStatus := C.fpe_encrypt_skip_specified(alphabet, alphabetSkip, key, tweak, &cBytesIn, &cBytesOut) 258 | if cStatus != C.S_OK { 259 | return "", newFPEError("failed to encrypt skip specified", int64(cStatus)) 260 | } 261 | return C.GoStringN(cBytesOut.data, (C.int)(cBytesOut.len)), nil 262 | } 263 | 264 | // decryptSkipSpecified performs decryption and skips specified characters. 265 | func decryptSkipSpecified(alphabet *C.FPEAlphabet, alphabetSkip *C.FPEAlphabet, key *C.FPEKey, tweak *C.FPETweak, in string) (string, error) { 266 | cString := C.CString(in) 267 | defer C.free(unsafe.Pointer(cString)) 268 | cBytesIn := C.FPEBytes{} 269 | cBytesIn.data = cString 270 | cBytesIn.len = C.size_t(len(in)) 271 | 272 | cData := C.malloc(C.size_t(len(in))) 273 | defer C.free(unsafe.Pointer(cData)) 274 | 275 | cBytesOut := C.FPEBytes{} 276 | cBytesOut.data = (*C.char)(cData) 277 | cBytesOut.len = C.size_t(len(in)) 278 | 279 | cStatus := C.fpe_decrypt_skip_specified(alphabet, alphabetSkip, key, tweak, &cBytesIn, &cBytesOut) 280 | if cStatus != C.S_OK { 281 | return "", newFPEError("failed to decrypt skip specified", int64(cStatus)) 282 | } 283 | return C.GoStringN(cBytesOut.data, (C.int)(cBytesOut.len)), nil 284 | } 285 | 286 | func convertStr(alphabet, key, tweak string) (*C.FPEAlphabet, *C.FPEKey, *C.FPETweak, error) { 287 | cAlphabet, err := newAlphabet(alphabet) 288 | if err != nil { 289 | return nil, nil, nil, err 290 | } 291 | cKey, err := newKey() 292 | if err != nil { 293 | return nil, nil, nil, err 294 | } 295 | err = keyFromBytes([]byte(key), cKey) 296 | if err != nil { 297 | return nil, nil, nil, err 298 | } 299 | cTweak, err := newTweak() 300 | if err != nil { 301 | return nil, nil, nil, err 302 | } 303 | err = tweakFill([]byte(tweak), cTweak) 304 | if err != nil { 305 | return nil, nil, nil, err 306 | } 307 | return cAlphabet, cKey, cTweak, nil 308 | } 309 | -------------------------------------------------------------------------------- /shadow/fpe_export/fpe_export_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "shadow/fpe_export/fpe_export.h" 16 | 17 | #include 18 | 19 | #include "gtest/gtest.h" 20 | 21 | namespace shadowtest { 22 | 23 | TEST(FPEBytesTest, fpe_alphabet_new) { 24 | char str[] = "0123456789"; 25 | FPEBytes charset; 26 | charset.data = str; 27 | charset.len = strlen(str); 28 | FPEStatus status = S_FALSE; 29 | 30 | FPEAlphabet* alphabet = fpe_alphabet_new(&charset, &status); 31 | EXPECT_EQ(status, S_OK); 32 | 33 | std::size_t size = 0; 34 | status = fpe_alphabet_size(alphabet, &size); 35 | EXPECT_EQ(charset.len, size); 36 | 37 | status = fpe_alphabet_free(alphabet); 38 | EXPECT_EQ(status, S_OK); 39 | } 40 | 41 | TEST(FPEBytesTest, fpe_alphabet_are_identical) { 42 | char str0[] = "0123456789"; 43 | char str1[] = "9876543210"; 44 | char str2[] = "abcd"; 45 | FPEBytes charset0; 46 | charset0.data = str0; 47 | charset0.len = strlen(str0); 48 | FPEBytes charset1; 49 | charset1.data = str1; 50 | charset1.len = strlen(str1); 51 | FPEBytes charset2; 52 | charset2.data = str2; 53 | charset2.len = strlen(str2); 54 | 55 | FPEStatus status = S_FALSE; 56 | 57 | FPEAlphabet* alphabet0 = fpe_alphabet_new(&charset0, &status); 58 | EXPECT_EQ(status, S_OK); 59 | FPEAlphabet* alphabet1 = fpe_alphabet_new(&charset1, &status); 60 | EXPECT_EQ(status, S_OK); 61 | FPEAlphabet* alphabet2 = fpe_alphabet_new(&charset2, &status); 62 | EXPECT_EQ(status, S_OK); 63 | 64 | bool result; 65 | fpe_alphabet_are_identical(alphabet0, alphabet1, &result); 66 | EXPECT_EQ(result, true); 67 | fpe_alphabet_are_identical(alphabet0, alphabet2, &result); 68 | EXPECT_EQ(result, false); 69 | fpe_alphabet_are_exclusive(alphabet0, alphabet2, &result); 70 | EXPECT_EQ(result, true); 71 | 72 | status = fpe_alphabet_free(alphabet0); 73 | EXPECT_EQ(status, S_OK); 74 | status = fpe_alphabet_free(alphabet1); 75 | EXPECT_EQ(status, S_OK); 76 | status = fpe_alphabet_free(alphabet2); 77 | EXPECT_EQ(status, S_OK); 78 | } 79 | 80 | TEST(FPEBytesTest, fpe_key_new) { 81 | FPEStatus status = S_FALSE; 82 | 83 | FPEKey* key0 = fpe_key_new(&status); 84 | EXPECT_EQ(status, S_OK); 85 | 86 | FPEKey* key1 = fpe_key_new(&status); 87 | EXPECT_EQ(status, S_OK); 88 | 89 | status = fpe_key_generate(key0); 90 | EXPECT_EQ(status, S_OK); 91 | 92 | std::vector buffer0(16, 0); 93 | std::vector buffer1(16, 0); 94 | FPEBytes bytes0; 95 | bytes0.data = buffer0.data(); 96 | bytes0.len = buffer0.size(); 97 | 98 | status = fpe_key_to_bytes(key0, &bytes0); 99 | EXPECT_EQ(status, S_OK); 100 | EXPECT_NE(buffer0, buffer1); 101 | 102 | status = fpe_key_from_bytes(key1, &bytes0); 103 | EXPECT_EQ(status, S_OK); 104 | 105 | FPEBytes bytes1; 106 | bytes1.data = buffer1.data(); 107 | bytes1.len = buffer1.size(); 108 | 109 | status = fpe_key_to_bytes(key1, &bytes1); 110 | EXPECT_EQ(status, S_OK); 111 | EXPECT_EQ(buffer0, buffer1); 112 | 113 | status = fpe_key_free(key0); 114 | EXPECT_EQ(status, S_OK); 115 | 116 | status = fpe_key_free(key1); 117 | EXPECT_EQ(status, S_OK); 118 | } 119 | 120 | TEST(FPEBytesTest, fpe_tweak_new) { 121 | std::vector buffer = {0, 1, 2, 3}; 122 | 123 | FPEStatus status = S_FALSE; 124 | 125 | FPETweak* tweak = fpe_tweak_new(&status); 126 | EXPECT_EQ(status, S_OK); 127 | EXPECT_NE(*reinterpret_cast*>(tweak), buffer); 128 | 129 | FPEBytes bytes; 130 | bytes.data = buffer.data(); 131 | bytes.len = buffer.size(); 132 | 133 | status = fpe_tweak_fill(tweak, &bytes); 134 | EXPECT_EQ(status, S_OK); 135 | EXPECT_EQ(*reinterpret_cast*>(tweak), buffer); 136 | 137 | status = fpe_tweak_free(tweak); 138 | EXPECT_EQ(status, S_OK); 139 | } 140 | 141 | TEST(FPEBytesTest, encrypt_decrypt) { 142 | char charset_chr[] = "0123456789"; 143 | FPEBytes charset; 144 | charset.data = charset_chr; 145 | charset.len = strlen(charset_chr); 146 | FPEStatus status = S_FALSE; 147 | 148 | FPEAlphabet* alphabet = fpe_alphabet_new(&charset, &status); 149 | EXPECT_EQ(status, S_OK); 150 | 151 | char pt_str[] = "0123456789"; 152 | FPEBytes pt; 153 | pt.data = pt_str; 154 | pt.len = strlen(pt_str); 155 | 156 | std::vector ct_buffer(strlen(pt_str), 0); 157 | FPEBytes ct; 158 | ct.data = ct_buffer.data(); 159 | ct.len = ct_buffer.size(); 160 | 161 | std::vector test_key_buffer = { 162 | 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}; 163 | FPEBytes key_bytes; 164 | key_bytes.data = reinterpret_cast(test_key_buffer.data()); 165 | key_bytes.len = test_key_buffer.size(); 166 | 167 | FPEKey* key = fpe_key_new(&status); 168 | EXPECT_EQ(status, S_OK); 169 | status = fpe_key_from_bytes(key, &key_bytes); 170 | EXPECT_EQ(status, S_OK); 171 | 172 | FPETweak* tweak = fpe_tweak_new(&status); 173 | EXPECT_EQ(status, S_OK); 174 | 175 | status = fpe_encrypt(alphabet, key, tweak, &pt, &ct); 176 | EXPECT_EQ(status, S_OK); 177 | 178 | std::string ct_str(ct.data, ct.len); 179 | EXPECT_EQ(ct_str, "2433477484"); 180 | 181 | std::vector pt_check_buffer(strlen(pt_str), 0); 182 | FPEBytes pt_check; 183 | pt_check.data = pt_check_buffer.data(); 184 | pt_check.len = pt_check_buffer.size(); 185 | 186 | status = fpe_decrypt(alphabet, key, tweak, &ct, &pt_check); 187 | 188 | std::string pt_check_str(pt_check.data, pt_check.len); 189 | EXPECT_EQ(pt_check_str, "0123456789"); 190 | 191 | status = fpe_alphabet_free(alphabet); 192 | EXPECT_EQ(status, S_OK); 193 | status = fpe_key_free(key); 194 | EXPECT_EQ(status, S_OK); 195 | status = fpe_tweak_free(tweak); 196 | EXPECT_EQ(status, S_OK); 197 | } 198 | 199 | TEST(FPEBytesTest, encrypt_decrypt_skip_unsupported) { 200 | char charset_chr[] = "0123456789"; 201 | FPEBytes charset; 202 | charset.data = charset_chr; 203 | charset.len = strlen(charset_chr); 204 | FPEStatus status = S_FALSE; 205 | 206 | FPEAlphabet* alphabet = fpe_alphabet_new(&charset, &status); 207 | EXPECT_EQ(status, S_OK); 208 | 209 | char pt_str[] = "01234@56789"; 210 | FPEBytes pt; 211 | pt.data = pt_str; 212 | pt.len = strlen(pt_str); 213 | 214 | std::vector ct_buffer(strlen(pt_str), 0); 215 | FPEBytes ct; 216 | ct.data = ct_buffer.data(); 217 | ct.len = ct_buffer.size(); 218 | 219 | std::vector test_key_buffer = { 220 | 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}; 221 | FPEBytes key_bytes; 222 | key_bytes.data = reinterpret_cast(test_key_buffer.data()); 223 | key_bytes.len = test_key_buffer.size(); 224 | 225 | FPEKey* key = fpe_key_new(&status); 226 | EXPECT_EQ(status, S_OK); 227 | status = fpe_key_from_bytes(key, &key_bytes); 228 | EXPECT_EQ(status, S_OK); 229 | 230 | FPETweak* tweak = fpe_tweak_new(&status); 231 | EXPECT_EQ(status, S_OK); 232 | 233 | status = fpe_encrypt_skip_unsupported(alphabet, key, tweak, &pt, &ct); 234 | EXPECT_EQ(status, S_OK); 235 | 236 | std::string ct_str(ct.data, ct.len); 237 | EXPECT_EQ(ct_str, "24334@77484"); 238 | 239 | std::vector pt_check_buffer(strlen(pt_str), 0); 240 | FPEBytes pt_check; 241 | pt_check.data = pt_check_buffer.data(); 242 | pt_check.len = pt_check_buffer.size(); 243 | 244 | status = fpe_decrypt_skip_unsupported(alphabet, key, tweak, &ct, &pt_check); 245 | 246 | std::string pt_check_str(pt_check.data, pt_check.len); 247 | EXPECT_EQ(pt_check_str, "01234@56789"); 248 | 249 | status = fpe_alphabet_free(alphabet); 250 | EXPECT_EQ(status, S_OK); 251 | status = fpe_key_free(key); 252 | EXPECT_EQ(status, S_OK); 253 | status = fpe_tweak_free(tweak); 254 | EXPECT_EQ(status, S_OK); 255 | } 256 | 257 | TEST(FPEBytesTest, encrypt_decrypt_skip_specified) { 258 | char charset_chr[] = "0123456789"; 259 | FPEBytes charset; 260 | charset.data = charset_chr; 261 | charset.len = strlen(charset_chr); 262 | FPEStatus status = S_FALSE; 263 | 264 | FPEAlphabet* alphabet = fpe_alphabet_new(&charset, &status); 265 | EXPECT_EQ(status, S_OK); 266 | 267 | char specified_chr[] = "-@"; 268 | FPEBytes specified; 269 | specified.data = specified_chr; 270 | specified.len = strlen(specified_chr); 271 | 272 | FPEAlphabet* specification = fpe_alphabet_new(&specified, &status); 273 | EXPECT_EQ(status, S_OK); 274 | 275 | char pt_str[] = "01234-56789"; 276 | FPEBytes pt; 277 | pt.data = pt_str; 278 | pt.len = strlen(pt_str); 279 | 280 | std::vector ct_buffer(strlen(pt_str), 0); 281 | FPEBytes ct; 282 | ct.data = ct_buffer.data(); 283 | ct.len = ct_buffer.size(); 284 | 285 | std::vector test_key_buffer = { 286 | 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}; 287 | FPEBytes key_bytes; 288 | key_bytes.data = reinterpret_cast(test_key_buffer.data()); 289 | key_bytes.len = test_key_buffer.size(); 290 | 291 | FPEKey* key = fpe_key_new(&status); 292 | EXPECT_EQ(status, S_OK); 293 | status = fpe_key_from_bytes(key, &key_bytes); 294 | EXPECT_EQ(status, S_OK); 295 | 296 | FPETweak* tweak = fpe_tweak_new(&status); 297 | EXPECT_EQ(status, S_OK); 298 | 299 | status = fpe_encrypt_skip_specified(alphabet, specification, key, tweak, &pt, &ct); 300 | EXPECT_EQ(status, S_OK); 301 | 302 | std::string ct_str(ct.data, ct.len); 303 | EXPECT_EQ(ct_str, "24334-77484"); 304 | 305 | std::vector pt_check_buffer(strlen(pt_str), 0); 306 | FPEBytes pt_check; 307 | pt_check.data = pt_check_buffer.data(); 308 | pt_check.len = pt_check_buffer.size(); 309 | 310 | status = fpe_decrypt_skip_specified(alphabet, specification, key, tweak, &ct, &pt_check); 311 | 312 | std::string pt_check_str(pt_check.data, pt_check.len); 313 | EXPECT_EQ(pt_check_str, "01234-56789"); 314 | 315 | status = fpe_alphabet_free(alphabet); 316 | EXPECT_EQ(status, S_OK); 317 | status = fpe_alphabet_free(specification); 318 | EXPECT_EQ(status, S_OK); 319 | status = fpe_key_free(key); 320 | EXPECT_EQ(status, S_OK); 321 | status = fpe_tweak_free(tweak); 322 | EXPECT_EQ(status, S_OK); 323 | } 324 | 325 | } // namespace shadowtest 326 | 327 | int main(int argc, char** argv) { 328 | testing::InitGoogleTest(&argc, argv); 329 | return RUN_ALL_TESTS(); 330 | } 331 | -------------------------------------------------------------------------------- /shadow/fpe/fpe.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace shadow { 24 | namespace fpe { 25 | 26 | // @brief Alphabet should have at least two characters. 27 | #define SHADOW_FPE_ALPHABET_SIZE_MIN 2 28 | 29 | // @brief Restricts alphabets to 8-bit characters. 30 | #define SHADOW_FPE_ALPHABET_SIZE_MAX 256 31 | 32 | // @brief The minimum number of characters in an input or output message. 33 | #define SHADOW_FPE_MESSAGE_LEN_MIN 0x2 34 | 35 | // @brief The maximum number of characters in an input or output message. 36 | #define SHADOW_FPE_MESSAGE_LEN_MAX 0xFFFFFFFF 37 | 38 | // @brief The number of bytes in a key. 39 | #define SHADOW_FPE_KEY_BYTE_COUNT 16 40 | 41 | // @brief The number of bytes in a key. 42 | #define SHADOW_FPE_KEY_BIT_COUNT (SHADOW_FPE_KEY_BYTE_COUNT * 8) 43 | 44 | // @brief The maximum number of bytes in a tweak. 45 | #define SHADOW_FPE_TWEAK_BYTE_COUNT_MAX 0xFFFFFFFF 46 | 47 | // @brief The number of rounds in FF1. 48 | #define SHADOW_FF1_NUM_ROUNDS 10 49 | 50 | // @brief Arabic number characters 0-9. 51 | extern const std::string kCharsetNumbers; 52 | 53 | // @brief English lower-case letter characters a-z. 54 | extern const std::string kCharsetLettersLowercase; 55 | 56 | // @brief English upper-case letter characters A-Z. 57 | extern const std::string kCharsetLettersUppercase; 58 | 59 | /** 60 | * @brief Defines an alphabet from a set of unique and sorted characters. 61 | */ 62 | class Alphabet { 63 | public: 64 | /** 65 | * @brief Constructs an alphabet from a given set of characters. 66 | * 67 | * @param[in] charset A set of characters. 68 | * @throws std::invalid_argument if charset's size is empty or larger than SHADOW_FPE_ALPHABET_SIZE_MAX, or if 69 | * character set has duplication. 70 | */ 71 | explicit Alphabet(const std::string& charset); 72 | 73 | /** 74 | * @brief Returns the size of this alphabet. 75 | */ 76 | std::size_t size() const; 77 | 78 | friend bool are_identical(const Alphabet& in_0, const Alphabet& in_1); 79 | 80 | friend bool are_exclusive(const Alphabet& in_0, const Alphabet& in_1); 81 | 82 | protected: 83 | std::vector map_digit_to_char_{}; 84 | 85 | std::unordered_map map_char_to_digit_{}; 86 | }; 87 | 88 | /** 89 | * @brief Returns true if two alphabets are identical. 90 | * 91 | * @param[in] in_0 An alphabet. 92 | * @param[in] in_1 The other alphabet. 93 | */ 94 | bool are_identical(const Alphabet& in_0, const Alphabet& in_1); 95 | 96 | /** 97 | * @brief Returns true if two alphabets have no overlapping characters. 98 | * 99 | * @param[in] in_0 An alphabet. 100 | * @param[in] in_1 The other alphabet. 101 | */ 102 | bool are_exclusive(const Alphabet& in_0, const Alphabet& in_1); 103 | 104 | // @brief A key has 128 bits. 105 | class Key : public std::array { 106 | public: 107 | /** 108 | * @brief Constructs an empty key. 109 | */ 110 | Key() : std::array() { 111 | } 112 | 113 | /** 114 | * @brief Constructs a key from an array of 16 bytes. 115 | * @param[in] tweak A array of 16 bytes. 116 | */ 117 | Key(const std::array& copy) 118 | : std::array(copy) { 119 | } 120 | 121 | /** 122 | * @brief Destructs and wipe data. 123 | */ 124 | ~Key() { 125 | std::fill(this->begin(), this->end(), 0); 126 | } 127 | }; 128 | 129 | /** 130 | * @brief Generate a random key. 131 | */ 132 | Key generate_key(); 133 | 134 | /** 135 | * @brief A tweak has 0 ~ 2^32-1 bytes. 136 | */ 137 | class Tweak : public std::vector { 138 | public: 139 | /** 140 | * @brief Constructs an empty tweak. 141 | */ 142 | Tweak() : std::vector() { 143 | } 144 | 145 | /** 146 | * @brief Constructs a tweak from a vector of bytes. 147 | * @param[in] tweak A vector of bytes. 148 | * @throws std::invadlid_argument if tweak is longer than SHADOW_FPE_TWEAK_BYTE_COUNT_MAX 149 | */ 150 | Tweak(const std::vector& tweak); 151 | 152 | /** 153 | * @brief Constructs a tweak from a string. 154 | * @param[in] tweak A string. 155 | * @throws std::invadlid_argument if tweak is longer than SHADOW_FPE_TWEAK_BYTE_COUNT_MAX 156 | */ 157 | Tweak(const std::string& tweak); 158 | }; 159 | 160 | /** 161 | * @brief Performs encryption and throws if any character is unsupported. 162 | * 163 | * If alphabet is '0'-'9', "37413222" --> "93947487"; "SF3741-NE32:F22" throws. 164 | * If alphabet is '0'-'9' and 'A'-'Z', "SF3741NE32F22" --> "KL9394TC74M87"; "SF3741-NE32:F22" throws. 165 | * 166 | * @param[in] alphabet An alphabet of supported characters. 167 | * @param[in] key An encryption key. 168 | * @param[in] tweak A tweak. 169 | * @param[in] in A string to be encrypted. 170 | * @param[out] out Encryption result. 171 | * @throws std::invalid_argument if input domain is less than 1 million, if message's length is less than 172 | * SHADOW_FPE_MESSAGE_LEN_MIN or larger than SHADOW_FPE_MESSAGE_LEN_MAX, or if message contains an unsupported 173 | * character. 174 | * @throws std::logic_error if encryption generates an unsupported digit. 175 | */ 176 | void encrypt(const Alphabet& alphabet, const Key& key, const std::vector& tweak, const std::string& in, 177 | std::string& out); 178 | 179 | /** 180 | * @brief Performs decryption and throws if any character is unsupported. 181 | * 182 | * If alphabet is '0'-'9', "37413222" --> "93947487"; "SF3741-NE32:F22" throws. 183 | * If alphabet is '0'-'9' and 'A'-'Z', "SF3741NE32F22" --> "KL9394TC74M87"; "SF3741-NE32:F22" throws. 184 | * 185 | * @param[in] alphabet An alphabet of supported characters. 186 | * @param[in] key A decryption key. 187 | * @param[in] tweak A tweak. 188 | * @param[in] in A string to be decrypted. 189 | * @param[out] out Decryption result. 190 | * @throws std::invalid_argument if input domain is less than 1 million, if message's length is less than 191 | * SHADOW_FPE_MESSAGE_LEN_MIN or larger than SHADOW_FPE_MESSAGE_LEN_MAX, or if message contains an unsupported 192 | * character. 193 | * @throws std::logic_error if decryption generates an unsupported digit. 194 | */ 195 | void decrypt(const Alphabet& alphabet, const Key& key, const std::vector& tweak, const std::string& in, 196 | std::string& out); 197 | 198 | /** 199 | * @brief Performs encryption and skips unsupported characters. 200 | * 201 | * @par If alphabet is '0'-'9', "SF3741-NE32:F22" --> "SF9394-NE74:F87". 202 | * 203 | * @param[in] alphabet An alphabet of supported characters. 204 | * @param[in] key An encryption key. 205 | * @param[in] tweak A tweak. 206 | * @param[in] in A string to be encrypted. 207 | * @param[out] out Encryption result. 208 | * @throws std::invalid_argument if input domain is less than 1 million or if message's length is less than 209 | * SHADOW_FPE_MESSAGE_LEN_MIN or larger than SHADOW_FPE_MESSAGE_LEN_MAX. 210 | * @throws std::logic_error if encryption generates an unsupported digit. 211 | */ 212 | void encrypt_skip_unsupported(const Alphabet& alphabet, const Key& key, const std::vector& tweak, 213 | const std::string& in, std::string& out); 214 | 215 | /** 216 | * @brief Performs decryption and skips unsupported characters. 217 | * 218 | * @par If alphabet is '0'-'9', "SF3741-NE32:F22" --> "SF9394-NE74:F87". 219 | * 220 | * @par If alphabet contains only numbers "SF3741-NE32:F22" --> "SF9394-NE74:F87". 221 | * @param[in] alphabet An alphabet of supported characters. 222 | * @param[in] key A decryption key. 223 | * @param[in] tweak A tweak. 224 | * @param[in] in A string to be decrypted. 225 | * @param[out] out Decryption result. 226 | * @throws std::invalid_argument if input domain is less than 1 million or if message's length is less than 227 | * SHADOW_FPE_MESSAGE_LEN_MIN or larger than SHADOW_FPE_MESSAGE_LEN_MAX. 228 | * @throws std::logic_error if decryption generates an unsupported digit. 229 | */ 230 | void decrypt_skip_unsupported(const Alphabet& alphabet, const Key& key, const std::vector& tweak, 231 | const std::string& in, std::string& out); 232 | 233 | /** 234 | * @brief Performs encryption and skips specified characters. 235 | * 236 | * If alphabet is '0'-'9' and specification is 'A'-'Z', '-', and ':', "SF3741-NE32:F22" --> "SF9394-NE74:F87". 237 | * If alphabet is '0'-'9' and 'A'-'Z' and specification is '-' and ':', "SF3741-NE32:F22" --> "KL9394-TC74:M87". 238 | * If alphabet is '0'-'9' and specification is 'A'-'Z', "SF3741-NE32:F22" throws. 239 | * 240 | * @param[in] alphabet An alphabet of supported characters. 241 | * @param[in] specification An alphabet of specified characters to skip. 242 | * @param[in] key An encryption key. 243 | * @param[in] tweak A tweak. 244 | * @param[in] in A string to be encrypted. 245 | * @param[out] out Encryption result. 246 | * @throws std::invalid_argument if input domain is less than 1 million, if message's length is less than 247 | * SHADOW_FPE_MESSAGE_LEN_MIN or larger than SHADOW_FPE_MESSAGE_LEN_MAX, or if message contains an unsupported and 248 | * unspecified character. 249 | * @throws std::logic_error if encryption generates an unsupported digit. 250 | */ 251 | void encrypt_skip_specified(const Alphabet& alphabet, const Alphabet& specification, const Key& key, 252 | const std::vector& tweak, const std::string& in, std::string& out); 253 | 254 | /** 255 | * @brief Performs decryption and skips specified characters. 256 | * 257 | * If alphabet is '0'-'9' and specification is 'A'-'Z', '-', and ':', "SF3741-NE32:F22" --> "SF9394-NE74:F87". 258 | * If alphabet is '0'-'9' and 'A'-'Z' and specification is '-' and ':', "SF3741-NE32:F22" --> "KL9394-TC74:M87". 259 | * If alphabet is '0'-'9' and specification is 'A'-'Z', "SF3741-NE32:F22" throws. 260 | * 261 | * @param[in] alphabet An alphabet of supported characters. 262 | * @param[in] specification An alphabet of specified characters to skip. 263 | * @param[in] key A decryption key. 264 | * @param[in] tweak A tweak. 265 | * @param[in] in A string to be decrypted. 266 | * @param[out] out Decryption result. 267 | * @throws std::invalid_argument if input domain is less than 1 million, if message's length is less than 268 | * SHADOW_FPE_MESSAGE_LEN_MIN or larger than SHADOW_FPE_MESSAGE_LEN_MAX, or if message contains an unsupported and 269 | * unspecified character. 270 | * @throws std::logic_error if decryption generates an unsupported digit. 271 | */ 272 | void decrypt_skip_specified(const Alphabet& alphabet, const Alphabet& specification, const Key& key, 273 | const std::vector& tweak, const std::string& in, std::string& out); 274 | 275 | } // namespace fpe 276 | } // namespace shadow 277 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shadowgraphy 2 | 3 | 4 | 5 | Shadowgraphy is a collection of cryptographic pseudonymization techniques implemented in C/C++ and wrapped in Go. 6 | 7 | ## Supported Cryptographic Algorithms 8 | 9 | ### Format-Preserving Encryption 10 | 11 | Shadowgraphy implements FF1 specified in [NIST SP 800-38G Rev. 1](https://csrc.nist.gov/pubs/sp/800/38/g/r1/ipd). 12 | AES-128 is the only supported block cipher at the moment. 13 | The implementation is heavily optimized, inspired by the research work below. 14 | Encryption and decryption are an order of magnitude faster compared to a baseline implementation (https://github.com/comForte/Format-Preserving-Encryption). 15 | 16 | > F. Betül Durak, Henning Horst, Michael Horst, and Serge Vaudenay. 2021. FAST: Secure and High Performance Format-Preserving Encryption and Tokenization. In Advances in Cryptology – ASIACRYPT 2021: 27th International Conference on the Theory and Application of Cryptology and Information Security, Singapore, December 6–10, 2021, Proceedings, Part III. Springer-Verlag, Berlin, Heidelberg, 465–489. https://doi.org/10.1007/978-3-030-92078-4_16 17 | 18 | 19 | 20 | ## Building Shadowgraphy Components 21 | 22 | ### Building C++ Core Libraries 23 | 24 | #### Requirements 25 | 26 | | System | Toolchain | 27 | |--------|-------------------------------------------------------| 28 | | Linux | Clang++ (>= 5.0) or GNU G++ (>= 5.5), CMake (>= 3.15) | 29 | | macOS | Xcode toolchain (>= 9.3), CMake (>= 3.15) | 30 | 31 | | Optional dependency | Tested version | Use | 32 | |----------------------------------------------------|----------------|-------------------| 33 | | [GoogleTest](https://github.com/google/googletest) | 1.12.1 | For running tests | 34 | 35 | #### CMake Options 36 | 37 | | Compile Options | Values | Default | Description | 38 | |-------------------------|--------|---------|--------------------------------------------| 39 | | `SHADOW_BUILD_TEST` | ON/OFF | OFF | Build C++ and C export test if set to ON. | 40 | | `SHADOW_BUILD_EXAMPLE` | ON/OFF | OFF | Build C++ example if set to ON. | 41 | | `SHADOW_BUILD_UTILS` | ON/OFF | OFF | Download and build utilities if set to ON. | 42 | | `SHADOW_BUILD_C_EXPORT` | ON/OFF | OFF | Build C export library. | 43 | 44 | Assume that all commands presented below are executed in the root directory of Shadowgraphy. 45 | 46 | ```bash 47 | cmake -B build -S . -DCMAKE_BUILD_TYPE=Release 48 | cmake --build build -j 49 | ``` 50 | 51 | Output binaries can be found in "build/lib/" and "build/bin/" directories. 52 | 53 | ### Building C Export Libraries 54 | 55 | Assume that all commands presented below are executed in the root directory of Shadowgraphy. 56 | 57 | ```bash 58 | cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DSHADOW_BUILD_C_EXPORT=ON 59 | cmake --build build -j 60 | ``` 61 | 62 | ### Building Go Wrapper 63 | 64 | Assume that C export libraries are already built and stored in the "build/lib" directory. 65 | Pass `CGO_LDFLAGS='-L../../build/lib'` to build or test Go wrappers. 66 | For example, to test Go wrappers, execute the following comment from the directory "shadow/fpe_go". 67 | 68 | ```bash 69 | CGO_LDFLAGS='-L../../build/lib' go test ./ 70 | ``` 71 | 72 | ## Using Shadowgraphy 73 | 74 | We provide examples on how to use Shadowgraphy in the directory ["example"](example). 75 | 76 | ### Format-Preserving Encryption 77 | 78 | We provide example codes for all the use cases described below. 79 | You can find them in "example/fpe_example.cpp". 80 | To compile the example code, simply turn on the option `SHADOW_BUILD_EXAMPLE` when you build Shadowgraphy, and the executable can be found in "build/bin/". 81 | 82 | Before using format-preserving encryption, there are some questions that you should consider first: 83 | 84 | 1. If the input domain is very small (e.g., less than one million), it is not recommended to use format-preserving encryption according to [NIST SP 800-38G Rev. 1](https://csrc.nist.gov/pubs/sp/800/38/g/r1/ipd). 85 | 2. Tweaks are recommended to enhance security, because format-preserving encryption may be used in settings where the number of possible character strings is relatively small. 86 | Tweaks can be significantly helpful in defending against attacks such as frequency analysis. 87 | 3. The format in the plaintext that you want to preserve should be specified by defining alphabets. 88 | Refer to the email address encryption example below for more details. 89 | 90 | #### Example 1: Credit Card Number 91 | 92 | One example use of format-preserving encryption is to encrypt a 16-digit credit card number. 93 | If you directly apply AES to it, the result will contain symbols that are not printable. 94 | However, if you encrypt it with format-preserving encryption, the result will be another 16-digit number. 95 | The alphabet here is "0123456789". 96 | Here is minimalism code sample in C++ that encrypts a credit card number using format-preserving encryption. 97 | 98 | ```c++ 99 | shadow::fpe::Alphabet alphabet(shadow::fpe::kCharsetNumbers); 100 | std::string pt = "4263982640269299"; 101 | std::string ct; 102 | shadow::fpe::Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 103 | shadow::fpe::encrypt(alphabet, key, shadow::fpe::Tweak(), pt, ct); 104 | // output: "1689887046359822" 105 | ``` 106 | 107 | Tweaks are some plaintext values that can be regarded as a changeable part of the key. 108 | If you encrypt the same plaintext with the same key, but with different tweaks, the result will be different. 109 | Below is an example usage of tweaks in credit card number encryption from [NIST SP 800-38G Rev. 1](https://csrc.nist.gov/pubs/sp/800/38/g/r1/ipd). 110 | 111 | > "Suppose that in an application for credit card numbers, the leading six digits and the trailing four digits need to be available to the application, so that only the remaining six digits in the middle of the credit card numbers are encrypted. 112 | There are a million different possibilities for these middle-six digits. 113 | If some credit card numbers that shared a given value for the middle-six digits were encrypted with the same tweak, then their ciphertexts would be the same. 114 | If, however, the other ten digits had been the tweak for the encryption of the middle-six digits, then the ciphertexts would almost certainly be different." 115 | 116 | This code block follows the previous code block. 117 | 118 | ```c++ 119 | // input: "264026" 120 | // tweak: "4263989299" 121 | shadow::fpe::encrypt(alphabet, key, shadow::fpe::Tweak(pt.substr(0, 6) + pt.substr(12, 4)), pt.substr(6, 6), ct); 122 | // output: "514968" 123 | ``` 124 | 125 | #### Example 2: Email Address 126 | 127 | There are multiple ways to encrypt email addresses. 128 | The simplest method treats an email address as a string and directly encrypts it. 129 | Special characters such as '@' and '.' are also encrypted and consequently put into the alphabet. 130 | 131 | The same key is used here. 132 | 133 | ```c++ 134 | shadow::fpe::Alphabet alphabet_email_1(shadow::fpe::kCharsetNumbers + shadow::fpe::kCharsetLettersLowercase + "@."); 135 | shadow::fpe::encrypt(alphabet_email_1, key, shadow::fpe::Tweak(), "my.personal.email@hotmail.com", ct); 136 | // output: ri6lur.mqsaai92lmbxa5s4@ntqso 137 | ``` 138 | 139 | As you can see, the encryption result does not preserve any email address format and it just looks like a random string. 140 | An alternative here is that we can leave the special characters ('@' and '.') as it is, treat the rest of the address as a string, use the traditional alphabet to encrypt this string, then put special characters back after the encryption. 141 | 142 | We provide an API `encrypt_skip_unsupported()` to encrypt the plaintext meanwhile excluding characters that are not in the alphabet ('@' and '.' in this example). 143 | 144 | ```c++ 145 | shadow::fpe::Alphabet alphabet_email_2(shadow::fpe::kCharsetNumbers + shadow::fpe::kCharsetLettersLowercase); 146 | shadow::fpe::encrypt_skip_unsupported(alphabet_email_2, key, shadow::fpe::Tweak(), "my.personal.email@hotmail.com", ct); 147 | // output: "2s.48hwgyu0.yn12e@vvfbunl.cua" 148 | ``` 149 | 150 | In this way, the ciphertext keeps the format of email addresses and might be more compatible with legacy systems. 151 | We also provide another function `encrypt_skip_specified()`, which takes two alphabets as inputs. 152 | The first alphabet is the same as others, and the second alphabet stores the characters that we want to skip. 153 | If the input messages have any character that is not in either alphabet, an error will be thrown. 154 | 155 | If you want to add tweaks for email address encryption, one good candidate is the domain. 156 | You can treat the prefix (the parts before '@') as the message to encrypt and use the part after '@' as the tweak. 157 | 158 | ```c++ 159 | shadow::fpe::encrypt_skip_unsupported(alphabet_email_2, key, shadow::fpe::Tweak("@hotmail.com"), "my.personal.email", ct); 160 | // output: "ws.sx9n2dir.pyqvb" 161 | ``` 162 | 163 | #### Example 3: Physical Address 164 | 165 | Physical addresses share a similar format to email addresses, while the special characters here are spaces and commas. 166 | 167 | ```c++ 168 | shadow::fpe::Alphabet alphabet_address(shadow::fpe::kCharsetNumbers + shadow::fpe::kCharsetLettersLowercase); 169 | std::string pt_address= "6666 fpe avenue , san jose, ca, 94000"; 170 | shadow::fpe::encrypt_skip_unsupported(alphabet_address, key, shadow::fpe::Tweak(), pt_address, ct); 171 | // output: "nxr7 sau 0c930c , h0j k59r, vs, n0exe" 172 | ``` 173 | 174 | If you want to preserve the format of street numbers and zip codes (e.g., encryptions of digits are still digits, and encryptions of letters remain letters), you can use `encrypt_skip_unsupported()` twice. 175 | In the first round of the encryption, you use `kCharsetNumbers` as the alphabet so that only the numbers are encrypted and letters are skipped. 176 | In the second round, you use `kCharsetLettersLowercase` as the alphabet, so that the function skips all numbers and encrypts only letters. 177 | 178 | ```c++ 179 | std::string ct_temp; 180 | shadow::fpe::encrypt_skip_unsupported(shadow::fpe::Alphabet(shadow::fpe::kCharsetNumbers), key, shadow::fpe::Tweak(), pt_address, ct_temp); 181 | shadow::fpe::encrypt_skip_unsupported(shadow::fpe::Alphabet(shadow::fpe::kCharsetLettersLowercase), key, shadow::fpe::Tweak(), ct_temp, ct); 182 | // output: "5014 dpl rurqiz , eau qtwp, xu, 01756" 183 | ``` 184 | 185 | There are more options for physical addresses. 186 | For instance, you can keep the state and zip code in plaintext and use them as tweaks. 187 | 188 | If you consider encrypting street code, street name, and unit numbers separately, please make sure that the domain of each block is at least one million. 189 | 190 | ## Contribution 191 | 192 | Please check [Contributing](CONTRIBUTING.md) for more details. 193 | 194 | ## Code of Conduct 195 | 196 | Please check [Code of Conduct](CODE_OF_CONDUCT.md) for more details. 197 | 198 | ## License 199 | 200 | This project is licensed under the [Apache-2.0 License](LICENSE). 201 | 202 | ## Citing Shadowgraphy 203 | 204 | To cite Shadowgraphy in academic papers, please use the following BibTeX entries. 205 | 206 | ### Version 0.1.0 207 | 208 | ```tex 209 | @misc{shadowgraphy, 210 | title = {Shadowgraphy (release 0.1.0)}, 211 | howpublished = {\url{https://github.com/tiktok-privacy-innovation/Shadowgraphy}}, 212 | month = Aug, 213 | year = 2023, 214 | note = {TikTok Pte. Ltd.}, 215 | key = {Shadowgraphy} 216 | } 217 | ``` 218 | -------------------------------------------------------------------------------- /shadow/fpe_export/fpe_export.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "shadow/fpe_export/fpe_export.h" 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "shadow/fpe/fpe.h" 24 | 25 | FPEAlphabet* fpe_alphabet_new(FPEBytes* charset, FPEStatus* status) { 26 | if (charset == nullptr || status == nullptr) { 27 | *status = E_POINTER; 28 | return nullptr; 29 | } 30 | try { 31 | std::string charset_str(charset->data, charset->len); 32 | shadow::fpe::Alphabet* t_alphabet = new shadow::fpe::Alphabet(charset_str); 33 | *status = S_OK; 34 | return reinterpret_cast(t_alphabet); 35 | } catch (const std::invalid_argument&) { 36 | *status = E_INVALIDARG; 37 | return nullptr; 38 | } 39 | } 40 | 41 | FPEStatus fpe_alphabet_free(FPEAlphabet* alphabet) { 42 | if (alphabet == nullptr) { 43 | return E_POINTER; 44 | } 45 | shadow::fpe::Alphabet* t_alphabet = reinterpret_cast(alphabet); 46 | delete t_alphabet; 47 | return S_OK; 48 | } 49 | 50 | FPEStatus fpe_alphabet_size(FPEAlphabet* alphabet, size_t* size) { 51 | if (alphabet == nullptr || size == nullptr) { 52 | return E_POINTER; 53 | } 54 | shadow::fpe::Alphabet* t_alphabet = reinterpret_cast(alphabet); 55 | *size = t_alphabet->size(); 56 | return S_OK; 57 | } 58 | 59 | FPEStatus fpe_alphabet_are_identical(FPEAlphabet* alphabet_0, FPEAlphabet* alphabet_1, bool* result) { 60 | if (alphabet_0 == nullptr || alphabet_1 == nullptr || result == nullptr) { 61 | return E_POINTER; 62 | } 63 | shadow::fpe::Alphabet* t_alphabet_0 = reinterpret_cast(alphabet_0); 64 | shadow::fpe::Alphabet* t_alphabet_1 = reinterpret_cast(alphabet_1); 65 | 66 | *result = shadow::fpe::are_identical(*t_alphabet_0, *t_alphabet_1); 67 | return S_OK; 68 | } 69 | 70 | FPEStatus fpe_alphabet_are_exclusive(FPEAlphabet* alphabet_0, FPEAlphabet* alphabet_1, bool* result) { 71 | if (alphabet_0 == nullptr || alphabet_1 == nullptr || result == nullptr) { 72 | return E_POINTER; 73 | } 74 | shadow::fpe::Alphabet* t_alphabet_0 = reinterpret_cast(alphabet_0); 75 | shadow::fpe::Alphabet* t_alphabet_1 = reinterpret_cast(alphabet_1); 76 | 77 | *result = shadow::fpe::are_exclusive(*t_alphabet_0, *t_alphabet_1); 78 | return S_OK; 79 | } 80 | 81 | FPEKey* fpe_key_new(FPEStatus* status) { 82 | if (status == nullptr) { 83 | *status = E_POINTER; 84 | return nullptr; 85 | } 86 | shadow::fpe::Key* t_key = new shadow::fpe::Key(); 87 | *status = S_OK; 88 | return reinterpret_cast(t_key); 89 | } 90 | 91 | FPEStatus fpe_key_free(FPEKey* key) { 92 | if (key == nullptr) { 93 | return E_POINTER; 94 | } 95 | shadow::fpe::Key* t_key = reinterpret_cast(key); 96 | delete t_key; 97 | return S_OK; 98 | } 99 | 100 | FPEStatus fpe_key_from_bytes(FPEKey* key, FPEBytes* bytes) { 101 | if (key == nullptr || bytes == nullptr) { 102 | return E_POINTER; 103 | } 104 | if (bytes->len != SHADOW_FPE_KEY_BYTE_COUNT) { 105 | return E_INVALIDARG; 106 | } 107 | shadow::fpe::Key* t_key = reinterpret_cast(key); 108 | std::copy_n(bytes->data, SHADOW_FPE_KEY_BYTE_COUNT, t_key->data()); 109 | return S_OK; 110 | } 111 | 112 | FPEStatus fpe_key_to_bytes(FPEKey* key, FPEBytes* bytes) { 113 | if (key == nullptr || bytes == nullptr) { 114 | return E_POINTER; 115 | } 116 | if (bytes->len != SHADOW_FPE_KEY_BYTE_COUNT) { 117 | return E_INVALIDARG; 118 | } 119 | shadow::fpe::Key* t_key = reinterpret_cast(key); 120 | std::copy_n(t_key->data(), SHADOW_FPE_KEY_BYTE_COUNT, bytes->data); 121 | return S_OK; 122 | } 123 | 124 | FPEStatus fpe_key_generate(FPEKey* key) { 125 | if (key == nullptr) { 126 | return E_POINTER; 127 | } 128 | // todo[yindong] strange 129 | shadow::fpe::Key* t_key = reinterpret_cast(key); 130 | shadow::fpe::Key tmp_key = shadow::fpe::generate_key(); 131 | std::copy_n(tmp_key.data(), SHADOW_FPE_KEY_BYTE_COUNT, t_key->data()); 132 | return S_OK; 133 | } 134 | 135 | FPETweak* fpe_tweak_new(FPEStatus* status) { 136 | if (status == nullptr) { 137 | *status = E_POINTER; 138 | return nullptr; 139 | } 140 | shadow::fpe::Tweak* t_tweak = new shadow::fpe::Tweak(); 141 | *status = S_OK; 142 | return reinterpret_cast(t_tweak); 143 | } 144 | 145 | FPEStatus fpe_tweak_free(FPETweak* tweak) { 146 | if (tweak == nullptr) { 147 | return E_POINTER; 148 | } 149 | shadow::fpe::Tweak* t_tweak = reinterpret_cast(tweak); 150 | delete t_tweak; 151 | return S_OK; 152 | } 153 | 154 | FPEStatus fpe_tweak_fill(FPETweak* tweak, FPEBytes* bytes) { 155 | if (tweak == nullptr || bytes == nullptr) { 156 | return E_POINTER; 157 | } 158 | shadow::fpe::Tweak* t_tweak = reinterpret_cast(tweak); 159 | t_tweak->resize(bytes->len); 160 | std::copy_n(bytes->data, bytes->len, t_tweak->data()); 161 | return S_OK; 162 | } 163 | 164 | FPEStatus fpe_encrypt(FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out) { 165 | if (alphabet == nullptr || key == nullptr || tweak == nullptr || in == nullptr || out == nullptr) { 166 | return E_POINTER; 167 | } 168 | try { 169 | shadow::fpe::Alphabet* t_alphabet = reinterpret_cast(alphabet); 170 | shadow::fpe::Key* t_key = reinterpret_cast(key); 171 | shadow::fpe::Tweak* t_tweak = reinterpret_cast(tweak); 172 | std::string in_str(in->data, in->len); 173 | std::string out_str; 174 | shadow::fpe::encrypt(*t_alphabet, *t_key, *t_tweak, in_str, out_str); 175 | std::copy_n(out_str.c_str(), out_str.length(), out->data); 176 | return S_OK; 177 | } catch (const std::invalid_argument&) { 178 | return E_INVALIDARG; 179 | } 180 | } 181 | 182 | FPEStatus fpe_decrypt(FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out) { 183 | if (alphabet == nullptr || key == nullptr || tweak == nullptr || in == nullptr || out == nullptr) { 184 | return E_POINTER; 185 | } 186 | try { 187 | shadow::fpe::Alphabet* t_alphabet = reinterpret_cast(alphabet); 188 | shadow::fpe::Key* t_key = reinterpret_cast(key); 189 | shadow::fpe::Tweak* t_tweak = reinterpret_cast(tweak); 190 | std::string in_str(in->data, in->len); 191 | std::string out_str; 192 | shadow::fpe::decrypt(*t_alphabet, *t_key, *t_tweak, in_str, out_str); 193 | std::copy_n(out_str.c_str(), out_str.length(), out->data); 194 | return S_OK; 195 | } catch (const std::invalid_argument&) { 196 | return E_INVALIDARG; 197 | } 198 | } 199 | FPEStatus fpe_encrypt_skip_unsupported( 200 | FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out) { 201 | if (alphabet == nullptr || key == nullptr || tweak == nullptr || in == nullptr || out == nullptr) { 202 | return E_POINTER; 203 | } 204 | try { 205 | shadow::fpe::Alphabet* t_alphabet = reinterpret_cast(alphabet); 206 | shadow::fpe::Key* t_key = reinterpret_cast(key); 207 | shadow::fpe::Tweak* t_tweak = reinterpret_cast(tweak); 208 | std::string in_str(in->data, in->len); 209 | std::string out_str; 210 | shadow::fpe::encrypt_skip_unsupported(*t_alphabet, *t_key, *t_tweak, in_str, out_str); 211 | std::copy_n(out_str.c_str(), out_str.length(), out->data); 212 | return S_OK; 213 | } catch (const std::invalid_argument&) { 214 | return E_INVALIDARG; 215 | } 216 | } 217 | 218 | FPEStatus fpe_decrypt_skip_unsupported( 219 | FPEAlphabet* alphabet, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out) { 220 | if (alphabet == nullptr || key == nullptr || tweak == nullptr || in == nullptr || out == nullptr) { 221 | return E_POINTER; 222 | } 223 | try { 224 | shadow::fpe::Alphabet* t_alphabet = reinterpret_cast(alphabet); 225 | shadow::fpe::Key* t_key = reinterpret_cast(key); 226 | shadow::fpe::Tweak* t_tweak = reinterpret_cast(tweak); 227 | std::string in_str(in->data, in->len); 228 | std::string out_str; 229 | shadow::fpe::decrypt_skip_unsupported(*t_alphabet, *t_key, *t_tweak, in_str, out_str); 230 | std::copy_n(out_str.c_str(), out_str.length(), out->data); 231 | return S_OK; 232 | } catch (const std::invalid_argument&) { 233 | return E_INVALIDARG; 234 | } 235 | } 236 | 237 | FPEStatus fpe_encrypt_skip_specified( 238 | FPEAlphabet* alphabet, FPEAlphabet* specification, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out) { 239 | if (alphabet == nullptr || specification == nullptr || key == nullptr || tweak == nullptr || in == nullptr || 240 | out == nullptr) { 241 | return E_POINTER; 242 | } 243 | try { 244 | shadow::fpe::Alphabet* t_alphabet = reinterpret_cast(alphabet); 245 | shadow::fpe::Alphabet* t_specification = reinterpret_cast(specification); 246 | shadow::fpe::Key* t_key = reinterpret_cast(key); 247 | shadow::fpe::Tweak* t_tweak = reinterpret_cast(tweak); 248 | std::string in_str(in->data, in->len); 249 | std::string out_str; 250 | shadow::fpe::encrypt_skip_specified(*t_alphabet, *t_specification, *t_key, *t_tweak, in_str, out_str); 251 | std::copy_n(out_str.c_str(), out_str.length(), out->data); 252 | return S_OK; 253 | } catch (const std::invalid_argument&) { 254 | return E_INVALIDARG; 255 | } 256 | } 257 | 258 | FPEStatus fpe_decrypt_skip_specified( 259 | FPEAlphabet* alphabet, FPEAlphabet* specification, FPEKey* key, FPETweak* tweak, FPEBytes* in, FPEBytes* out) { 260 | if (alphabet == nullptr || specification == nullptr || key == nullptr || tweak == nullptr || in == nullptr || 261 | out == nullptr) { 262 | return E_POINTER; 263 | } 264 | try { 265 | shadow::fpe::Alphabet* t_alphabet = reinterpret_cast(alphabet); 266 | shadow::fpe::Alphabet* t_specification = reinterpret_cast(specification); 267 | shadow::fpe::Key* t_key = reinterpret_cast(key); 268 | shadow::fpe::Tweak* t_tweak = reinterpret_cast(tweak); 269 | std::string in_str(in->data, in->len); 270 | std::string out_str; 271 | shadow::fpe::decrypt_skip_specified(*t_alphabet, *t_specification, *t_key, *t_tweak, in_str, out_str); 272 | std::copy_n(out_str.c_str(), out_str.length(), out->data); 273 | return S_OK; 274 | } catch (const std::invalid_argument&) { 275 | return E_INVALIDARG; 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Configuration 2 | !cmake/*.cmake.in 3 | !cmake/*.cmake 4 | cmake/*Config.cmake 5 | cmake/*ConfigVersion.cmake 6 | cmake/*Targets.cmake 7 | .vscode 8 | CMakeSettings.json 9 | 10 | # Build 11 | bin/ 12 | lib/ 13 | build/ 14 | *.build 15 | env 16 | thirdparty/ 17 | 18 | ######################################################################################## 19 | #### The following are based on templates from https://github.com/github/gitignore. #### 20 | ######################################################################################## 21 | 22 | *.pc 23 | .config 24 | *.args.json 25 | # You Complete Me 26 | .ycm_extra_conf.py 27 | # Vim 28 | .vimrc 29 | .lvimrc 30 | .local_vimrc 31 | 32 | #### Archives #### 33 | # It's better to unpack these files and commit the raw source because 34 | # git has its own built in compression methods. 35 | *.7z 36 | *.jar 37 | *.rar 38 | *.zip 39 | *.gz 40 | *.gzip 41 | *.tgz 42 | *.bzip 43 | *.bzip2 44 | *.bz2 45 | *.xz 46 | *.lzma 47 | *.cab 48 | *.xar 49 | # Packing-only formats 50 | *.iso 51 | *.tar 52 | # Package management formats 53 | *.dmg 54 | *.xpi 55 | *.gem 56 | *.egg 57 | *.deb 58 | *.rpm 59 | *.msi 60 | *.msm 61 | *.msp 62 | *.txz 63 | 64 | #### Backup #### 65 | *.bak 66 | *.gho 67 | *.ori 68 | *.orig 69 | *.tmp 70 | 71 | #### Git #### 72 | *.patch 73 | *.diff 74 | 75 | #### Windows #### 76 | # Windows thumbnail cache files 77 | Thumbs.db 78 | Thumbs.db:encryptable 79 | ehthumbs.db 80 | ehthumbs_vista.db 81 | # Dump file 82 | *.stackdump 83 | # Folder config file 84 | [Dd]esktop.ini 85 | # Recycle Bin used on file shares 86 | $RECYCLE.BIN/ 87 | # Windows Installer files 88 | *.cab 89 | *.msi 90 | *.msix 91 | *.msm 92 | *.msp 93 | # Windows shortcuts 94 | *.lnk 95 | 96 | #### Linux #### 97 | *~ 98 | # temporary files which can be created if a process still has a handle open of a deleted file 99 | .fuse_hidden* 100 | # KDE directory preferences 101 | .directory 102 | # Linux trash folder which might appear on any partition or disk 103 | .Trash-* 104 | # .nfs files are created when an open file is removed but is still being accessed 105 | .nfs* 106 | 107 | #### macOS #### 108 | # General 109 | .DS_Store 110 | .AppleDouble 111 | .LSOverride 112 | # Icon must end with two \r 113 | Icon 114 | # Thumbnails 115 | ._* 116 | # Files that might appear in the root of a volume 117 | .DocumentRevisions-V100 118 | .fseventsd 119 | .Spotlight-V100 120 | .TemporaryItems 121 | .Trashes 122 | .VolumeIcon.icns 123 | .com.apple.timemachine.donotpresent 124 | # Directories potentially created on remote AFP share 125 | .AppleDB 126 | .AppleDesktop 127 | Network Trash Folder 128 | Temporary Items 129 | .apdisk 130 | 131 | #### Autotools #### 132 | # http://www.gnu.org/software/automake 133 | Makefile.in 134 | /ar-lib 135 | /mdate-sh 136 | /py-compile 137 | /test-driver 138 | /ylwrap 139 | .deps/ 140 | .dirstamp 141 | # http://www.gnu.org/software/autoconf 142 | autom4te.cache 143 | /autoscan.log 144 | /autoscan-*.log 145 | /aclocal.m4 146 | /compile 147 | /config.guess 148 | /config.h.in 149 | /config.log 150 | /config.status 151 | /config.sub 152 | /configure 153 | /configure.scan 154 | /depcomp 155 | /install-sh 156 | /missing 157 | /stamp-h1 158 | # https://www.gnu.org/software/libtool/ 159 | /ltmain.sh 160 | # http://www.gnu.org/software/texinfo 161 | /texinfo.tex 162 | # http://www.gnu.org/software/m4/ 163 | m4/libtool.m4 164 | m4/ltoptions.m4 165 | m4/ltsugar.m4 166 | m4/ltversion.m4 167 | m4/lt~obsolete.m4 168 | # Generated Makefile 169 | # (meta build system like autotools, 170 | # can automatically generate from config.status script 171 | # (which is called by configure script)) 172 | Makefile 173 | 174 | #### C/C++ #### 175 | # Prerequisites 176 | *.d 177 | # Compiled Object files 178 | *.slo 179 | *.lo 180 | *.ko 181 | *.o 182 | *.obj 183 | *.elf 184 | # Linker output 185 | *.lik 186 | *.map 187 | *.exp 188 | # Precompiled Headers 189 | *.gch 190 | *.pch 191 | # Compiled Static libraries 192 | *.lai 193 | *.la 194 | *.a 195 | *.lib 196 | # Compiled Dynamic libraries 197 | *.so 198 | *.so.* 199 | *.dylib 200 | *.dll 201 | # Kernel Module Compile Results 202 | # conflict with go 203 | # *.mod* 204 | *.smod 205 | *.cmd 206 | .tmp_versions/ 207 | modules.order 208 | Module.symvers 209 | Mkfile.old 210 | dkms.conf 211 | # Executables 212 | *.exe 213 | *.out 214 | *.app 215 | *.i*86 216 | *.x86_64 217 | *.hex 218 | # Debug files 219 | *.dSYM/ 220 | *.su 221 | *.idb 222 | *.pdb 223 | 224 | #### CMake #### 225 | CMakeLists.txt.user 226 | CMakeCache.txt 227 | CMakeFiles 228 | CMakeScripts 229 | Testing 230 | Makefile 231 | cmake_install.cmake 232 | install_manifest.txt 233 | compile_commands.json 234 | CTestTestfile.cmake 235 | CPackConfig.cmake 236 | CPackSourceConfig.cmake 237 | 238 | #### Gradle #### 239 | .gradle 240 | **/build/ 241 | !src/**/build/ 242 | # Ignore Gradle GUI config 243 | gradle-app.setting 244 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 245 | !gradle-wrapper.jar 246 | # Cache of project 247 | .gradletasknamecache 248 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 249 | # gradle/wrapper/gradle-wrapper.properties 250 | android/.idea/ 251 | 252 | #### Visual Studio Code #### 253 | .vscode/* 254 | #!.vscode/settings.json 255 | #!.vscode/tasks.json 256 | #!.vscode/launch.json 257 | #!.vscode/extensions.json 258 | *.code-workspace 259 | # Local History for Visual Studio Code 260 | .history/ 261 | 262 | #### Visual Stduio #### 263 | ## Ignore Visual Studio temporary files, build results, and 264 | ## files generated by popular Visual Studio add-ons. 265 | ## 266 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 267 | 268 | # User-specific files 269 | *.rsuser 270 | *.suo 271 | *.user 272 | *.userosscache 273 | *.sln.docstates 274 | 275 | # User-specific files (MonoDevelop/Xamarin Studio) 276 | *.userprefs 277 | 278 | # Mono auto generated files 279 | mono_crash.* 280 | 281 | # Build results 282 | [Dd]ebug/ 283 | [Dd]ebugPublic/ 284 | [Rr]elease/ 285 | [Rr]eleases/ 286 | x64/ 287 | x86/ 288 | [Ww][Ii][Nn]32/ 289 | [Aa][Rr][Mm]/ 290 | [Aa][Rr][Mm]64/ 291 | bld/ 292 | [Bb]in/ 293 | [Oo]bj/ 294 | [Ll]og/ 295 | [Ll]ogs/ 296 | 297 | # Visual Studio 2015/2017 cache/options directory 298 | .vs/ 299 | # Uncomment if you have tasks that create the project's static files in wwwroot 300 | #wwwroot/ 301 | 302 | # Visual Studio 2017 auto generated files 303 | Generated\ Files/ 304 | 305 | # MSTest test Results 306 | [Tt]est[Rr]esult*/ 307 | [Bb]uild[Ll]og.* 308 | 309 | # NUnit 310 | *.VisualState.xml 311 | TestResult.xml 312 | nunit-*.xml 313 | 314 | # Build Results of an ATL Project 315 | [Dd]ebugPS/ 316 | [Rr]eleasePS/ 317 | dlldata.c 318 | 319 | # Benchmark Results 320 | BenchmarkDotNet.Artifacts/ 321 | 322 | # .NET Core 323 | project.lock.json 324 | project.fragment.lock.json 325 | artifacts/ 326 | 327 | # ASP.NET Scaffolding 328 | ScaffoldingReadMe.txt 329 | 330 | # StyleCop 331 | StyleCopReport.xml 332 | 333 | # Files built by Visual Studio 334 | *_i.c 335 | *_p.c 336 | *_h.h 337 | *.ilk 338 | *.meta 339 | *.obj 340 | *.iobj 341 | *.pch 342 | *.pdb 343 | *.ipdb 344 | *.pgc 345 | *.pgd 346 | *.rsp 347 | *.sbr 348 | *.tlb 349 | *.tli 350 | *.tlh 351 | *.tmp 352 | *.tmp_proj 353 | *_wpftmp.csproj 354 | *.log 355 | *.vspscc 356 | *.vssscc 357 | .builds 358 | *.pidb 359 | *.svclog 360 | *.scc 361 | 362 | # Chutzpah Test files 363 | _Chutzpah* 364 | 365 | # Visual C++ cache files 366 | ipch/ 367 | *.aps 368 | *.ncb 369 | *.opendb 370 | *.opensdf 371 | *.sdf 372 | *.cachefile 373 | *.VC.db 374 | *.VC.VC.opendb 375 | 376 | # Visual Studio profiler 377 | *.psess 378 | *.vsp 379 | *.vspx 380 | *.sap 381 | 382 | # Visual Studio Trace Files 383 | *.e2e 384 | 385 | # TFS 2012 Local Workspace 386 | $tf/ 387 | 388 | # Guidance Automation Toolkit 389 | *.gpState 390 | 391 | # ReSharper is a .NET coding add-in 392 | _ReSharper*/ 393 | *.[Rr]e[Ss]harper 394 | *.DotSettings.user 395 | 396 | # TeamCity is a build add-in 397 | _TeamCity* 398 | 399 | # DotCover is a Code Coverage Tool 400 | *.dotCover 401 | 402 | # AxoCover is a Code Coverage Tool 403 | .axoCover/* 404 | !.axoCover/settings.json 405 | 406 | # Coverlet is a free, cross platform Code Coverage Tool 407 | coverage*[.json, .xml, .info] 408 | 409 | # Visual Studio code coverage results 410 | *.coverage 411 | *.coveragexml 412 | 413 | # NCrunch 414 | _NCrunch_* 415 | .*crunch*.local.xml 416 | nCrunchTemp_* 417 | 418 | # MightyMoose 419 | *.mm.* 420 | AutoTest.Net/ 421 | 422 | # Web workbench (sass) 423 | .sass-cache/ 424 | 425 | # Installshield output folder 426 | [Ee]xpress/ 427 | 428 | # DocProject is a documentation generator add-in 429 | DocProject/buildhelp/ 430 | DocProject/Help/*.HxT 431 | DocProject/Help/*.HxC 432 | DocProject/Help/*.hhc 433 | DocProject/Help/*.hhk 434 | DocProject/Help/*.hhp 435 | DocProject/Help/Html2 436 | DocProject/Help/html 437 | 438 | # Click-Once directory 439 | publish/ 440 | 441 | # Publish Web Output 442 | *.[Pp]ublish.xml 443 | *.azurePubxml 444 | # Note: Comment the next line if you want to checkin your web deploy settings, 445 | # but database connection strings (with potential passwords) will be unencrypted 446 | *.pubxml 447 | *.publishproj 448 | 449 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 450 | # checkin your Azure Web App publish settings, but sensitive information contained 451 | # in these scripts will be unencrypted 452 | PublishScripts/ 453 | 454 | # NuGet Packages 455 | *.nupkg 456 | # NuGet Symbol Packages 457 | *.snupkg 458 | # The packages folder can be ignored because of Package Restore 459 | **/[Pp]ackages/* 460 | # except build/, which is used as an MSBuild target. 461 | !**/[Pp]ackages/build/ 462 | # Uncomment if necessary however generally it will be regenerated when needed 463 | #!**/[Pp]ackages/repositories.config 464 | # NuGet v3's project.json files produces more ignorable files 465 | *.nuget.props 466 | *.nuget.targets 467 | 468 | # Microsoft Azure Build Output 469 | csx/ 470 | *.build.csdef 471 | 472 | # Microsoft Azure Emulator 473 | ecf/ 474 | rcf/ 475 | 476 | # Windows Store app package directories and files 477 | AppPackages/ 478 | BundleArtifacts/ 479 | Package.StoreAssociation.xml 480 | _pkginfo.txt 481 | *.appx 482 | *.appxbundle 483 | *.appxupload 484 | 485 | # Visual Studio cache files 486 | # files ending in .cache can be ignored 487 | *.[Cc]ache 488 | # but keep track of directories ending in .cache 489 | !?*.[Cc]ache/ 490 | 491 | # Others 492 | ClientBin/ 493 | ~$* 494 | *~ 495 | *.dbmdl 496 | *.dbproj.schemaview 497 | *.jfm 498 | *.pfx 499 | *.publishsettings 500 | orleans.codegen.cs 501 | 502 | # Including strong name files can present a security risk 503 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 504 | #*.snk 505 | 506 | # Since there are multiple workflows, uncomment next line to ignore bower_components 507 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 508 | #bower_components/ 509 | 510 | # RIA/Silverlight projects 511 | Generated_Code/ 512 | 513 | # Backup & report files from converting an old project file 514 | # to a newer Visual Studio version. Backup files are not needed, 515 | # because we have git ;-) 516 | _UpgradeReport_Files/ 517 | Backup*/ 518 | UpgradeLog*.XML 519 | UpgradeLog*.htm 520 | ServiceFabricBackup/ 521 | *.rptproj.bak 522 | 523 | # SQL Server files 524 | *.mdf 525 | *.ldf 526 | *.ndf 527 | 528 | # Business Intelligence projects 529 | *.rdl.data 530 | *.bim.layout 531 | *.bim_*.settings 532 | *.rptproj.rsuser 533 | *- [Bb]ackup.rdl 534 | *- [Bb]ackup ([0-9]).rdl 535 | *- [Bb]ackup ([0-9][0-9]).rdl 536 | 537 | # Microsoft Fakes 538 | FakesAssemblies/ 539 | 540 | # GhostDoc plugin setting file 541 | *.GhostDoc.xml 542 | 543 | # Node.js Tools for Visual Studio 544 | .ntvs_analysis.dat 545 | node_modules/ 546 | 547 | # Visual Studio 6 build log 548 | *.plg 549 | 550 | # Visual Studio 6 workspace options file 551 | *.opt 552 | 553 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 554 | *.vbw 555 | 556 | # Visual Studio LightSwitch build output 557 | **/*.HTMLClient/GeneratedArtifacts 558 | **/*.DesktopClient/GeneratedArtifacts 559 | **/*.DesktopClient/ModelManifest.xml 560 | **/*.Server/GeneratedArtifacts 561 | **/*.Server/ModelManifest.xml 562 | _Pvt_Extensions 563 | 564 | # Paket dependency manager 565 | .paket/paket.exe 566 | paket-files/ 567 | 568 | # FAKE - F# Make 569 | .fake/ 570 | 571 | # CodeRush personal settings 572 | .cr/personal 573 | 574 | # Python Tools for Visual Studio (PTVS) 575 | __pycache__/ 576 | *.pyc 577 | 578 | # Cake - Uncomment if you are using it 579 | # tools/** 580 | # !tools/packages.config 581 | 582 | # Tabs Studio 583 | *.tss 584 | 585 | # Telerik's JustMock configuration file 586 | *.jmconfig 587 | 588 | # BizTalk build output 589 | *.btp.cs 590 | *.btm.cs 591 | *.odx.cs 592 | *.xsd.cs 593 | 594 | # OpenCover UI analysis results 595 | OpenCover/ 596 | 597 | # Azure Stream Analytics local run output 598 | ASALocalRun/ 599 | 600 | # MSBuild Binary and Structured Log 601 | *.binlog 602 | 603 | # NVidia Nsight GPU debugger configuration file 604 | *.nvuser 605 | 606 | # MFractors (Xamarin productivity tool) working folder 607 | .mfractor/ 608 | 609 | # Local History for Visual Studio 610 | .localhistory/ 611 | 612 | # BeatPulse healthcheck temp database 613 | healthchecksdb 614 | 615 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 616 | MigrationBackup/ 617 | 618 | # Ionide (cross platform F# VS Code tools) working folder 619 | .ionide/ 620 | 621 | # Fody - auto-generated XML schema 622 | FodyWeavers.xsd 623 | -------------------------------------------------------------------------------- /shadow/fpe_go/fpe_wrapper_test.go: -------------------------------------------------------------------------------- 1 | package fpe 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewAlphabet(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | charset string 15 | wantErr bool 16 | }{ 17 | { 18 | name: "Valid Charset", 19 | charset: "abc", 20 | wantErr: false, 21 | }, 22 | { 23 | name: "Empty Charset", 24 | charset: "", 25 | wantErr: true, 26 | }, 27 | { 28 | name: "Charset Size Equal Max", 29 | charset: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()", 30 | wantErr: false, 31 | }, 32 | { 33 | name: "Duplicate Characters in Charset", 34 | charset: "abcabc", 35 | wantErr: true, 36 | }, 37 | } 38 | 39 | for _, tt := range tests { 40 | t.Run(tt.name, func(t *testing.T) { 41 | _, err := newAlphabet(tt.charset) 42 | if (err != nil) != tt.wantErr { 43 | t.Errorf("newAlphabet() error = %v, wantErr %v", err, tt.wantErr) 44 | return 45 | } 46 | }) 47 | } 48 | } 49 | 50 | func TestKeyFromBytes(t *testing.T) { 51 | tests := []struct { 52 | name string 53 | bytes []byte 54 | wantErr bool 55 | }{ 56 | { 57 | name: "Valid Key", 58 | bytes: []byte("1234567890123456"), // 16 bytes 59 | wantErr: false, 60 | }, 61 | { 62 | name: "Invalid Key", 63 | bytes: []byte("123456789012345"), // 15 bytes 64 | wantErr: true, 65 | }, 66 | } 67 | 68 | for _, tt := range tests { 69 | t.Run(tt.name, func(t *testing.T) { 70 | key, err := newKey() 71 | assert.Nil(t, err) 72 | err = keyFromBytes(tt.bytes, key) 73 | if (err != nil) != tt.wantErr { 74 | t.Errorf("keyFromBytes() error = %v, wantErr %v", err, tt.wantErr) 75 | return 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func TestKeyToBytes(t *testing.T) { 82 | key, err := newKey() 83 | defer freeKey(key) 84 | assert.Nil(t, err) 85 | bytesIn :=[]byte("1234567890123456") 86 | keyFromBytes(bytesIn, key) 87 | bytesOut, err:= keyToBytes(key) 88 | assert.Nil(t, err) 89 | assert.Equal(t, bytesIn, bytesOut) 90 | } 91 | 92 | func TestKeyGen(t *testing.T) { 93 | key, err := newKey() 94 | defer freeKey(key) 95 | assert.Nil(t, err) 96 | err = keyGenerate(key) 97 | assert.Nil(t, err) 98 | } 99 | 100 | func TestTweakFill(t *testing.T) { 101 | tests := []struct { 102 | name string 103 | bytes []byte 104 | wantErr bool 105 | }{ 106 | { 107 | name: "Valid Tweak", 108 | bytes: []byte("1234567890123456"), // Assume this is a valid length for a tweak 109 | wantErr: false, 110 | }, 111 | } 112 | 113 | for _, tt := range tests { 114 | t.Run(tt.name, func(t *testing.T) { 115 | tweak, err := newTweak() 116 | assert.Nil(t, err) 117 | err = tweakFill(tt.bytes, tweak) 118 | if (err != nil) != tt.wantErr { 119 | t.Errorf("tweakFill() error = %v, wantErr %v", err, tt.wantErr) 120 | return 121 | } 122 | }) 123 | } 124 | } 125 | 126 | func TestEncrypt(t *testing.T) { 127 | alphabet := "zyxwvutsrqponmlkjihgfedcba" + " " 128 | key := "7gHJ4D58F6hj27L1" 129 | tweak := "" 130 | cAlphabet, err := newAlphabet(alphabet) 131 | assert.Nil(t, err) 132 | cKey, err := newKey() 133 | assert.Nil(t, err) 134 | cTweak, err := newTweak() 135 | assert.Nil(t, err) 136 | err = keyFromBytes([]byte(key), cKey) 137 | assert.Nil(t, err) 138 | err = tweakFill([]byte(tweak), cTweak) 139 | assert.Nil(t, err) 140 | 141 | tests := []struct { 142 | name string 143 | in string 144 | wantErr bool 145 | }{ 146 | { 147 | name: "Valid Input", 148 | in: "hello", 149 | wantErr: false, 150 | }, 151 | { 152 | name: "Invalid Input", 153 | in: "hello!", 154 | wantErr: true, 155 | }, 156 | } 157 | 158 | for _, tt := range tests { 159 | t.Run(tt.name, func(t *testing.T) { 160 | _, err := encrypt(cAlphabet, cKey, cTweak, tt.in) 161 | if (err != nil) != tt.wantErr { 162 | t.Errorf("encrypt() error = %v, wantErr %v", err, tt.wantErr) 163 | return 164 | } 165 | }) 166 | } 167 | } 168 | 169 | func TestDecrypt(t *testing.T) { 170 | alphabet := "zyxwvutsrqponmlkjihgfedcba" + " " 171 | key := "7gHJ4D58F6hj27L1" 172 | tweak := "" 173 | cAlphabet, err := newAlphabet(alphabet) 174 | assert.Nil(t, err) 175 | cKey, err := newKey() 176 | assert.Nil(t, err) 177 | cTweak, err := newTweak() 178 | assert.Nil(t, err) 179 | err = keyFromBytes([]byte(key), cKey) 180 | assert.Nil(t, err) 181 | err = tweakFill([]byte(tweak), cTweak) 182 | assert.Nil(t, err) 183 | tests := []struct { 184 | name string 185 | in string 186 | wantErr bool 187 | }{ 188 | { 189 | name: "Valid Input", 190 | in: "hello", 191 | wantErr: false, 192 | }, 193 | { 194 | name: "Invalid Input", 195 | in: "hello!", 196 | wantErr: true, 197 | }, 198 | } 199 | 200 | for _, tt := range tests { 201 | t.Run(tt.name, func(t *testing.T) { 202 | _, err := decrypt(cAlphabet, cKey, cTweak, tt.in) 203 | if (err != nil) != tt.wantErr { 204 | t.Errorf("decrypt() error = %v, wantErr %v", err, tt.wantErr) 205 | return 206 | } 207 | }) 208 | } 209 | } 210 | 211 | func TestEncryptSkipUnsupported(t *testing.T) { 212 | alphabet := "zyxwvutsrqponmlkjihgfedcba" + " " 213 | key := "7gHJ4D58F6hj27L1" 214 | tweak := "" 215 | cAlphabet, err := newAlphabet(alphabet) 216 | assert.Nil(t, err) 217 | cKey, err := newKey() 218 | assert.Nil(t, err) 219 | cTweak, err := newTweak() 220 | assert.Nil(t, err) 221 | err = keyFromBytes([]byte(key), cKey) 222 | assert.Nil(t, err) 223 | err = tweakFill([]byte(tweak), cTweak) 224 | assert.Nil(t, err) 225 | 226 | tests := []struct { 227 | name string 228 | in string 229 | wantErr bool 230 | }{ 231 | { 232 | name: "Valid Input", 233 | in: "hello", 234 | wantErr: false, 235 | }, 236 | { 237 | name: "Invalid Input", 238 | in: "hello!", 239 | wantErr: false, // This function skips unsupported characters, so it should not return an error 240 | }, 241 | } 242 | 243 | for _, tt := range tests { 244 | t.Run(tt.name, func(t *testing.T) { 245 | _, err := encryptSkipUnsupported(cAlphabet, cKey, cTweak, tt.in) 246 | if (err != nil) != tt.wantErr { 247 | t.Errorf("encryptSkipUnsupported() error = %v, wantErr %v", err, tt.wantErr) 248 | return 249 | } 250 | }) 251 | } 252 | } 253 | 254 | func TestDecryptSkipUnsupported(t *testing.T) { 255 | alphabet := "zyxwvutsrqponmlkjihgfedcba" + " " 256 | key := "7gHJ4D58F6hj27L1" 257 | tweak := "" 258 | cAlphabet, err := newAlphabet(alphabet) 259 | assert.Nil(t, err) 260 | cKey, err := newKey() 261 | assert.Nil(t, err) 262 | cTweak, err := newTweak() 263 | assert.Nil(t, err) 264 | err = keyFromBytes([]byte(key), cKey) 265 | assert.Nil(t, err) 266 | err = tweakFill([]byte(tweak), cTweak) 267 | assert.Nil(t, err) 268 | tests := []struct { 269 | name string 270 | in string 271 | wantErr bool 272 | }{ 273 | { 274 | name: "Valid Input", 275 | in: "hello", 276 | wantErr: false, 277 | }, 278 | { 279 | name: "Invalid Input", 280 | in: "hello!", 281 | wantErr: false, 282 | }, 283 | } 284 | 285 | for _, tt := range tests { 286 | t.Run(tt.name, func(t *testing.T) { 287 | _, err := decryptSkipUnsupported(cAlphabet, cKey, cTweak, tt.in) 288 | if (err != nil) != tt.wantErr { 289 | t.Errorf("decrypt() error = %v, wantErr %v", err, tt.wantErr) 290 | return 291 | } 292 | }) 293 | } 294 | } 295 | 296 | func TestEncryptSkipSpecified(t *testing.T) { 297 | alphabet := "zyxwvutsrqponmlkjihgfedcba" + " " 298 | skipAlphabet := "!" 299 | key := "7gHJ4D58F6hj27L1" 300 | tweak := "" 301 | cAlphabet, err := newAlphabet(alphabet) 302 | assert.Nil(t, err) 303 | cSkipAlphabet, err := newAlphabet(skipAlphabet) 304 | assert.Nil(t, err) 305 | cKey, err := newKey() 306 | assert.Nil(t, err) 307 | cTweak, err := newTweak() 308 | assert.Nil(t, err) 309 | err = keyFromBytes([]byte(key), cKey) 310 | assert.Nil(t, err) 311 | err = tweakFill([]byte(tweak), cTweak) 312 | assert.Nil(t, err) 313 | tests := []struct { 314 | name string 315 | in string 316 | wantErr bool 317 | }{ 318 | { 319 | name: "Valid Input", 320 | in: "hello", 321 | wantErr: false, 322 | }, 323 | { 324 | name: "Valid Input", 325 | in: "hello!", 326 | wantErr: false, 327 | }, 328 | { 329 | name: "Invalid Input", 330 | in: "hello#", 331 | wantErr: true, 332 | }, 333 | } 334 | 335 | for _, tt := range tests { 336 | t.Run(tt.name, func(t *testing.T) { 337 | _, err := encryptSkipSpecified(cAlphabet, cSkipAlphabet, cKey, cTweak, tt.in) 338 | if (err != nil) != tt.wantErr { 339 | t.Errorf("decrypt() error = %v, wantErr %v", err, tt.wantErr) 340 | return 341 | } 342 | }) 343 | } 344 | } 345 | 346 | func TestDecryptSkipSpecified(t *testing.T) { 347 | alphabet := "zyxwvutsrqponmlkjihgfedcba" + " " 348 | skipAlphabet := "!" 349 | key := "7gHJ4D58F6hj27L1" 350 | tweak := "" 351 | cAlphabet, err := newAlphabet(alphabet) 352 | assert.Nil(t, err) 353 | cSkipAlphabet, err := newAlphabet(skipAlphabet) 354 | assert.Nil(t, err) 355 | cKey, err := newKey() 356 | assert.Nil(t, err) 357 | cTweak, err := newTweak() 358 | assert.Nil(t, err) 359 | err = keyFromBytes([]byte(key), cKey) 360 | assert.Nil(t, err) 361 | err = tweakFill([]byte(tweak), cTweak) 362 | assert.Nil(t, err) 363 | tests := []struct { 364 | name string 365 | in string 366 | wantErr bool 367 | }{ 368 | { 369 | name: "Valid Input", 370 | in: "hello", 371 | wantErr: false, 372 | }, 373 | { 374 | name: "Valid Input", 375 | in: "hello!", 376 | wantErr: false, 377 | }, 378 | { 379 | name: "Invalid Input", 380 | in: "hello#", 381 | wantErr: true, 382 | }, 383 | } 384 | 385 | for _, tt := range tests { 386 | t.Run(tt.name, func(t *testing.T) { 387 | _, err := decryptSkipSpecified(cAlphabet, cSkipAlphabet, cKey, cTweak, tt.in) 388 | if (err != nil) != tt.wantErr { 389 | t.Errorf("decrypt() error = %v, wantErr %v", err, tt.wantErr) 390 | return 391 | } 392 | }) 393 | } 394 | } 395 | 396 | func TestWrapperEncryption(t *testing.T) { 397 | alphabet := "zyxwvurstqponmlkjihgfedcba" + " " 398 | skipAlphabet := "!" 399 | key := "7gHJ4D58F6hj27L2" 400 | tweak := "" 401 | cAlphabet, err := newAlphabet(alphabet) 402 | assert.Nil(t, err) 403 | cSkipAlphabet, err := newAlphabet(skipAlphabet) 404 | assert.Nil(t, err) 405 | cKey, err := newKey() 406 | assert.Nil(t, err) 407 | cTweak, err := newTweak() 408 | assert.Nil(t, err) 409 | err = keyFromBytes([]byte(key), cKey) 410 | assert.Nil(t, err) 411 | err = tweakFill([]byte(tweak), cTweak) 412 | assert.Nil(t, err) 413 | msg := "tell u a secret!" 414 | ciphertext, err := encryptSkipSpecified(cAlphabet, cSkipAlphabet, cKey, cTweak, msg) 415 | assert.Nil(t, err) 416 | plaintext, err := decryptSkipSpecified(cAlphabet, cSkipAlphabet, cKey, cTweak, ciphertext) 417 | assert.Nil(t, err) 418 | assert.Equal(t, plaintext, msg) 419 | } 420 | 421 | func TestNIST(t *testing.T) { 422 | keyBytes := []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} 423 | cKey, err := newKey() 424 | assert.Nil(t, err) 425 | err = keyFromBytes(keyBytes, cKey) 426 | assert.Nil(t, err) 427 | 428 | tests := []struct { 429 | name string 430 | pt string 431 | cipher string 432 | tweak []byte 433 | alphabet string 434 | wantErr bool 435 | }{ 436 | { 437 | name: "Case 1", 438 | pt: "0123456789", 439 | cipher: "2433477484", 440 | tweak: []byte{}, 441 | alphabet:KCharsetNumbers, 442 | wantErr: false, 443 | }, 444 | { 445 | name: "Case 2", 446 | pt: "0123456789", 447 | cipher: "6124200773", 448 | tweak: []byte{0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30}, 449 | alphabet:KCharsetNumbers, 450 | wantErr: false, 451 | }, 452 | { 453 | name: "Case 3", 454 | pt: "0123456789abcdefghi", 455 | cipher: "a9tv40mll9kdu509eum", 456 | tweak: []byte{0x37, 0x37, 0x37, 0x37, 0x70, 0x71, 0x72, 0x73, 0x37, 0x37, 0x37}, 457 | alphabet:KCharsetNumbers + KCharsetLettersLowercase, 458 | wantErr: false, 459 | }, 460 | } 461 | 462 | for _, tt := range tests { 463 | t.Run(tt.name, func(t *testing.T) { 464 | cAlphabet, err := newAlphabet(tt.alphabet) 465 | assert.Nil(t, err) 466 | cTweak, err := newTweak() 467 | assert.Nil(t, err) 468 | err = tweakFill(tt.tweak, cTweak) 469 | assert.Nil(t, err) 470 | cipher, err := encrypt(cAlphabet, cKey, cTweak, tt.pt) 471 | assert.Nil(t, err) 472 | assert.Equal(t, cipher, tt.cipher) 473 | ptCheck, err := decrypt(cAlphabet, cKey, cTweak, cipher) 474 | assert.Nil(t, err) 475 | assert.Equal(t, ptCheck, tt.pt) 476 | }) 477 | } 478 | } 479 | 480 | func randomString(length int) string { 481 | rand.Seed(time.Now().UnixNano()) 482 | chars := []rune("abcdefghijklmnopqrstuvwxyz") 483 | result := make([]rune, length) 484 | for i := range result { 485 | result[i] = chars[rand.Intn(len(chars))] 486 | } 487 | return string(result) 488 | } 489 | 490 | func benchmarkWrapperEncryption(b *testing.B, msgLength int) { 491 | alphabet := "zyxwvutsrqponmlkjihgfedcba" + " " 492 | key := "7gHJ4D58F6hj27L1" 493 | tweak := "" 494 | msg := randomString(msgLength) 495 | 496 | cAlphabet, err := newAlphabet(alphabet) 497 | if err != nil { 498 | b.Fatal(err) 499 | } 500 | cKey, err := newKey() 501 | if err != nil { 502 | b.Fatal(err) 503 | } 504 | err = keyFromBytes([]byte(key), cKey) 505 | if err != nil { 506 | b.Fatal(err) 507 | } 508 | cTweak, err := newTweak() 509 | if err != nil { 510 | b.Fatal(err) 511 | } 512 | err = tweakFill([]byte(tweak), cTweak) 513 | if err != nil { 514 | b.Fatal(err) 515 | } 516 | b.ResetTimer() 517 | for i := 0; i < b.N; i++ { 518 | encrypted, _ := encrypt(cAlphabet, cKey, cTweak, msg) 519 | decrypt(cAlphabet, cKey, cTweak, encrypted) 520 | } 521 | } 522 | 523 | func BenchmarkWrapperEncryption16(b *testing.B) { 524 | benchmarkWrapperEncryption(b, 16) 525 | } 526 | 527 | func BenchmarkWrapperEncryption64(b *testing.B) { 528 | benchmarkWrapperEncryption(b, 64) 529 | } 530 | 531 | func BenchmarkWrapperEncryption255(b *testing.B) { 532 | benchmarkWrapperEncryption(b, 255) 533 | } 534 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TikTok Pte. Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | cmake_minimum_required(VERSION 3.14) 16 | 17 | ##################################################### 18 | # Project SHADOW includes the following components: # 19 | # 1. FPE C++ library # 20 | # 2. FPE C library # 21 | ##################################################### 22 | 23 | # [OPTION] CMAKE_BUILD_TYPE (DEFAULT: "Release") 24 | # Select from Release, Debug, MiniSizeRel, or RelWithDebInfo. 25 | if(NOT CMAKE_BUILD_TYPE) 26 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) 27 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY 28 | STRINGS "Release" "Debug" "MinSizeRel" "RelWithDebInfo") 29 | endif() 30 | message(STATUS "Build type (CMAKE_BUILD_TYPE): ${CMAKE_BUILD_TYPE}") 31 | 32 | project(SHADOW VERSION 0.1.0 LANGUAGES C CXX) 33 | 34 | ######################## 35 | # Global configuration # 36 | ######################## 37 | 38 | # CMake modules 39 | include(CMakeDependentOption) 40 | include(CMakePushCheckState) 41 | include(CheckIncludeFiles) 42 | include(CheckCXXSourceCompiles) 43 | include(CheckCXXSourceRuns) 44 | 45 | # Extra modules 46 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) 47 | include(ShadowCustomMacros) 48 | 49 | # In Debug mode, define SHADOW_DEBUG. 50 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 51 | set(SHADOW_DEBUG ON) 52 | else() 53 | set(SHADOW_DEBUG OFF) 54 | endif() 55 | message(STATUS "SHADOW debug mode: ${SHADOW_DEBUG}") 56 | 57 | # In Debug mode, enable extra compiler flags. 58 | include(EnableDebugFlags) 59 | 60 | # Always build position-independent-code 61 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 62 | 63 | # Make the install target depend on the all target (required by vcpkg) 64 | set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY OFF) 65 | 66 | # Use C++14 67 | set(CMAKE_CXX_STANDARD 14) 68 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 69 | set(CMAKE_CXX_EXTENSIONS OFF) 70 | 71 | # Required files and directories 72 | include(GNUInstallDirs) 73 | 74 | # Runtime path setup 75 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") 76 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 77 | 78 | # Source Tree 79 | set(SHADOW_INCLUDES_DIR ${CMAKE_CURRENT_LIST_DIR}) 80 | set(SHADOW_CONFIG_IN_FILENAME ${CMAKE_CURRENT_LIST_DIR}/cmake/ShadowConfig.cmake.in) 81 | set(SHADOW_CONFIG_H_IN_FILENAME ${SHADOW_INCLUDES_DIR}/shadow/common/config.h.in) 82 | 83 | # Build tree 84 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) 85 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) 86 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) 87 | set(SHADOW_TARGETS_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/cmake/ShadowTargets.cmake) 88 | set(SHADOW_CONFIG_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/cmake/ShadowConfig.cmake) 89 | set(SHADOW_CONFIG_VERSION_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/cmake/ShadowConfigVersion.cmake) 90 | set(SHADOW_CONFIG_H_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/shadow/common/config.h) 91 | set(SHADOW_THIRDPARTY_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty) 92 | 93 | # Install 94 | set(SHADOW_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/Shadow-${SHADOW_VERSION_MAJOR}.${SHADOW_VERSION_MINOR}) 95 | set(SHADOW_INCLUDES_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/Shadow-${SHADOW_VERSION_MAJOR}.${SHADOW_VERSION_MINOR}) 96 | 97 | # Supported target operating systems are Linux and macOS. 98 | if (NOT DEFINED LINUX) 99 | if (UNIX AND NOT APPLE AND NOT CYGWIN AND NOT MINGW) 100 | set(LINUX ON) 101 | endif() 102 | endif() 103 | if (UNIX AND APPLE) 104 | set(MACOS ON) 105 | endif() 106 | if (NOT LINUX AND NOT MACOS) 107 | message(FATAL_ERROR "Supported target operating systems are Linux and macOS") 108 | endif() 109 | 110 | # Enable test coverage 111 | set(SHADOW_ENABLE_GCOV_STR "Enable gcov") 112 | option(SHADOW_ENABLE_GCOV ${SHADOW_ENABLE_GCOV_STR} OFF) 113 | message(STATUS "SHADOW_ENABLE_GCOV: ${SHADOW_ENABLE_GCOV}") 114 | if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND SHADOW_ENABLE_GCOV) 115 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") 116 | set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -fprofile-arcs -ftest-coverage -lgcov") 117 | endif() 118 | 119 | ######################### 120 | # External dependencies # 121 | ######################### 122 | 123 | # OpenSSL 124 | message(STATUS "OpenSSL: download ...") 125 | shadow_fetch_thirdparty_content(ExternalOpenSSL) 126 | 127 | # [OPTION] SHADOW_BUILD_UTILS (DEFAULT: OFF) 128 | # Look for utilities using find_package first. 129 | # If a utility is not found, download and build it if set to ON. 130 | # FATAL_ERROR if set to OFF. 131 | set(SHADOW_BUILD_UTILS_OPTION_STR "Automatically download and build utilities") 132 | option(SHADOW_BUILD_UTILS ${SHADOW_BUILD_UTILS_OPTION_STR} OFF) 133 | message(STATUS "SHADOW_BUILD_UTILS: ${SHADOW_BUILD_UTILS}") 134 | 135 | if(SHADOW_BUILD_UTILS) 136 | include(FetchContent) 137 | endif() 138 | 139 | ######################### 140 | # COMMON object library # 141 | ######################### 142 | 143 | # Create the config file 144 | configure_file(${SHADOW_CONFIG_H_IN_FILENAME} ${SHADOW_CONFIG_H_FILENAME}) 145 | 146 | add_library(common OBJECT) 147 | target_sources(common PRIVATE ${CMAKE_CURRENT_LIST_DIR}/shadow/common/common.cpp) 148 | target_compile_features(common PUBLIC cxx_std_14) 149 | target_include_directories(common PRIVATE 150 | $ 151 | $) 152 | target_include_directories(common PRIVATE 153 | $) 154 | install(TARGETS common EXPORT ShadowTargets 155 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 156 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 157 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 158 | 159 | ###################### 160 | # SHADOW C++ library # 161 | ###################### 162 | 163 | set(SHADOW_CARRY_OPENSSL TRUE) 164 | 165 | # fpe 166 | add_library(fpe STATIC) 167 | target_sources(fpe PRIVATE ${CMAKE_CURRENT_LIST_DIR}/shadow/fpe/fpe.cpp) 168 | install( 169 | FILES 170 | ${CMAKE_CURRENT_LIST_DIR}/shadow/fpe/fpe.h 171 | DESTINATION 172 | ${SHADOW_INCLUDES_INSTALL_DIR}/shadow/fpe 173 | ) 174 | target_compile_features(fpe PUBLIC cxx_std_14) 175 | target_include_directories(fpe PUBLIC $ 176 | $) 177 | target_include_directories(fpe PUBLIC $) 178 | target_link_libraries(fpe PRIVATE common) 179 | add_dependencies(fpe OpenSSL::crypto) 180 | target_include_directories(fpe PRIVATE $) 181 | target_include_directories(fpe PRIVATE $>) 182 | set_target_properties(fpe PROPERTIES VERSION ${SHADOW_VERSION}) 183 | set_target_properties(fpe PROPERTIES OUTPUT_NAME shadow_fpe-${SHADOW_VERSION_MAJOR}.${SHADOW_VERSION_MINOR}) 184 | shadow_combine_archives(fpe OpenSSL::crypto) 185 | install(TARGETS fpe EXPORT ShadowTargets 186 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 187 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 188 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 189 | if(TARGET fpe) 190 | add_library(Shadow::fpe ALIAS fpe) 191 | endif() 192 | 193 | ########################### 194 | # SHADOW C export library # 195 | ########################### 196 | 197 | # [option] SHADOW_BUILD_C_EXPORT (default: ON) 198 | set(SHADOW_BUILD_C_EXPORT_OPTION_STR "Build C export library for Shadow") 199 | option(SHADOW_BUILD_C_EXPORT ${SHADOW_BUILD_C_EXPORT_OPTION_STR} OFF) 200 | message(STATUS "SHADOW_BUILD_C_EXPORT: ${SHADOW_BUILD_C_EXPORT}") 201 | 202 | if(SHADOW_BUILD_C_EXPORT) 203 | add_library(fpe_export SHARED) 204 | target_sources(fpe_export PRIVATE ${CMAKE_CURRENT_LIST_DIR}/shadow/fpe_export/fpe_export.cpp) 205 | target_include_directories(fpe_export PUBLIC $ 206 | $) 207 | target_include_directories(fpe_export PUBLIC $) 208 | target_link_libraries(fpe_export PRIVATE fpe) 209 | set_target_properties(fpe_export PROPERTIES VERSION ${SHADOW_VERSION}) 210 | set_target_properties(fpe_export PROPERTIES OUTPUT_NAME shadow_fpe_export) 211 | set_target_properties(fpe_export PROPERTIES SOVERSION ${SHADOW_VERSION_MAJOR}.${SHADOW_VERSION_MINOR}) 212 | install(TARGETS fpe_export EXPORT ShadowTargets 213 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 214 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 215 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 216 | if(TARGET fpe_export) 217 | add_library(Shadow::fpe_export ALIAS fpe_export) 218 | endif() 219 | endif() 220 | 221 | 222 | ################################# 223 | # Installation and CMake config # 224 | ################################# 225 | 226 | # Create the CMake config file 227 | include(CMakePackageConfigHelpers) 228 | configure_package_config_file( 229 | ${SHADOW_CONFIG_IN_FILENAME} ${SHADOW_CONFIG_FILENAME} 230 | INSTALL_DESTINATION ${SHADOW_CONFIG_INSTALL_DIR}) 231 | 232 | # Install the export 233 | install( 234 | EXPORT ShadowTargets 235 | NAMESPACE Shadow:: 236 | DESTINATION ${SHADOW_CONFIG_INSTALL_DIR}) 237 | 238 | # Version file; we require exact version match for downstream 239 | write_basic_package_version_file( 240 | ${SHADOW_CONFIG_VERSION_FILENAME} 241 | VERSION ${SHADOW_VERSION} 242 | COMPATIBILITY SameMinorVersion) 243 | 244 | # Install config and module files 245 | install( 246 | FILES 247 | ${SHADOW_CONFIG_FILENAME} 248 | ${SHADOW_CONFIG_VERSION_FILENAME} 249 | DESTINATION ${SHADOW_CONFIG_INSTALL_DIR}) 250 | 251 | # We export ShadowTargets from the build tree so it can be used by other projects 252 | # without requiring an install. 253 | export( 254 | EXPORT ShadowTargets 255 | NAMESPACE Shadow:: 256 | FILE ${SHADOW_TARGETS_FILENAME}) 257 | 258 | ############################## 259 | # SHADOW C++ / C export test # 260 | ############################## 261 | 262 | # [option] SHADOW_BUILD_TEST 263 | set(SHADOW_BUILD_TEST_OPTION_STR "Build C++ test for SHADOW") 264 | option(SHADOW_BUILD_TEST ${SHADOW_BUILD_TEST_OPTION_STR} OFF) 265 | message(STATUS "SHADOW_BUILD_TEST: ${SHADOW_BUILD_TEST}") 266 | 267 | # GoogleTest 268 | if(SHADOW_BUILD_TEST) 269 | find_package(GTest 1 CONFIG) 270 | if(GTest_FOUND) 271 | message(STATUS "GoogleTest: found") 272 | else() 273 | if(SHADOW_BUILD_UTILS) 274 | message(STATUS "GoogleTest: download ...") 275 | shadow_fetch_thirdparty_content(ExternalGTest) 276 | add_library(GTest::gtest ALIAS gtest) 277 | else() 278 | message(FATAL_ERROR "GoogleTest: not found, please download and install manually") 279 | endif() 280 | endif() 281 | endif() 282 | 283 | if(SHADOW_BUILD_TEST) 284 | add_executable(fpe_test ${CMAKE_CURRENT_LIST_DIR}/shadow/fpe/fpe_test.cpp) 285 | target_link_libraries(fpe_test PRIVATE fpe GTest::gtest) 286 | if (SHADOW_BUILD_C_EXPORT) 287 | add_executable(fpe_export_test ${CMAKE_CURRENT_LIST_DIR}/shadow/fpe_export/fpe_export_test.cpp) 288 | target_link_libraries(fpe_export_test PRIVATE fpe_export GTest::gtest) 289 | endif() 290 | 291 | # In Debug mode, enable AddressSanitizer (and LeakSanitizer) on Unix-like platforms. 292 | if(SHADOW_DEBUG AND UNIX) 293 | # On macOS, only AddressSanitizer is enabled. 294 | # On Linux, LeakSanitizer is enabled by default. 295 | target_compile_options(fpe_test PUBLIC -fsanitize=address) 296 | target_link_options(fpe_test PUBLIC -fsanitize=address) 297 | if (SHADOW_BUILD_C_EXPORT) 298 | target_compile_options(fpe_export_test PUBLIC -fsanitize=address) 299 | target_link_options(fpe_export_test PUBLIC -fsanitize=address) 300 | endif() 301 | if(NOT APPLE) 302 | message(STATUS "Sanitizers enabled: address, leak") 303 | else() 304 | message(STATUS "Sanitizers enabled: address") 305 | endif() 306 | endif() 307 | 308 | add_custom_target(test_report 309 | COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/fpe_test --gtest_output="xml:${CMAKE_CURRENT_BINARY_DIR}/report/fpe_test.xml" 310 | DEPENDS fpe_test) 311 | if(SHADOW_DEBUG AND SHADOW_ENABLE_GCOV) 312 | add_custom_target(test_coverage 313 | COMMAND gcovr -r ${CMAKE_CURRENT_LIST_DIR} -f \"shadow\" -e \".+\(test\\.cpp\)\" --xml-pretty -o "${CMAKE_CURRENT_BINARY_DIR}/report/coverage.xml" 314 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 315 | endif() 316 | endif() 317 | 318 | #################### 319 | # SHADOW C++ bench # 320 | #################### 321 | 322 | # [option] SHADOW_BUILD_BENCH 323 | set(SHADOW_BUILD_BENCH_OPTION_STR "Build C++ benchmark for SHADOW") 324 | option(SHADOW_BUILD_BENCH ${SHADOW_BUILD_BENCH_OPTION_STR} OFF) 325 | message(STATUS "SHADOW_BUILD_BENCH: ${SHADOW_BUILD_BENCH}") 326 | 327 | # GoogleBenchmark 328 | if(SHADOW_BUILD_BENCH) 329 | find_package(benchmark QUIET CONFIG) 330 | if(benchmark_FOUND) 331 | message(STATUS "GoogleBenchmark: found") 332 | else() 333 | if(SHADOW_BUILD_UTILS) 334 | message(STATUS "GoogleBenchmark: download ...") 335 | shadow_fetch_thirdparty_content(ExternalBenchmark) 336 | else() 337 | message(FATAL_ERROR "GoogleBenchmark: not found, please download and install manually") 338 | endif() 339 | endif() 340 | endif() 341 | 342 | if(SHADOW_BUILD_BENCH) 343 | add_executable(fpe_bench ${CMAKE_CURRENT_LIST_DIR}/shadow/fpe/fpe_bench.cpp) 344 | target_link_libraries(fpe_bench PRIVATE fpe benchmark::benchmark) 345 | endif() 346 | 347 | ###################### 348 | # SHADOW C++ example # 349 | ###################### 350 | 351 | # [option] SHADOW_BUILD_EXAMPLE 352 | set(SHADOW_BUILD_EXAMPLE_OPTION_STR "Build C++ example for SHADOW") 353 | option(SHADOW_BUILD_TEST ${SHADOW_BUILD_EXAMPLE_OPTION_STR} OFF) 354 | message(STATUS "SHADOW_BUILD_EXAMPLE: ${SHADOW_BUILD_EXAMPLE}") 355 | 356 | if(SHADOW_BUILD_EXAMPLE) 357 | add_subdirectory(example) 358 | endif() 359 | -------------------------------------------------------------------------------- /shadow/fpe/fpe_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "shadow/fpe/fpe.h" 16 | 17 | #include "gtest/gtest.h" 18 | #include "shadow/fpe/fpe_internal.h" 19 | 20 | namespace shadowtest { 21 | 22 | using namespace shadow::fpe; 23 | using namespace std; 24 | 25 | TEST(AlphabetTest, Constructor) { 26 | { 27 | Alphabet alphabet(kCharsetNumbers); 28 | ASSERT_EQ(alphabet.size(), 10); 29 | ASSERT_TRUE(are_identical(alphabet, Alphabet("9876543210"))); 30 | ASSERT_TRUE(are_exclusive(alphabet, Alphabet(kCharsetLettersLowercase))); 31 | ASSERT_FALSE(are_exclusive(alphabet, Alphabet("%*&5@()"))); 32 | } 33 | { 34 | Alphabet alphabet(kCharsetLettersLowercase); 35 | ASSERT_EQ(alphabet.size(), 26); 36 | ASSERT_TRUE(are_identical(alphabet, Alphabet("zyxwvutsrqponmlkjihgfedcba"))); 37 | ASSERT_TRUE(are_exclusive(alphabet, Alphabet(kCharsetLettersUppercase))); 38 | ASSERT_FALSE(are_exclusive(alphabet, Alphabet("%*&f@()"))); 39 | } 40 | { 41 | Alphabet alphabet(kCharsetNumbers + kCharsetLettersLowercase + kCharsetLettersUppercase); 42 | ASSERT_EQ(alphabet.size(), 62); 43 | ASSERT_TRUE( 44 | are_identical(alphabet, Alphabet("uZo1rXjk45lVdzeAqiBwOsHfMWPQL8TSxGapchgKCItU27m6JEYbFRnNy3v09D"))); 45 | ASSERT_TRUE(are_exclusive(alphabet, Alphabet("%*&?[]<>@()"))); 46 | ASSERT_FALSE(are_exclusive(alphabet, Alphabet("%*&J@()"))); 47 | } 48 | { 49 | ASSERT_THROW(Alphabet(string(257, 'a')), invalid_argument); 50 | ASSERT_THROW(Alphabet("aa"), invalid_argument); 51 | } 52 | } 53 | 54 | TEST(AlphabetTest, CharDigitConversion) { 55 | { 56 | AlphabetInternal alphabet(kCharsetNumbers); 57 | ASSERT_EQ(alphabet.to_digit('0'), 0); 58 | ASSERT_EQ(alphabet.to_digit('1'), 1); 59 | ASSERT_EQ(alphabet.to_digit('2'), 2); 60 | ASSERT_EQ(alphabet.to_digit('3'), 3); 61 | ASSERT_EQ(alphabet.to_digit('4'), 4); 62 | ASSERT_EQ(alphabet.to_digit('5'), 5); 63 | ASSERT_EQ(alphabet.to_digit('6'), 6); 64 | ASSERT_EQ(alphabet.to_digit('7'), 7); 65 | ASSERT_EQ(alphabet.to_digit('8'), 8); 66 | ASSERT_EQ(alphabet.to_digit('9'), 9); 67 | ASSERT_FALSE(alphabet.validate('\0')); 68 | ASSERT_FALSE(alphabet.validate('a')); 69 | ASSERT_TRUE(alphabet.validate('0')); 70 | ASSERT_FALSE(alphabet.validate(kCharsetLettersLowercase)); 71 | ASSERT_TRUE(alphabet.validate(kCharsetNumbers)); 72 | } 73 | { 74 | AlphabetInternal alphabet(kCharsetLettersLowercase); 75 | ASSERT_EQ(alphabet.radix(), 26); 76 | ASSERT_EQ(alphabet.to_digit('a'), 0); 77 | ASSERT_EQ(alphabet.to_digit('b'), 1); 78 | ASSERT_EQ(alphabet.to_digit('c'), 2); 79 | ASSERT_EQ(alphabet.to_digit('d'), 3); 80 | ASSERT_EQ(alphabet.to_digit('e'), 4); 81 | ASSERT_EQ(alphabet.to_digit('f'), 5); 82 | ASSERT_EQ(alphabet.to_digit('g'), 6); 83 | ASSERT_EQ(alphabet.to_digit('h'), 7); 84 | ASSERT_EQ(alphabet.to_digit('i'), 8); 85 | ASSERT_EQ(alphabet.to_digit('j'), 9); 86 | ASSERT_EQ(alphabet.to_digit('k'), 10); 87 | ASSERT_EQ(alphabet.to_digit('l'), 11); 88 | ASSERT_EQ(alphabet.to_digit('m'), 12); 89 | ASSERT_EQ(alphabet.to_digit('n'), 13); 90 | ASSERT_EQ(alphabet.to_digit('o'), 14); 91 | ASSERT_EQ(alphabet.to_digit('p'), 15); 92 | ASSERT_EQ(alphabet.to_digit('q'), 16); 93 | ASSERT_EQ(alphabet.to_digit('r'), 17); 94 | ASSERT_EQ(alphabet.to_digit('s'), 18); 95 | ASSERT_EQ(alphabet.to_digit('t'), 19); 96 | ASSERT_EQ(alphabet.to_digit('u'), 20); 97 | ASSERT_EQ(alphabet.to_digit('v'), 21); 98 | ASSERT_EQ(alphabet.to_digit('w'), 22); 99 | ASSERT_EQ(alphabet.to_digit('x'), 23); 100 | ASSERT_EQ(alphabet.to_digit('y'), 24); 101 | ASSERT_EQ(alphabet.to_digit('z'), 25); 102 | ASSERT_FALSE(alphabet.validate('\0')); 103 | ASSERT_FALSE(alphabet.validate('0')); 104 | ASSERT_TRUE(alphabet.validate('a')); 105 | ASSERT_FALSE(alphabet.validate(kCharsetNumbers)); 106 | ASSERT_TRUE(alphabet.validate(kCharsetLettersLowercase)); 107 | } 108 | { 109 | AlphabetInternal alphabet(kCharsetLettersUppercase + kCharsetNumbers + kCharsetLettersLowercase); 110 | ASSERT_EQ(alphabet.to_digit('3'), 3); 111 | ASSERT_EQ(alphabet.to_digit('8'), 8); 112 | ASSERT_EQ(alphabet.to_digit('E'), 14); 113 | ASSERT_EQ(alphabet.to_digit('T'), 29); 114 | ASSERT_EQ(alphabet.to_digit('i'), 44); 115 | ASSERT_EQ(alphabet.to_digit('u'), 56); 116 | ASSERT_FALSE(alphabet.validate('\0')); 117 | ASSERT_FALSE(alphabet.validate('%')); 118 | ASSERT_TRUE(alphabet.validate('a')); 119 | ASSERT_FALSE(alphabet.validate("%*&")); 120 | ASSERT_TRUE(alphabet.validate(kCharsetLettersUppercase)); 121 | ASSERT_TRUE(alphabet.validate(kCharsetNumbers)); 122 | ASSERT_TRUE(alphabet.validate(kCharsetLettersLowercase)); 123 | } 124 | } 125 | 126 | TEST(AlphabetTest, CharDigitSequenceConversion) { 127 | { 128 | AlphabetInternal alphabet(kCharsetLettersUppercase + kCharsetNumbers + kCharsetLettersLowercase); 129 | vector digit_seq; 130 | ASSERT_THROW(alphabet.to_digit("Ei3!Tu8", digit_seq), invalid_argument); 131 | alphabet.to_digit("Ei3Tu8", digit_seq); 132 | ASSERT_EQ(digit_seq, vector({14, 44, 3, 29, 56, 8})); 133 | string char_seq; 134 | ASSERT_THROW(alphabet.to_char(vector({14, 44, 3, 63, 29, 56, 8}), char_seq), invalid_argument); 135 | alphabet.to_char(vector({14, 44, 3, 29, 56, 8}), char_seq); 136 | ASSERT_EQ(char_seq, "Ei3Tu8"); 137 | } 138 | { 139 | AlphabetInternal alphabet(kCharsetLettersUppercase + kCharsetNumbers + kCharsetLettersLowercase); 140 | vector digit_seq; 141 | string schema; 142 | alphabet.to_digit("&Ei3!Tu8#", schema, digit_seq); 143 | ASSERT_EQ(schema[0], '&'); 144 | ASSERT_EQ(schema[1], '\0'); 145 | ASSERT_EQ(schema[2], '\0'); 146 | ASSERT_EQ(schema[3], '\0'); 147 | ASSERT_EQ(schema[4], '!'); 148 | ASSERT_EQ(schema[5], '\0'); 149 | ASSERT_EQ(schema[6], '\0'); 150 | ASSERT_EQ(schema[7], '\0'); 151 | ASSERT_EQ(schema[8], '#'); 152 | ASSERT_EQ(digit_seq, vector({14, 44, 3, 29, 56, 8})); 153 | string char_seq; 154 | ASSERT_THROW( 155 | alphabet.to_char(vector({14, 44, 3, 29, 56, 8}), schema + '\0', char_seq), invalid_argument); 156 | ASSERT_THROW( 157 | alphabet.to_char(vector({14, 44, 3, 29, 56, 8, 15}), schema, char_seq), invalid_argument); 158 | ASSERT_THROW( 159 | alphabet.to_char(vector({14, 44, 3, 63, 29, 56, 8}), schema, char_seq), invalid_argument); 160 | alphabet.to_char(vector({14, 44, 3, 29, 56, 8}), schema, char_seq); 161 | ASSERT_EQ(char_seq, "&Ei3!Tu8#"); 162 | } 163 | } 164 | 165 | TEST(KeyTest, Constructor) { 166 | { Key key(); } 167 | { Key key = generate_key(); } 168 | { Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); } 169 | } 170 | 171 | TEST(TweakTest, Constructor) { 172 | { 173 | vector tweak_vec = {0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30}; 174 | Tweak tweak(tweak_vec); 175 | } 176 | { 177 | string tweak_str = {0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30}; 178 | Tweak tweak(tweak_str); 179 | } 180 | { Tweak tweak(); } 181 | } 182 | 183 | // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/FF1samples.pdf. 184 | TEST(FPETest, EncryptDecrypt) { 185 | { 186 | Alphabet alphabet(kCharsetNumbers); 187 | string pt = "0123456789"; 188 | string ct; 189 | string pt_check; 190 | vector tweak; 191 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 192 | encrypt(alphabet, key, tweak, pt, ct); 193 | ASSERT_EQ(ct, "2433477484"); 194 | decrypt(alphabet, key, tweak, ct, pt_check); 195 | ASSERT_EQ(pt_check, "0123456789"); 196 | } 197 | { 198 | Alphabet alphabet(kCharsetNumbers); 199 | string pt = "0123456789"; 200 | string ct; 201 | string pt_check; 202 | vector tweak = {0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30}; 203 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 204 | encrypt(alphabet, key, tweak, pt, ct); 205 | ASSERT_EQ(ct, "6124200773"); 206 | decrypt(alphabet, key, tweak, ct, pt_check); 207 | ASSERT_EQ(pt_check, "0123456789"); 208 | } 209 | { 210 | Alphabet alphabet("0123456789ABCDEF"); 211 | string pt = "0123456789"; 212 | string ct; 213 | string pt_check; 214 | vector tweak = {0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30}; 215 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 216 | encrypt(alphabet, key, tweak, pt, ct); 217 | decrypt(alphabet, key, tweak, ct, pt_check); 218 | ASSERT_EQ(pt_check, "0123456789"); 219 | } 220 | { 221 | Alphabet alphabet(kCharsetNumbers + kCharsetLettersLowercase); 222 | string pt = "0123456789abcdefghi"; 223 | string ct; 224 | string pt_check; 225 | vector tweak = {0x37, 0x37, 0x37, 0x37, 0x70, 0x71, 0x72, 0x73, 0x37, 0x37, 0x37}; 226 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 227 | encrypt(alphabet, key, tweak, pt, ct); 228 | ASSERT_EQ(ct, "a9tv40mll9kdu509eum"); 229 | decrypt(alphabet, key, tweak, ct, pt_check); 230 | ASSERT_EQ(pt_check, "0123456789abcdefghi"); 231 | } 232 | { 233 | Alphabet alphabet(kCharsetNumbers); 234 | string pt(128, '9'); 235 | string ct; 236 | string pt_check; 237 | vector tweak = {0x37, 0x37, 0x37, 0x37, 0x70, 0x71, 0x72, 0x73, 0x37, 0x37, 0x37}; 238 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 239 | encrypt(alphabet, key, tweak, pt, ct); 240 | decrypt(alphabet, key, tweak, ct, pt_check); 241 | ASSERT_EQ(pt_check, pt); 242 | } 243 | { 244 | Alphabet alphabet(kCharsetNumbers); 245 | string ct; 246 | vector tweak; 247 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 248 | ASSERT_THROW(encrypt(Alphabet("x"), key, tweak, "xxxxxx", ct), invalid_argument); 249 | ASSERT_THROW(encrypt(alphabet, key, tweak, "1", ct), invalid_argument); 250 | ASSERT_THROW(encrypt(alphabet, key, tweak, "1234", ct), invalid_argument); 251 | ASSERT_THROW(encrypt(alphabet, key, tweak, "1234abc", ct), invalid_argument); 252 | ASSERT_THROW(encrypt(alphabet, key, tweak, "SF3741-NE32:F22", ct), invalid_argument); 253 | } 254 | } 255 | 256 | TEST(FPETest, EncryptDecryptSkipUnsupported) { 257 | { 258 | Alphabet alphabet(kCharsetNumbers); 259 | string pt = "SF3741-NE32:F22"; 260 | string ct; 261 | string pt_check; 262 | vector tweak; 263 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 264 | encrypt_skip_unsupported(alphabet, key, tweak, pt, ct); 265 | ASSERT_EQ(ct, "SF2639-NE49:F99"); 266 | decrypt_skip_unsupported(alphabet, key, tweak, ct, pt_check); 267 | ASSERT_EQ(pt_check, pt); 268 | } 269 | { 270 | Alphabet alphabet(kCharsetNumbers); 271 | string pt = "01234-56789"; 272 | string ct; 273 | string pt_check; 274 | vector tweak; 275 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 276 | encrypt_skip_unsupported(alphabet, key, tweak, pt, ct); 277 | ASSERT_EQ(ct, "24334-77484"); 278 | decrypt_skip_unsupported(alphabet, key, tweak, ct, pt_check); 279 | ASSERT_EQ(pt_check, "01234-56789"); 280 | } 281 | { 282 | Alphabet alphabet(kCharsetNumbers); 283 | string pt = "01234@56789"; 284 | string ct; 285 | string pt_check; 286 | vector tweak = {0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30}; 287 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 288 | encrypt_skip_unsupported(alphabet, key, tweak, pt, ct); 289 | ASSERT_EQ(ct, "61242@00773"); 290 | decrypt_skip_unsupported(alphabet, key, tweak, ct, pt_check); 291 | ASSERT_EQ(pt_check, "01234@56789"); 292 | } 293 | { 294 | Alphabet alphabet(kCharsetNumbers + kCharsetLettersLowercase); 295 | string pt = "01234-56789@abcdefghi"; 296 | string ct; 297 | string pt_check; 298 | vector tweak = {0x37, 0x37, 0x37, 0x37, 0x70, 0x71, 0x72, 0x73, 0x37, 0x37, 0x37}; 299 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 300 | encrypt_skip_unsupported(alphabet, key, tweak, pt, ct); 301 | ASSERT_EQ(ct, "a9tv4-0mll9@kdu509eum"); 302 | decrypt_skip_unsupported(alphabet, key, tweak, ct, pt_check); 303 | ASSERT_EQ(pt_check, "01234-56789@abcdefghi"); 304 | } 305 | } 306 | 307 | TEST(FPETest, EncryptDecryptSkipSpecified) { 308 | { 309 | Alphabet alphabet(kCharsetNumbers); 310 | Alphabet specification(kCharsetLettersUppercase + "-:"); 311 | string pt = "SF3741-NE32:F22"; 312 | string ct; 313 | string pt_check; 314 | vector tweak; 315 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 316 | encrypt_skip_specified(alphabet, specification, key, tweak, pt, ct); 317 | ASSERT_EQ(ct, "SF2639-NE49:F99"); 318 | decrypt_skip_specified(alphabet, specification, key, tweak, ct, pt_check); 319 | ASSERT_EQ(pt_check, pt); 320 | } 321 | { 322 | Alphabet alphabet(kCharsetNumbers + kCharsetLettersUppercase); 323 | Alphabet specification("-:"); 324 | string pt = "SF3741-NE32:F22"; 325 | string ct; 326 | string pt_check; 327 | vector tweak; 328 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 329 | encrypt_skip_specified(alphabet, specification, key, tweak, pt, ct); 330 | ASSERT_EQ(ct, "5RMWCT-3E5R:2HI"); 331 | decrypt_skip_specified(alphabet, specification, key, tweak, ct, pt_check); 332 | ASSERT_EQ(pt_check, pt); 333 | } 334 | { 335 | Alphabet alphabet(kCharsetNumbers); 336 | Alphabet specified("-"); 337 | string pt = "01234-56789"; 338 | string ct; 339 | string pt_check; 340 | vector tweak; 341 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 342 | encrypt_skip_specified(alphabet, specified, key, tweak, pt, ct); 343 | ASSERT_EQ(ct, "24334-77484"); 344 | decrypt_skip_specified(alphabet, specified, key, tweak, ct, pt_check); 345 | ASSERT_EQ(pt_check, "01234-56789"); 346 | } 347 | { 348 | Alphabet alphabet(kCharsetNumbers); 349 | Alphabet specified(kCharsetLettersLowercase); 350 | string pt = "01234abc56789"; 351 | string ct; 352 | string pt_check; 353 | vector tweak = {0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30}; 354 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 355 | encrypt_skip_specified(alphabet, specified, key, tweak, pt, ct); 356 | ASSERT_EQ(ct, "61242abc00773"); 357 | decrypt_skip_specified(alphabet, specified, key, tweak, ct, pt_check); 358 | ASSERT_EQ(pt_check, "01234abc56789"); 359 | } 360 | { 361 | Alphabet alphabet(kCharsetNumbers + kCharsetLettersLowercase); 362 | Alphabet specified("ABCD-@"); 363 | string pt = "01234-56789@ABCabcdefghi"; 364 | string ct; 365 | string pt_check; 366 | vector tweak = {0x37, 0x37, 0x37, 0x37, 0x70, 0x71, 0x72, 0x73, 0x37, 0x37, 0x37}; 367 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 368 | encrypt_skip_specified(alphabet, specified, key, tweak, pt, ct); 369 | ASSERT_EQ(ct, "a9tv4-0mll9@ABCkdu509eum"); 370 | decrypt_skip_specified(alphabet, specified, key, tweak, ct, pt_check); 371 | ASSERT_EQ(pt_check, "01234-56789@ABCabcdefghi"); 372 | } 373 | { 374 | Alphabet alphabet(kCharsetNumbers); 375 | Alphabet specification(kCharsetLettersUppercase); 376 | string pt = "SF3741-NE32:F22"; 377 | string ct; 378 | string pt_check; 379 | vector tweak; 380 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 381 | ASSERT_THROW(encrypt_skip_specified(alphabet, specification, key, tweak, pt, ct), invalid_argument); 382 | } 383 | { 384 | Alphabet alphabet(kCharsetNumbers); 385 | Alphabet specification(kCharsetLettersUppercase + kCharsetNumbers); 386 | string pt = "SF3741-NE32:F22"; 387 | string ct; 388 | string pt_check; 389 | vector tweak; 390 | Key key({0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}); 391 | ASSERT_THROW(encrypt_skip_specified(alphabet, specification, key, tweak, pt, ct), invalid_argument); 392 | } 393 | } 394 | 395 | } // namespace shadowtest 396 | 397 | int main(int argc, char** argv) { 398 | testing::InitGoogleTest(&argc, argv); 399 | return RUN_ALL_TESTS(); 400 | } 401 | -------------------------------------------------------------------------------- /shadow/fpe/fpe.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TikTok Pte. Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "shadow/fpe/fpe.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "shadow/common/common.h" 29 | #include "shadow/common/config.h" 30 | #include "shadow/fpe/fpe_internal.h" 31 | 32 | namespace shadow { 33 | namespace fpe { 34 | 35 | const std::string kCharsetNumbers = "0123456789"; 36 | 37 | const std::string kCharsetLettersLowercase = "abcdefghijklmnopqrstuvwxyz"; 38 | 39 | const std::string kCharsetLettersUppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 40 | 41 | Alphabet::Alphabet(const std::string& charset) { 42 | if (charset.length() == 0 || charset.length() > SHADOW_FPE_ALPHABET_SIZE_MAX) { 43 | throw std::invalid_argument("Invalid character set size"); 44 | } 45 | map_digit_to_char_.reserve(charset.length()); 46 | for (std::size_t i = 0; i < charset.length(); i++) { 47 | if (charset[i] == '\0') { 48 | throw std::invalid_argument("Character set contains an empty character"); 49 | } 50 | map_digit_to_char_.emplace_back(charset[i]); 51 | } 52 | // This ensures that alphabets are comparable. 53 | std::sort(map_digit_to_char_.begin(), map_digit_to_char_.end()); 54 | 55 | map_char_to_digit_.reserve(map_digit_to_char_.size()); 56 | std::pair::iterator, bool> result{}; 57 | for (std::size_t i = 0; i < map_digit_to_char_.size(); i++) { 58 | result = map_char_to_digit_.emplace(map_digit_to_char_[i], static_cast(i)); 59 | if (!result.second) { 60 | throw std::invalid_argument("Character set has duplication"); 61 | } 62 | } 63 | } 64 | 65 | std::size_t Alphabet::size() const { 66 | return map_char_to_digit_.size(); 67 | } 68 | 69 | bool are_identical(const Alphabet& in_0, const Alphabet& in_1) { 70 | #ifdef SHADOW_DEBUG 71 | if ((in_0.map_digit_to_char_ == in_1.map_digit_to_char_) != (in_0.map_char_to_digit_ == in_1.map_char_to_digit_)) { 72 | throw std::logic_error("Bad alphahets"); 73 | } 74 | #endif 75 | return in_0.map_digit_to_char_ == in_1.map_digit_to_char_; 76 | } 77 | 78 | bool are_exclusive(const Alphabet& in_0, const Alphabet& in_1) { 79 | for (const auto& i : in_0.map_digit_to_char_) { 80 | if (in_1.map_char_to_digit_.find(i) != in_1.map_char_to_digit_.end()) { 81 | return false; 82 | } 83 | } 84 | for (const auto& i : in_1.map_digit_to_char_) { 85 | if (in_0.map_char_to_digit_.find(i) != in_0.map_char_to_digit_.end()) { 86 | return false; 87 | } 88 | } 89 | return true; 90 | } 91 | 92 | AlphabetInternal::AlphabetInternal(const std::string& charset) : Alphabet(charset) { 93 | } 94 | 95 | AlphabetInternal::AlphabetInternal(const Alphabet& alphabet) : Alphabet(alphabet) { 96 | } 97 | 98 | std::uint32_t AlphabetInternal::radix() const { 99 | return static_cast(size()); 100 | } 101 | 102 | bool AlphabetInternal::validate(const unsigned char& in) const { 103 | return in != '\0' && map_char_to_digit_.find(in) != map_char_to_digit_.end(); 104 | } 105 | 106 | bool AlphabetInternal::validate(const std::string& in) const { 107 | for (const auto& i : in) { 108 | if (map_char_to_digit_.find(i) == map_char_to_digit_.end()) { 109 | return false; 110 | } 111 | } 112 | return true; 113 | } 114 | 115 | std::uint32_t AlphabetInternal::to_digit(const unsigned char& in) const { 116 | try { 117 | return map_char_to_digit_.at(in); 118 | } catch (const std::out_of_range&) { 119 | throw std::invalid_argument("Invalid character"); 120 | } 121 | } 122 | 123 | unsigned char AlphabetInternal::to_char(std::uint32_t in) const { 124 | if (in > map_digit_to_char_.size()) { 125 | throw std::invalid_argument("Invalid digit"); 126 | } 127 | return map_digit_to_char_[in]; 128 | } 129 | 130 | void AlphabetInternal::to_digit(const std::string& in, std::vector& out) const { 131 | out.clear(); 132 | out.reserve(in.length()); 133 | for (std::size_t i = 0; i < in.length(); i++) { 134 | try { 135 | out.emplace_back(to_digit(in[i])); 136 | } catch (const std::invalid_argument&) { 137 | throw std::invalid_argument("Invalid character"); 138 | return; 139 | } 140 | } 141 | } 142 | 143 | void AlphabetInternal::to_char(const std::vector& in, std::string& out) const { 144 | out.clear(); 145 | out.reserve(in.size()); 146 | for (std::size_t i = 0; i < in.size(); i++) { 147 | try { 148 | out.push_back(to_char(in[i])); 149 | } catch (const std::invalid_argument&) { 150 | throw std::invalid_argument("Invalid digit"); 151 | return; 152 | } 153 | } 154 | } 155 | 156 | void AlphabetInternal::to_digit(const std::string& in, std::string& schema, std::vector& out) const { 157 | out.clear(); 158 | out.reserve(in.length()); 159 | schema.clear(); 160 | schema.reserve(in.length()); 161 | for (std::size_t i = 0; i < in.length(); i++) { 162 | try { 163 | out.emplace_back(to_digit(in[i])); 164 | schema.push_back('\0'); 165 | } catch (const std::invalid_argument&) { 166 | schema.push_back(in[i]); 167 | } 168 | } 169 | } 170 | 171 | void AlphabetInternal::to_char( 172 | const std::vector& in, const std::string& schema, std::string& out) const { 173 | out.clear(); 174 | out.reserve(schema.size()); 175 | 176 | std::size_t j = 0; 177 | for (std::size_t i = 0; i < schema.size(); i++) { 178 | if (schema[i] != '\0') { 179 | out.push_back(schema[i]); 180 | } else { 181 | // in is too short 182 | if (j == in.size()) { 183 | throw std::invalid_argument("Invalid schema"); 184 | return; 185 | } 186 | try { 187 | out.push_back(to_char(in[j])); 188 | } catch (const std::invalid_argument&) { 189 | throw std::invalid_argument("Invalid digit"); 190 | return; 191 | } 192 | j++; 193 | } 194 | } 195 | 196 | // schema is too short 197 | if (j != in.size()) { 198 | throw std::invalid_argument("Invalid schema"); 199 | return; 200 | } 201 | } 202 | 203 | Key generate_key() { 204 | Key key; 205 | common::get_bytes_from_random_device(key.size(), key.data()); 206 | return key; 207 | } 208 | 209 | Tweak::Tweak(const std::vector& tweak) : std::vector(tweak) { 210 | if (tweak.size() > SHADOW_FPE_TWEAK_BYTE_COUNT_MAX) { 211 | throw std::invalid_argument("Tweak is too long"); 212 | } 213 | } 214 | 215 | Tweak::Tweak(const std::string& tweak) { 216 | if (tweak.size() > SHADOW_FPE_TWEAK_BYTE_COUNT_MAX) { 217 | throw std::invalid_argument("Tweak is too long"); 218 | } 219 | this->reserve(tweak.size()); 220 | for (auto i : tweak) { 221 | this->emplace_back(i); 222 | } 223 | } 224 | 225 | // See Algorithm 1 in https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf. 226 | // out = in[0] * (radix^num_digits-1) + in[1] * (radix^num_digits-2) + ... + in[num_digits] * radix^0 227 | static void digit_to_bn( 228 | std::uint32_t radix, BN_CTX* bn_ctx, const std::uint32_t* in, std::size_t num_digits, BIGNUM* bn_out) { 229 | BN_CTX_start(bn_ctx); 230 | std::size_t i = 1; 231 | 232 | std::uint32_t out_32 = in[0]; 233 | std::uint32_t tmp_32 = out_32; 234 | std::uint32_t inv_radix_32 = std::numeric_limits::max() / radix; 235 | for (; i < num_digits;) { 236 | if (tmp_32 > inv_radix_32) { 237 | break; 238 | } 239 | tmp_32 *= radix; 240 | tmp_32 += in[i]; 241 | if (tmp_32 < in[i]) { 242 | break; 243 | } 244 | out_32 = tmp_32; 245 | i++; 246 | } 247 | if (i == num_digits) { 248 | BN_lebin2bn(reinterpret_cast(&out_32), sizeof(std::uint32_t), bn_out); 249 | BN_CTX_end(bn_ctx); 250 | return; 251 | } 252 | 253 | std::uint64_t out_64 = out_32; 254 | std::uint64_t tmp_64 = out_32; 255 | std::uint64_t inv_radix_64 = std::numeric_limits::max() / radix; 256 | for (; i < num_digits;) { 257 | if (tmp_64 > inv_radix_64) { 258 | break; 259 | } 260 | tmp_64 *= radix; 261 | tmp_64 += in[i]; 262 | if (tmp_64 < in[i]) { 263 | break; 264 | } 265 | out_64 = tmp_64; 266 | i++; 267 | } 268 | if (i == num_digits) { 269 | BN_lebin2bn(reinterpret_cast(&out_64), sizeof(std::uint64_t), bn_out); 270 | BN_CTX_end(bn_ctx); 271 | return; 272 | } 273 | 274 | unsigned __int128 out_128 = out_64; 275 | unsigned __int128 tmp_128 = out_64; 276 | unsigned __int128 inv_radix_128 = std::numeric_limits::max() / radix; 277 | for (; i < num_digits;) { 278 | if (tmp_128 > inv_radix_128) { 279 | break; 280 | } 281 | tmp_128 *= radix; 282 | tmp_128 += in[i]; 283 | if (tmp_128 < in[i]) { 284 | break; 285 | } 286 | out_128 = tmp_128; 287 | i++; 288 | } 289 | 290 | BN_lebin2bn(reinterpret_cast(&out_128), sizeof(unsigned __int128), bn_out); 291 | 292 | if (i == num_digits) { 293 | BN_CTX_end(bn_ctx); 294 | return; 295 | } 296 | 297 | std::size_t radix_digist_num_lt_u32 = 298 | static_cast(std::ceil(32.0 / std::log2(static_cast(radix)))) - 1; 299 | std::vector radix_pow_j(radix_digist_num_lt_u32, radix); 300 | bool radix_filled_flag = false; 301 | 302 | for (; i < num_digits; i += radix_digist_num_lt_u32) { 303 | std::uint32_t u32_cache = in[i]; 304 | std::size_t j = 1; 305 | std::size_t radix_max = radix; 306 | for (; (j < radix_digist_num_lt_u32) && (i + j < num_digits); j++) { 307 | u32_cache *= radix; 308 | u32_cache += in[i + j]; 309 | if (!radix_filled_flag) { 310 | radix_pow_j[j] = radix_pow_j[j - 1] * radix; 311 | } 312 | } 313 | if (radix_pow_j[radix_digist_num_lt_u32 - 1] != radix) { 314 | radix_filled_flag = true; 315 | } 316 | BN_mul_word(bn_out, radix_pow_j[j - 1]); 317 | BN_add_word(bn_out, u32_cache); 318 | } 319 | BN_CTX_end(bn_ctx); 320 | } 321 | 322 | // See Algorithm 3 in https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf. 323 | // in = out[0] * (radix^num_digits-1) + out[1] * (radix^num_digits-2) + ... + out[num_digits] * radix^0 324 | static void bn_to_digit( 325 | std::uint32_t radix, BN_CTX* bn_ctx, const BIGNUM* in, std::size_t num_digits, std::uint32_t* out) { 326 | std::size_t in_byte_count = static_cast(BN_num_bytes(in)); 327 | std::size_t i = 0; 328 | BN_CTX_start(bn_ctx); 329 | BIGNUM* bn_q = BN_CTX_get(bn_ctx); 330 | BN_copy(bn_q, in); 331 | 332 | std::size_t radix_digist_num_lt_u128 = 0; 333 | std::size_t radix_digist_num_lt_u32 = 0; 334 | std::uint32_t radix_lt_u32 = radix; 335 | if (in_byte_count > sizeof(std::uint32_t)) { 336 | radix_digist_num_lt_u128 = 337 | static_cast(std::ceil(128.0 / std::log2(static_cast(radix)))) - 1; 338 | radix_digist_num_lt_u32 = radix_digist_num_lt_u128 / 4; 339 | for (std::size_t j = 1; j < radix_digist_num_lt_u32; ++j) { 340 | radix_lt_u32 *= radix; 341 | } 342 | } 343 | 344 | if (in_byte_count > sizeof(unsigned __int128)) { 345 | BIGNUM* bn_radix_lt_u128 = BN_CTX_get(bn_ctx); 346 | BIGNUM* bn_exponent = BN_CTX_get(bn_ctx); 347 | BN_set_word(bn_exponent, radix_digist_num_lt_u128); 348 | BN_set_word(bn_radix_lt_u128, radix); 349 | BN_exp(bn_radix_lt_u128, bn_radix_lt_u128, bn_exponent, bn_ctx); 350 | 351 | BIGNUM* bn_rem = BN_CTX_get(bn_ctx); 352 | for (; i < num_digits;) { 353 | BN_div(bn_q, bn_rem, bn_q, bn_radix_lt_u128, bn_ctx); 354 | unsigned __int128 q_128; 355 | BN_bn2lebinpad(bn_rem, (unsigned char*)&q_128, sizeof(unsigned __int128)); 356 | 357 | std::size_t j = 0; 358 | for (; j < 3; ++j) { 359 | std::uint32_t q_32 = q_128 % radix_lt_u32; 360 | for (std::size_t k = 0; k < radix_digist_num_lt_u32; ++k) { 361 | out[num_digits - 1 - i] = static_cast(q_32 % radix); 362 | q_32 /= radix; 363 | i++; 364 | } 365 | q_128 /= radix_lt_u32; 366 | } 367 | 368 | std::uint64_t q_64 = static_cast(q_128); 369 | for (std::size_t k = 0; k < radix_digist_num_lt_u128 - 3 * radix_digist_num_lt_u32; ++k) { 370 | out[num_digits - 1 - i] = static_cast(q_64 % radix); 371 | q_64 /= radix; 372 | i++; 373 | } 374 | 375 | in_byte_count = static_cast(BN_num_bytes(bn_q)); 376 | if (in_byte_count <= sizeof(unsigned __int128)) { 377 | break; 378 | } 379 | } 380 | } 381 | 382 | if (in_byte_count <= sizeof(std::uint32_t)) { 383 | uint32_t q_32; 384 | BN_bn2lebinpad(bn_q, (unsigned char*)&q_32, sizeof(std::uint32_t)); 385 | for (; i < num_digits; i++) { 386 | out[num_digits - 1 - i] = static_cast(q_32 % radix); 387 | q_32 /= radix; 388 | } 389 | BN_CTX_end(bn_ctx); 390 | return; 391 | } 392 | 393 | if (in_byte_count <= sizeof(std::uint64_t)) { 394 | uint64_t q_64; 395 | BN_bn2lebinpad(bn_q, (unsigned char*)&q_64, sizeof(std::uint64_t)); 396 | std::uint32_t q_32 = q_64 % radix_lt_u32; 397 | q_64 = q_64 / radix_lt_u32; 398 | for (std::size_t k = 0; k < radix_digist_num_lt_u32 && i < num_digits; ++k) { 399 | out[num_digits - 1 - i] = static_cast(q_32 % radix); 400 | q_32 /= radix; 401 | i++; 402 | } 403 | for (; i < num_digits; i++) { 404 | out[num_digits - 1 - i] = static_cast(q_64 % radix); 405 | q_64 /= radix; 406 | } 407 | BN_CTX_end(bn_ctx); 408 | return; 409 | } 410 | 411 | if (in_byte_count <= sizeof(unsigned __int128)) { 412 | unsigned __int128 q_128; 413 | BN_bn2lebinpad(bn_q, (unsigned char*)&q_128, sizeof(unsigned __int128)); 414 | std::size_t j = 0; 415 | for (; j < 3; ++j) { 416 | std::uint32_t q_32 = q_128 % radix_lt_u32; 417 | for (std::size_t k = 0; k < radix_digist_num_lt_u32 && i < num_digits; ++k) { 418 | out[num_digits - 1 - i] = static_cast(q_32 % radix); 419 | q_32 /= radix; 420 | i++; 421 | } 422 | q_128 /= radix_lt_u32; 423 | } 424 | std::uint64_t q_64 = static_cast(q_128); 425 | for (std::size_t k = 0; k < radix_digist_num_lt_u128 - 3 * radix_digist_num_lt_u32 && i < num_digits; ++k) { 426 | out[num_digits - 1 - i] = static_cast(q_64 % radix); 427 | q_64 /= radix; 428 | i++; 429 | } 430 | BN_CTX_end(bn_ctx); 431 | return; 432 | } 433 | } 434 | 435 | // See Section 3.3 in https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf. 436 | // out[out_byte_count - 1 - i] = (bn_in / 256^i) % 256 437 | static void bn_to_byte(const BIGNUM* bn_in, std::size_t out_byte_count, unsigned char* out) { 438 | std::size_t byte_count = BN_bn2bin(bn_in, out); 439 | for (std::size_t i = 0; i < byte_count; i++) { 440 | out[out_byte_count - 1 - i] = out[byte_count - 1 - i]; 441 | } 442 | std::fill_n(out, out_byte_count - byte_count, 0); 443 | } 444 | 445 | // See Algorithm 2 in https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf. 446 | // (bn_in / 256^i) % 256 = in[in_byte_count - 1 - i] 447 | static void byte_to_bn(unsigned char* in, std::size_t in_byte_count, BIGNUM* bn_out) { 448 | BN_bin2bn(in, static_cast(in_byte_count), bn_out); 449 | } 450 | 451 | void ff1_encrypt(std::uint32_t radix, const Key& key, const std::vector& tweak, bool encrypt, 452 | const std::uint32_t* in, std::size_t in_len, std::uint32_t* out) { 453 | // 0 <= t <= 2^32-1, represted with 32 bits 454 | const std::uint32_t t = static_cast(tweak.size()); 455 | // 2 <= n <= 2^32-1, represented with 32 bits 456 | const std::uint32_t n = static_cast(in_len); 457 | // allocate and set OpenSSL AES_KEY 458 | AES_KEY aes_key; 459 | AES_set_encrypt_key(key.data(), SHADOW_FPE_KEY_BIT_COUNT, &aes_key); 460 | 461 | // create cipher with the key 462 | auto cipher = [&](const unsigned char* pt, unsigned char* ct) { AES_ecb_encrypt(pt, ct, &aes_key, 1); }; 463 | 464 | // Step 1-5 465 | // 1 <= u <= 2^31-1, represented with 32 bits 466 | const std::uint32_t u = n / 2; 467 | // 1 <= v <= 2^31, represented with 32 bits 468 | const std::uint32_t v = n - u; 469 | // 1 <= b <= 2^32, represented with 64 bits 470 | const std::uint64_t b = 471 | (static_cast(ceil(static_cast(v) * std::log2(static_cast(radix)))) + 7) / 8; 472 | // 5 <= d <= 2^30+4, represented with 32 bits 473 | const std::uint32_t d = static_cast((b + 3) / 4 * 4 + 4); 474 | // See Section 3.3 in https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf. 475 | const unsigned char p[16] = {1, 2, 1, static_cast(radix >> 16), 476 | static_cast((radix >> 8) % 256), static_cast(radix % 256), 10, 477 | static_cast(u % 256), static_cast(n >> 24), 478 | static_cast((n >> 16) % 256), static_cast((n >> 8) % 256), 479 | static_cast(n % 256), static_cast(t >> 24), 480 | static_cast((t >> 16) % 256), static_cast((t >> 8) % 256), 481 | static_cast(t % 256)}; 482 | 483 | // Step 6.ii: the first block 484 | unsigned char r0[16]; 485 | cipher(reinterpret_cast(p), r0); 486 | 487 | // Pre-allocate buffers 488 | const std::size_t q_padding_size = (-t - b - 1) & 0xF; 489 | const std::size_t q_size = t + q_padding_size + 1 + b; 490 | std::size_t buf_uint32_count = std::max(u, v); 491 | std::size_t buf_size = q_size + 16 + (d + 15) / 16 * 16 + buf_uint32_count * sizeof(std::uint32_t) * 3; 492 | std::vector buf(buf_size); 493 | unsigned char* q = buf.data(); 494 | unsigned char* buf_r = q + q_size; 495 | unsigned char* buf_s = buf_r + 16; 496 | std::uint32_t* buf_a = reinterpret_cast(buf_s + (d + 15) / 16 * 16); 497 | std::copy_n(in, u, buf_a); 498 | std::uint32_t* buf_b = buf_a + buf_uint32_count; 499 | std::copy_n(in + u, v, buf_b); 500 | std::uint32_t* buf_c = buf_b + buf_uint32_count; 501 | 502 | // Step 6.i: the beginning bytes 503 | std::copy_n(tweak.cbegin(), t, q); 504 | std::fill_n(q + t, q_padding_size, 0x00); 505 | 506 | // Pre-allocate BIGNUM 507 | BN_CTX* bn_ctx = BN_CTX_new(); 508 | BIGNUM* bn_a = BN_new(); 509 | BIGNUM* bn_b = BN_new(); 510 | BIGNUM* bn_y = BN_new(); 511 | BIGNUM* bn_c = BN_new(); 512 | BIGNUM* bn_radix = BN_new(); 513 | BN_set_word(bn_radix, radix); 514 | BIGNUM* bn_u = BN_new(); 515 | BN_set_word(bn_u, u); 516 | BIGNUM* bn_radix_to_u = BN_new(); 517 | BIGNUM* bn_radix_to_v = BN_new(); 518 | BN_exp(bn_radix_to_u, bn_radix, bn_u, bn_ctx); 519 | if (u == v) { 520 | BN_copy(bn_radix_to_v, bn_radix_to_u); 521 | } else if (u == v - 1) { 522 | BN_mul(bn_radix_to_v, bn_radix_to_u, bn_radix, bn_ctx); 523 | } 524 | BIGNUM* bn_radix_to_m = nullptr; 525 | 526 | for (std::uint32_t round = 0; round < SHADOW_FF1_NUM_ROUNDS; round++) { 527 | std::uint32_t i; 528 | if (encrypt) { 529 | i = round; 530 | } else { 531 | i = SHADOW_FF1_NUM_ROUNDS - 1 - round; 532 | } 533 | 534 | // Step 6.v: if i is even, let m = u; else let m = v 535 | const std::uint32_t m = i % 2 ? v : u; 536 | 537 | // Step 6.vii 538 | bn_radix_to_m = i % 2 ? bn_radix_to_v : bn_radix_to_u; 539 | 540 | // Step 6.i and 6.vi 541 | if (encrypt) { 542 | digit_to_bn(radix, bn_ctx, buf_a, m, bn_a); 543 | digit_to_bn(radix, bn_ctx, buf_b, n - m, bn_b); 544 | } else { 545 | digit_to_bn(radix, bn_ctx, buf_a, n - m, bn_a); 546 | digit_to_bn(radix, bn_ctx, buf_b, m, bn_b); 547 | } 548 | 549 | // Step 6.i: if encrypt, buf_b; otherwise, buf_a 550 | q[q_size - b - 1] = static_cast(i); 551 | if (encrypt) { 552 | bn_to_byte(bn_b, b, q + q_size - b); 553 | } else { 554 | bn_to_byte(bn_a, b, q + q_size - b); 555 | } 556 | 557 | // Step 6.ii 558 | unsigned char temp[16]; 559 | std::copy_n(r0, 16, buf_r); 560 | for (std::uint32_t j = 0; j < q_size / 16; j++) { 561 | reinterpret_cast(temp)[0] = 562 | reinterpret_cast(q + 16 * j)[0] ^ reinterpret_cast(buf_r)[0]; 563 | reinterpret_cast(temp)[1] = 564 | reinterpret_cast(q + 16 * j)[1] ^ reinterpret_cast(buf_r)[1]; 565 | cipher(temp, buf_r); 566 | } 567 | 568 | // Step 6.iii 569 | std::copy_n(buf_r, 16, buf_s); 570 | std::copy_n(buf_r, 16, temp); 571 | for (std::uint32_t j = 1; j < (d + 15) / 16; j++) { 572 | reinterpret_cast(temp)[3] = reinterpret_cast(buf_r)[3] ^ j; 573 | cipher(temp, buf_s + 16 * j); 574 | } 575 | 576 | // Step 6.iv 577 | byte_to_bn(buf_s, d, bn_y); 578 | 579 | // Step 6.vi 580 | if (encrypt) { 581 | BN_mod_add(bn_c, bn_a, bn_y, bn_radix_to_m, bn_ctx); 582 | } else { 583 | BN_mod_sub(bn_c, bn_b, bn_y, bn_radix_to_m, bn_ctx); 584 | } 585 | 586 | // Step 6.vii 587 | bn_to_digit(radix, bn_ctx, bn_c, m, buf_c); 588 | 589 | // Step 6.viii and 6.ix 590 | if (encrypt) { 591 | std::uint32_t* buf_t = buf_b; 592 | buf_a = buf_b; 593 | buf_b = buf_c; 594 | buf_c = buf_t; 595 | } else { 596 | uint32_t* buf_t = buf_a; 597 | buf_b = buf_a; 598 | buf_a = buf_c; 599 | buf_c = buf_t; 600 | } 601 | } 602 | 603 | // Step 7 604 | std::copy_n(buf_a, u, out); 605 | std::copy_n(buf_b, v, out + u); 606 | 607 | // Clean up 608 | std::fill(buf.begin(), buf.end(), 0); 609 | 610 | BN_clear_free(bn_a); 611 | BN_clear_free(bn_b); 612 | BN_clear_free(bn_y); 613 | BN_clear_free(bn_c); 614 | BN_clear_free(bn_radix); 615 | BN_clear_free(bn_u); 616 | BN_clear_free(bn_radix_to_u); 617 | BN_clear_free(bn_radix_to_v); 618 | BN_CTX_free(bn_ctx); 619 | } 620 | 621 | static void encrypt_internal(const AlphabetInternal& alphabet, const Key& key, const std::vector& tweak, 622 | bool encrypt, const std::vector& in, std::vector& out) { 623 | std::size_t length = in.size(); 624 | if (length < SHADOW_FPE_MESSAGE_LEN_MIN || length > SHADOW_FPE_MESSAGE_LEN_MAX) { 625 | throw std::invalid_argument("Invalid message length"); 626 | } 627 | 628 | double log_domain = std::log10(static_cast(alphabet.radix())) * static_cast(length); 629 | if (log_domain < 6) { 630 | throw std::invalid_argument("Domain is less than 1 million"); 631 | } 632 | 633 | out.resize(in.size()); 634 | ff1_encrypt(alphabet.radix(), key, tweak, encrypt, in.data(), in.size(), out.data()); 635 | } 636 | 637 | void encrypt_internal(const Alphabet& alphabet, const Key& key, const std::vector& tweak, bool encrypt, 638 | const std::string& in, std::string& out) { 639 | AlphabetInternal alphabet_internal(alphabet); 640 | std::vector in_digits; 641 | std::vector out_digits; 642 | 643 | try { 644 | alphabet_internal.to_digit(in, in_digits); 645 | } catch (const std::invalid_argument&) { 646 | throw std::invalid_argument("Unsupported character"); 647 | } 648 | 649 | encrypt_internal(alphabet_internal, key, tweak, encrypt, in_digits, out_digits); 650 | 651 | try { 652 | alphabet_internal.to_char(out_digits, out); 653 | } catch (const std::invalid_argument&) { 654 | throw std::logic_error("Unsupported digit from encryption"); 655 | } 656 | } 657 | 658 | void encrypt(const Alphabet& alphabet, const Key& key, const std::vector& tweak, const std::string& in, 659 | std::string& out) { 660 | encrypt_internal(alphabet, key, tweak, true, in, out); 661 | } 662 | 663 | void decrypt(const Alphabet& alphabet, const Key& key, const std::vector& tweak, const std::string& in, 664 | std::string& out) { 665 | encrypt_internal(alphabet, key, tweak, false, in, out); 666 | } 667 | 668 | void encrypt_skip_unsupported_internal(const Alphabet& alphabet, const Key& key, 669 | const std::vector& tweak, bool encrypt, const std::string& in, std::string& out) { 670 | AlphabetInternal alphabet_internal(alphabet); 671 | std::string schema; 672 | std::vector in_digits; 673 | std::vector out_digits; 674 | 675 | alphabet_internal.to_digit(in, schema, in_digits); 676 | 677 | encrypt_internal(alphabet_internal, key, tweak, encrypt, in_digits, out_digits); 678 | 679 | try { 680 | alphabet_internal.to_char(out_digits, schema, out); 681 | } catch (const std::invalid_argument&) { 682 | throw std::logic_error("Unsupported digit from encryption"); 683 | } 684 | } 685 | 686 | void encrypt_skip_unsupported(const Alphabet& alphabet, const Key& key, const std::vector& tweak, 687 | const std::string& in, std::string& out) { 688 | encrypt_skip_unsupported_internal(alphabet, key, tweak, true, in, out); 689 | } 690 | 691 | void decrypt_skip_unsupported(const Alphabet& alphabet, const Key& key, const std::vector& tweak, 692 | const std::string& in, std::string& out) { 693 | encrypt_skip_unsupported_internal(alphabet, key, tweak, false, in, out); 694 | } 695 | 696 | void encrypt_skip_specified_internal(const Alphabet& alphabet, const Alphabet& specification, const Key& key, 697 | const std::vector& tweak, bool encrypt, const std::string& in, std::string& out) { 698 | if (!are_exclusive(alphabet, specification)) { 699 | throw std::invalid_argument("Alphabet and specification have an overlapping character"); 700 | } 701 | 702 | AlphabetInternal alphabet_internal(alphabet); 703 | AlphabetInternal specification_internal(specification); 704 | std::string schema; 705 | std::vector in_digits; 706 | std::vector out_digits; 707 | 708 | alphabet_internal.to_digit(in, schema, in_digits); 709 | 710 | for (auto i : schema) { 711 | if (i != '\0') { 712 | if (!specification_internal.validate(i)) { 713 | throw std::invalid_argument("Unsupported and unspecified character"); 714 | } 715 | } 716 | } 717 | 718 | encrypt_internal(alphabet_internal, key, tweak, encrypt, in_digits, out_digits); 719 | 720 | try { 721 | alphabet_internal.to_char(out_digits, schema, out); 722 | } catch (const std::invalid_argument&) { 723 | throw std::logic_error("Unsupported digit from encryption"); 724 | } 725 | } 726 | 727 | void encrypt_skip_specified(const Alphabet& alphabet, const Alphabet& specification, const Key& key, 728 | const std::vector& tweak, const std::string& in, std::string& out) { 729 | encrypt_skip_specified_internal(alphabet, specification, key, tweak, true, in, out); 730 | } 731 | 732 | void decrypt_skip_specified(const Alphabet& alphabet, const Alphabet& specification, const Key& key, 733 | const std::vector& tweak, const std::string& in, std::string& out) { 734 | encrypt_skip_specified_internal(alphabet, specification, key, tweak, false, in, out); 735 | } 736 | 737 | } // namespace fpe 738 | } // namespace shadow 739 | --------------------------------------------------------------------------------