diff --git a/README.md b/README.md index 3ca5415..1077533 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ Synopsis The SlunkCypt command-line program is invoked as follows: - slunkcrypt --encrypt [[@][:]] - slunkcrypt --decrypt [[@][:]] + slunkcrypt --encrypt [pass:|file:] + slunkcrypt --decrypt [pass:|file:] slunkcrypt --make-pw [] Commands @@ -39,50 +39,55 @@ One of the following commands must be chosen: - **`--decrypt` (`-d`):** Run application in ***decrypt*** mode. Reads the given *ciphertext* and restores *plaintext*. - **`--make-pw` (`-p`):** - Generate and output a "strong" random passphrase suitable for use with SlunkCrypt. + Generate a "strong" random passphrase, suitable for use with SlunkCrypt. - **`--self-test` (`-t`):** - Run application in ***self-test*** mode. Program will exit when test is completed. + Run the application in ***self-test*** mode. Program will exit after all test are completed. Options ------- The following options are available: -- **``**: - * The passphrase used to "protect" the message. The same passphrase must be used for both, ***encrypt*** and ***decrypt*** mode. - * It will only be possible decrypt the ciphertext, if the "correct" passphrase is known. - * Use **`--make-pw`** to generate a random passphrase. The passphrase must be kept confidential under all circumstances! - * **Syntax:** - - If the passphrase is prefixed with an **`@`** character, then it specifies the file to read the passphrase from. - - If the passphrase is set to **`@-`**, then the passphrase is read from the standard input stream. - - If the passphrase is prefixed with an **`:`** character, then the leading character is ignored; use if passphrase contains **`@`** character. - - If the parameter is *omitted*, then the passphrase is read from the `SLUNK_PASSPHRASE` environment variable. - * *Note:* In order to thwart brute force attacks, it is recommended to choose a "random" password that is at least 12 characters in length and that consists of upper-case characters, lower-case characters, digits as well as other "special" characters. +- **`pass:`**: + * Specifies the "secret" passphrase directly on the command-line. This is considered *insecure*. +- **`file:`**: + * Specifies a file to read the passphrase from. Only the *first* line of the file will be read! + * *Note:* It is also possible to specify **`-`** in order to read the passphrase from the *stdin*. - **``**: - * In ***encrypt*** mode, specifies the *plaintext* (unencrypted information) file that is to be encrypted. - * In ***decrypt*** mode, specifies the *ciphertext* (result of encryption) file that is to be decrypted. + * In ***encrypt*** mode – specifies the *plaintext* file (unencrypted information) that is to be encrypted. + * In ***decrypt*** mode – specifies the *ciphertext* file (result of encryption) that is to be decrypted. - **``**: - * In ***encrypt*** mode, specifies the file where the *ciphertext* (result of encryption) will be stored. - * In ***decrypt*** mode, specifies the file where the *plaintext* (unencrypted information) will be stored. + * In ***encrypt*** mode – specifies the file where the *ciphertext* (result of encryption) will be stored. + * In ***decrypt*** mode – specifies the file where the *plaintext* (unencrypted information) will be stored. - **``**: - * Speicifes the length of the passphrase to be generated, in characters. If *not* specified, defaults to 24. + * Specifies the length of the passphrase to be generated. If *not* specified, defaults to 24. + +### Remarks + +- If the passphrase is **not** specified on the command-line, it will be read from the `SLUNK_PASSPHRASE` environment variable. + +- The same passphrase must be used for both, ***encrypt*** and ***decrypt*** mode. The decryption of the ciphertext will only be possible, if the "correct" passphrase is known. It is recommended to choose a "random" password that is at least 12 characters in length and consists of a mix of upper-case characters, lower-case characters, digits as well as special characters. + +- Passing the passphrase directly on the command-line is insecure, because the command-line may be visible to other users. Examples -------- -Examples on how to use the SlunkCrypt command-line application: +Here are some examples on how to use the SlunkCrypt command-line application: -1. Let's generate a new secure password first: +### Example #1 + +1. Let's generate a new random (secure) password first: slunkcrypt --make-pw - Example output: + *Example output:* cdG2=fh passwd.txt + + Optionally, output the generated password to the terminal: + + cat passwd.txt + +2. Encrypt file by reading the password from the text file: + + slunkcrypt --encrypt file:passwd.txt plaintext.txt ciphertext.enc + +### Example #3 + +1. Generate a new password directly to an environment variable: + + MY_PASSWD="$(slunkcrypt --make-pw)" + + Optionally, output the generated password to the terminal: + + echo "${MY_PASSWD}" + +2. Encrypt file by reading the password from the *stdin*: + + slunkcrypt --encrypt - plaintext.txt ciphertext.enc <<< "${MY_PASSWD}" Encryption algorithm diff --git a/frontend/src/main.c b/frontend/src/main.c index 4a6c2c4..7fec81f 100644 --- a/frontend/src/main.c +++ b/frontend/src/main.c @@ -36,7 +36,11 @@ #define SLUNK_MODE_PASS 4 #define SLUNK_MODE_TEST 5 -static const CHR* const ENVV_PASSWD_NAME = T("SLUNK_PASSPHRASE"); +static const CHR* const PREFIX_PASS = T("pass:"); +static const CHR* const PREFIX_FILE = T("file:"); + +static const CHR* const ENV_PASSWD_NAME = T("SLUNK_PASSPHRASE"); + static const char PASSWD_SYMBOLS[] = { '!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', @@ -44,7 +48,7 @@ static const char PASSWD_SYMBOLS[] = 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', ']', '^', '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '}', '~' + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~' }; static const uint64_t MAGIC_NUMBER = 0x243F6A8885A308D3ull; @@ -93,34 +97,52 @@ static void print_manpage(const CHR *const program) FPUTS(T("https://creativecommons.org/publicdomain/zero/1.0/legalcode\n"), stderr); FPUTS(T("====================================================================\n\n"), stderr); FPUTS(T("Usage:\n"), stderr); - FPRINTF(stderr, T(" %") T(PRISTR) T(" --encrypt [[@][:]] \n"), program); - FPRINTF(stderr, T(" %") T(PRISTR) T(" --decrypt [[@][:]] \n"), program); + FPRINTF(stderr, T(" %") T(PRISTR) T(" --encrypt [pass:|file:] \n"), program); + FPRINTF(stderr, T(" %") T(PRISTR) T(" --decrypt [pass:|file:] \n"), program); FPRINTF(stderr, T(" %") T(PRISTR) T(" --make-pw []\n\n"), program); + FPRINTF(stderr, T("Optionally, reads passphrase from the %") T(PRISTR) T(" environment variable.\n\n"), ENV_PASSWD_NAME); +} + +static char *duplicate_string(const CHR *const passphrase) +{ + char *const buffer = CHR_to_utf8(passphrase); + if (!buffer) + { + FPUTS(T("Error: Failed to allocate the string buffer!\n\n"), stderr); + } + return buffer; } static char *read_passphrase(const CHR* const file_name) { - const size_t max_len = SLUNKCRYPT_PWDLEN_MAX + 2U; - char *buffer = (char*) malloc(max_len * sizeof(char)); - if (!buffer) + if (file_name && (!file_name[0U])) { + FPUTS(T("Error: The passphrase input file name must not be empty!\n\n"), stderr); return NULL; } - const int use_stdin = (STRICMP(file_name, T("-")) == 0); - FILE *const file = use_stdin ? stdin : FOPEN(file_name, T("rb")); - if (!file) + const size_t max_len = SLUNKCRYPT_PWDLEN_MAX + 2U; + char *const buffer = (char*) malloc(max_len * sizeof(char)); + if (!buffer) { + FPUTS(T("Error: Failed to allocate the passphrase buffer!\n\n"), stderr); + return NULL; + } + + const int use_stdin = (!file_name) || (!STRICMP(file_name, T("-"))); + FILE *const file_in = use_stdin ? stdin : FOPEN(file_name, T("rb")); + if (!file_in) + { + FPRINTF(stderr, T("Error: Failed to open file \"%") T(PRISTR) T("\" for reading!\n\n"), file_name); free(buffer); return NULL; } do { - if (!fgets(buffer, (int)max_len, file)) + if (!fgets(buffer, (int)max_len, file_in)) { - free(buffer); - buffer = NULL; + buffer[0U] = '\0'; goto finish; } size_t length = strlen(buffer); @@ -133,9 +155,9 @@ static char *read_passphrase(const CHR* const file_name) finish: - if ((!use_stdin) && file) + if ((!use_stdin) && file_in) { - fclose(file); + fclose(file_in); } return buffer; @@ -168,7 +190,7 @@ static int generate_passphrase(const size_t length) char *const buffer = (char*) malloc((passwd_len + 1U) * sizeof(char)); if (!buffer) { - FPUTS(T("\n\nError: Failed to allocate memory buffer!\n\n"), stderr); + FPUTS(T("Error: Failed to allocate memory buffer!\n\n"), stderr); return EXIT_FAILURE; } @@ -179,14 +201,14 @@ static int generate_passphrase(const size_t length) uint64_t value; if (slunkcrypt_generate_nonce(&value) != SLUNKCRYPT_SUCCESS) { - FPUTS(T("\n\nError: Failed to generate next random number!\n\n"), stderr); + FPUTS(T("Error: Failed to generate next random number!\n\n"), stderr); goto clean_up; } buffer[i] = PASSWD_SYMBOLS[value % ARRAY_SIZE(PASSWD_SYMBOLS)]; } buffer[passwd_len] = '\0'; } - while (weak_passphrase(buffer)); + while ((!isalpha(buffer[0U])) || (!isalnum(buffer[passwd_len - 1U])) || weak_passphrase(buffer)); FPRINTF(stdout, T("%") T(PRIstr) T("\n\n"), buffer); fflush(stdout); @@ -208,15 +230,15 @@ static int open_files(FILE **const file_in, FILE **const file_out, const CHR* co *file_in = FOPEN(input_path, T("rb")); if (!(*file_in)) { - FPUTS(T("Error: Failed to open input file for reading!\n\n"), stderr); + FPRINTF(stderr, T("Error: Failed to open file \"%") T(PRISTR) T("\" for reading!\n\n"), input_path); return EXIT_FAILURE; } *file_out = FOPEN(output_path, T("wb")); if (!(*file_out)) { - FPUTS(T("Error: Failed to open output file for writing!\n\n"), stderr); - fclose(*file_out); + FPRINTF(stderr, T("Error: Failed to open file \"%") T(PRISTR) T("\" for writing!\n\n"), output_path); + fclose(*file_in); return EXIT_FAILURE; } @@ -763,12 +785,18 @@ int MAIN(const int argc, CHR *const argv[]) goto clean_up; } - const CHR* const passphrase = (argc > 4) ? argv[2U] : GETENV(ENVV_PASSWD_NAME); - const CHR* const input_file = argv[(argc > 4) ? 3U : 2U], * const output_file = argv[(argc > 4) ? 4U : 3U]; + const CHR *const passphrase = (argc > 4) ? argv[2U] : GETENV(ENV_PASSWD_NAME); + const CHR *const input_file = argv[(argc > 4) ? 3U : 2U], *const output_file = argv[(argc > 4) ? 4U : 3U]; - if ((!passphrase) || (!passphrase[0U]) || (((passphrase[0U] == T('@')) || (passphrase[0U] == T(':'))) && (!passphrase[1U]))) + if ((!passphrase) || (!passphrase[0U])) { - FPUTS(T("Error: The specified passphrase must not be empty!\n\n"), stderr); + FPUTS(T("Error: The passphrase must be specified, directly or indirectly!\n\n"), stderr); + goto clean_up; + } + + if (STRICMP(passphrase, T("-")) && (!STARTS_WITH(passphrase, PREFIX_PASS)) && (!STARTS_WITH(passphrase, PREFIX_FILE))) + { + FPRINTF(stderr, T("Error: The passphrase must start with a '%") T(PRISTR) T("' or '%") T(PRISTR) T("' prefix!\n\n"), PREFIX_PASS, PREFIX_FILE); goto clean_up; } @@ -782,10 +810,10 @@ int MAIN(const int argc, CHR *const argv[]) /* Initialize passphrase */ /* ----------------------------------------------------- */ - passphrase_buffer = (passphrase[0U] == T('@')) ? read_passphrase(passphrase + 1U) : CHR_to_utf8((passphrase[0U] == T(':')) ? (passphrase + 1U) : passphrase); + passphrase_buffer = STARTS_WITH(passphrase, PREFIX_PASS) ? duplicate_string(passphrase + STRLEN(PREFIX_PASS)) : + (STARTS_WITH(passphrase, PREFIX_FILE) ? read_passphrase(passphrase + STRLEN(PREFIX_FILE)) : read_passphrase(NULL)); if (!passphrase_buffer) { - FPUTS(T("Error: Failed to read the passphrase!\n\n"), stderr); goto clean_up; } diff --git a/frontend/src/platform.h b/frontend/src/platform.h index 73596e8..7f4c90a 100644 --- a/frontend/src/platform.h +++ b/frontend/src/platform.h @@ -55,6 +55,7 @@ # define GETENV(X) _wgetenv((X)) # define STRLEN(X) wcslen((X)) # define STRICMP(X,Y) _wcsicmp((X),(Y)) +# define STRNICMP(X,Y,Z) _wcsnicmp((X),(Y),(Z)) # define STRRCHR(X,Y) wcsrchr((X),(Y)) # define STRTOUL(X) wcstoul((X), NULL, 0) # define FPUTS(X,Y) fputws((X),(Y)) @@ -76,6 +77,7 @@ # define GETENV(X) getenv((X)) # define STRLEN(X) strlen((X)) # define STRICMP(X,Y) strcasecmp((X),(Y)) +# define STRNICMP(X,Y,Z) strncasecmp((X),(Y),(Z)) # define STRRCHR(X,Y) strrchr((X),(Y)) # define STRTOUL(X) strtoul((X), NULL, 0) # define FPUTS(X,Y) fputs((X),(Y)) diff --git a/frontend/src/utils.c b/frontend/src/utils.c index 4baa4c5..8658ffd 100644 --- a/frontend/src/utils.c +++ b/frontend/src/utils.c @@ -108,19 +108,19 @@ void setup_signal_handler(const int signo, signal_handler_t* const handler) // Character set conversion // ========================================================================== -char* CHR_to_utf8(const CHR*const input) +char* CHR_to_utf8(const CHR *const input) { #ifdef _WIN32 - char* buffer; + char *buffer = NULL; DWORD buffer_size = 0U, result = 0U; - buffer_size = WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL); + buffer_size = input ? WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL) : 0U; if (buffer_size < 1U) { return NULL; } - buffer = (char*)malloc(sizeof(char) * buffer_size); + buffer = (char*) malloc(sizeof(char) * buffer_size); if (!buffer) { return NULL; @@ -135,7 +135,7 @@ char* CHR_to_utf8(const CHR*const input) free(buffer); return NULL; #else - return strdup(input); + return strdup(input); /*simple string copy*/ #endif } diff --git a/frontend/src/utils.h b/frontend/src/utils.h index b306402..c3b62fe 100644 --- a/frontend/src/utils.h +++ b/frontend/src/utils.h @@ -27,7 +27,7 @@ uint64_t round_down(const uint64_t value, const uint64_t base); #define ARRAY_SIZE(X) (sizeof((X)) / sizeof(*(X))) #define BOUND(MIN,VAL,MAX) (((VAL) < (MIN)) ? (MIN) : (((VAL) > (MAX)) ? (MAX) : (VAL))) +#define STARTS_WITH(X,Y) (!STRNICMP((X), (Y), STRLEN((Y)))) #define GET_LOWBITS(X) ((X) & 0x07) -#define SET_LOWBITS(X, Y) do { X = ((X) & 0xF8) | ((Y) & 0x07); } while(0) - +#define SET_LOWBITS(X,Y) do { X = ((X) & 0xF8) | ((Y) & 0x07); } while(0) #endif diff --git a/mk-profiled.cmd b/mk-profiled.cmd index 6b20718..e85cfa2 100644 --- a/mk-profiled.cmd +++ b/mk-profiled.cmd @@ -32,9 +32,9 @@ for %%p in (x86,x64) do ( if not "%ERRORLEVEL%"=="0" goto:BuildFailed "%~dp0.\bin\%%p\Release\slunkcrypt.exe" --self-test if not "!ERRORLEVEL!"=="0" goto:BuildFailed - "%~dp0.\bin\%%p\Release\slunkcrypt.exe" --encrypt "q4cmK7FEK7@v" "%TMP%\!TEMP_NAME!.dat" "%TMP%\!TEMP_NAME!.enc" + "%~dp0.\bin\%%p\Release\slunkcrypt.exe" --encrypt pass:"KVeW9;z4$#]d9~}z>(WC?v&f" "%TMP%\!TEMP_NAME!.dat" "%TMP%\!TEMP_NAME!.enc" if not "!ERRORLEVEL!"=="0" goto:BuildFailed - "%~dp0.\bin\%%p\Release\slunkcrypt.exe" --decrypt "q4cmK7FEK7@v" "%TMP%\!TEMP_NAME!.enc" "%TMP%\!TEMP_NAME!.out" + "%~dp0.\bin\%%p\Release\slunkcrypt.exe" --decrypt pass:"KVeW9;z4$#]d9~}z>(WC?v&f" "%TMP%\!TEMP_NAME!.enc" "%TMP%\!TEMP_NAME!.out" if not "!ERRORLEVEL!"=="0" goto:BuildFailed del /F "%TMP%\!TEMP_NAME!.dat" "%TMP%\!TEMP_NAME!.enc" "%TMP%\!TEMP_NAME!.out" %ECHO% white "\n------------------------------------------------------------------------------" diff --git a/mk-profiled.sh b/mk-profiled.sh index 8b025be..e667671 100755 --- a/mk-profiled.sh +++ b/mk-profiled.sh @@ -37,8 +37,8 @@ printf "\n" PASSWRD="$(./frontend/bin/slunkcrypt${SUFFIX} --make-pw)" ./frontend/bin/slunkcrypt${SUFFIX} --self-test -./frontend/bin/slunkcrypt${SUFFIX} --encrypt ":${PASSWRD}" /tmp/${TMP_NAME}.dat /tmp/${TMP_NAME}.enc -./frontend/bin/slunkcrypt${SUFFIX} --decrypt ":${PASSWRD}" /tmp/${TMP_NAME}.enc /tmp/${TMP_NAME}.out +./frontend/bin/slunkcrypt${SUFFIX} --encrypt pass:"${PASSWRD}" /tmp/${TMP_NAME}.dat /tmp/${TMP_NAME}.enc +./frontend/bin/slunkcrypt${SUFFIX} --decrypt pass:"${PASSWRD}" /tmp/${TMP_NAME}.enc /tmp/${TMP_NAME}.out printf "\033[1;36m\n------------------------------------------------------------------------------\033[0m\n" printf "\033[1;36mRe-compile\n"