데이터 보호

"서버를 두세요"가 답이 안 되는 팀을 위해 — 서버리스 게임의 데이터 방어

"중요한 값은 서버에서 판정하세요" — 그런데 서버가 없다면

게임 보안 이야기는 대체로 같은 문장으로 끝납니다. "재화나 점수처럼 중요한 값은 클라이언트를 믿지 말고 서버가 최종 판정해야 합니다."

맞는 말입니다. 저희도 여러 번 같은 이야기를 했습니다.

문제는 그다음입니다. 서버가 없는 팀에게 이 조언은 사실상 "지금 구조를 다시 만드세요"와 같습니다. 백엔드를 세우려면 서버 비용, 계정 시스템, 장애 대응, 개인정보 처리, 그리고 그걸 유지할 사람이 필요합니다. 싱글 플레이 위주의 게임이라면 그 비용이 얻는 것보다 클 수 있습니다.

그래서 이 글은 다른 질문을 다룹니다. 서버를 두지 않기로 한 상태에서, 어디까지 할 수 있고 무엇은 포기해야 하는가.

먼저 정직하게: 서버 없이 못 하는 것

시작하기 전에 분명히 해 둘 것이 있습니다. 클라이언트가 최종 판정하는 한, 결정적인 방어는 없습니다.

게임이 기기 안에서 "골드 5,000"이라고 판단하고 그 판단을 검증할 외부 기준이 없다면, 충분한 시간을 들인 공격자는 결국 그 값을 원하는 대로 만들 수 있습니다. 저장 형식을 바꾸든, 서명을 붙이든, 메모리를 숨기든 마찬가지입니다. 검증하는 코드도 같은 기기 안에 있기 때문입니다.

그러니 서버리스 환경에서 목표를 "막는다"로 잡으면 실패합니다. 현실적인 목표는 셋입니다.

  • 비용을 올린다 — 세이브 파일을 텍스트 편집기로 여는 수준에서, 바이너리를 분석해야 하는 수준으로
  • 흔적을 남긴다 — 조작이 일어났다는 사실을 나중에라도 알 수 있게
  • 되돌릴 수 있게 한다 — 문제가 드러났을 때 정상 상태로 복구할 경로를 남겨두기

이 셋만 해도 대부분의 캐주얼한 조작은 걸러집니다. 세이브 파일 자체를 어떻게 지킬지는 게임 세이브 파일 변조에서 다뤘습니다. 이 글은 그다음 이야기입니다.

서버가 없을 때 특히 잘 통하는 세 가지

1. 세이브 롤백 — 되돌리기 버튼이 공짜인 상태

세이브 파일을 통째로 복사해 두었다가, 결과가 마음에 들지 않으면 되돌립니다.

  • 가챠를 돌리고 결과가 나쁘면 복원해서 다시 돌린다
  • 소모품을 쓰고 복원해서 소모를 취소한다
  • 어려운 구간에서 죽으면 복원해서 패널티를 지운다

이 공격의 특징은 파일을 조작할 필요가 없다는 점입니다. 정상적인 세이브를 정상적으로 복원할 뿐이라, 무결성 검증이나 암호화로는 걸러지지 않습니다. 서명도 통과합니다. 그 세이브는 진짜니까요.

2. 기기 시간 조작 — 기다림을 없애는 방법

기기 시계를 앞으로 돌리면 시간을 소비하는 설계가 무너집니다.

  • 일일 보상을 하루에 여러 번 받는다
  • 건물 건설이나 성장 타이머를 즉시 끝낸다
  • 쿨타임을 초기화한다

System.DateTime.Now는 기기의 시계를 읽습니다. 서버가 없으면 그 시계가 맞는지 확인할 기준이 없습니다.

3. 클라우드 세이브 충돌 — 두 기기를 오가며 유리한 쪽을 고르기

클라우드 세이브를 쓰면 기기 두 대에서 서로 다른 진행 상태가 생길 수 있습니다. 이때 어느 쪽을 살릴지 결정해야 하는데, 그 결정 규칙을 공격자가 이용합니다. 다음 절에서 따로 다루겠습니다.

서버 없이 쓸 수 있는 구조 네 가지

단조 증가 카운터로 롤백을 감지한다

세이브를 저장할 때마다 1씩 증가하는 번호를 함께 기록합니다. 그리고 이 번호의 최댓값을 세이브와 다른 곳에 따로 남깁니다.

불러올 때 세이브의 번호가 기록된 최댓값보다 작으면 되돌려진 것입니다. 되돌아간 사실 자체는 알 수 있습니다.

물론 공격자가 두 곳을 모두 되돌리면 통합니다. 그래서 이건 막는 수단이 아니라 비용을 올리는 수단입니다. 세이브 파일 하나만 복사하면 되던 것이, 저장 위치 두 곳을 찾아 함께 다뤄야 하는 일이 됩니다.

시간은 "역행"을 감지하는 데까지만 쓴다

마지막으로 관측한 시각을 저장해 두고, 다음에 켰을 때 그보다 이전이면 시계가 뒤로 갔다고 판단할 수 있습니다.

반대로 앞으로 간 것은 판별하기 어렵습니다. 실제로 하루가 지난 것인지 시계를 하루 앞당긴 것인지, 기기 안에서는 구분할 근거가 없습니다. 여기서 흔한 실수는 시간 역행을 발견하고 곧바로 제재하는 것입니다. 해외여행, 서머타임, 시계 자동 보정 같은 정상적인 이유로도 시간은 흔들립니다.

네트워크를 아주 쓰지 않는 게임이 아니라면, 신뢰할 수 있는 시각을 한 번씩 받아 오는 것이 가장 확실합니다. 전면적인 백엔드가 아니라 시각만 확인하는 정도라면 부담이 훨씬 작습니다.

보상 설계를 시간 의존에서 진행 의존으로 옮긴다

가장 근본적인 해법은 기술이 아니라 설계 쪽에 있습니다. 시간이 흘러야 얻는 보상 대신, 플레이해야 얻는 보상으로 바꾸면 시계 조작으로 얻을 것이 사라집니다.

모든 게임에 적용할 수 있는 방법은 아닙니다. 다만 서버 없이 시간 기반 설계를 지키는 일은 원래 어렵다는 점은 알고 시작하는 편이 낫습니다.

세이브를 기기에 묶는다

세이브에 기기 식별자를 함께 서명해 두면, 다른 사람의 세이브 파일을 그대로 가져다 쓰는 방식은 걸러집니다. 커뮤니티에 "완성된 세이브 파일"이 도는 유형에 효과가 있습니다.

기기 변경과 백업 복원 시나리오를 반드시 함께 설계해야 합니다. 그러지 않으면 정상 사용자가 기기를 바꿨을 때 진행이 날아갑니다.

클라우드 세이브의 함정 — 충돌 해결은 당신 몫입니다

여기가 서버리스 게임에서 가장 자주 놓치는 지점입니다.

Google의 Play Games Services 저장된 게임 문서는 이 점을 분명히 합니다. 기기를 여러 대 쓰거나, 저장 중 연결이 끊기거나, 게스트 진행과 계정 진행이 만나면 충돌이 생깁니다. 그리고 서비스는 충돌을 자동으로 해결해 주지 않습니다. 어느 쪽을 살릴지는 게임이 판단해야 합니다.

문서가 제공하는 판단 재료는 ID, 이름, 설명, 마지막 수정 시각, 플레이 시간, 커버 이미지입니다. 여기서 보안 관점의 문제가 하나 나옵니다.

플레이 시간과 설명은 개발자, 즉 클라이언트가 넣는 값입니다. 충돌을 해결하려고 "플레이 시간이 더 긴 쪽을 살린다"는 규칙을 쓴다면, 그 값을 조작할 수 있는 공격자에게 원하는 쪽을 고를 권한을 주는 셈입니다.

실무적으로 정할 것은 이렇습니다.

  • 충돌 해결 규칙을 명시적으로 정한다. 정하지 않으면 우연히 만들어진 규칙이 쓰이고, 공격자는 그 규칙을 이용합니다.
  • 클라이언트가 넣은 값 하나만으로 판단하지 않는다. 여러 근거를 함께 보거나, 사용자에게 선택을 맡기는 편이 안전합니다.
  • 사용자에게 선택을 맡길 때는 무엇을 잃는지 보여 준다. 문서도 게스트 진행 충돌에서 사용자에게 알리고 고르게 하라고 권합니다.
  • 정상 사용자의 데이터가 사라지지 않게 한다. 이게 최우선입니다. 조작을 막으려다 정상 진행을 날리면 손해가 더 큽니다.

구체적인 API와 최신 동작은 반드시 각 플랫폼 공식 문서에서 확인하세요. 플랫폼 정책은 이 글보다 빠르게 바뀝니다.

언제 서버를 도입해야 하는가

서버 없이 버티는 것이 언제까지나 합리적이지는 않습니다. 아래 중 하나라도 해당하면 시점이 온 것입니다.

  • 랭킹이나 경쟁 요소가 있어, 한 사람의 조작이 다른 사람의 경험을 해친다.
  • 인앱 결제로 얻은 재화가 게임 진행에 영향을 준다.
  • 유저 간 거래, 선물, 길드처럼 데이터가 오가는 기능이 있다.
  • 조작 사례를 확인했는데 어디까지 퍼졌는지 파악할 방법이 없다.
  • 정상 유저의 진행을 복구해 달라는 문의에 답할 근거가 없다.

마지막 두 항목이 실무에서 결정적입니다. 서버가 없으면 무슨 일이 일어났는지 알 수 없고, 되돌려 줄 수도 없습니다. 조작 자체보다 이 무력함이 운영을 더 힘들게 합니다.

전면적인 백엔드가 아니어도 됩니다. 신뢰할 수 있는 시각, 세이브 무결성 기록, 이상 징후 수집처럼 좁은 범위부터 시작할 수 있습니다.

점검 항목

세이브 구조

  • 세이브에 단조 증가하는 번호가 있고, 그 최댓값을 별도 위치에 기록한다.
  • 세이브 무결성 검증이 있고, 기기 식별자가 함께 묶여 있다.
  • 기기 변경과 백업 복원 시나리오가 설계에 포함되어 있다.

시간

  • 시간 역행을 감지할 수 있다.
  • 역행을 감지했을 때 곧바로 제재하지 않고, 정상적인 사유를 고려한다.
  • 시간 의존 보상이 있다면, 신뢰할 수 있는 시각을 확인할 경로가 있다.

클라우드 세이브

  • 충돌 해결 규칙이 명시적으로 정해져 있다.
  • 그 규칙이 클라이언트가 넣은 값 하나에만 의존하지 않는다.
  • 충돌 시 정상 사용자의 데이터가 보존된다.

운영

  • 조작 정황을 나중에라도 확인할 기록이 남는다.
  • 위 「언제 서버를 도입해야 하는가」 항목을 주기적으로 다시 본다.

OZero Security가 맡는 영역과 맡지 않는 영역

맡지 않는 영역

  • 서버를 대신하지 않습니다. 클라이언트가 최종 판정하는 구조를 저희가 바꿔 드릴 수는 없습니다. 서버 판정이 필요한 값은 결국 서버가 필요합니다.
  • 클라우드 세이브의 충돌 해결 규칙, 보상 설계, 데이터 복구 정책은 게임 설계와 운영의 몫입니다.

맡는 영역

  • 세이브를 읽고 쓰는 클라이언트 자체가 변조되지 않았는지를 판단합니다. 실행 파일 바이너리 변조, 디버거 부착과 후킹, 에뮬레이터·루팅 같은 비신뢰 실행 환경을 식별해 우회 난이도를 높입니다.
  • 메모리 변조와 게임 속도 조작 시도를 네이티브 레벨에서 교차 검증해 감지합니다. 속도 조작은 시간 의존 설계를 노리는 대표적인 수단입니다.
  • 세이브 파일·PlayerPrefs 변조 보호와, 다른 기기의 세이브를 복사해 오는 것을 막는 기기 바인딩을 제공합니다. 정상적인 기기 변경을 위해 직접 제어하는 리셋 콜백이 있습니다. 선택 기능인 웹 시간 확인은 기기 시계를 공개 시간 소스와 비교해 시계 조작 탐지를 돕습니다.
  • Pro Add-on 연동 시 의심 클라이언트의 텔레메트리를 수집해 백오피스에서 운영 정책에 맞게 대응할 수 있도록 지원합니다. 서버가 없는 팀에게는 이것이 「무슨 일이 일어났는지 아는」 첫 경로가 되기도 합니다.

검증 로직을 C# 바깥의 Native C++ 계층에 두면, 검증 코드 자체를 끄는 일이 별개의 작업이 됩니다. 서버가 없는 환경에서 「비용을 올린다」는 목표에 해당하는 부분입니다.

결론: 못 막는다는 것을 알고 설계하기

서버 없는 게임의 데이터 보호는 완전한 방어를 만드는 일이 아닙니다. 그건 구조적으로 불가능합니다.

대신 이렇게 접근하세요. 캐주얼한 조작은 걸러내고, 진지한 공격은 비용을 올리고, 무슨 일이 일어났는지는 알 수 있게 하고, 정상 유저는 복구해 줄 수 있게. 여기까지가 서버 없이 가능한 범위입니다.

그리고 그 범위를 넘는 순간 — 랭킹이 생기거나, 결제 재화가 진행에 영향을 주거나, 유저 간에 데이터가 오가기 시작하면 — 서버는 선택이 아니라 필요가 됩니다. 그 선을 미리 알고 있는 것만으로도 판단이 빨라집니다.

다음 단계

  1. 보호 범위 확인하기 — 플랫폼별 지원 사양과 플랜(Standard / Pro)별 방어 범위를 확인하세요.
  2. 빠른 통합 시작하기 — 패키지 임포트와 설정 절차는 통합 가이드에 있습니다.
  3. 기술 문의 및 상담 — 서버 없는 구성에서 어디까지 가능한지 문의해 주세요.

서버가 없어도 클라이언트 무결성은 검증할 수 있습니다 — OZero Security.

함께 읽으면 좋은 글