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]++;
}
}