Caesar

tl;dr

카이사르 암호를 이용한 암호화 프로그램을 이용하시오

$ ./caesar 13
plaintext:  HELLO
ciphertext: URYYB

Background

고전 서저에 의하면, 율리우스 카이사르는 군사적 기밀을 보낼 때, 글자를 '옮겨서' 내용을 암호화하였다는 . 예를 들어 A를 B라고 쓰고, B를 C라고 쓰고, C를 D라고 쓰고... 그리고. 글자가 초과되면 다시 감싸 돌아왔다. 즉, Z는 A라고 쓴 것이다. 그러하여, 누군가에게 HELLO라고 말할 려고 한다면, 카이사르는 IFMMP라고 쓸 수도 있었다. 카이사르에게 메세지를 받은 사람은 암호를 반대 방향으로 글자를 옮기면서 "해독"을 해야한다.

이 '알고리즘'의 안전성은 카이사르와 받는 사람만 옮기는 자릿 수를 안다는 것에 걸려있다. 즉 암호화의 "비밀(key)"을 오르지 단 둘만 알고 있다는 가정이 있는 것이다.(위에 있는 암호의 key는 1이다) 물론 현대 암호와 비교하면 그렇게 안전하지는 않지만, 처음 이용될 때는 가장 안전한 암호였죠!

암호화 돼지 않은 내용은 대부분 plaintext라고 불리고, 암호화가 됀 글자, 내용은 ciphertext라고 불립니다. 그리고 옮긴 자릿 수 개수, 그 비밀을 key라고 부릅니다.

Table 1. HELLO를 비밀을 1로 설정하고 암호할 때 나오는 값

plaintext

H

E

L

L

O

+ key

1

1

1

1

1

= ciphertext

I

F

M

M

P

좀 더 정확하게 말하자면 카이사르 알고리즘은 텍스트 k만큼 자리를 옮겨서 암호를 만듭니다. 좀 더 수학적으로 말하자면 p가 어떤 plaintext(암호화 되지 않은 텍스트)라고 하고, pipi번 째 문자, k가 그 비밀, key라고 하면 (i.e., 음수가 아닌 정수), 그러면 ciphertext, c의 각각 글자,ci는 다음과 같이 계산된다.

\[c_i = (p_i + k) \bmod 26\]

여기서 \(\bmod 26\)은 "26으로 나눌 때 나머지"라는 뜻이다. 이렇게 어렵게 쓰면 암호가 복잡해 보이지만, 그냥 수학적으로 표현할라고 하니까 그렇게 보이는 것이다.

Specification

카이사르의 '알고리즘'을 통해 암호화를 하는 프로그램을 디자인하고 작성하라. 프로그램의 이름은 caesar로 해야한다.

  • 소스코드의 이름은 caesar.c으로 caesar라는 폴더 안에 넣어야한다.

  • 당신의 프로그램은 음수가 아닌 정수를 받아야한다. 여기서는 k라고 부르자

  • 당신의 프로그램이 받았던 argument의 개수가 2개 이상이거나 받지 않았다면, 당신의 프로그램은 printf를 통해 에러 메세지를 출력하고, main함수는 1를 리턴해야한다. (exit.c 참고)

  • 유저가 올바른 양의 argument를 주었다면, 그 argument는 음수가 아닌 정수라는 가정을 해도 괜찮다. 즉, 음수인지 확인하거나 숫자인지는 확인을 하지 않아도 된다는 것이다.

  • k가 25보다 작다는 가정은 할 수 없다. 즉 k가 26보다 크다면, 모든 글자들은 다시 감싸 돌아와야 한다. 예를 들어 k가 26이라고 하면, 넣은 plaintext와 똑같아야 하지만, 27이라고 하면 A{가 되면 안 된다. 이 asciichart.com에 의하면 A{보다 26자리 떨어져 있지만, AB가 되어야 한다. 즉 Z다음은 A로 가는 "감싸 돌아오는" 작업이 있어야 한다.

  • 프로그램은 plaintext:를 출력하고 (\n 없이) 그 후 get_string을 통해 유저에게 plaintext일 string을 받아야 한다.

  • 프로그램은 ciphertext:를 출력하고 (\n 없이) 암호화된 문자를 출력해야 한다. 즉, 자리 k개를 '옮긴' 글자들을 출력하는 것이다. 여기서 중요한 것은 글씨가 아닌 것은 (띄어쓰기, 물음표, 느낌표 등등) 바뀌면 안 된다.

  • 대문자는 대문자로, 소문자는 소문자로 보존되어야 한다.

  • ciphertext가 출력이 완성된 이후, \nprintf하고, main함수가 0을 리턴해야 한다.

Walkthrough

Usage

프로그램은 다음과 같이 행동해야 한다. 밑 줄 친 부분은 유저의 입력이라고 가정한다.

$ ./caesar 1
plaintext:  HELLO
ciphertext: IFMMP
$ ./caesar 13
plaintext:  hello, world
ciphertext: uryyb, jbeyq
$ ./caesar 13
plaintext:  be sure to drink your Ovaltine
ciphertext: or fher gb qevax lbhe Binygvar
$ ./caesar
Usage: ./caesar k
$ ./caesar 1 2 3 4 5
Usage: ./caesar k

Testing

답 확인

check50 cs50/2018/x/caesar

예시 답안

~cs50/pset2/caesar

Hints

이 프로그램은 k라는 argument를 받아야 한다. 그러면 main함수를 다음과 같이 만들어야 될 것이다
(argv0.c, argv1.c, argv2.c참고)

int main(int argc, string argv[])

argv는 string의 배열이라는 것을 기억해야한다. 배열은 연속된 byte라고 생각할 수 있지만, 마치 연속된 서랍이라고 생각할 수도 있다. 각각의 서랍 안에는 어떤 값이 들어있고 (혹은 교과서), argv라는 연속된 서랍은 각각의 서랍 안에 string이 들어있다. 첫 번째 서랍을 열기 위해서는 (혹은 '접속'하기 위해서는) argv[0]같은 코드를 수행한다. 배열은 0부터 시작한다는 것도 까먹으면 안 된다. 그러하여 배열의 길이, 즉 서랍의 갯수는 n-1이기 때문에 n번 째 서랍(argv[n])을 접속하면 안 된다. 왜냐하면 없기 때문에!! (혹은 나의 서랍들이 아닐 수도 있다, 물론 그럴 때도 열면 안 된다)

그러하여 k를 다음과 같은 코드로 접속할 수 있다.

string k = argv[1];

거기 존재한다는 가정하에! argcargv의 string이 몇 개 들어있는 지를 알려주기 때문에, 존재하지도 모르는 사물함을 열기 전에, argc로 확인 하는 것이 좋을 것이다. 유저가 올바르게 넣었다면, argc는 2일 것이다. 왜? 프로그램을 싱행했을 때 그 실행한 명령어가 자동으로 들어가기 때문에 argc는 최소한 1이다.(argv[0] == ./caesar). 그러면 유저가 단 하나의 값, k를 줄 때는 argc가 2일 것이다. 하지만 유저가 값을 한 개만 줄 수 있는 것은 아니길 때문에, argc거 3이나 4일 수 도 있다. 이 때는 프로그램은 에러를 출력하고 1을 리턴해야 한다.

또한 유저가 넣은 값이 숫자처럼 보인다고, 자동으로 int에 넣을 수 있는 것은 아니다. 유저가 넣은 값은 단순히 숫자처럼 보이는 string이기 때문이다. 그러하여 stringint로 변환해야 한다. 다행이도, atoi라는 함수가 이 역할을 수행해준다. atoi는 다음처럼 이용할 수 있다.

int k = atoi(argv[1]);

이 때는, 위와 달리 실제로 kint에 넣었다.

atoistdlib.h 안에 정의 되어있기 때문에, 그 라이브러리를 #include해야 할 것이다. (따지고 보면, cs50.h에 이미 stdlib.h가 들어있기 때문에 컴파일이 되지면, 외부 라이브러리로 필요한 라이브러리를 #include하는 것은 위험하기 때문에, 프로그램 안에 넣는 것이 좋다)

이제 k가 있으면, 유저에게 plaintext를 받아야 할 것이다. get_string을 이용하면 할 수 있을 것이다.

k라는 key와 plaintext p를 받으면, 이제 암호화를 해야한다. argv2.c를 참고하면, 아래와 비슷한 코드로 string 속의 글자 하나하나를 각각 접속할 수 있다.

for (int i = 0, n = strlen(p); i < n; i++)
{
    printf("%c", p[i]);
}

다른 말로, argv가 string의 배열인 것 처럼, string은 글자, char의 배열인 것이다. 그래서 argv를 접속한 방법처럼 대괄호를 이용해서 string의 글자 하나하나를 접속할 수 있는 것이다. 물론, 접속을 하고 단순히 출력을 하면, 암호화한 것이 아니죠, (k가 0이 아닌 이상). 그러면 어떻게 변형을 해야지, 암호화를 하느냐? 그게 이번 숙제의 관건이다!

참고로, strlen을 이용하기 위해서는 또 다른 라이브러를 #include해야 할 것이다.

atoi 말고도, reference.cs50.net에서 유용한 함수 몇 개를 찾을 수 있을 것이다. 특히 ctype.hstdlib.h 쪽에 있는 isalpha가 있다.

그리고, Z에서 A로 다시 돌아오는 기능을 넣을 때, %를 까먹으면 안 된다. 또한 코딩을 하는데 나중에 알파벳이 아닌 암호를 출력할 수도 있기 때문에 옆에 http://asciichart.com/ 하나 열어 놓고, 알파벳이 아닌 글자도 눈 여겨 보는 곳이 좋을 것이다.