Skip to content

Caesar Cipher Program in C

A complete program you can compile right away. Copy it, or type your own text and see what the code does to each letter.

caesar.c
#include <stdio.h> void caesar(const char *text, int shift, char *result) {    int i;    for (i = 0; text[i] != '\0'; i++) {        char c = text[i];        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {            char base = (c <= 'Z') ? 'A' : 'a';            int index = ((c - base + shift) % 26 + 26) % 26;            result[i] = base + index;        } else {            result[i] = c;        }    }    result[i] = '\0';} int main(void) {    char result[100];    caesar("Hello World!", 3, result);    printf("%s\n", result);    // Khoor Zruog!    return 0;}
advertisement

Compiling and running it

Save the program as caesar.c and compile it with gcc or clang. On Windows, the second command is caesar.exe.

Terminal
gcc caesar.c -o caesar./caesar

The program prints the encrypted text. If the cipher itself is new to you, the guide explains it without code.

How the code works

A char in C is a small number, so you can do arithmetic on it: 'H' - 'A' gives 7, the letter’s place in the alphabet. Add the shift and the modulo keeps the result between 0 and 25, so with a shift of 3 the letter Z lands on C. base + index turns the number back into a letter.

C functions can’t return a string the way Python or Java can, so the caller passes in result, a buffer to write to. It has to be long enough for the text plus one more byte, because the last line of the function adds the '\0' that marks the end of every C string.

Spaces, punctuation and digits take the else branch and are copied unchanged. So are accented letters: in UTF-8, é is two bytes outside the A to Z ranges, and both are copied as they are.

Decrypting in C

You don’t need a second function. Decrypting is the same shift in the other direction, so inside main pass a negative number:

C
caesar("Khoor Zruog!", -3, result);printf("%s\n", result);// Hello World!

This is why the formula looks odd. In C, % keeps the sign of the left number, so -3 % 26 is -3, which is not a letter position. Adding 26 and taking the modulo again gives 23, and A goes back to X. Python’s % does this for you, as the Python version shows.

Reading text from the keyboard

Replace main with this version when an assignment asks for keyboard input. fgets reads a whole line, spaces included, and never writes past the size you give it, which scanf("%s") can’t promise.

C
#include <string.h> int main(void) {    char text[100], result[100];    int shift;     printf("Text: ");    fgets(text, sizeof text, stdin);    text[strcspn(text, "\n")] = '\0';     printf("Shift: ");    scanf("%d", &shift);     caesar(text, shift, result);    printf("%s\n", result);    return 0;}

fgets keeps the Enter key in the string. The strcspn line removes it, and it is also safe on an empty line.

Taking the key from the command line

Some courses want the program to take its input as arguments. CS50’s Caesar problem, for example, gets the key this way, so check your course’s own rules before reusing code. Here the shift and the text both come from argv:

C
#include <stdlib.h>#include <string.h> int main(int argc, char *argv[]) {    if (argc != 3) {        printf("Usage: %s <shift> <text>\n", argv[0]);        return 1;    }     char result[strlen(argv[2]) + 1];    caesar(argv[2], atoi(argv[1]), result);    printf("%s\n", result);    return 0;} // ./caesar 3 "Hello World!"// Khoor Zruog!

argv[1] is still text, so atoi turns it into a number. The buffer is sized from the input, so a long argument can’t overflow it.

Breaking the cipher with brute force

An alphabet of 26 letters leaves only 25 shifts to try, so a short loop can read a message without the key. Look through the output for the line that makes sense:

C
void crack(const char *text) {    char result[100];    for (int shift = 1; shift < 26; shift++) {        caesar(text, -shift, result);        printf("%d %s\n", shift, result);    }} int main(void) {    crack("Khoor Zruog!");    // 1 Jgnnq Yqtnf!    // 2 Ifmmp Xpsme!    // 3 Hello World!    // ...    return 0;}

The article on brute force attacks goes deeper, and the online solver does the same in your browser.

Digits and other alphabets

To shift more than A to Z, keep the characters in a string and look each one up with strchr. This version shifts digits too. The unsigned char matters, because passing a negative char to tolower or isupper is undefined behaviour.

C
#include <ctype.h>#include <string.h> void caesar_alphabet(const char *text, int shift, char *result) {    const char *alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";    int size = strlen(alphabet);    int i;    for (i = 0; text[i] != '\0'; i++) {        unsigned char c = text[i];        const char *found = strchr(alphabet, tolower(c));        if (found == NULL) {            result[i] = c;        } else {            int index = ((found - alphabet + shift) % size + size) % size;            result[i] = isupper(c) ? toupper(alphabet[index]) : alphabet[index];        }    }    result[i] = '\0';} int main(void) {    char result[100];    caesar_alphabet("Room 101", 3, result);    printf("%s\n", result);    // Urrp 434    return 0;}

This works only for characters that fit in one byte. National letters take two bytes in UTF-8, so you would need wchar_t and a locale setup that differs between systems. If you need them, the Python version is much simpler, and our Caesar cipher tool has ready alphabets for Polish, German, Spanish and French.

Encrypting a text file

Read the file line by line and write each result to a new file. Always check fopen, because it returns NULL when a file can’t be opened.

C
int main(void) {    char line[1000], result[1000];    FILE *in = fopen("message.txt", "r");    FILE *out = fopen("encrypted.txt", "w");     if (in == NULL || out == NULL) {        perror("fopen");        return 1;    }     while (fgets(line, sizeof line, in) != NULL) {        caesar(line, 3, result);        fputs(result, out);    }     fclose(in);    fclose(out);    return 0;}

Testing your code

Compare strings with strcmp, because == only compares their addresses. A failed assert stops the program and names the line. If nothing is wrong, it prints OK.

C
#include <assert.h>#include <string.h> int main(void) {    char r[100];    caesar("ABC", 3, r);    assert(strcmp(r, "DEF") == 0);    caesar("xyz", 3, r);    assert(strcmp(r, "abc") == 0);    caesar("Hello, World!", 5, r);    assert(strcmp(r, "Mjqqt, Btwqi!") == 0);    caesar("A", -27, r);    assert(strcmp(r, "Z") == 0);    caesar("é", 3, r);    assert(strcmp(r, "é") == 0);    printf("OK\n");    return 0;}

The same cipher in C++

In C++ a std::string manages its own memory, so the function can simply return the result, with no buffer to size. The arithmetic stays the same, and so does the extra step for negative numbers, because C++ handles % as C does.

C++
#include <iostream>#include <string> std::string caesar(std::string text, int shift) {    for (char &c : text) {        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {            char base = (c <= 'Z') ? 'A' : 'a';            c = base + ((c - base + shift) % 26 + 26) % 26;        }    }    return text;} int main() {    std::cout << caesar("Hello World!", 3) << "\n";    // Khoor Zruog!}

Common mistakes

The classic one is forgetting the '\0', so printf keeps reading past the end of the text. Close behind is a buffer one byte too short. Removing the newline with text[strlen(text) - 1] also goes wrong, since it reads before the start of an empty string. And without the extra + 26, decrypting produces symbols where letters should be.

Frequently asked questions

Which compiler and C version do I need?

Any compiler that supports C99 will do, such as gcc or clang. We compiled the code on this page with clang in C99 mode, and the C++ version as C++17, with no warnings.

Can I use this to protect real data?

No. Anyone can break it in seconds with the loop shown above. Use it to learn and for puzzles, and use a maintained library such as libsodium for real data.

How can I check that my result is right?

Enter the same text and shift in our Caesar cipher tool and compare. You can also call the free REST API from your own tests.

Keep going