├── .gitignore ├── CMake └── Findlibsodium.cmake ├── CMakeLists.txt ├── LICENSE ├── Readme.md ├── cli ├── CMakeLists.txt └── main.cpp ├── common ├── CMakeLists.txt ├── encrypt.cpp ├── encrypt.h ├── fd.cpp ├── fd.h ├── format.h ├── options.cpp ├── options.h ├── symbols.h ├── term_echo.cpp ├── term_echo.h ├── utf8.cpp ├── utf8.h └── version.h ├── docs ├── css │ └── index.css ├── index.html └── js │ ├── channels.js │ ├── emocrypt.js │ ├── emocrypt.wasm │ ├── index.js │ ├── marquee.js │ └── worker.js ├── emscripten ├── CMakeLists.txt ├── emocrypt.cpp └── emocrypt.h ├── test ├── CMakeLists.txt ├── test_bitstream.cpp ├── test_encrypt.cpp ├── test_stego.cpp └── test_utf8.cpp ├── third-party ├── CMakeLists.txt └── mingw-w64 │ ├── CMakeLists.txt │ ├── getopt.c │ └── getopt.h └── util ├── CMakeLists.txt └── generate_data.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | Build 2 | .vscode 3 | *.bak 4 | ._* 5 | .DS_Store 6 | .buildenv 7 | .emscripten 8 | Build-* 9 | .cache 10 | .vs 11 | CMakeSettings.json 12 | *.swp 13 | .clangd 14 | -------------------------------------------------------------------------------- /CMake/Findlibsodium.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Finds libsodium and populates variables and imported targets 3 | # 4 | # Hints: 5 | # libsodium_ROOT 6 | # libsodium_INCLUDEDIR 7 | # libsodium_LIBRARYDIR 8 | # libsodium_NO_SYSTEM_PATHS 9 | # 10 | # Variables: 11 | # libsodium_FOUND 12 | # libsodium_INCLUDE_DIR 13 | # libsodium_LIBRARY 14 | # 15 | # Targets: 16 | # libsodium::libsodium 17 | # 18 | 19 | set(INCLUDE_SEARCH_PATHS) 20 | set(LIBRARY_SEARCH_PATHS) 21 | 22 | if(libsodium_ROOT) 23 | list(APPEND INCLUDE_SEARCH_PATHS ${libsodium_ROOT}/include) 24 | list(APPEND LIBRARY_SEARCH_PATHS ${libsodium_ROOT}/lib) 25 | endif() 26 | 27 | if(libsodium_INCLUDEDIR) 28 | list(APPEND INCLUDE_SEARCH_PATHS ${libsodium_INCLUDEDIR}) 29 | endif() 30 | 31 | if(libsodium_LIBRARYDIR) 32 | list(APPEND LIBRARY_SEARCH_PATHS ${libsodium_LIBRARYDIR}) 33 | endif() 34 | 35 | find_package(PkgConfig) 36 | pkg_check_modules(PC_libsodium QUIET libsodium) 37 | 38 | list(APPEND INCLUDE_SEARCH_PATHS ${PC_libsodium_INCLUDE_DIRS}) 39 | list(APPEND LIBRARY_SEARCH_PATHS ${PC_libsodium_LIBRARY_DIRS}) 40 | set(libsodium_VERSION ${PC_libsodium_VERSION}) 41 | 42 | 43 | if(libsodium_NO_SYSTEM_PATHS) 44 | find_path(libsodium_INCLUDE_DIR 45 | NAMES sodium.h 46 | PATHS 47 | ${INCLUDE_SEARCH_PATHS} 48 | ${CMAKE_INCLUDE_PATH} 49 | ${CMAKE_SYSTEM_INCLUDE_PATH} 50 | ${CMAKE_INSTALL_PREFIX}/include 51 | NO_DEFAULT_PATH) 52 | 53 | find_library(libsodium_LIBRARY 54 | NAMES sodium 55 | PATHS 56 | ${LIBRARY_SEARCH_PATHS} 57 | ${CMAKE_LIBRARY_PATH} 58 | ${CMAKE_SYSTEM_LIBRARY_PATH} 59 | ${CMAKE_INSTALL_PREFIX}/lib 60 | NO_DEFAULT_PATH) 61 | else() 62 | find_path(libsodium_INCLUDE_DIR 63 | NAMES sodium.h 64 | PATHS ${INCLUDE_SEARCH_PATHS}) 65 | find_library(libsodium_LIBRARY 66 | NAMES sodium 67 | PATHS ${LIBRARY_SEARCH_PATHS}) 68 | endif() 69 | 70 | mark_as_advanced( 71 | libsodium_INCLUDE_DIR 72 | libsodium_LIBRARY) 73 | 74 | include(FindPackageHandleStandardArgs) 75 | find_package_handle_standard_args(libsodium 76 | FOUND_VAR libsodium_FOUND 77 | REQUIRED_VARS 78 | libsodium_LIBRARY 79 | libsodium_INCLUDE_DIR 80 | VERSION_VAR libsodium_VERSION) 81 | 82 | if(libsodium_FOUND) 83 | set(libsodium_LIBRARIES ${libsodium_LIBRARY}) 84 | set(libsodium_INCLUDE_DIRS ${libsodium_INCLUDE_DIR}) 85 | 86 | if(NOT TARGET libsodium::libsodium) 87 | add_library(libsodium::libsodium UNKNOWN IMPORTED) 88 | set_target_properties(libsodium::libsodium PROPERTIES 89 | IMPORTED_LOCATION ${libsodium_LIBRARY} 90 | INTERFACE_INCLUDE_DIRECTORIES ${libsodium_INCLUDE_DIR}) 91 | endif() 92 | endif() 93 | 94 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | cmake_policy(SET CMP0091 NEW) 3 | project(emocrypt) 4 | 5 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/CMake) 6 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 7 | 8 | set(EMSCRIPTEN OFF) 9 | if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") 10 | set(EMSCRIPTEN ON) 11 | endif() 12 | 13 | option(ENABLE_ASAN "Enable AddressSanitizer" OFF) 14 | option(ENABLE_TESTING "Enable unit tests" OFF) 15 | 16 | find_package(libsodium REQUIRED) 17 | add_library(project_options INTERFACE) 18 | target_compile_features(project_options INTERFACE cxx_std_14) 19 | target_link_libraries(project_options INTERFACE libsodium::libsodium) 20 | 21 | if(WIN32) 22 | target_compile_definitions(project_options INTERFACE SODIUM_STATIC) 23 | endif() 24 | 25 | if(ENABLE_ASAN) 26 | target_compile_options(project_options INTERFACE -fsanitize=address -fno-omit-frame-pointer) 27 | target_link_options(project_options INTERFACE -fsanitize=address -fno-omit-frame-pointer) 28 | endif() 29 | 30 | add_subdirectory(third-party) 31 | add_subdirectory(common) 32 | 33 | if(EMSCRIPTEN) 34 | add_subdirectory(emscripten) 35 | else() 36 | add_subdirectory(cli) 37 | add_subdirectory(util) 38 | endif() 39 | 40 | if(ENABLE_TESTING) 41 | add_subdirectory(test) 42 | endif() 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2038] [Jean Chrysostome DelaCroix de Bonaventure Modeste Jean Baptiste Jean François de la Salle de l'enfant Jésus de la croix de Nazareth Saint Francis Drake Bob d'Oakland] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # emocrypt 2 | 3 | ## Encrypts data into emojipastas 4 | 5 | This program encrypts arbitrary data into emojipastas. 6 | It uses XSalsa20 stream cipher with Poly1305 MAC encryption, and Argon2id key derivation. 7 | 8 | ## Example output 9 | 10 | ``` 11 | I'd just like 🐃to interject for🔒 a moment. 🏨What you're refering🈵 to as 12 | 🍑Linux, is in🔙 fact, GNU/Linux, or🐱 as I've 🏩recently taken to📚 calling 13 | it, 🔪GNU plus Linux.🍒 Linux is 🍟not an operating🛃 system unto itself,😁 14 | but rather 👊another free component🐁 of a 👅fully functioning GNU😡 system 15 | made 📏useful by the🍔 GNU corelibs, shell💆 utilities and 🌸vital system 16 | components🔡 comprising a 😌full OS as🕐 defined by 🔎POSIX. 17 | 18 | Many computer 🕐users run a🈸 modified version 💖of the GNU💋 system every 19 | 💾day, without realizing😸 it. Through 💡a peculiar turn 🍃of events, the🆑 20 | version of ⛄GNU which is✋ widely used 🚛today is often💖 called Linux, 📇 21 | and many of 🎰its users are🎯 not aware 🕠that it is📈 basically the 📗GNU 22 | system, developed📦 by the GNU🕔 Project. 23 | 24 | There 🙍really is a🎰 Linux, and 😹these people are🐧 using it, 🏁but it is 25 | 💜 just a part📠 of the 🎐system they use.📬 Linux is 🐶the kernel: the🔍 26 | program in 💏the system that🍜 allocates the machine's🌀 resources to 💘the 27 | other programs💂 that you 📳run. The kernel🔰 is an 💩essential part of 🔡an 28 | operating system,🍕 but useless 👴by itself; it👎 can only 🕐function in 29 | the🕜 context of 🐧a complete operating 🚫system. Linux is🐥 normally used 30 | 💟in combination with🚟 the GNU 🗽operating system: the🎠 whole system 🕟is 31 | basically GNU 🌺with Linux added,🌟 or GNU/Linux. 🙅All the so-called📛 32 | Linux distributions 📱are really distributions🔆 of GNU/Linux!🔱 33 | ``` 34 | 35 | The above ciphertext can be decrypted using the password 'hunter2'. 36 | It's also possible to just encrypt into a simple emoji soup: 37 | 38 | ``` 39 | 🐲💣💩🍗😤📲🐽👹💠🔚🔝🐯🌷😾📩💎📀👍👈🆓 40 | 🌴🍗🎇🎃😊💟📃💯🐁🐘💅🛃🎰🌟🐠🎺🍞🍫🐻🎳 41 | 😼💰🔚💮🏠🎵🎷🎷💣😌💀🍹🚟🌷👨🕙📕🍑👋🔥 42 | 🎫😅🎄🐳👒🆕🌰💹🐸🕝🕘🕗🐫🏯🕘🍎📝🐹👢🎭 43 | 🛃🌲😣😢🌀🍺🔊 44 | ``` 45 | 46 | ## Online version 47 | 48 | https://degaart.github.io/emocrypt/ 49 | 50 | ## Usage - command line client 51 | 52 | ### Encrypt a file 53 | 54 | ``` 55 | emocrypt -i -o 56 | ``` 57 | 58 | ### Encrypt then conceal a file 59 | 60 | ``` 61 | emocrypt -i -o -c 62 | ``` 63 | 64 | ### Decrypt file 65 | 66 | ``` 67 | emocrypt -d -i -o 68 | ``` 69 | 70 | ## Building 71 | 72 | Requirements: 73 | 74 | - c++ compiler with c++14 support 75 | - cmake >= 3.15 76 | - libsodium 77 | 78 | 79 | ### Unix/macOS 80 | 81 | Build commands: 82 | 83 | ``` 84 | cmake -DCMAKE_BUILD_TYPE=Release -B Build -DCMAKE_INSTALL_PREFIX=/opt/emocrypt 85 | cmake --build Build 86 | cmake --build Build --target install 87 | ``` 88 | 89 | ### Windows (Visual Studio) 90 | 91 | - Download and extract libsodium from https://download.libsodium.org/libsodium/releases/LATEST.tar.gz 92 | - Open libsodium.sln and build the Debug (or the Release) configuration 93 | - Open emocrypt's source code folder in Visual Studio 94 | - Open menu: Project > CMake Settings for emocrypt 95 | - Tweak "CMake command arguments": ```-Dlibsodium_INCLUDE_DIR=C:\some-path\libsodium\include -Dlibsodium_LIBRARY=C:\some-path\libsodium-stable\Build\Debug\x64\libsodium.lib -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded``` 96 | - Build the solution 97 | 98 | -------------------------------------------------------------------------------- /cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROGRAM emocrypt) 2 | add_executable(${PROGRAM} 3 | main.cpp 4 | ) 5 | target_link_libraries(${PROGRAM} PRIVATE 6 | project_options 7 | common 8 | ) 9 | install(TARGETS ${PROGRAM}) 10 | -------------------------------------------------------------------------------- /cli/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #ifdef WIN32 16 | # define WIN32_LEAN_AND_MEAN 17 | # include 18 | #else 19 | # include 20 | #endif 21 | 22 | using std::unique_ptr; 23 | using std::make_unique; 24 | 25 | namespace { 26 | 27 | static constexpr auto FLAG_APPEND_NEWLINE = 0x1; // Append newline to outpur 28 | 29 | std::string get_password(const std::string& prompt) 30 | { 31 | #ifdef DEBUG_PASSWORD 32 | return "hunter2"; 33 | #elif defined(WIN32) 34 | ec::fprint(std::cerr, prompt); 35 | 36 | std::string password(254, '\0'); 37 | HANDLE handle = CreateFileA( 38 | "CONIN$", 39 | GENERIC_READ | GENERIC_WRITE, 40 | 0, 41 | 0, 42 | OPEN_EXISTING, 43 | 0, 44 | 0 45 | ); 46 | 47 | DWORD orig = 0; 48 | GetConsoleMode(handle, &orig); 49 | 50 | DWORD mode = orig; 51 | mode |= ENABLE_PROCESSED_INPUT; 52 | mode &= ~ENABLE_ECHO_INPUT; 53 | SetConsoleMode(handle, mode); 54 | 55 | DWORD readBytes; 56 | ReadConsole( 57 | handle, 58 | &password[0], password.size(), 59 | &readBytes, 60 | nullptr); 61 | WriteConsole(handle, "\n", 1, 0, 0); 62 | SetConsoleMode(handle, orig); 63 | CloseHandle(handle); 64 | 65 | if(readBytes >= 2) 66 | password.resize(readBytes - 2); 67 | else 68 | password.resize(0); 69 | 70 | assert(password.find('\n') == std::string::npos); 71 | assert(password.find('\r') == std::string::npos); 72 | ec::fprint(std::cerr, "\n"); 73 | return password; 74 | #else 75 | ec::fprint(std::cerr, prompt); 76 | std::cout.flush(); 77 | 78 | std::string password; 79 | char ch; 80 | ec::FD tty("/dev/tty", O_RDONLY); 81 | 82 | ec::TermEcho term_echo(tty.fd()); 83 | term_echo.disable(); 84 | 85 | while(true) { 86 | auto ret = tty.read(&ch, sizeof(ch)); 87 | if(!ret || ch == '\n') 88 | break; 89 | 90 | password.append(1, ch); 91 | } 92 | 93 | ec::fprint(std::cerr, "\n"); 94 | assert(password.find('\n') == std::string::npos); 95 | 96 | return password; 97 | #endif 98 | } 99 | 100 | bool decrypt(const std::string& infile, const std::string& outfile, const std::string& supplied_password, unsigned flags) 101 | { 102 | std::string password = supplied_password; 103 | if(password.empty()) { 104 | password = get_password("Password: "); 105 | if(password.empty()) { 106 | ec::fprintln(std::cerr, "Password is required"); 107 | return false; 108 | } 109 | } 110 | 111 | std::ifstream fis; 112 | std::istream* is = &std::cin; 113 | if(infile.size()) { 114 | fis.open(infile, std::ios::in|std::ios::binary); 115 | if(!fis) { 116 | ec::fprintln(std::cerr, "open(\"", infile, "\"): ", strerror(errno)); 117 | return false; 118 | } 119 | fis.exceptions(std::ios::badbit); 120 | is = &fis; 121 | } 122 | 123 | ec::Symbols symbols = ec::load_symbols(); 124 | std::string encoded_ciphertext((std::istreambuf_iterator(*is)), 125 | std::istreambuf_iterator()); 126 | ec::byte_string ciphertext = ec::decode(symbols, encoded_ciphertext); 127 | if(ciphertext.empty()) { 128 | ec::fprintln(std::cerr, "Invalid ciphertext data"); 129 | return false; 130 | } 131 | 132 | ec::byte_string plaintext = ec::decrypt(ciphertext.data(), ciphertext.size(), password); 133 | if(plaintext.empty()) { 134 | ec::fprintln(std::cerr, "Decryption failed"); 135 | return false; 136 | } 137 | 138 | std::ofstream fos; 139 | std::ostream* os = &std::cout; 140 | if(outfile.size()) { 141 | fos.open(outfile, std::ios::out|std::ios::binary|std::ios::trunc); 142 | if(!fos) { 143 | ec::fprintln(std::cerr, "open(\"", outfile, "\"): ", strerror(errno)); 144 | return false; 145 | } 146 | fos.exceptions(std::ios::badbit); 147 | os = &fos; 148 | } 149 | 150 | if(flags & FLAG_APPEND_NEWLINE) { 151 | #ifdef WIN32 152 | plaintext.append(reinterpret_cast("\r\n")); 153 | #else 154 | plaintext.append(reinterpret_cast("\n")); 155 | #endif 156 | } 157 | os->write(reinterpret_cast(plaintext.data()), plaintext.size()); 158 | 159 | return true; 160 | } 161 | 162 | bool encrypt(const std::string& infile, const std::string& outfile, const std::string& supplied_password, const std::string& channel, int line_length, unsigned flags) 163 | { 164 | std::string password = supplied_password; 165 | if(password.empty()) { 166 | password = get_password("Password: "); 167 | if(password.empty()) { 168 | ec::fprintln(std::cerr, "Password is required"); 169 | return false; 170 | } 171 | 172 | std::string confirmation = get_password("Confirmation: "); 173 | if(confirmation != password) { 174 | ec::fprintln(std::cerr, "Password and confirmation do not match"); 175 | return false; 176 | } 177 | } 178 | 179 | std::ifstream fis; 180 | std::istream* is = &std::cin; 181 | if(infile.size()) { 182 | fis.open(infile, std::ios::binary|std::ios::in); 183 | if(!fis) { 184 | ec::fprintln(std::cerr, "open(\"", infile, "\"): ", strerror(errno)); 185 | return false; 186 | } 187 | fis.exceptions(std::ios::badbit); 188 | is = &fis; 189 | } 190 | 191 | std::string channel_text; 192 | if(!channel.empty()) { 193 | ec::FD fd(channel, O_RDONLY); 194 | channel_text = fd.read_all(); 195 | } 196 | 197 | std::string plaintext((std::istreambuf_iterator(*is)), 198 | std::istreambuf_iterator()); 199 | if(is->bad()) { 200 | ec::fprintln(std::cerr, "read(): ", strerror(errno)); 201 | return false; 202 | } 203 | 204 | ec::byte_string ciphertext = ec::encrypt(plaintext.data(), plaintext.size(), password); 205 | if(ciphertext.empty()) { 206 | ec::fprintln(std::cerr, "Encryption failed"); 207 | return false; 208 | } 209 | 210 | std::random_device rd; 211 | std::mt19937 rng(rd()); 212 | ec::Symbols symbols = ec::load_symbols(); 213 | std::string output = ec::encode(rng, symbols, ciphertext.data(), ciphertext.size(), line_length); 214 | 215 | std::ofstream fos; 216 | std::ostream* os = &std::cout; 217 | if(outfile.size()) { 218 | fos.open(outfile, std::ios::binary|std::ios::out|std::ios::trunc); 219 | if(!fos) { 220 | ec::fprintln(std::cerr, "open(\"", outfile, "\"): ", strerror(errno)); 221 | return false; 222 | } 223 | fos.exceptions(std::ios::badbit); 224 | os = &fos; 225 | } 226 | 227 | if(!channel.empty()) { 228 | output = ec::conceal(output, channel_text, symbols); 229 | if(output.empty()) { 230 | ec::fprintln(std::cerr, "Failed to conceal message"); 231 | return false; 232 | } 233 | } 234 | 235 | if(flags & FLAG_APPEND_NEWLINE) { 236 | #ifdef WIN32 237 | output.append("\r\n"); 238 | #else 239 | output.append("\n"); 240 | #endif 241 | } 242 | 243 | os->write(output.data(), output.size()); 244 | return true; 245 | } 246 | } 247 | 248 | int main(int argc, char** argv) 249 | { 250 | ec::Options opt; 251 | opt.add("infile", ec::ArgType::Required, 'i'); 252 | opt.add("outfile", ec::ArgType::Required, 'o'); 253 | opt.add("decrypt", ec::ArgType::None, 'd'); 254 | opt.add("encrypt", ec::ArgType::None, 'e'); 255 | opt.add("password", ec::ArgType::Required, 'p'); 256 | opt.add("conceal", ec::ArgType::Required, 'c'); 257 | opt.add("line-length", ec::ArgType::Required, 'l'); 258 | opt.add("newline", ec::ArgType::None, 'n'); 259 | opt.add("version", ec::ArgType::None, 'v'); 260 | opt.add("help", ec::ArgType::None, 'h'); 261 | opt.parse(argc, argv); 262 | 263 | std::string infile; 264 | if(opt.isPresent("infile")) 265 | infile = opt.arg("infile"); 266 | 267 | std::string outfile; 268 | if(opt.isPresent("outfile")) 269 | outfile = opt.arg("outfile"); 270 | 271 | std::string channel; 272 | if(opt.isPresent("conceal")) 273 | channel = opt.arg("conceal"); 274 | 275 | std::string password; 276 | if(opt.isPresent("password")) 277 | password = opt.arg("password"); 278 | 279 | int line_length = 20; 280 | if(opt.isPresent("line-length")) 281 | line_length = std::stoi(opt.arg("line-length")); 282 | 283 | unsigned flags = 0; 284 | if(opt.isPresent("newline")) 285 | flags |= FLAG_APPEND_NEWLINE; 286 | 287 | if(opt.isPresent("help")) { 288 | ec::fprintln(std::cerr, "Usage: ", argv[0], " "); 289 | ec::fprintln(std::cerr, "Options:"); 290 | ec::fprintln(std::cerr, " --infile,-i Input file (default: stdin)"); 291 | ec::fprintln(std::cerr, " --outfile,-o Output file (default: stdout)"); 292 | ec::fprintln(std::cerr, " --decrypt,-d Decrypt"); 293 | ec::fprintln(std::cerr, " --encrypt,-e Encrypt (default)"); 294 | ec::fprintln(std::cerr, " --password,-p Specify password (DANGEROUS!)"); 295 | ec::fprintln(std::cerr, " --conceal,-c Hide ciphertext by using this file as a channel"); 296 | ec::fprintln(std::cerr, " --line-length,-l Line length (default: 20)"); 297 | ec::fprintln(std::cerr, " --newline,-n Append a newline to output"); 298 | ec::fprintln(std::cerr, " --version,-v Show program version"); 299 | ec::fprintln(std::cerr, " --help,-h Show help"); 300 | return 0; 301 | } else if(opt.isPresent("version")) { 302 | ec::println(argv[0], " ", ec::VERSION_MAJOR, ".", ec::VERSION_MINOR); 303 | } else if(opt.isPresent("decrypt")) { 304 | return !decrypt(infile, outfile, password, flags); 305 | } else { 306 | return !encrypt(infile, outfile, password, channel, line_length, flags); 307 | } 308 | return 0; 309 | } 310 | -------------------------------------------------------------------------------- /common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND SRCS 2 | encrypt.cpp 3 | fd.cpp 4 | term_echo.cpp 5 | options.cpp 6 | utf8.cpp 7 | ) 8 | add_library(common ${SRCS}) 9 | target_link_libraries(common PUBLIC project_options) 10 | target_include_directories(common INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 11 | 12 | if(WIN32) 13 | target_link_libraries(common PUBLIC getopt) 14 | endif() 15 | -------------------------------------------------------------------------------- /common/encrypt.cpp: -------------------------------------------------------------------------------- 1 | #include "encrypt.h" 2 | #include "format.h" 3 | #include "symbols.h" 4 | #include "utf8.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ec { 10 | 11 | static const auto KD_VERSION = 1U; 12 | static const auto KD_OPSLIMIT = 4U; 13 | static const auto KD_MEMLIMIT = 20U; /* 1MB (lowered to help with the webasm version) */ 14 | static const auto KD_ALG = crypto_pwhash_argon2id_ALG_ARGON2ID13; 15 | 16 | Symbols load_symbols() 17 | { 18 | Symbols result; 19 | for(int i = 0; i < sizeof(ec::symbols) / sizeof(ec::symbols[0]); i++) { 20 | std::string s; 21 | for(int j = 0; j < 4; j++) { 22 | if(ec::symbols[i][j]) 23 | s.append(1, static_cast(ec::symbols[i][j])); 24 | } 25 | 26 | result.enc[i % 256].push_back(s); 27 | result.dec[s] = i % 256; 28 | } 29 | return result; 30 | } 31 | 32 | std::string encode(std::mt19937& rng, const Symbols& symbols, const void* buffer, size_t size, int line_length) 33 | { 34 | std::uniform_int_distribution dist1(0, 1); 35 | std::uniform_int_distribution dist2(0, 2); 36 | 37 | int cur_line_length = 0; 38 | std::string result; 39 | for(const unsigned char* ptr = reinterpret_cast(buffer); size; ptr++, size--) { 40 | int value = *ptr; 41 | 42 | // we have two possible symbols per value 43 | // let's just choose the symbol to use randomly 44 | auto symbol_choices = symbols.enc.at(value); 45 | if(symbol_choices.size() == 2) 46 | result.append(symbol_choices.at(dist1(rng))); 47 | else 48 | result.append(symbol_choices.at(dist2(rng))); 49 | 50 | cur_line_length++; 51 | if(cur_line_length == line_length) { 52 | cur_line_length = 0; 53 | result.append("\n"); 54 | } 55 | } 56 | return result; 57 | } 58 | 59 | byte_string decode(const Symbols& symbols, const std::string& s) 60 | { 61 | byte_string result; 62 | auto seq_length = [](int ch) { 63 | if((ch & 0xF0) == 0xF0) 64 | return 4; 65 | else if((ch & 0xE0) == 0xE0) 66 | return 3; 67 | else if((ch & 0xC0) == 0xC0) 68 | return 2; 69 | else if((ch & 0x80) == 0x80) 70 | return -1; 71 | else 72 | return 1; 73 | }; 74 | 75 | for(auto it = s.begin(); it < s.end();) { 76 | int ch = static_cast(*it); 77 | assert(ch >= 0); 78 | auto length = seq_length(ch); 79 | if(length > 0) { 80 | std::string sequence(it, it + length); 81 | auto dec_it = symbols.dec.find(sequence); 82 | if(dec_it != symbols.dec.end()) 83 | result.push_back(dec_it->second); 84 | 85 | it += length; 86 | } else { 87 | ++it; 88 | } 89 | } 90 | return result; 91 | } 92 | 93 | byte_string encrypt(const void* buffer, size_t length, const std::string& password) 94 | { 95 | size_t nonce_size = std::max(crypto_pwhash_SALTBYTES, crypto_secretbox_NONCEBYTES); 96 | size_t result_size = nonce_size + 4 + length + crypto_secretbox_MACBYTES; 97 | byte_string result(result_size, '\0'); 98 | unsigned char* nonce = &result[0]; 99 | unsigned char* version = nonce + nonce_size; 100 | unsigned char* opslimit = version + sizeof(unsigned char); 101 | unsigned char* memlimit = opslimit + sizeof(unsigned char); 102 | unsigned char* algo = memlimit + sizeof(unsigned char); 103 | unsigned char* ciphertext = algo + sizeof(unsigned char); 104 | 105 | /* salt */ 106 | randombytes_buf(nonce, nonce_size); 107 | 108 | /* Params */ 109 | assert(KD_MEMLIMIT > 1 && KD_MEMLIMIT < 64); 110 | *version = KD_VERSION ^ nonce[0]; 111 | *opslimit = KD_OPSLIMIT ^ nonce[1]; 112 | *memlimit = KD_MEMLIMIT ^ nonce[2]; 113 | *algo = KD_ALG ^ nonce[3]; 114 | 115 | /* key derivation */ 116 | byte_string key(crypto_secretbox_KEYBYTES, '\0'); 117 | int ret = crypto_pwhash(&key[0], key.size(), 118 | password.data(), password.size(), 119 | nonce, 120 | KD_OPSLIMIT, 121 | (1 << KD_MEMLIMIT), 122 | KD_ALG); 123 | if(ret) 124 | return byte_string(); 125 | 126 | /* nonce */ 127 | ret = crypto_secretbox_easy(ciphertext, 128 | reinterpret_cast(buffer), length, 129 | nonce, 130 | key.data()); 131 | if(ret) 132 | return byte_string(); 133 | return result; 134 | } 135 | 136 | byte_string decrypt(const void* buffer, size_t length, const std::string& password) 137 | { 138 | size_t nonce_size = std::max(crypto_pwhash_SALTBYTES, crypto_secretbox_NONCEBYTES); 139 | assert(length >= nonce_size + 4 + crypto_secretbox_MACBYTES); 140 | 141 | const unsigned char* nonce = reinterpret_cast(buffer); 142 | const unsigned char* version = nonce + nonce_size; 143 | const unsigned char* opslimit = version + sizeof(unsigned char); 144 | const unsigned char* memlimit = opslimit + sizeof(unsigned char); 145 | const unsigned char* algo = memlimit + sizeof(unsigned char); 146 | const unsigned char* ciphertext = algo + sizeof(unsigned char); 147 | 148 | /* Check version */ 149 | int v_version = *version ^ nonce[0]; 150 | int v_opslimit = *opslimit ^ nonce[1]; 151 | int v_memlimit = *memlimit ^ nonce[2]; 152 | int v_algo = *algo ^ nonce[3]; 153 | if(v_version != KD_VERSION) 154 | return byte_string(); 155 | else if(v_opslimit == 0 || v_opslimit > 256) 156 | return byte_string(); 157 | else if(v_memlimit < 1 || v_memlimit > 64) 158 | return byte_string(); 159 | 160 | /* key derivation */ 161 | byte_string key(crypto_secretbox_KEYBYTES, '\0'); 162 | int ret = crypto_pwhash(&key[0], key.size(), 163 | password.data(), password.size(), 164 | nonce, 165 | v_opslimit, 166 | 1 << v_memlimit, 167 | v_algo); 168 | if(ret) 169 | return byte_string(); 170 | 171 | /* decrypt */ 172 | size_t result_size = length - nonce_size - 4 - crypto_secretbox_MACBYTES; 173 | byte_string result(result_size, '\0'); 174 | ret = crypto_secretbox_open_easy(&result[0], 175 | ciphertext, 176 | result.size() + crypto_secretbox_MACBYTES, 177 | nonce, 178 | key.data()); 179 | if(ret) 180 | return byte_string(); 181 | return result; 182 | } 183 | 184 | std::string strip_emojis(const std::string& s, const Symbols& symbols) 185 | { 186 | std::string result; 187 | 188 | ec::utf8_iterator end(s.data() + s.size()); 189 | for(ec::utf8_iterator it(s.data()); it != end; ++it) { 190 | if(symbols.dec.find(*it) == symbols.dec.end()) 191 | result.append(*it); 192 | } 193 | 194 | return result; 195 | } 196 | 197 | std::string conceal(const std::string& payload, std::string channel, const Symbols& symbols) 198 | { 199 | if(payload.empty() || channel.empty()) 200 | return ""; 201 | 202 | std::vector emojis; 203 | { 204 | ec::utf8_iterator it(payload.data()); 205 | ec::utf8_iterator end(payload.data() + payload.size()); 206 | for(; it != end; ++it) { 207 | auto value = *it; 208 | auto sym_it = symbols.dec.find(value); 209 | if(sym_it != symbols.dec.end()) 210 | emojis.push_back(value); 211 | } 212 | } 213 | channel = strip_emojis(channel, symbols); 214 | 215 | static std::regex rex("[^\\s]+|[\\s]+"); 216 | auto words_begin = std::sregex_iterator(channel.begin(), channel.end(), rex); 217 | auto words_end = std::sregex_iterator(); 218 | 219 | std::vector words; 220 | for(auto i = words_begin; i != words_end; ++i) { 221 | words.push_back(i->str()); 222 | } 223 | 224 | float emojis_per_word = static_cast(emojis.size()) / words.size(); 225 | float words_per_emoji = static_cast(words.size()) / emojis.size(); 226 | 227 | std::string result; 228 | float acc = 0.0f; 229 | auto emoji_it = emojis.cbegin(); 230 | auto words_it = words.cbegin(); 231 | if(words_per_emoji > emojis_per_word) { 232 | while(emoji_it != emojis.cend() && words_it != words.cend()) { 233 | while(words_it != words.cend() && acc < words_per_emoji) { 234 | result.append(*words_it++); 235 | acc += 1.0f; 236 | } 237 | acc -= words_per_emoji; 238 | result.append(*emoji_it++); 239 | } 240 | } else { 241 | while(emoji_it != emojis.cend() && words_it != words.cend()) { 242 | while(emoji_it != emojis.cend() && acc < emojis_per_word) { 243 | result.append(*emoji_it++); 244 | acc += 1.0f; 245 | } 246 | acc -= emojis_per_word; 247 | result.append(*words_it++); 248 | } 249 | } 250 | while(words_it != words.cend()) 251 | result.append(*words_it++); 252 | 253 | while(emoji_it != emojis.cend()) 254 | result.append(*emoji_it++); 255 | 256 | return result; 257 | } 258 | 259 | } 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /common/encrypt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace ec { 9 | 10 | using byte_string = std::basic_string; 11 | 12 | struct Symbols { 13 | std::map> enc; 14 | std::map dec; 15 | }; 16 | 17 | Symbols load_symbols(); 18 | std::string strip_emojis(const std::string& s, const Symbols& symbols); 19 | std::string conceal(const std::string& payload, std::string channel, const Symbols& symbols); 20 | 21 | std::string encode(std::mt19937& rng, const Symbols& symbols, const void* buffer, size_t size, int line_length = 0); 22 | byte_string decode(const Symbols& symbols, const std::string& s); 23 | byte_string encrypt(const void* buffer, size_t length, const std::string& password); 24 | byte_string decrypt(const void* buffer, size_t length, const std::string& password); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /common/fd.cpp: -------------------------------------------------------------------------------- 1 | #include "fd.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #ifndef WIN32 7 | # include 8 | #else 9 | # include 10 | 11 | typedef int ssize_t; 12 | #endif 13 | 14 | namespace ec { 15 | 16 | FD::FD(const std::string& path, int mode) 17 | { 18 | _fd = ::open(path.c_str(), mode); 19 | if(_fd == -1) 20 | throw std::system_error(errno, std::system_category()); 21 | } 22 | 23 | FD::FD(FD&& other) 24 | : _fd(-1) 25 | { 26 | std::swap(_fd, other._fd); 27 | } 28 | 29 | FD::~FD() 30 | { 31 | close(); 32 | } 33 | 34 | FD& FD::operator=(FD&& other) 35 | { 36 | if(this != &other) { 37 | close(); 38 | std::swap(_fd, other._fd); 39 | } 40 | return *this; 41 | } 42 | 43 | size_t FD::read(void* buffer, size_t len) 44 | { 45 | ssize_t ret; 46 | do { 47 | ret = ::read(_fd, buffer, len); 48 | } while(ret == -1 && errno == EINTR); 49 | if(ret == -1) 50 | throw std::system_error(errno, std::system_category()); 51 | return ret; 52 | } 53 | 54 | void FD::write(const void* buffer, size_t len) 55 | { 56 | const unsigned char* ptr = reinterpret_cast(buffer); 57 | while(len) { 58 | ssize_t ret; 59 | do { 60 | ret = ::write(_fd, ptr, len); 61 | } while(ret == -1 && errno == EINTR); 62 | if(ret == -1) 63 | throw std::system_error(errno, std::system_category()); 64 | 65 | len -= ret; 66 | ptr += ret; 67 | } 68 | } 69 | 70 | void FD::close() 71 | { 72 | if(_fd != -1) 73 | ::close(_fd); 74 | _fd = -1; 75 | } 76 | 77 | int FD::fd() 78 | { 79 | return _fd; 80 | } 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /common/fd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ec { 8 | 9 | class FD { 10 | private: 11 | int _fd; 12 | public: 13 | FD(const std::string& path, int mode); 14 | FD(const FD&) = delete; 15 | FD(FD&& other); 16 | ~FD(); 17 | FD& operator=(const FD&) = delete; 18 | FD& operator=(FD&& other); 19 | size_t read(void*, size_t); 20 | void write(const void*, size_t); 21 | void close(); 22 | int fd(); 23 | 24 | template 25 | T read_all() { 26 | T result; 27 | char buffer[512]; 28 | while(true) { 29 | auto r = read(buffer, sizeof(buffer)); 30 | if(r == 0) 31 | break; 32 | std::copy_n(buffer, r, std::back_inserter(result)); 33 | } 34 | return result; 35 | } 36 | }; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /common/format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ec { 8 | 9 | template 10 | inline void fprint(std::ostream& os, Arg&& arg) 11 | { 12 | os << arg; 13 | } 14 | 15 | template 16 | inline void fprint(std::ostream& os, Arg&& arg, Args&&... args) 17 | { 18 | os << arg; 19 | fprint(os, std::forward(args)...); 20 | } 21 | 22 | template 23 | inline void fprintln(std::ostream& os, Args&&... args) 24 | { 25 | fprint(os, std::forward(args)..., "\n"); 26 | } 27 | 28 | template 29 | inline void print(Args&&... args) 30 | { 31 | fprint(std::cout, std::forward(args)...); 32 | } 33 | 34 | template 35 | inline void println(Args&&... args) 36 | { 37 | fprint(std::cout, std::forward(args)..., "\n"); 38 | } 39 | 40 | template 41 | inline std::string format(Args&&... args) 42 | { 43 | std::stringstream ss; 44 | fprint(ss, std::forward(args)...); 45 | return ss.str(); 46 | } 47 | 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /common/options.cpp: -------------------------------------------------------------------------------- 1 | #include "format.h" 2 | #include "options.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using std::unique_ptr; 10 | using std::make_unique; 11 | 12 | namespace ec { 13 | 14 | struct Option { 15 | ArgType type; 16 | char shortopt; 17 | int value; 18 | bool present; 19 | std::string arg; 20 | }; 21 | 22 | class Options::Impl { 23 | private: 24 | std::map _options; 25 | int _nextValue; 26 | std::vector _positionals; 27 | 28 | unique_ptr