Skip to content

Substitution

Problem to Solve

A substitution cipher encrypts a message by replacing every letter with a different letter. The replacements are defined by a key that maps each letter of the alphabet to another letter. Anyone who wants to decrypt the message must know that key and reverse the mapping from ciphertext back to plaintext.

Consider this key:

NQXPOMAFTRHLZGECYJIUWSKDVB

The key has 26 characters. Its first character says that A becomes N; its second says that B becomes Q; its third says that C becomes X; and the remaining characters define the mappings through Z. Under this key, HELLO is encrypted as FOLLE.

Create a file named substitution.c inside a folder named substitution. Write a program that accepts the substitution key as a command-line argument, asks the user for plaintext, and prints the resulting ciphertext.

Specification

Design and implement a program named substitution that encrypts messages with a substitution cipher.

  • Accept exactly one command-line argument: the substitution key.
  • Treat the key itself as case-insensitive. Uppercase and lowercase versions of the same key must define the same mapping.
  • If the program receives no key or more than one argument, print a clear error message and return 1 from main.
  • A valid key contains exactly 26 alphabetic characters and includes every letter exactly once, ignoring case.
  • If the key has the wrong length, contains a non-letter, or repeats a letter, print a clear error message and return 1 immediately. You may use Usage: ./substitution key for all invalid keys.
  • Prompt for the message by printing plaintext: without first printing a newline.
  • Print ciphertext: followed by the encrypted message.
  • Substitute alphabetic characters according to the key. Copy spaces, punctuation, digits, and other non-letter characters without changing them.
  • Preserve the case of the plaintext: uppercase letters remain uppercase, and lowercase letters remain lowercase.
  • Print one newline after the ciphertext and return 0 from main.
  • Use standard C and character arrays.

For this local version, you may assume that the plaintext contains no more than 254 characters.

Advice and Hints

The validity of the key and the substitution of the plaintext are separate problems. Implement and test them separately.

Validate the command-line arguments

A valid invocation has argc == 2, because argc also counts the program name. The key is stored in argv[1].

Begin by testing the number of arguments. Then pass the key to a separate validation function:

int valid_key(const char key[]);
Check the key's length and characters

Use strlen, declared in string.h, to verify that the key has exactly 26 characters. Then visit each character and use isalpha from ctype.h to verify that it is a letter.

Convert a char to unsigned char before passing it to a ctype.h function.

Check for repeated letters

Use an array to remember which letters have already appeared:

int seen[26] = {0};

Convert each key character to one consistent case. Subtract 'A' to turn it into an index from 0 to 25. If that position in seen is already 1, the key repeats a letter and is invalid. Otherwise, set the position to 1.

Substitute one character

Add a function with this prototype:

char substitute(char c, const char key[]);

If c is uppercase, subtract 'A' to find its position in the alphabet; if it is lowercase, subtract 'a'. Use that position as the index into the key, then convert the selected key character to the original character's case. Return non-letter characters unchanged.

Read a complete message

Use fgets with a 256-character array so that the plaintext may contain spaces. Process characters until you reach the newline stored by fgets or the null character, \0. Print exactly one newline after the ciphertext.

Starter Code

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

int valid_key(const char key[]);
char substitute(char c, const char key[]);

int main(int argc, char *argv[])
{
    // Check the number of arguments and validate the key.

    // Read the plaintext.

    // Substitute and print each character.
}

int valid_key(const char key[])
{
    // Check the key's length, characters, and uniqueness.
}

char substitute(char c, const char key[])
{
    // Substitute letters, preserve case, and return other characters as-is.
}

How to Test

Compile and run the program from the substitution folder:

cc substitution.c -o substitution
./substitution NQXPOMAFTRHLZGECYJIUWSKDVB

If your system provides gcc instead of cc, run gcc substitution.c -o substitution.

Test missing and invalid keys:

$ ./substitution
Usage: ./substitution key
$ ./substitution ABC
Usage: ./substitution key
$ ./substitution ABCDEFGHIJKLMNOPQRSTUVWXY1
Usage: ./substitution key
$ ./substitution AAAAAAAAAAAAAAAAAAAAAAAAAA
Usage: ./substitution key

Test a valid key:

$ ./substitution NQXPOMAFTRHLZGECYJIUWSKDVB
plaintext: HELLO
ciphertext: FOLLE
$ ./substitution NQXPOMAFTRHLZGECYJIUWSKDVB
plaintext: Hello, World!
ciphertext: Folle, Kejlp!

Also test a lowercase key, mixed-case plaintext, punctuation, digits, and a message containing spaces.

Return to the Problem Set 2 overview.