0

Basic encryption with input validation, program works as needed if input is within defined parameters requiring user input to advance through each stage, if input is larger than defined parameters, else should trigger saying password is invalid but instead runs through the entire program without user input to trigger advance.


#include <stdio.h> //library containing built in functions for C
#include <stdlib.h> // contains general purpose library for C
#include <string.h>  //library containing methods for manipulating strings
int main()
{
    // creates character input array of size 5
    char input[20];
    //initialize variable i
    int i;
    //initialize variable length
    int length;
    //prints the phrase
   //printf("Your pass phrase is: ");
    // used to read input string, gets replaced with fgets to secure input to array length
       //fgets(input,11,stdin);
        //assigns input to array length
        //length = strlen(input);
        //prints the phrase
    printf("Your pass phrase is: ");
    fgets(input,11,stdin);
    //assigns input to array length
    length = strlen(input);
    // if loop that if entered text is longer than 0 but shorter than 6 will encrypt data
    if ((length > 0 && length <=10)){
        // encrypts array iteratting through elements
        for (i=0; i<length; i++)
            // uses bitwise xor cipher to encrypt using 0x7f which shifts the characters out of the standard ascii range
            input[i] = 0x7F ^ input[i];
    // prints the encrypted text in an unreadable format
    puts("To see the encrypted text press enter: ");
    getchar();
    // iterates through the area printing the encrypted characters.
    for(i=0;i<length; i++)
        putchar(input[i]);
    // uses xor cipher to shift data back into array by undoing the shift caused by 0x7f
    for(i=0; i<length; i++)
        input[i] = 0x7F ^ input[i];
    // prints the now readable array
    puts("\nTo see recovered text press enter:");
    getchar();
    //iterates through the array printing the contained characters
    for(i=0; i<length; i++)
        putchar(input[i]);
    }
    // prints the following phrase if the array is empty.
    else{
        puts("User input has been checked and a valid pass phrase was not entered.");
        }
    return 0;
    }

  • 1
    [man 3 fgets](http://man7.org/linux/man-pages/man3/fgets.3p.html) "The `fgets()` function shall read bytes from stream into the array pointed to by s, ***until*** `n−1` bytes are read, or a `` is read ... The string is then terminated with a null byte." (you could enter `1,000,000` characters, but `fgets` will only read `n - 1` of them and store only those characters along with the null byte in your array) Which is one reason new C programmers are encourage to use `fgets` and in contrast why `gets` is no longer part of the C standard. – David C. Rankin Sep 11 '19 at 22:31
  • so because fgets takes the initial input it cuts it down to the specified size rendering the else statement useless, but why does it run through the program without requiring hitting enter if the initial size is larger, but requires hitting enter if within the given parameters? – user7143384 Sep 11 '19 at 22:43
  • Post the _exact_ input such as `"qazwsxedc\n"`. Notice I included the as `'\n'`. – chux - Reinstate Monica Sep 11 '19 at 23:03
  • works with entry of 123456789 then hit enter 3 times to progress through code. 1234567890 and enter twice to progress through code. 12345678901 require only 1 enter to progress through entire code. anything below 10 characters takes 3 Enter hits to progress through entire code – user7143384 Sep 11 '19 at 23:16
  • When you enter 123456789 and then hit enter, `input` becomes `"123456789\n"` - a strlen() of 10. see [this](https://stackoverflow.com/a/26831985/2410359) – chux - Reinstate Monica Sep 11 '19 at 23:23
  • @chux okay so adjusted length <=10 to 11, yet I still get the odd enter progression, I'm not normally a coding guy so im probably missing something obvious. for fgets(11) 9 characters takes 3 /n, 10 takes 2/n , anything above 11 characters takes 3/n – user7143384 Sep 11 '19 at 23:28
  • If `'\n'` is not meant to be part of the PW, see https://stackoverflow.com/q/2693776/2410359 – chux - Reinstate Monica Sep 11 '19 at 23:30
  • Why `input[]` so small? Try `char input[100]; fgets(input, sizeof input,stdin);` – chux - Reinstate Monica Sep 11 '19 at 23:32
  • its just a test program tasked to alter to make a bit more secure, originally the program used gets with a char input [5] – user7143384 Sep 11 '19 at 23:37
  • Confucius say *Never Skimp on Buffer Size...* You may also want to look at [Hide password input on terminal](https://stackoverflow.com/questions/6856635/hide-password-input-on-terminal/32421674?r=SearchResults&s=1|47.6501#32421674) – David C. Rankin Sep 11 '19 at 23:41
  • @DavidC.Rankin had a chuckle on that one, password isnt really an issue as this wont actually be implemented, just a concept thing, plus the password gets shown anyway during the decrypt to show it works. – user7143384 Sep 11 '19 at 23:52
  • Your program gets unblocked on the read from the interactive input device when the user presses Enter. At that point, all of the input right through the newline character is available for the program to read without blocking. `fgets` returns with a portion of that input up to the buffer size, leaving the remaining input in the stream. Then when `getchar` is called, it has no reason to wait for a character; there is already one available. Only if `getchar` finds the stream empty will the program pause for another line of input, which mean that `fgets` must read the complete line. – Kaz Sep 12 '19 at 01:51

1 Answers1

3

I have tried to decipher what your objective was with the code, and I've read the tea-leaves and arrived at the conclusion (perhaps wrongly) that the crux of your question is aimed at input and branching control that on the encryption itself.

The "Confucius" comment was only half a half-hearted suggestion as the "Never Skimp on Buffer Size" goes to the heart of your ability to control what branches are taken by prompting the user for directions. Why?

  • If, as in your original code, you have a buffer size of 20, and tell fgets to read at most 10 chars, then if the user enters anything more than 10 chars, the remainder are left in stdin unread.
  • any characters that are left in stdin unread, will happily be used as input for each of your later getchar() statements causing you to lose complete control over how your code branches.
  • using an adequately sized buffer and allowing fgets to consume a complete line at a time will ensure there are no characters left unread in stdin (your input buffer). You will find this to be key on taking most user input in C.

So ensuring you have an adequately sized buffer and consume a line of input at each time is fundamental to being able to control where your code goes next.

An alternative to providing a buffer capable of holding each line of input is to read the wanted number of characters and then manually emptying stdin before the next input. That can be as easy as reading characters until a '\n' or EOF is reached. But... there is a catch. You have to know that characters remain before attempting to empty stdin with whatever function you use (e.g. getchar() will block -- waiting for input until input is present to be read). If you attempt to empty stdin in this manner when there is nothing to be emptied, your user will be left staring at a blinking cursor wondering what to do.

Back to your code. The logic is a bit hard to follow, but from the included comments, it appears that you consider a correct passphrase for purposes of encrypting it, a phrase with 1-5 characters. An empty passphrase or a passphrase with 6 or more characters is considered invalid. (it doesn't matter what the actual numbers are from a passphase standpoint -- this is an exercise about input and branching control)

If I understand the 1-5 char (encrypt) or 0 or 6 or more (invalid) logic, then you will want to adjust the structure of your code to follow that a bit closer. From an overview standpoint, you either have a good passphrase to encrypt or you don't. That would control the primary branches in your code. You can provide the logic in a single if {..} else {..} statement, e.g.

    /* if input is longer than 0 but shorter than 6, encrypt data */
    if (length && length < 6) {
        /* all your good pass phrase code goes here */
    }
    else {  /* invalid pass phrase to begin with */
        fputs ("error: pass phrase empty or greater than 5 chars.\n", stderr);
        return 1;
    }

Contrast this above with the multiple independent statement groups you have in your example and it should make sense why you were having trouble with the decision tree in your code.

Now on to your prompts to the user to press Enter. When you attempt to catch that input with a single getchar() it is horribly fragile and subject to being skipped if any characters exist in stdin and it has the same problem of leaving characters in stdin unread if more than Enter is pressed. Here, just declare another character array to use for temporary input the same size as your input[] array. Now instead of getchar(), just use fgets() again even to capture the Enter alone.

(which it is quite capable of doing, since it reads and include the '\n' in the buffer it fills, it will not block waiting on input like getchar() will)

Concerned about size in using 512-bytes in two buffers -- don't be. Most OS's provide a minimum of 1-Meg of stack space, so that 512-bytes represents only 0.048% of the stack storage you have available. (you still have 1048576 - 512 = 1048064 byte of stack space avaialble)

With that change, your code can be rewritten to contain all logic related to handling a good/encrypted password within the first if {...} block, e.g.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXC 256    /* if you need a constant, #define one (or more) */

int main (void) {

    char    input[MAXC],    /* buffer to hold pass phrase */
            tmp[MAXC];      /* temporary buffer for [enter] input */
    size_t  i,              /* declares an UNINTIALIZED size_t */
            length;         /*           (ditto)              */

    fputs ("Your pass phrase is: ", stdout); /* no conversion, fputs is fine */
    if (!fgets (input, MAXC, stdin)) {
        fputs ("(user canceled input)\n", stderr);
        return 1;
    }
    /* remove '\n' by overwriting with '\0' (if present)
     * saving length of input in length
     */
    input[(length = strcspn(input, "\n"))] = 0;

    /* if input is longer than 0 but shorter than 6, encrypt data */
    if (length && length < 6) {
        int     encrypted = 0;  /* flag keeping state of if pw encrypted */

        for (i = 0; i < length; i++)    /* encrypt the pass phrase */
            input[i] ^= 0x7f;
        encrypted = 1;                  /* set flag true */

        /* print encrypted text in an hex format (change as desired) */
        fputs ("\nTo see the encrypted text press [enter]: ", stdout);
        if (fgets (tmp, MAXC, stdin) && *tmp == '\n') {
            for (i = 0; i < length; i++)
                printf (" %02x", input[i]);
            putchar ('\n');
        }

        /* decrypt restoring plain-text pass phrase */
        fputs ("\ndecrypted pass phrase, press [enter]: ", stdout);
        if (fgets (tmp, MAXC, stdin) && *tmp == '\n') {
            for (i = 0; i < length; i++)
                input[i] ^= 0x7f;
            encrypted = 0;              /* set flag false after decryption */
        }
        else {  /* if user pressed any other key (or generated EOF) */
            fputs ("error: user chose not to decrypt pass phrase.\n", stderr);
            return 1;
        }

        /* output decrypted plain-text pass pharase (if decrypted) */
        fputs ("\nTo see recovered text press [enter]: ", stdout);
        if (fgets (tmp, MAXC, stdin) && *tmp == '\n' && !encrypted) {
            for (i = 0; i < length; i++)
                putchar (input[i]);
            putchar ('\n');
        }
    }
    else {  /* invalid pass phrase to begin with */
        fputs ("error: pass phrase empty or greater than 5 chars.\n", stderr);
        return 1;
    }

    return 0;
}

(note: above the encrypted flag is simply used to hold the state of whether the contents of input[] is currently encrypted or not to provide an additional conditional check before you attempt to print the decrypted passphrase)

Example Use/Output

Valid passphrse case:

$ ./bin/pass0-5
Your pass phrase is: abcde

To see the encrypted text press [enter]:
 1e 1d 1c 1b 1a

decrypted pass phrase, press [enter]:

To see recovered text press [enter]:
abcde

User chooses not to decrypt case:

$ ./bin/pass0-5
Your pass phrase is: abcde

To see the encrypted text press [enter]:
 1e 1d 1c 1b 1a

decrypted pass phrase, press [enter]: No, I don't want to!
error: user chose not to decrypt pass phrase.

Passphrase too long case:

$ ./bin/pass0-5
Your pass phrase is: abcdef
error: pass phrase empty or greater than 5 chars.

Passphrase empty case:

$ ./bin/pass0-5
Your pass phrase is:
error: pass phrase empty or greater than 5 chars.

I don't know if I read the tea-leaves properly, but from your comments and original question it looked like this area was the one that was actually your focus. Look things over and let me know if you have further questions, and if I got the main gist of your question wrong, let me know and I'm happy to help further.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85