[보안] 게임 서버 암호화에 대한 오해와 JWT의 올바른 사용법
보안과 관련된 기술은 언뜻 보면 비슷해 보이지만, 목적에 따라 완전히 다르게 사용해야 한다. 나중에 개발할 때 헷갈리지 않도록, 게임 서버 개발자로서 꼭 챙겨야 할 실전 암호화 개념들을 정리해둔다.
암호화는 모든 보안 문제를 해결해주는 마법이 아니기에, 각 도구의 특성을 정확히 이해하고 기록해두는 것이 중요하다.
복잡한 이론이나 수학적 원리보다는, 실제 구현 시 사용 용도에 따라 3가지를 명확히 구분하여 기록해둔다.
| 구분 | 진짜 목적 | 대표 선수 |
|---|---|---|
| 단방향 | "복원 불가능한 형태로 변환" (검증용) | bcrypt, Argon2 |
| 대칭키 | "고속 암호화/복호화" (데이터용) | AES, ChaCha20 |
| 비대칭키 | "신뢰 관계 수립" (키 교환/서명) | RSA, ECDSA |
이 3가지를 명확히 구분해야 적재적소에 올바른 기술을 적용할 수 있다.
flowchart TB
subgraph Hash["단방향 (복구 불가능)"]
H1["bcrypt"]
H2["Argon2"]
end
subgraph Symmetric["대칭키 (빠른 속도)"]
S1["AES-GCM"]
S2["ChaCha20"]
end
subgraph Asymmetric["비대칭키 (서명/교환용)"]
A1["RSA"]
A2["ECDSA (서명 전용)"]
end
Hash --> PW["비밀번호"]
Symmetric --> Data["패킷 암호화"]
Asymmetric --> Trust["인증/키교환"]2. 비밀번호 저장: SHA-256을 피해야 하는 이유
보안을 위해 비밀번호를 해싱할 때, 잊지 말아야 할 포인트들을 정리한다.
// [위험] 취약한 방식
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(password));"SHA-256은 안전한 알고리즘이 아닌가요?"라고 반문할 수 있다.
문제는 '너무 빠르다'는 점이다. SHA-256 같은 해시는 원래 파일 무결성 검증하려고 만든 거라 매우 빠르다. 요즘 그래픽카드 쓰면 1초에 수십억 번도 돌린다. 공격자가 레인보우 테이블 돌리거나 무차별 대입하면 금방 뚫린다는 소리다.
비밀번호는 "의도적으로 느리게" 만들어야 한다.
// [권장] 안전한 방식
// workFactor를 조절해서 해싱에 0.5초 정도 걸리게 만들어야 안전함
var hash = BCrypt.Net.BCrypt.HashPassword(password, workFactor: 12);따라서 비밀번호 저장 시에는 bcrypt나 Argon2를 사용하는 것이 안전하다.
3. 대칭키: 키 배포의 어려움과 해결책
AES나 ChaCha20은 빠르다. 실시간 게임 패킷 암호화하기 딱 좋다. 근데 치명적인 단점이 있다.
"클라이언트에게 키를 안전하게 전달하는 방법"이 문제다.
코드 안에 키를 하드코딩한다? 리버스 엔지니어링을 통해 키가 유출될 위험이 매우 크다. 클라이언트 코드에 포함된 키는 언제든 노출될 수 있다고 가정해야 한다.
그래서 보통 이 과정을 거친다:
- 접속 시 비대칭키(RSA, ECDH)를 통해 안전하게 "세션 키"를 교환한다.
- 교환된 세션 키로 대칭키(AES) 통신을 수행한다.
또한, 암호화뿐만 아니라 무결성(HMAC) 검증도 필수적이다. 암호화된 데이터를 변조해서 보냈을 때 서버가 이를 감지하지 못하면 심각한 문제가 발생할 수 있다. 따라서 암호화와 무결성 검증을 동시에 수행하는 AES-GCM 같은 AEAD 모드를 반드시 사용해야 한다.
4. 비대칭키: RSA, ECDSA... 뭘 써야 해?
여기서 많이들 헷갈려한다.
RSA (구관이 명관)
- 암호화도 되고 서명도 된다.
- 근데 키가 너무 크고 연산이 무겁다.
- 옛날 시스템이랑 연동해야 하면 어쩔 수 없이 쓴다.
ECDSA (요즘 대세)
- 타원곡선 어쩌구 하는데 몰라도 된다.
- 같은 보안 수준일 때 키가 훨씬 작다. (트래픽을 아껴야 하는 모바일 환경에서 매우 유리하다)
- 중요: 이건 서명 전용이다. 암호화는 안 된다. (키 교환은 ECDH라고 따로 있다)
결론: RSA를 반드시 써야 하는 레거시 환경이 아니라면, ECDSA를 사용하는 것이 효율적이다.
5. JWT: 암호화 수단이 아니다
보안을 위해 JWT를 사용하지만, 동작 원리에 대해 명확히 기록해둔다. JWT는 암호화가 아니다. 단순히 인코딩된 문자열일 뿐이다.
Header.Payload.Signature
이거 Base64로 풀면 누구나 내용 다 볼 수 있다.
{"userId": 1, "money": 999999} 이렇게 데이터를 보내더라도, 서버는 이를 곧이곧대로 믿어서는 안 된다.
JWT가 해주는 것
- 이거 내가 발급한 거 맞음 (위조 방지, 서명)
- 내용물 안 바뀌었음 (변조 방지)
JWT가 못 해주는 것
- 남들이 못 봄 (암호화 아님. 다 보인다.)
- 이 유저가 정직함 (토큰 훔친 놈인지 원래 주인인지 모름.)
- 로직 검증 (클라가 보낸 데미지 값이 정상인지 알 바 아님.)
게임 서버에서의 교훈
패킷 암호화만으로는 치트를 완벽하게 막기 어렵다. 클라이언트 데이터는 환경에 따라 변조될 수 있기 때문이다. 따라서 치트 방지는 암호화에 전적으로 의존하기보다, 서버에서의 검증 로직을 견고하게 설계하는 것이 핵심이다.
[취약한 방식] 클라: "나 1000 데미지 줬음" (암호화해서 보냄) -> 서버: "승인"
[안전한 방식] 클라: "나 공격 버튼 눌렀음" -> 서버: "계산해보니 10 데미지네. 적용."
서버가 모든 권한을 쥐고 있어야 한다. 클라이언트가 보내주는 값을 검증 없이 신뢰하는 순간 그 게임은 취약해질 수밖에 없다.
마무리
단순히 암호화 기술의 이름을 외우는 것보다, "이 기술이 뭘 막아주고 뭘 못 막아주는지"를 아는 게 중요하다.
- 비밀번호는 느린 해시 (bcrypt)
- 패킷 암호화는 키 교환부터 (ECDH + AES)
- ECDSA는 서명 전용이다. (암호화용이 아님)
- JWT는 신분증이지 금고가 아니다.
이 핵심 원칙들만 잘 챙겨도 실무에서 발생할 수 있는 보안 사고들을 상당 부분 예방할 수 있을 것 같다. 깊이 있는 이론도 중요하지만, 내가 맡은 시스템에 맞는 실질적인 보안 원칙을 세워나가는 것이 무엇보다 중요하다는 점을 다시 한번 새긴다.
참고 자료
Loading comments...