Gamemaker Studio 2 存档系统/安全设计指北
数据结构嵌套存储、加密、编码、混淆。不得不说 GMS2 是个十分方便却傻逼的工具。
基本存档引入
使用映射表数据结构,即 ds_map 。对 ds_map 添加键值之后,可以将 ds_map 存储在文件中。
GMS2 提供两个基础函数ds_map_write()
和ds_map_secure_save()
将映射表存储在字符串或文件中。
ds_map_write
ini_open("map.ini");
var t_string;
t_string = ds_map_write(inventory);
ini_write_string("Saved", "0", t_string);
ini_close();
上面的例子中,函数返回一个人类不可读的字符串(数据结构内存中内容),然后将其存储在文件 map.ini 中。
ds_map_secure_save
ds_map_secure_save(purchase_map, "p_data.dat")
上面的例子中,函数将 purchase_map 用“安全”的方式“加密”存储在 p_data.dat 中,使得文件不可被修改。
ds_map_secure_save
并没有想象中的安全。参见一个 旧主题 ,函数的实现方式为将一个二进制 hash 码与该 map 的 JSON 纯文本形式接在一起用 base64 编码储存。二进制 hash 码或为阻止文件被修改或文件被跨设备移动。
与上方两个函数相对的也有ds_map_secure_load
和ds_map_read
。这部分请自行查阅 GMS2 文档。
HMAC-SHA1
HMAC 是哈希运算消息认证码 (Hash-based Message Authentication Code),HMAC 运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。HMAC-SHA1 签名算法是一种常用的签名算法,用于对一段信息进行生成签名摘要。
HMAC-SHA1 或许能够成为理想的防止文件被修改的哈希算法。你可以在 这里 下载到可用于 GMS2 的 hmac_sha1 版本的 ds_map_secure_save。(你可以在 这里 访问发布者的原帖)
注意到其中的函数string_build()
,给定数个参数后返回将其拼接在一起的字符串。主要是为了避免字符串参数在生成的程序内以明文方式储存。
实现方式大体与ds_map_secure_save
类似,获取ds_map
的字符串形式,将其用HMAC-SHA1
方式哈希后,输出哈希码和未经混淆的映射表字符串。
官方也有一篇使用 HMAC-SHA1 来保护存档的教程,你可以在 这里 查阅到。
文件加密 / 混淆
RC4
在密码学中,RC4(来自 Rivest Cipher 4 的缩写)是一种流加密算法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法。RC4 是有线等效加密(WEP)中采用的加密算法,也曾经是 TLS 可采用的算法之一。
由美国密码学家罗纳德·李维斯特(Ronald Rivest)在 1987 年设计的。由于 RC4 算法存在弱点,2015 年 2 月所发布的 RFC 7465 规定禁止在 TLS 中使用 RC4 加密算法。
RC4 由伪随机数生成器和异或运算组成。RC4 的密钥长度可变,范围是 $[1,255]$。RC4 一个字节一个字节地加解密。给定一个密钥,伪随机数生成器接受密钥并产生一个 S 盒。S 盒用来加密数据,而且在加密过程中 S 盒会变化。
由于异或运算的对合性,RC4 加密解密使用同一套算法。
RC4 用于游戏存档中或许是一个方便的选择(虽然该算法在多次传输重复内容的情况下已经 不再安全 )。
你可以访问 这个 github 页面来获取 RC4 在 GMS2 下的可用版本。将你想要存储的字符串先用 RC4 方式加密,再用 HMAC-SHA1 以防止修改文件即可。但这里提供的 RC4 实现事实上效率较为低下。你可以手动修改其源代码,将取模运算更改为位运算,并使用外置 DLL 来避免 GMS 的傻逼效率问题。
我修改了上方提供的 github 页面中的 RC4 在 GML 下的实现,供参考:
/**
Encrypt the buffer in-place using RC4.
*/
function rc4(buffer, key, offset, length){
var i, j, k, s, temp, keyLength, pos;
keyLength = string_byte_length(key);
for (i = 255; i >= 0; --i) {
s[i] = i;
}
j = 0;
k = 0;
for (i = 0; i <= 255; ++i) {
j = (j + s[i] + string_byte_at(key, k)) & ((1<<8)-1);
temp = s[i];
s[i] = s[j];
s[j] = temp;
k = (k+1==keyLength) ? 0 : k+1;
}
i = 0;
j = 0;
pos = 0;
buffer_seek(buffer, buffer_seek_start, offset);
var currentByte;
repeat (length) {
i = (i+1) & ((1<<8)-1);
j = (j+s[i]) & ((1<<8)-1);
temp = s[i];
s[i] = s[j];
s[j] = temp;
currentByte = buffer_peek(buffer, buffer_tell(buffer), buffer_u8);
buffer_write(buffer, buffer_u8, s[(s[i]+s[j]) & ((1<<8)-1)] ^ currentByte);
}
buffer_seek(buffer, buffer_seek_start, offset);
}
AES
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),又称 Rijndael 加密法(荷兰语发音:[ˈrɛindaːl],音似英文的“Rhine doll”),是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的 DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于 2001 年 11 月 26 日发布于 FIPS PUB 197,并在 2002 年 5 月 26 日成为有效的标准。现在,高级加密标准已然成为对称密钥加密中最流行的算法之一。
与 RC4 同理,但比 RC4 要更加安全。GMS2 上 AES 目前没有很好的免费支持。你可以在 YYG 的 Marketplace 中搜索 AES,目前提供了三个相关加密插件。由于我完全是密码学未入门... 基于免费的 AES For Gamemaker 的低效率和不明晰的 bug,这可能并不适合用于含大量内容的加密(如存档文件)。
混淆
如果无需高强度的加密方式,你也可以自己创造一些加密与混淆方式。
常用的简单加密 / 混淆可以是异或加密、凯撒密码或维吉尼亚密码等,简单且较易于实现。它虽然不能有效保护存档的不可读性,但足以阻止大部分玩家轻易读取到存档内容。配合 HMAC-SHA1 等方式则可以达成有效保护存档的目的。
数据结构嵌套存储
比如,ds_map 里套 ds_list,ds_map 里套 ds_map。
一个显然的事实是,ds_map 中储存的 ds_map 是一个数字,即 ds_map 被临时分配的一个独有 ID。所以如果直接将 ds_map 不加修饰地写入文件(如ds_map_write()
),那么存储下来的 ID 并不包含 map 中的内容,在下一次读取的时候只会读取到一个已经没有什么用处的 ID,或者出现更加不可预料的后果。
GMS2 提供了几种可以配合 JSON 格式编码的函数,如ds_map_add_map()
和ds_map_add_list()
。如果你使用了这个函数,数据结构的 ID 将会被存储到 ds_map,并打上对应数据结构的标记,表明这个 ID 的数据结构类型。如果你想在 list 中套 map 或 list,那你也可以使用ds_list_mark_as_map()
或ds_list_mark_as_list()
来手动为 list 的某一部分打上标记。
对你想要储存的 map 使用函数json_encode(map)
进行 JSON 格式编码,它会将其中被打上标记的数据结构递归编码并返回一个字符串。
对你想要读取的 JSON 使用函数json_decode()
,它会自动将 JSON 解码成包含一系列子 map 和 list 的 map。
注意:如果你在读取存档之后想要对临时用 map 释放内存,注意 map 的释放内存会释放 map 中包含的子 map 和 list 的内存,因此如果想要避免这种情况,将包含子 map 的 id 改为 undefined 等无关内容。
值得一提的是,ds_map_secure_save()
使用的已经是 JSON 格式的 map,因此支持递归存储。如果想使用自己的 hash 和加密方式,那就可以使用json_encode()
和json_decode()
。
JSON 官方文档解释
JSON (JavaScript Object Notation) 是一个易于人或机器读写的轻量数据交换结构。它基于两种基本结构:
- 一对键与值的配对,在 GMS 中也被称作 ds_map,或“映射”、“词典”。
- 一个值的有序序列,在 GMS 中也被称作 ds_list,或“列表”、“数组”。
最后
建议使用 YYC(Yoyo Compiler)以得到更高的加密效率。
以及 GMS2 真是个方便又傻逼的工具。
本文链接:https://pst.iorinn.moe/archives/gms2-save-games.html
许可: https://pst.iorinn.moe/license.html若无特别说明,博客内的文章默认将采用 CC BY 4.0 许可协议 进行许可☆