通用配置定义(草案)
跨客户端共享服务配置的 JSON + AES 加密格式定义
本章为草案
本格式当前仍在迭代,字段定义、加密参数与密钥分发约定都可能在后续版本中调整。在没有正式签发的"对接合作"前提下,请不要在第三方应用里硬编码本格式作为对外协议;接入前请通过 issue 或邮件与维护者确认当前版本细节。
格式本身被设计为可在多客户端之间互通,但密钥分发是受控的——是否能解析特定客户端导出的配置,取决于该客户端是否选择把密钥分享给你。
该 JSON 配置定义了一些通用的配置字段,遵循此格式以尽可能跨客户端共享配置,降低数据迁移阻碍。
格式自由度较高,加密密钥由各客户端开发者自行管理,并自主决定要把密钥分享给哪些客户端,从而实现定向配置共享。
数据定义与序列化流程
客户端把自身一份服务配置转换成下面的数据模型(以 C# 为例):
public sealed class CommonServiceConfig
{
/// <summary>
/// 服务类型,可选值:emby, jellyfin, webdav, alist, smb 等,全小写,无分隔符.
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; }
/// <summary>
/// 配置标识符,全局唯一.
/// </summary>
[JsonPropertyName("id")]
public string? Id { get; set; }
/// <summary>
/// 配置名称,用户自定义,用于区分不同配置.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// 服务地址,支持 IP 和域名。应该是完整的包含 protocol 的链接.
/// </summary>
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// 授权信息.
/// </summary>
[JsonPropertyName("authorization")]
public CommonServiceAuthorization? Authorization { get; set; }
/// <summary>
/// 自定义排序序号,数值越小优先级越高,值大于等于 0.
/// </summary>
[JsonPropertyName("sort_order")]
public uint? SortOrder { get; set; }
/// <summary>
/// 配置创建时间,Unix 时间戳,单位为秒.
/// </summary>
[JsonPropertyName("create_time")]
public long? CreateTime { get; set; }
/// <summary>
/// 配置最后更新时间,Unix 时间戳,单位为秒.
/// </summary>
[JsonPropertyName("last_update_time")]
public long? LastUpdateTime { get; set; }
/// <summary>
/// 配置最后一次播放的时间,Unix 时间戳,单位为秒.
/// </summary>
[JsonPropertyName("last_play_time")]
public long? LastPlayTime { get; set; }
/// <summary>
/// 配置最后一次打开的时间,Unix 时间戳,单位为秒.
/// </summary>
[JsonPropertyName("last_open_time")]
public long? LastOpenTime { get; set; }
/// <summary>
/// 图标,最好是网络图片.
/// </summary>
[JsonPropertyName("icon")]
public string? Icon { get; set; }
/// <summary>
/// 线路信息列表.
/// </summary>
[JsonPropertyName("lines")]
public List<CommonServiceLineInfo>? Lines { get; set; }
/// <summary>
/// 其他选项.
/// </summary>
[JsonPropertyName("options")]
public Dictionary<string, string>? Options { get; set; }
}
public sealed class CommonServiceAuthorization
{
[JsonPropertyName("user_name")]
public string? UserName { get; set; }
[JsonPropertyName("user_id")]
public string? UserId { get; set; }
/// <summary>明文存储.</summary>
[JsonPropertyName("password")]
public string? Password { get; set; }
[JsonPropertyName("access_token")]
public string? AccessToken { get; set; }
[JsonPropertyName("data")]
public Dictionary<string, string>? Data { get; set; }
}
public sealed class CommonServiceLineInfo
{
[JsonPropertyName("url")]
public string Url { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
/// <summary>附加数据,用于存储其他未定义的信息.</summary>
[JsonPropertyName("additional_data")]
public Dictionary<string, string>? AdditionalData { get; set; }
}可空类型表示该字段可根据客户端需求留空。序列化后的属性名按 [JsonPropertyName] 定义,统一使用蛇形命名(小写 + 下划线)。
完成数据映射后,将 JSON 字符串用 AES 加密:
- Mode:CBC
- Padding:PKCS7
- Key:固定 32 字节(256 位)
- IV:取 Key 的前 16 字节(128 位)
加密 / 解密示例(C#):
public static string ConvertToEncryptedString(YourConfig config, string key)
{
var keyBytes = Encoding.UTF8.GetBytes(key); // 32 字节 = 256 位
var ivBytes = new byte[16];
Array.Copy(keyBytes, ivBytes, 16); // 16 字节 = 128 位(IV)
var serviceConfig = new CommonServiceConfig
{
Id = config.Id,
Type = "emby", // or "jellyfin", "webdav", etc.
// ... other properties
};
var json = JsonSerializer.Serialize<CommonServiceConfig>(serviceConfig);
using var aesAlg = Aes.Create();
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Key = keyBytes;
aesAlg.IV = ivBytes;
var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using var msEncrypt = new MemoryStream();
using var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
using var swEncrypt = new StreamWriter(csEncrypt, Encoding.UTF8);
swEncrypt.Write(json);
swEncrypt.Flush();
return Convert.ToBase64String(msEncrypt.ToArray());
}
public static string ConvertFromEncryptedString(string encrypted, string key)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
var ivBytes = new byte[16];
Array.Copy(keyBytes, ivBytes, 16);
using var aesAlg = Aes.Create();
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Key = keyBytes;
aesAlg.IV = ivBytes;
var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
var ciphertextBytes = Convert.FromBase64String(encrypted);
using var msDecrypt = new MemoryStream(ciphertextBytes);
using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
using var srDecrypt = new StreamReader(csDecrypt, Encoding.UTF8);
return srDecrypt.ReadToEnd();
// 如需反序列化:JsonSerializer.Deserialize<CommonServiceConfig>(json);
}将每条服务配置依次加密,得到一个加密字符串数组,最后包装成 CommonConfig:
public sealed class CommonConfig
{
/// <summary>标识配置来源.</summary>
[JsonPropertyName("from")]
public string? From { get; set; }
/// <summary>配置版本号,当前为 1.0.</summary>
[JsonPropertyName("version")]
public string Version { get; set; } = "1.0";
/// <summary>导出时间,Unix 时间戳,单位为秒.</summary>
[JsonPropertyName("export_time")]
public long? ExportTime { get; set; }
/// <summary>加密后的服务配置 JSON 字符串列表.</summary>
[JsonPropertyName("configs")]
public List<string> Configs { get; set; }
/// <summary>图标包,键为标识符,值为图标包地址.</summary>
[JsonPropertyName("icon_packages")]
public Dictionary<string, string>? IconPackages { get; set; }
/// <summary>设置项列表,键为名称,值为序列化值.</summary>
[JsonPropertyName("settings")]
public Dictionary<string, string>? Settings { get; set; }
/// <summary>附加数据,由对接双方自行约定结构.</summary>
[JsonPropertyName("additional_data")]
public Dictionary<string, string>? AdditionalData { get; set; }
/// <summary>解密密钥(仅在希望对所有客户端公开时填写).</summary>
[JsonPropertyName("_key")]
public string? Key { get; set; }
}各字段含义:
| 属性 | 说明 |
|---|---|
from | 客户端标识,例如小幻影视导出时写 RodelPlayer。接收方据此决定使用哪一份密钥解密 |
version | 配置版本,目前固定 1.0 |
export_time | 导出时间,可选 |
configs | 加密后的服务配置字符串数组 |
icon_packages | 可选的图标包字典 |
settings | 跨端共享的用户设置字典 |
additional_data | 自定义附加数据,由双方协商定义 |
_key | 解密所需密钥;仅当你希望让所有客户端无差别解析此配置时才填写,否则务必留空 |
通常服务配置在客户端之间流动时,互信的客户端通过私下分享的密钥彼此解密。如果某条配置确实需要被任意客户端解析,可以把密钥写入 _key 字段;接收方应优先检查 _key 是否存在,存在则直接使用,不存在则回落到本地保存的密钥表。