1
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

int main(int argc, string argv[])
{
    // two arguments
    if (argc != 2)
    {
        printf("Give two arguments\n");
        return 1;
    }
    printf("plaintext: ");
    string plaintext = get_string();
    printf("ciphertext: ");

    string key = argv[1];

    for (int i = 0, t = 0, n = strlen(plaintext); i < n; i++, t++)
    {
        // if it's no letter, then:

        if (!isalpha(plaintext[i]) && plaintext[i] != ' ')
        {
            printf("False");

            return 1;

        }

        int number = 0;

        if (isalpha(plaintext[i]))
        {
            number += 1;
        }

        if (strlen(key) > number)
        {
            number = 0;
        }


        if (isupper(plaintext[i]))
        {
            printf("%c", (((plaintext[i] - 65) + key[number]) % 26) + 65);
        }

        //if it is lowercase
        else if (islower(plaintext[i]))
        {
            printf("%c", (((plaintext[i] - 97) + key[number]) % 26) + 97);
        }

        else
        {
            printf("%c", plaintext[i]);
        }

    }
    printf("\n");
}

So there's something missing with my code. When I do ./vigenere baz and then type as plaintext: Hello, world!, I get ciphertext: ByffiFalse. I should be getting iekmo, vprke! Also, when I type ./vigenere hello, and then type bye as the plaintext, I get ciphertext bye too while it should be icp. Can someone figure out what's missing or wrong with my code?

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 2
    Before anyone reading this asks, if you're unfamiliar with what `string` means in this code, it is from Harvard `cs50.h`, which chose the arguably *terrible* idea of protecting students from the apparently-evil nature of `char *` by hiding it in a type alias. – WhozCraig Sep 14 '18 at 18:49
  • 1
    @4386427 Given a key string on the command line, and some input text prompted during run time, the program symmetrically transforms the input text using the given key and a [Vigenere](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher) algorithm, thereby encrypting (or decrypting) the input text. That's what the OP is trying, anyway. – WhozCraig Sep 14 '18 at 18:55
  • 1
    Obligatory [magic numbers are bad numbers](https://stackoverflow.com/questions/47882/what-is-a-magic-number-and-why-is-it-bad) comment. – George Sep 14 '18 at 19:00
  • The `#include` header files are missing. – Weather Vane Sep 14 '18 at 19:27
  • @WeatherVane Oh yeah i forgot to paste them here but they are in my code –  Sep 14 '18 at 19:29
  • The way to figure out what's wrong is to step through the program in your debugger. – Barmar Sep 14 '18 at 19:37
  • Your program exits with `return 1` when it sees the first non-alpha, non-space character. So `Hello, world` stops when it gets to the comma. You should just copy any non-alpha characters to the cyphertext. – Barmar Sep 14 '18 at 19:38
  • The input "Hello, world!" has a comma at its 6th character. When your code finds this, if prints "False" and returns. So the behaviour of your program seems reasonable. – Weather Vane Sep 14 '18 at 19:39
  • The way `number` is used is wrong, the cypher part too. See e.g. https://ideone.com/2WvENw. Also, take a look at https://cs50.stackexchange.com/ – Bob__ Sep 14 '18 at 19:39
  • `if (strlen(key) > number)` is backwards, it should be `if (number > strlen(key)) – Barmar Sep 14 '18 at 19:40

2 Answers2

2

The biggest two problems with your code are the calculating the correct key differential value (you're not), and key advancement. I'll talk about them in reverse order.

Key advancement should start with the first key character, then advance one by one with each plain text being processed. When the key position reaches end-of-string, it is restarted. The most basic pseudo code for that would be

char *keyp = argv[1];

for (loop through plainttext)
{
    if (*keyp == 0) // reached the terminator ?
        keyp = argv[1]; // then reset to beginning.

   //... process the current plain text character, using *keyp
   //...  as the next key character to use.

   // advance key to next position (possibly conditionally)
   ++keyp;
}

But your code doesn't do that. Rather, it advances the key immediately, meaning you're starting with the second character onward.

int number = 0;

if (isalpha(plaintext[i]))
{
    number += 1; // HERE. first pass will use key[1]. it should be key[0]
}

if (strlen(key) > number) // this is backward
{
    number = 0;
}

Secondly, and probably more important, the whole point if a Vigenere cipher is effectively using a square shading table. See this link for a picture of that. The point of the algorithm you're coding is to act like that table exists using math. The offsets are the important part.When you do this calculation:

(((plaintext[i] - 65) + key[number]) % 26) + 65

which in reality should look like this:

(((plaintext[i] - 'A') + key[number]) % 26) + 'A'

consider what that key character addition is doing. Take your example:

key: baz
plaintext: Hello, World!

The first ciphertext character by your calculation will be:

((('H' - 'A') + 'a') % 26) + 'A'

Note: the 'a' is there because your first-pass is broken by one, remember? That crunches down as follows

(((7) + 97) % 26) + 'A'
((105) % 26) + 'A'
(1 % 26) + 'A'
1 + 'A'
'B'

And that's exactly what you're getting. But its wrong. Its wrong because this is wrong:

(((plaintext[i] - 'A') + key[number]) % 26) + 'A'
                         ^^^^^^^^^^^

That's the raw ascii value of the input character. What it should be is a calculated value between 1..26. In short, you're not adjusting your key input correctly.

Assumptive Solution

The following assumes the key will always be lower-case. It also fixes your first-skip logic, and decouples using cs50.h (which, frankly, I think does more harm than good). Finally it uses a `char* to track which key character is being used next. I leave the task of supporting mixed case input keys to you:

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

int main(int argc, char *argv[])
{
    // two arguments
    if (argc != 2)
    {
        printf("Give two arguments\n");
        return 1;
    }

    printf("plaintext: ");
    char pt[256] = { 0 };
    if (fgets(pt, sizeof pt, stdin))
    {
        // get the plaintext length
        size_t ptlen = strlen(pt);

        // remove trailing newline if present, and adjust ptlen
        if (ptlen > 0 && pt[ptlen - 1] == '\n')
            pt[--ptlen] = 0;

        // the key we're using. intially at the start
        char *key = argv[1];

        for (size_t i = 0; i < ptlen; ++i)
        {
            // reset key if prior iteration landed on terminator
            if (!*key)
                key = argv[1];

            if (isalpha((unsigned char)pt[i]))
            {
                if (isupper((unsigned char)pt[i]))
                {
                    printf("%c", (((pt[i] - 'A') + (*key-'a')) % 26) + 'A');
                    ++key;
                }

                //if it is lowercase
                else if (islower((unsigned char)pt[i]))
                {
                    printf("%c", (((pt[i] - 'a') + (*key-'a')) % 26) + 'a');
                    ++key;
                }
                else
                {
                    fputc(pt[i], stdout);
                }
            }
            else
            {
                fputc(pt[i], stdout);
            }
        }

        fputc('\n', stdout);
    }
    else
    {
        perror("Failed to read string");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;

}

Output from ./progname baz

plaintext: Hello, World!
Iekmo, Vprke!
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
0
  1. All non-alpha characters (not spaces only) should be skipped without encoding. Do not print "False" and return on, for example ',' symbol in "Hello, world!" string. Also, you can encode string in-place. Thus, main loop may looks like
printf("plaintext: ");
string s = GetString();
if (s == NULL)
    return 1;

for (int i = 0, len = strlen(s); i < len; ++i) {
    if (isalpha(s[i])) {
        /* encode s[i] in-place,
         * all non-alpha characters left as is
         */
    }
}

printf("ciphertext: %s\n", s);
  1. Key characters should also be "shifted". For example, for uppercase letters
s[i] = ((s[i] - 'A') + (key[n] - 'A') % 26) + 'A';
if (++n >= keylen)
    n = 0;

I suggest to normalize key before main loop, so that you will be able to use (key[n] - 'A') both for lower and upper characters from input string:

string key = argv[1];
strupper(k);
int keylen = strlen(key);
int n = 0;

Although I don't want provide full code because this is your courses, I think it would be better if you do it by yourself. But… some pieces:

strupper function:

void strupper(string s)
{
    for (int i = 0, n = strlen(s); i < n; ++i)
        s[i] = toupper(s[i]);    
}

Compact main loop:

for (int i = 0, n = strlen(s); i < n; ++i) {
    if (isalpha(s[i])) {
        char ref = isupper(s[i]) ? 'A' : 'a';
        int shift = k[j] - 'A';
        s[i] = ref + (s[i] - ref + shift) % 26;
        if (++j >= klen) j = 0;
    }
}

p.s. You use the same key character for all input characters because of int number = 0; defined and zeroed inside for loop.

ReAl
  • 1,231
  • 1
  • 8
  • 19