게임 서버 – 클라이언트 안전하게 통신하기

상황 요약

게임 서버와 클라이언트 사이의 통신을 누가 엿보거나 수정할 수 있다면 어떤 일이 생길까? 특히 게임 서버와 클라이언트 중간에 끼어들 수 있다면 여려가지 시도를 해볼 수 있다. 예를 들어,

  • 클라이언트의 로그인 메시지를 가로채서 대신 로그인하거나
  • 클라이언트가 보낸 결제 토큰을 가로챈다거나
  • 해당 유저인척 다른 유저에게 메시지를 보낸다거나 (혹은 보내는 메시지를 엉뚱하게 위조하거나) 같은 일을 할 수도 있다.

어떻게 하면 중간 공격자가 있어도 열어볼 수 없는 메시지를 보낼까? (feat. 국정원)

예를 들어, 엄청난 인기를 끌고 있는 게임에서 딱 하나 존재하는 아이템을 가지고 있는 유저가 있다고 치자. 그리고 국정원 직원 한 명이 이 아이템이 너무 가지고 싶어서 중간에 도청+조작을 해서 뺏어가려는 상황이라고 치자. 국정원에선 통신사 장비를 감청할 수 있으니 중간에 끼어들어서 로그인 메시지를 가로채려는 상황이라고 해보면 어떻게 하면 이 아이템을 지킬 수 있을까?

일반적으로 다음과 같은 처리를 한다:

  1. 서버와 클라이언트 양쪽에서만 알고 있는 공유 비밀 을 만들고
  2. 공유 비밀 에서 암호화 키를 생성하고
  3. 이 암호화 키로 암호화 알고리즘을 적용한다.

중간에 국정원 직원이 듣고 있는 상황에서, 어떻게 하면 이 공유 비밀 을 만들까?

디피 헬만 키 교환

%ec%bb%ac%eb%9f%bc-%ec%9d%b4%eb%af%b8%ec%a7%801

여기서 디피-헬만 키 교환 알고리즘 을 쓴다.

  1. 양쪽에서 미리 공통 염료를 정한다. (여기선 노란색)
  2. 각자 비밀 염료를 한가지씩 고른다. (각각 다홍색과 청록색을 골랐다)
  3. 2에서 고른 염료를 공통 염료와 섞는다.
  4. 3에서 섞은 결과물을 서로 교환한다. (이건 누가 알아도 상관없다)
  5. 전달 받은 상대방의 혼합 염료에 자기가 고른 비밀 염료를 섞는다.
  6. 이제 결과물로 나온 색이 공유 비밀 이 된다.

여기선 색을 섞는 것으로 비유했는데, 실제 알고리즘에서는 “정수의 거듭제곱을 소수로 나눈 나머지” 를 이용한다.

상대방 확인하기

하지만 위 알고리즘엔 문제가 있다. 국정원 직원이 단순히 엿듣기만 하는게 아니라, 주고 받는 염료 통을 가로챈 뒤에 자기가 고른 거랑 바꿔치기하면 어떻게 될까? 그리고 서버에겐 자기가 클라이언트인 척, 클라이언트한텐 서버인 척 속이는게 가능해진다. 어떻게 하면 이런 문제를 피해갈까?

다음과 같은 방법이 가능하다:

  1. 믿을 수 있는 인증서 서비스 (PKI) 를 쓴다. 그리고 이걸 가지고 전자 서명한 공개키(=3에서 섞은 염료)를 보낸다.
  2. 3에서 서버가 보내는 결과물을 항상 미리 알고 있는 값으로 한다.

보통은 1을 쓰겠지만, 국정원에서 한국의 루트 인증서 서비스 제공자인 KISA에 영향력을 행사할 수 있을지 누가 아는가? [^2] 영향력을 행사 못할거라 생각하면 1을, 아니라면 2를 추천한다. 혹은 1의 방법과 유사하지만, 공인 루트 인증서 대신 서명키를 스스로 만든걸 쓰고, 미리 서명키(=공개키)를 클라이언트에 넣어두는 방법도 있다.


메시지 암호화하기

여기까지 진행했다면,

  • 상대방이 내가 연결하려는 서버임이 확실하고 (PKI 혹은 미리 전달한 공개키 이용)
  • 서로 같은 공유 비밀을 가진 상태가 되었다.

이제 공유 비밀에서 비밀키 암호화에서 쓸 비밀키를 만든다. [^3] 여기서부터는 비밀키와 몇 가지 추가 정보 (nonce 등등)으로 보통 말하는 암호화를 수행한다. HTTPS 같은 걸 쓸 때 들어봤을 3DES, RC4, AES, ChaCha20 같은 알고리즘이 여기에 속한다. [^4]

상대가 보낸 메시지가 맞는지 확인하기

국정원처럼 중간에 메시지를 들여다보고, 수정까지 할 수 있는 경우, 실제로 메시지를 수정하거나, 암호화된 메시지인척 추가로 메시지를 보내는 공격도 가능하다. 이런 경우를 어떻게 발견할까?

SSL/TLS 에서는 메시지 인증 코드 (Message Authentication Code; MAC ) 를 쓴다. 보낸 메시지에 대해서 공유 비밀을 이용해서 추가적인 인증 정보를 붙여서 보낸다.

모바일 환경에선 어떻게 해야할까? (feat. 종량제 통신 요금 & 배터리)

유선 인터넷 환경과 달리, 모바일 환경에서는 대부분 종량제 요금을 사용한다. 그래서 암호화 때문에 데이터 요금이 더 나온다면 그걸 좋아할 사람은 별로 없다. 그리고 유선 전원 대신 배터리에서 전원을 얻어와야 하기 때문에, 배터리가 빨리 닳는 것 역시 암호화로 인한 문제가 될 수 있다. 그래서 이 두 가지를 고려하면서 어떤 알고리즘이 좋을지 얘기해보겠다.

키 교환

국가 수준의 돈과 노력을 들이는 상대로부터 안전한 수준으로 권고하는 값이 있다. 대략 AES-128 정도의 보안을 제공하는 수준인데, NIST 권고안에선 다음과 같은 값을 갖는다.

  • 위에서 언급한 디피-헬만 알고리즘의 경우 적어도 3072-bit (384 bytes) 크기의 메시지를 서로 주고 받아야 한다
  • RSA 알고리즘을 이용해서 키를 보내려고 하면 역시 3072-bit 크기의 메시지가 가야한다.
  • 타원곡선 디피-헬만 알고리즘을 쓴다면 이 크기가 256-bit (32 bytes) 로 줄어든다.

그래서 요즘 모바일 환경에서는 타원곡선 디피-헬만 알고리즘 (ECDH)를 많이 쓴다.

비밀키 암호화

현재 널리 쓰이는 알고리즘 몇 가지를 Unity3D + C 라이브러리로 만들어서 테스트 해본 결과가 아래와 같다. 64 bytes 길이의 메시지를 100,000 번 반복 암호화하는데 걸린 시간을 ms 단위로 측정했다.

%ec%bb%ac%eb%9f%bc-%ec%9d%b4%eb%af%b8%ec%a7%802

구글 크롬 모바일 버전이 구글과 통신할 때 괜히 ChaCha20 을 이용하는게 아니다. 각 기기에서 AES-128 보다 ChaCha20 이 10배-30배 정도 빠르다.

메시지 인증 코드

  • 널리 쓰이는 해시 MAC (HMAC) 으로 SHA-256을 쓴다면 32 bytes 만큼 추가 태그를 붙인다.
  • AES128 에서 주로 쓰는 GCM 태그의 경우 16 bytes 만큼 추가 태그가 붙는다.
  • Poly1305 를 이용한다면 역시 16 bytes 추가 태그가 붙는다.

모든 메시지에 대해서 위 만큼 길이가 늘어나야하는 것이니 HMAC 계열은 모두 불가. (최소 32바이트) AES128-GCM 의 경우 하드웨어 가속이 없는 대부분의 모바일 기기에서 느리다. 그러면 현재로써 남은 선택지는 Poly1305 MAC을 쓰는 정도.

그래서 어떻게 해야하는가?

요약하자면,

  • ECDH 로 키를 교환한다
  • ChaCha20 으로 메시지를 암호화한다
  • Poly1305 인증 코드를 사용한다

… 이걸 실제로 만드려면 아래와 같은 기능을 구현해야한다:

  • (암호학적으로)믿을 수 있는 랜덤 함수
  • 타원곡선 연산 함수
  • ChaCha20 암호화 함수
  • Poly1305 인증 코드 계산 함수

를 만들어야 한다.

그런데 만들면서 주의를 기울여야 할 부분도 많다:

타이밍 공격

모든 함수 내에서 메시지 길이 처리와 관련된 부분이 아닌 곳에서 if 문을 쓸 수 없다. 만약 메시지 내용에 따라 if 문으로 처리하게 되면 어떤 값을 다루고 있는지 추측할 수 있게 된다. 2003년 즈음의 OpenSSL에 포함된 RSA 알고리즘이 이런 식으로 구현되어서 RSA 비밀키를 추측할 수 있는 문제가 있었다.

올해 초에도 OpenSSL에 포함된 DSA 알고리즘이 타이밍 정보를 누출해서 키를 추측할 수 있는 문제가 다시 발견되기도 했다.

잘못 만든 랜덤함수

랜덤 함수 출력값이 예측 가능하거나, 일부 랜덤 값을 보고 다음 값을 추측할 수 있는 경우 안전하지 않은 암호화 알고리즘이 많다. Debian linux 포함된 OpenSSL에 들어있는 랜덤 함수를 잘못 수정했다가 수 많은 서버들의 보안 키가 불안전하게 생성된 적이 있다. (대략 2006년 – 2008년 동안)

재사용하면 안되는 값을 재사용

타원 곡선 디지털 서명 알고리즘 (ECDSA) 의 경우 재사용하면 안되는 난수 (nonce) 값을 쓴다. 만약 이 값을 재사용하면 서명키가 어떤 값인지 알 수 있다. 2010년에 Sony가 PS3 전자 서명할 때 같은 nonce 값을 썼다가 전자 서명키 값이 공개되서 망신당했다.

이런 문제를 다 해결하면서 만들 수 있는가? 그렇지 않다.

대안: SSL/TLS를 쓴다

HTTPS 처럼 사용 시나리오가 명확하다면 SSL/TLS 를 쓰고, 위에서 언급한 알고리즘을 지정해서 쓰는 방법이 있다.

대안: libsodium

위에서 언급한 알고리즘 대다수를 좀 더 고수준 API 형태로 제공하는 라이브러리다. 대부분의 모바일 OS와 서버 환경에서 사용할 수 있다. (Windows, linux, macOS, Android, iOS 지원)

  • 랜덤 함수: OS 별로 제공하는 암호학적으로 적당한 랜덤 함수를 래핑해서 제공한다
  • 키교환: 타원 곡선 알고리즘을 제공한다. 이 알고리즘으로 ECDH 를 구현할 수 있다.
  • 암호화 + MAC: AEAD 란 분류로 해당 알고리즘을 제공한다. (AES128-GCM / ChaCha20-Poly1305)

요약

  • 충분한 시간과 악의가 있는 공격자에 대해서도 게임에서 사용하는 메시지를 읽지 못하게 막을 수 있다
  • 모바일 환경에서 쓰기에 적당한 현대적인 알고리즘들이 존재한다 (ECDH, ChaCha20, Poly1305 MAC)
  • 해당 알고리즘을 안전하게 구현한 라이브러리 널리 쓰이는 라이브러리들이 있으니 가져다 쓰자: OpenSSL, libsodium

[^1]: 중간자 공격 (MITM; man-in-the-middle-attack) 이라고 부른다.

[^2]: KISA Root CA 같은 한국의 루트 인증서 제공자가 있다. 국정원 같은 국가 기관이 여기에 영향력을 행사 할 수 있다고 상상하는 사람은 이 곳을 신뢰하지 않는 루트 인증서로 처리하는게 좋을 것이다.

[^3]: Key Derivation Function. 랜덤한 여러 바이트를 가지고 특정 길이의 키, nonce 등을 생성하기 위해 쓴다.

[^4]: 미국 국립 표준기술연구소 (NIST) 에서는 3DES 대신 AES를 사용할 것을 권고하고 있다. TLS에 대한 표준안 갱신판인 RFC 7465에서 RC4 알고리즘 사용을 금지하고 있다.

아이펀팩토리 김진욱 CTO

답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중