The Journey in C Continues - CS50 pset 2

I know it's been awhile, I've been sidetracked with work and fell behind on my classwork, but better late than never! Here are my solutions to the second problem set for CS50:

Initials.c

This was a simple little program to take in a name as an input, and then output that person's initials.

// Program to take name and return initials

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

int main(void)
{
    void initialize(string name);
    
    string name = GetString();
    
    // Check if null
    if (name != NULL)
    {
        initialize(name);
    }
    
    return 0;
}

void initialize(string name)
{
    int start = 0;
    
    // Get first letter as initial, checking for spaces
    while (name[start] == ' ')
    {
        start++;
    }
    
    printf("%c", toupper(name[start]));
    
    // Go through rest of name
    for (int i = start + 1, n = strlen(name); i < n; i++)
    {
        // Get Letter after space
        while (name[i] == ' ')
        {
            i++;
            
            // Only print if next char is not a space
            if (i < n && name[i] != ' ')
            {
                printf("%c", toupper(name[i]));
            }
        }   
    }
    
    // Print new line
    printf("\n");
}

Caesar.c

The next one is another simple program to encrypt text using the Caesar cipher. It offsets all characters by a certain number of letters (the command line argument in the program).

// Caesar cipher

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

int main(int argc, string argv[])
{
    // Check if correct # of arguments given
    if (argc != 2 && !isdigit(argv[1]))
    {
        printf ("Wrong number of arguments. Please try again.\n");
        
        return 1;
    }
    
    // Convert input to int type
    int k = atoi(argv[1]);
    
    // Get text to encode
    string p = GetString();
    
    // Loop through text
    for (int i = 0, n = strlen(p); i < n; i++)
    {            
        // Keep case of letter
        if (isupper(p[i]))
        {
            // Get modulo number and add to appropriate case
            printf("%c", 'A' + (p[i] - 'A' + k) % 26);
        }
        else if (islower(p[i]))
        {
            printf("%c", 'a' + (p[i] - 'a' + k) % 26);
        }
        else
        {
            // return unchanged
            printf("%c", p[i]);
        }
    }
    
    printf("\n");
    
    return 0;
}

Vigenere.c

Next up is an almost identical program but uses the Vigenere cipher instead, which uses a separate word/set of characters as a key, rather than a single integer. This requires using some modulo arithmetic to ensure we are still returning characters since the key could be above 26.

// Vigenere cipher

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

int main(int argc, string argv[])
{    
    // Check if correct # of arguments given
    if (argc != 2)
    {
        printf("Wrong number of arguments. Please try again.\n");
        
        return 1;
    }
    else 
    {
        for (int i = 0, n = strlen(argv[1]); i < n; i++)
        {
            if (!isalpha(argv[1][i]))
            {
                printf("Key must be alphabetic chars only.");
                
                return 1;
            }    
        }
    }
    
    // Store key as string and get length
    string k = argv[1];
    int kLen = strlen(k);
    
    // Get text to encode
    string p = GetString();
    
    // Loop through text
    for (int i = 0, j = 0, n = strlen(p); i < n; i++)
    {            
        // Get key for this letter
        int letterKey = tolower(k[j % kLen]) - 'a';
        
        // Keep case of letter
        if (isupper(p[i]))
        {
            // Get modulo number and add to appropriate case
            printf("%c", 'A' + (p[i] - 'A' + letterKey) % 26);
            
            // Only increment j when used
            j++;
        }
        else if (islower(p[i]))
        {
            printf("%c", 'a' + (p[i] - 'a' + letterKey) % 26);
            j++;
        }
        else
        {
            // return unchanged
            printf("%c", p[i]);
        }
    }
    
    printf("\n");
    
    return 0;
}

Crack.c

Last up is a program to decrypt text rather than encrypt it. This was definitely a challenge, and I spent several hours trying to get this one right. It takes an encrypted password and attempts to figure out what it was. Even though it uses an older form of encryption, it was still tough to crack! Don't count on me becoming a hacker anytime soon...

Basically it works by encrypting a word using the same encryption method the password used, and then comparing the results. If they match, the word the program encrypted is the password.

Obviously this would take awhile to brute force, so I start by utilizing the built in dictionary in the computer and looping through those first (only checking passwords up to 8 characters). This only worked on 2 out of 8 test cases in the problem set, so I had to move on to brute forcing.

I'm sure there are other, smarter methods to use before relying on brute force, but the problem emphasized accuracy above speed. Since the comparison function stops when it hits a null character in string, I initialize a string of null characters. I then created a helpful function to increment that string through the ASCII table as if it were a number and check each string.

After some testing I know that the increment function works accurately, so I know that eventually this algorithm would work. But after 8 hours I was unable to get a result, soo... again I won't be cracking anyone's password anytime soon. I guess that's the problem with counting in base 128!

// Brute Force crack crypt() encrypted password

#define _XOPEN_SOURCE
#include <cs50.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, string argv[])
{
    void incrementChar(char string[], int index);

    // Check if correct # of arguments given
    if (argc != 2)
    {
        printf("Wrong number of arguments. Please try again.\n");

        return 1;
    }

    // Get Salt
    char salt[3];
    strncpy(salt, argv[1], 2);
    salt[2] = '\0';

    // Load dictionary of words
    FILE *dict = fopen("/usr/share/dict/words", "r");
    char word[255];

    // Check if file loads, otherwise return error
    if (dict == NULL)
    {
        printf("Error opening file");

        return 1;
    }

    // Loop through file to get strings
    while (fgets(word, 255, (FILE*)dict) != NULL)
    {
        // Check if word is 8 chars and if running crypt on word equals input
        if (strlen(word) <= 9 && strcmp(crypt(word, salt), argv[1]) == 0)
        {
            // Print password
            printf("%s", word);

            return 0;
        }
    }

    // Close file
    fclose(dict);

    // If not in the dictionary, brute force it
    // Initialize string to all null characters
    char test[9] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' };

    do
    {
        // Compares until next null character
        if (strcmp(crypt(test, salt), argv[1]) == 0)
        {
            // Print password
            printf("%s\n", test);

            return 0;
        }

        // Increment letter
        incrementChar(test, 0);
    }
    while (test[8] == '\0');

    printf("Password not found\n");

    return 0;
}

// Function to increment character and carry over if necessary
void incrementChar(char string[], int index)
{
    if (string[index] == '\0')
    {
        string[index] = '!';
    }
    else if (string[index] == '~')
    {
        string[index] = '!';
        incrementChar(string, index + 1);
    }
    else
    {
        string[index]++;
    }
}