EveryDay.DevUp

[Unity] 게임 암호화 본문

R&D

[Unity] 게임 암호화

EveryDay.DevUp 2020. 5. 2. 22:45

게임의 암호화는 유저의 데이터를 지키고, 게임을 서비스 하면서 안정적인 운영을 할 수 있도록 도와주는 것 

▶ 메모리 핵, 앱 변조, 스피드 핵, 데미지 핵과 같은 많은 핵들이 존재하고 방어하는 입장 보다는 공격하는 입장이 좀 더 수월한 편

 클라이언트만으로는 완벽한 암호화를 할 수 없으며, 클라이언트의 암호화는 뚫기 번거롭고 귀찮게 만드는 역할을 하는 정도라고 보는 것이 좋음

 가장 좋은 것은 모든 데이터를 서버에서 관리하는 것이지만 현실적으로 그렇게 하기엔 부하가 심하기 때문에 중요한 데이터는 서버에서 관리

● 암호화 방법

▶ Base64 

: Base64는 암호화라기보다는 바이너리 데이터를 아스키 문자열로 표한하는 인코딩 방식 중의 하나. 하지만 Base64를 통해 데이터의 직접적인 확인을 막고, 데이터 자체의 안정성을 확보하는데 기본이 됨

: Base64는 영문 대문자, 소문자, 그리고 숫자 등 62개의 값들을 기본적으로 가지며, 마지막에 +와 / 2개를 합쳐 64개의 인코딩 문자를 가짐.

: 64개의 데이터는 6비트로 표현이 가능하고, 암호화 시 데이터를 6bit단위로 쪼개어 변환 한다.

ex) UTF-8 '가'는 => 111010101011000010000000 => 6bit씩 잘라서 Base64에 맞추면 6rCA 값이 나옴.

: 만약 바이트의 길이가 3의 배수가 아니라면 빈 비트들을 '='로 패딩하게 됨

▣ 사용 예시 : 게임에서 특정 문자나 문자열을 서버에 저장하거나 전송할 때, 특정 문자에 의한 오류 또는 파싱에 오류가 발생하지 않도록 하기 위해 사용

ex) 가령 유저가 닉네임을 { abc:111" } 과 같은 형태로 만들어서 서버에서 전송하면 서버에서 Json으로 오인하여 파싱할 수 있는 경우가 생길 수 있음

▶ 단방향 해시 암호화

: 단방향 암호화는 수학적인 연산을 통해 원본 메시지를 변환하여 암호화된 메시지인 다이제스트를 생성, 암호화된 메시지로는 원본 데이터를 알 수 없음

: 원본 데이터를 알 수 없기 때문에, 주로 값 비교를 할때 사용 EX) 로그인 시의 사용자 암호화 서버의 패스워드 해시 값 비교

▣ 이슈

: 서버의 저장된 다이제스트를 알수 있다면, 임의의 문자열과 다이제스트를 비교하면서 원본 데이터를 알 수 있는 문제가 생길 수 있음

▣ 추가 보안 방법

: 솔팅 ( Salting ) - 원본 메시지에 임의의 문자열을 추가하여 다이제스트를 생성, 솔팅의 사용되는 문자는 32 바이트 이상이어야 안전

: 키 스트레칭 ( Key Stretcing ) - 빠른 해시 속도를 일부러 늦추기 위해 여러번의 단방향 해시를 반복하는 것

▣ 사용 예시 

: 클라이언트에서는 단방향 암호화를 사용할 일이 상대적으로 적은데, 빠른 해시 함수를 이용해서 메모리 핵을 방지하는데 사용할 수 있음

ex) 메모리 핵의 일반적인 방법은 현재 상태의 메모리 정보를 캡쳐하고 원하는 값 ( 가령 게임 내 재화인 골드 ) 을 증가 시킨 후 메모리 정보를 이전과 비교해서 증가한 메로리의 데이터를 변조하는 방식을 사용

이 경우를 방지 하기 위해서 단방향 암호화를 한 변수를 하나 더 추가하여 ( GoldCrypto ), Gold 를 단방향 암호화한 값과 GoldCrypto에 있는 값이 불일치 하면 서버에서 다시 데이터를 받는 방식이 될 수 있음.

참고 자료 : https://d2.naver.com/helloworld/318732

▶ 양방향 암호화

▣ 대칭형 암호화

: 동일한 키를 사용하여 데이터를 암호화하거나 복호화 하는 방식

▣ 사용 예시 

: 암복호화시에 속도가 빠르기 때문에, 게임 서버와의 패킷 통신과 클라이언트에 저장된 데이터를 암복화할 때 등 다양하게 사용됨

▣ 이슈

: 키를 알고 있으면 쉽게 보안이 취약해지는데, 클라이언트에서의 암복호화를 위해 키가 대부분 클라이언트 코드에 있게 됨

▣ 비대칭형 암호화

: 암복호화 시에 서로 다른 키를 사용

: 공개키를 사용하여, 데이터를 암호화

: 개인키를 사용하여 데이터를 복호화 

▣ 사용 예시 

: 양방향 암호화의 비해 속도가 느리고, 클라이언트에서 사용할 때 키값을 코드에 넣는 다면 대칭형 암호화와 큰 차이가 없기 때문에 게임에서는 잘 사용하지 않음

▣ 추가 보안 방법

: 대칭형/비대칭형 암호화 키 모두 키를 클라이언트에서 코드에 가지고 있는 문제가 있음으로 항상 보안이 뚫릴 수 있다는 점을 감안하여 게임 플레이 시에 임계 값 설정 및 비정상적인 플레이에 대한 로그를 쌓을 수 있어야 함.

: 서버는 클라이언트 데이터가 비정상적인 데이터가 올 수 있음을 감안하여 보안 코드를 짜야함

▶ 코드 난독화

: 코드를 보기 어렵게 만들어서 디컴파일 시에 해당 코드가 어떤 동작을 하는지 알기 어렵게 하는 것

: 난독화 하는 외부 툴을 사용하거나, Android Proguard 를 사용하여 처리

int GetDamage() {
}

=> int a() {
}

▶ 리패키징 체크

: 앱의 코드를 일부 변환 시킨 후 다시 앱을 재배포 하는 것

: 앱 배포 시 해시 값을 생성 서버와 앱에서 전달하는 해시 값을 비교해서 다른 경우 리패키징으로 체크

: 앱의 크기 + 버전 정보를 합쳐서 해시 값을 만들거나, APK 서명의 해시값을 이용할 수 있음

● 전체 암호화 기법을 이용한 앱의 보안 

▶ 빌드 시 코드 난독화는 기본적으로 설정

▶ 해시 값을 매 패킷마다 비교함으로써 앱의 리패키징 여부를 체크

▶ 중요하지 않은 데이터는 코드에 노출된 대칭형 암호화 키를 사용하고, 이를 통해 최소한의 패킷 암호화.

▶ 비대칭형 암복호화를 로그인 패킷에 둠으로써 버전 별로 키 값을 관리할 수 있도록 함.

유저별로 생성한 대치형 암호화 키를 변경하지 않고, 전체 비대칭형 암호화 키를 변경할 수 있게 함으로써 앱 업데이트 마다 보안성 강화를 위함

▶ 로그인 이후 중요한 데이터 및 패킷은 유저 별로 생성 된 대칭형 암호화 키를 사용. 클라이언트에 키가 노출되어 있지 않아 보안성 강화

유저별로 대칭형 암호화 키를 설정함으로써, 모든 유저가 같은 대칭형 암호화 키를 사용하지 않는 장점 및 특정 유저의 대해서만 암호화 키를 변경 가능한 독립성이 생김

게임 보안에 있어서의 목적은 공격자로 하여금 귀찮을 수 있도록 하고, 앱의 보안이 풀렸을 때에도 대응할 수 있는 방법이 중요