프로그래밍/unity

AssetBundle의 암호화/복호화

Cheese Allergy Hamster 2021. 8. 4. 12:13

번들파일을 암호화하는 이유는 저작권이 있는 리소스를 보호하고 싶다거나 테이블 데이터를 보호하고 싶다거나.. 여러가지 이유가 있습니다.  AssetStudio같은걸 사용하면 너무나 쉽게 에셋번들의 데이터를 추출해볼 수 있습니다. 이를 방지하려면 어떻게 해야할까요?

 

 AssetBundle의 파일 사이즈는 100mb가 될 수도 있고, 1GB가 될 수도 있기 때문에 암호화를 할때 주의해야 할점은 파일 전체를 암호화 한다기 보다, 파일의 특정 부분만을 암호화하여 정상적으로 파일이 열리지 않게 하는게 포인트입니다.

 

위 사진은 큐브 오브젝트를 번들로 만들어서 뽑은 뒤 파일을 헥스 에디터로 열어본 사진입니다. 큐브 오브젝트를 번들로 만들어서 뽑았을때 맨 앞에서 4개의 바이트가 55 6E 69 74 로 시작합니다. 어떻게 번들을 뽑든 번들 파일의 첫 시작 바이트부분은 55 6E 69 74 고정입니다. 이것을 다른 바이트로 변경해서 유니티가 파일을 읽어오지 못하게 하면 암호화 성공입니다. 사실 고정 된 부분이 아닌 파일의 +30 번째 바이트를 바꾼다거나.. 방법은 자기 마음입니다. 저는 55 6e 69 74 라는 바이트배열을 변경해보겠습니다.

 

 먼저, 에셋을 보호하기위해 번들을 다운로드 했을때 파일을 저장시키기 전 암호화 를 해서 한번 저장합니다. 아래 코드는 번들 파일의 맨 앞 시작 바이트 부분을 임의의 바이트 (0x99, 0x94, 0x88, 0x24)로 변경하는 코드입니다.

public IEnumerator DownloadAndEncryptSaveBundle(string BundleURL)
    {
        // Download the file from the URL. It will not be saved in the Cache
        using (WWW www = new WWW(BundleURL))
        {
            yield return www;
            if (www.error != null)
                throw new Exception("WWW download had an error:" + www.error);

            //다운로드한 바이트를 스트림으로 읽어옴
            MemoryStream ms = new MemoryStream(www.bytes);

            ///www dispose로 기존 www 객체의 메모리는 해제
            www.Dispose();

            //writer 객체생성
            BinaryWriter writer = new BinaryWriter(ms);

            //맨 앞의 값을 수정
            writer.Seek(0, SeekOrigin.Begin);
            writer.Write(new byte[] { 0x99, 0x94, 0x88, 0x24}, 0, 4);

            //수정한 바이트를 쓴다.
            File.WriteAllBytes("testprefab.encrypt", ms.ToArray());
            writer.Close();
            ms.Close(); 
        } // memory is freed from the web stream (www.Dispose() gets called implicitly)
        yield return null;
    }

위 코드로 다운로드하고 파일을 저장하면 기존 번들이 아래와같이 바이트가 바뀝니다.

 

이제는 번들의 파일 구조가 변경되었으므로 일반적인 방법으로는 이제 열 수가 없지만, 유니티에서도 정상적으로 읽을 수 없기때문에 복호화 로직을 짜야합니다.

 

public void DecryptAndLoadBundle(string fileName)
    {
        //파일 스트림 생성
        FileStream stream = File.Open(fileName, FileMode.Open); 

        var length = stream.Length;
        var readedBytes = new byte[length]; 
        int numBytesRead = 0;

        //파일 스트림으로부터 byte 읽어오기
        while (length > 0)
        {
            // Read may return anything from 0 to numBytesToRead.
            int n = stream.Read(readedBytes, numBytesRead, (int)length);

            // Break when the end of the file is reached.
            if (n == 0)
                break;

            numBytesRead += n;
            length -= n;
        } 
        stream.Close();
        MemoryStream ms = new MemoryStream(readedBytes); 
        BinaryWriter writer = new BinaryWriter(ms);

        // 기존 앞 바이트 4자리는 5E 6E 69 74 였으므로 이것으로 다시 돌려준다. (복호화)
        writer.Seek(0, SeekOrigin.Begin);
        writer.Write(new byte[] { 0x55, 0x6E, 0x69, 0x74 }, 0, 4);

        var decryptedBytes = ms.ToArray();
        ms.Close();
        writer.Close();

        var bundle = AssetBundle.LoadFromMemory(decryptedBytes);
        var prefab = bundle.LoadAsset<GameObject>("번들 파일 이름");
        Instantiate(prefab);


    }

정상적으로 파일이 불러와지면 성공입니다. 저는 앞 자리 4자리만 암호화 하는 방식을 사용했는데 예시를 위한것이고, 소스코드를 조금 수정해서 암복호화 하시는게 좋습니다 :)

1 2 3 4