본문 바로가기

Game/Unity

[unity3d]안드로이드에서 암호화 팁 - PlayerPref 암호화

안드로이드에서 사용할 수 있는 암호화 팁

첫번째 - PlayerPref 암호화

iOS에서는 그런 이슈가 거의 없는 걸로 알고 있지만, 안드로이드에서는 많은 분들이 보안 관련 팁 또는 방법이 없는지 문의를 많이 해오셔서 준비를 해보았습니다.

여기 나온 팁들은 UNITE Seoul 2013에서 에릭 헤밍 (유니티 본사 개발자)이 발표했던 내용들 중 일부 입니다.

에릭 헤밍이 발표한 원문 ppt 자료는 아래 링크에 가시면 확인 가능합니다. http://www.slideshare.net/williamyang3910/unitekorea2013-protecting-your-android-content-21713675?from_search=1

그 중에서 암호화 관련 내용을 중점적으로 진행을 하려고 합니다.

암호화 진행 대상은 아래와 같습니다.


1. PlayerPrefs

2. 스크립트

3. 에셋

1번은 과정이 간단한 편이지만, 2번 3번은 유니티 외부에서 해야하는 작업들이 있기 때문에, 조금...복잡할 수 있습니다.


그럼 그 중에 제일 간단한 PlayerPrefs 부터 진행을 해보도록 하겠습니다.

간단히 설명부터 드리자면,


PlayerPrefs 에 값을 넣기전에 Key / Value 값의 암호화를 진행하고 값을 저장하고, 빼올 때는 거꾸로 복호화를 해서 값을 꺼내오는 방식입니다.

예제에서 제가 사용할 방법은 MD5 와 3DES(Triple DES) 라는 두 가지 암호화 방식입니다.

암호화 방식은 여러가지가 있기 때문에 입맛에 맛는 걸로 골라서 사용하시면 됩니다.


또한 C# 에서는 위의 두 가지 암호화 방식에 대해서 API를 제공합니다.
따라서 C#에서 API를 제공하는 암호화 방식을 사용하시는 걸 권장합니다.

(직접 코드로 작성을 하셔서 암호화를 하셔도 좋지만...정신 건강에 그리 좋지 않을 수도 있습니다...)


값을 저장하고 / 빼내기위해 두 가지 함수를 만들어서 사용을 할 텐데요.
SetString과 GetString 함수를 만들어서 사용을 했습니다.

코드를 보면서 설명을 드리겠습니다.

public static void SetString(string _key, string _value, byte[] _secret)  
{  
    // Hide '_key' string.  
    MD5 md5Hash = MD5.Create();  
    byte[] hashData = md5Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(_key));  
    string hashKey = System.Text.Encoding.UTF8.GetString(hashData);  

    // Encrypt '_value' into a byte array  
    byte[] bytes = System.Text.Encoding.UTF8.GetBytes(_value);  

    // Eecrypt '_value' with 3DES.  
    TripleDES des = new TripleDESCryptoServiceProvider();  
    des.Key = _secret;  
    des.Mode = CipherMode.ECB;  
    ICryptoTransform xform = des.CreateEncryptor();  
    byte[] encrypted = xform.TransformFinalBlock(bytes, 0, bytes.Length);  

    // Convert encrypted array into a readable string.  
    string encryptedString = Convert.ToBase64String(encrypted);  

    // Set the ( key, encrypted value ) pair in regular PlayerPrefs.  
    PlayerPrefs.SetString(hashKey, encryptedString);  
          
    Debug.Log("SetString hashKey: " + hashKey + " Encrypted Data: " + encryptedString);  
}

위의 코드 내용은 SetString 함수 내부 입니다.
주석 내용을 보면 대충 감이 오시겠지만, 내용을 설명해 드리겠습니다.


먼저 인자로 받는 _key 값을 MD5 방식으로 함호화를 해서 실제로 PlayerPref에 저장할 때 이 값을 키 값으로 사용합니다.

암호화가 되었기 때문에 읽을 수 없는 값이 되겠지요.
그리고 실제 저장할 _value 값은 3DES 방식으로 암호화를 합니다.


암호화가 진행이 되면 byte[] 바이트 배열값이 결과로 나오기 때문에 저장을 위해서 문자열로 변환을 해줍니다.

그리고 PlayerPref.SetString 함수에 위에서 암호화한 _key 값과 방금 암호화된 _value 값을 저장합니다.

참고로 함수의 인자 중 마지막에 있는 _secret 값은 3DES 방식으로 암호화가 진행될 때 사용되는 값 입니다.

이 부분이 많이 궁금하신 분들은 3DES 암호화 방식을 좀 더 찾아보시면 좋을 것 같습니다.

======================================================================
이해가 안되는 코드 내용이 있는 부분은 MS의 MSDN 등에서 MD5 / TripleDES 클래스 사용방법을 찾아보시면 이해가 되실겁니다. 그대로 가져다가 사용한 것이니까요.

다른 암호화 방식을 사용하시려는 분들도 사용방법을 먼저 참고하세요.

======================================================================

이제 값을 저장했으니 반대로 빼오는 함수도 필요하겠지요?

public static string GetString(string _key, byte[] _secret)  
{  
    // Hide '_key' string.  
    MD5 md5Hash = MD5.Create();  
    byte[] hashData = md5Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(_key));  
    string hashKey = System.Text.Encoding.UTF8.GetString(hashData);  

    // Retrieve encrypted '_value' and Base64 decode it.  
    string _value = PlayerPrefs.GetString(hashKey);  
    byte[] bytes = Convert.FromBase64String(_value);  

    // Decrypt '_value' with 3DES.  
    TripleDES des = new TripleDESCryptoServiceProvider();  
    des.Key = _secret;  
    des.Mode = CipherMode.ECB;  
    ICryptoTransform xform = des.CreateDecryptor();  
    byte[] decrypted = xform.TransformFinalBlock(bytes, 0, bytes.Length);  
          
    // decrypte_value as a proper string.  
    string decryptedString = System.Text.Encoding.UTF8.GetString(decrypted);  
          
    Debug.Log("GetString hashKey: " + hashKey + " GetData: " + _value + " Decrypted Data: " + decryptedString); 
    return decryptedString;  
}

PlayerPref.GetString 함수로 값을 빼오기 위해서는 키 값이 필요한데, 앞서 저장한 키 값은 암호화가 된 키 값이기 때문에 똑같이 암호화를 해줍니다.

_key 값은 암호화 전 키 값으로, 함수 내에서 똑같이 MD5로 암호화를 해줘야 우리가 저장할 때 사용했던 값이 나오겠죠?


그리고 암호화된 키를 이용해서 PlayerPref.GetString 함수를 이용해서 값을 빼냅니다.
문자열이 튀어 나올텐데, 저장할 때 암호화를 했기 때문에 알아볼 수 없는 값이 나올겁니다.


이 값을 앞에서 저장할 때 사용한 3DES 암호화 방식으로 복호화를 해줍니다.


이렇게 해주면 비로소 우리가 저장했던 온전한 _value 값이 나옵니다.
사용 예를 한번 보겠습니다.


void Start()  
{  
    string userName = "Ronnie Jang";  

    MD5 md5Hash = new MD5CryptoServiceProvider();  
    byte[] secret = md5Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(userName));  

    // Game progress ( key, value ) pair.  
    string key = "test_key";  
    string _value = "Encrypt_Example";  

    // Insert ( key, value ) pair.  
    CustomFunction.SetString(key, _value, secret);  

    // Retrieve ( key, value ) pair.  
    string ret = CustomFunction.GetString(key, secret);  

    // Output.  
    Debug.Log("userName: " + userName);  
    Debug.Log(key + " : " + ret);  
}

맨 위를 보시면, “Ronnie Jang”으로 “secret” 값을 생성 합니다. 이 부분은 간단히 MD5 방식으로 암호화를 했구요. 이 값은 SetString으로 값을 저장할 때 _value 값을 암호화하는 부분에서 사용됩니다.

(위에서 작성한 SetString 함수 인자 마지막에 들어가는 값입니다.)


저장하고 값을 얻어오는 데 사용되는 키 값과 _value값은 각각 “test_key”와 “Encrypt_Example” 로 해봤습니다.


그리고 이 값들을 이용해서 CustomFunction.SetString / GetString 이 두 함수를 사용한 것이 보일 겁니다. 이 함수는 앞에서 작성한 함수들입니다.

실행 결과를 한번 보겠습니다.



먼저 빨간색 박스 두개부터 보겠습니다.

첫 번째 빨간 박스는 값을 저장할 때 암호화 된 키 값의 형태를 보여줍니다.

그리고 두 번째는 GetString 을해서 값을 얻어올 때 사용된 키 값입니다.

이 값도 역시 암호화 되어 있고, 위의 값과 똑같죠?

그리고 두번째 주황색 박스 두개인데, 이 값은 _value 값으로 암호화 되어 있습니다.

두 번째 줄에 있는 값은 PlayerPref.GetString을 하고나서 바로 찍어본 값인데, 아직은 알아볼 수가 없습니다. 보시면 알겠지만 이 값은 저장할 때 암호화 했던 값과 동일합니다.

그리고 오른쪽에 보이는 보라색 값이 바로 복호화를 끝낸 우리가 원래 저장하려고 했던 값입니다.

처음에 “Encrypt_Example” 저장한 값이 잘 나왔죠?

마지막에 보라색 박스는 점검 차원에서 한번 더 확인을 해보았습니다.

뭔가 복잡하게 지나간 듯 한데, 사실 차근차근 살펴보시면 별것 없습니다.

중요한 요점은 “PlayerPref.SetString / GetString 함수를 사용하기 전에 인자로 사용되는 key / value 값을 모두 암호화해서 사용하자” 입니다.

이 간단한 내용이 실제 코드로 옮겨진 게 위에 지금까지 설명했던 내용들 입니다.


PlayerPref가 보안에는 취약할 수 있다는 점에서 진행을 해본 예제 입니다.

지금까지 설명드린 내용은 어디까지나 여러분에게 아이디어를 드리고자 설명드린 부분이기 때문에 “응용하셔서” 사용하시는 것이 좋을 것 같습니다.

암호화 방식은 어떤걸 사용하셔도 상관이 없지만, 저장할 때 방식과 값을 얻어올 때 방식이 동일해야 한다는 점 주의하시구요.

실제로 해보시면 그리 어렵지 않습니다. 단지, 조금 더 귀찮을 뿐입니다.

그렇기 때문에 여러분께서 만들고 계신 게임에서 PlayerPref를 사용하는데 개발시간이 조금 넉넉하다 싶으시면 응용해서 사용해 보시기 바랍니다.

이런 부분 때문에 여러분의 개발기간이 길어진다면 안되니까요...

시간이 남으셨을 때 진행하시는 게 좋을 것 같습니다.

다음에 이어서 스크립트와 에셋번들 암호화에 대해서 알아보겠습니다.


출처 : http://unitystudy.net/bbs/board.php?bo_table=newwriting&wr_id=355