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,什么也无法读取。

Unity引擎支持对资源文件进行加密,主要有两种常见的方法: 1. **Unity内置加密**: 使用Unity编辑器内置的AssetBundleEncryption工具可以对资源进行加密。这个工具允许你在项目设置中选择需要加密的AssetBundle,并输入密钥。加密后的资源在运行时需要相同的密钥才能解密加载。代码层面主要是通过`UnityEngine.Experimental.AssetManagement.AssetBundleManager` API来处理加密资产。 ```csharp using UnityEngine.Experimental.AssetManagement; // ... string encryptedAssetName = "EncryptedAsset"; string encryptionKey = "your_encryption_key"; // 这里替换为实际的加密密钥 // 加载加密AssetBundle AssetBundle bundle = AssetBundle.LoadFromMemory(new byte[] { /* encrypted data from file or generated by tool */ }, encryptionKey); bundle.LoadAssetAsync<GameObject>(encryptedAssetName).Then((asset) => { if (asset != null) Debug.Log("Loaded encrypted asset successfully."); else Debug.LogError("Failed to load encrypted asset."); }); ``` 2. **自定义加密**: 如果你需要更高级别的安全性,可以编写自定义的加密算法,如AES(Advanced Encryption Standard)。但这通常涉及到更多的编码和数据处理工作。例如,你可以将资源内容转换成字节数组,然后使用加密算法进行处理,再存储为二进制流。 ```csharp using System.Security.Cryptography.AES; // ... byte[] encryptionKey = Encoding.UTF8.GetBytes("your_custom_key"); byte[] resourceData = File.ReadAllBytes("unencrypted_resource.abc"); // 创建AES加密器 Aes aes = Aes.Create(); aes.Key = encryptionKey; ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); // 加密数据 byte[] encryptedData = encryptor.TransformFinalBlock(resourceData, 0, resourceData.Length); // 将加密数据保存到新文件 File.WriteAllBytes("encrypted_resource.encrypted", encryptedData); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值