Cryptography involves converting plaintext to ciphertext using a key. In this CS50 project, you'll learn how to write a C program that converts plain text to ciphered text using a 26-letter alphabetical key.
Project Requirements
- Validate a 26-character key from command line arguments
- Ensure key contains only letters with no repeats
- Convert plaintext to ciphertext using substitution cipher
- Preserve case and non-alphabetic characters
Step 1: Command Line Argument for Key
We start by validating the command line argument. The key must be a 26-character string containing only letters with no repeats.
int main(int argc, string argv[])
{
// Validate command line arguments
if (argc != 2 || strlen(argv[1]) != 26)
{
printf("Usage: ./substitution key or Key must contain 26 characters\n");
return 1;
}
int len = strlen(argv[1]);
// Validate key characters
for (int i = 0; i < len; i++)
{
if (!isalpha(argv[1][i]))
{
printf("Key must contain only letters\n");
return 1;
}
// Check for duplicate letters (case-insensitive)
for (int j = i + 1; j < len; j++)
{
if (toupper(argv[1][i]) == toupper(argv[1][j]))
{
printf("Letters cannot be repeated\n");
return 1;
}
}
}
}
Step 2: Enciphering Plain Text
This function takes the key and plaintext, converting each alphabetic character to its corresponding cipher character while preserving case and non-alphabetic characters.
Substitution Process
- For uppercase letters:
index = letter - 'A'
- For lowercase letters:
index = letter - 'a'
- Replace with corresponding key character
- Preserve original case in output
- Non-alphabetic characters remain unchanged
string enciphertext(string text, string key)
{
int len = strlen(text);
string ciphertext = malloc(len + 1);
int index;
for (int i = 0; i < len; i++)
{
if (isupper(text[i]))
{
// Convert uppercase letter
index = text[i] - 'A';
ciphertext[i] = toupper(key[index]);
}
else if (islower(text[i]))
{
// Convert lowercase letter
index = text[i] - 'a';
ciphertext[i] = tolower(key[index]);
}
else
{
// Preserve non-alphabetic characters
ciphertext[i] = text[i];
}
}
// Null-terminate the string
ciphertext[len] = '\0';
return ciphertext;
}
Step 3: Full Program
The complete program integrates key validation and text enciphering to create a substitution cipher tool.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <cs50.h>
// Function prototype
string enciphertext(string text, string key);
int main(int argc, string argv[])
{
// Validate command line arguments
if (argc != 2 || strlen(argv[1]) != 26)
{
printf("Usage: ./substitution key\nKey must contain 26 characters\n");
return 1;
}
int len = strlen(argv[1]);
// Validate key
for (int i = 0; i < len; i++)
{
if (!isalpha(argv[1][i]))
{
printf("Key must contain only letters\n");
return 1;
}
for (int j = i + 1; j < len; j++)
{
if (toupper(argv[1][i]) == toupper(argv[1][j]))
{
printf("Letters cannot be repeated\n");
return 1;
}
}
}
string key = argv[1];
string text = get_string("plaintext: ");
string ciphertext = enciphertext(text, key);
printf("ciphertext: %s\n", ciphertext);
free(ciphertext);
return 0;
}
string enciphertext(string text, string key)
{
int len = strlen(text);
string ciphertext = malloc(len + 1);
int index;
for (int i = 0; i < len; i++)
{
if (isupper(text[i]))
{
index = text[i] - 'A';
ciphertext[i] = toupper(key[index]);
}
else if (islower(text[i]))
{
index = text[i] - 'a';
ciphertext[i] = tolower(key[index]);
}
else
{
ciphertext[i] = text[i];
}
}
ciphertext[len] = '\0';
return ciphertext;
}
Key Considerations
- Memory Management: Properly allocate and free memory for ciphertext
- Case Preservation: Maintain original case of input characters
- Input Validation: Ensure key meets all requirements before processing
- Error Handling: Provide clear error messages for invalid input
- Efficiency: Optimized character processing with O(n) complexity
Example Usage
Input: ./substitution YTNSHKVEFXRBAUQZCLWDMIPGJO
Plaintext: hello, world!
Ciphertext: jrssb, ybwsp!