AssetBundle资源加密

前言

AssetBundle中无论是美术素材,还是关键的热更代码,一旦被不法分子破解,都会给项目带来不可估计的损失。这里介绍一下AssetBundle加解密使用流程。

测试资源

在这里插入图片描述
手动设置打上标签
在这里插入图片描述

资源加密打包

加密脚本

using System;
using System.IO;
using System.Security.Cryptography;

public class SeekableAesStream : Stream
{
    private Stream baseStream;
    private AesManaged aes;
    private ICryptoTransform encryptor;
    public bool autoDisposeBaseStream { get; set; } = true;

    /// <param name="salt">//** WARNING **: MUST be unique for each stream otherwise there is NO security</param>
    public SeekableAesStream(Stream baseStream, string password, byte[] salt)
    {
        this.baseStream = baseStream;
        using (var key = new PasswordDeriveBytes(password, salt))
        {
            aes = new AesManaged();
            aes.KeySize = 128;
            aes.Mode = CipherMode.ECB;
            aes.Padding = PaddingMode.None;
            aes.Key = key.GetBytes(aes.KeySize / 8);
            aes.IV = new byte[16]; //zero buffer is adequate since we have to use new salt for each stream
            encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
        }
    }

    private void cipher(byte[] buffer, int offset, int count, long streamPos)
    {
        //find block number
        var blockSizeInByte = aes.BlockSize / 8;
        var blockNumber = (streamPos / blockSizeInByte) + 1;
        var keyPos = streamPos % blockSizeInByte;

        //buffer
        var outBuffer = new byte[blockSizeInByte];
        var nonce = new byte[blockSizeInByte];
        var init = false;

        for (int i = offset; i < count; i++)
        {
            //encrypt the nonce to form next xor buffer (unique key)
            if (!init || (keyPos % blockSizeInByte) == 0)
            {
                BitConverter.GetBytes(blockNumber).CopyTo(nonce, 0);
                encryptor.TransformBlock(nonce, 0, nonce.Length, outBuffer, 0);
                if (init) keyPos = 0;
                init = true;
                blockNumber++;
            }
            buffer[i] ^= outBuffer[keyPos]; //simple XOR with generated unique key
            keyPos++;
        }
    }

    public override bool CanRead { get { return baseStream.CanRead; } }
    public override bool CanSeek { get { return baseStream.CanSeek; } }
    public override bool CanWrite { get { return baseStream.CanWrite; } }
    public override long Length { get { return baseStream.Length; } }
    public override long Position { get { return baseStream.Position; } set { baseStream.Position = value; } }
    public override void Flush() { baseStream.Flush(); }
    public override void SetLength(long value) { baseStream.SetLength(value); }
    public override long Seek(long offset, SeekOrigin origin) { return baseStream.Seek(offset, origin); }

    public override int Read(byte[] buffer, int offset, int count)
    {
        var streamPos = Position;
        var ret = baseStream.Read(buffer, offset, count);
        cipher(buffer, offset, count, streamPos);
        return ret;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        cipher(buffer, offset, count, Position);
        baseStream.Write(buffer, offset, count);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            encryptor?.Dispose();
            aes?.Dispose();
            if (autoDisposeBaseStream)
                baseStream?.Dispose();
        }

        base.Dispose(disposing);
    }
}

打包脚本

[MenuItem("AssetBundle/Build Encrypt")]
static void BuildEncrypt()
{
    var exportPath = Application.streamingAssetsPath;
    if (!Directory.Exists(exportPath))
        Directory.CreateDirectory(exportPath);

    var manifest = BuildPipeline.BuildAssetBundles(exportPath, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows64);
    foreach (var name in manifest.GetAllAssetBundles())
    {
        var uniqueSalt = Encoding.UTF8.GetBytes(name);

        var data = File.ReadAllBytes($"{Application.streamingAssetsPath}/{name}");
        using (var baseStream = new FileStream($"{Application.streamingAssetsPath}/e{name}", FileMode.OpenOrCreate))
        {
            var cryptor = new SeekableAesStream(baseStream, password, uniqueSalt);
            cryptor.Write(data, 0, data.Length);
        }
    }
}

资源解析加载


public class LoadAssetbundle : MonoBehaviour
{
    const string password = "password";

    AssetBundle bundle;
    FileStream fileStream;

    void OnEnable()
    {
        // 暗号化AssetBundle取得
        fileStream = new FileStream($"{Application.streamingAssetsPath}/esprite", FileMode.Open);
        var uniqueSalt = Encoding.UTF8.GetBytes("sprite"); // 打包时设置的salt名

        // Stream暗号化解除
        var uncryptor = new SeekableAesStream(fileStream, password, uniqueSalt);
        bundle = AssetBundle.LoadFromStream(uncryptor);
    }

    IEnumerator Start()
    {
        var request = bundle.LoadAssetAsync<Sprite>("01");
        yield return request;
        GetComponent<SpriteRenderer>().sprite = request.asset as Sprite;
    }

    void OnDisable()
    {
        bundle.Unload(true);
        fileStream.Close();
    }
}

运行,加载成功。
在这里插入图片描述

反编译资源

普通资源

File -> Load file 读取单个资源,先加载未加密的sprite
在这里插入图片描述
成功解包,得到里面的素材。
在这里插入图片描述

加密资源

同样方法再尝试读取加密的资源 esprite,什么也无法读取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值