固件请求UOTA下载地址之http协议
sidebar_position: 1
固件请求UOTA下载地址之HTTP协议
一、概述 (固件UOTA HTTP协议)
本文档描述了基于HTTP协议的UOTA(Over-The-Air Update)固件更新接入方式。HTTP协议版本采用RESTful API设计,支持域名解析和负载均衡,适用于标准的设备端应用场景。
1.1 协议特点 (固件UOTA HTTP协议)
- 基于HTTP/HTTPS协议,兼容性好
- 使用RESTful API设计
- 支持AES加密保证数据安全
- 支持域名解析和负载均衡
- 支持断点续传和分片下载(RFC 7233)
- 请求和响应均采用text/plain格式
1.2 服务器地址信息 (固件UOTA HTTP协议)
- 测试环境:http://192.168.1.87:8419
- 线上环境:域名待定
1.3 域名解析策略 (固件UOTA HTTP协议)
域名解析支持两种模式:
- 权重模式:weight.akrdapp.cn
- 轮询模式:polling.akrdapp.cn(权重全部为1的特殊情况)
重要说明:
- 客户端需先通过域名获取IP列表,如果获取到一个IP则直接使用
- 如果获取到多个IP,需要判断是权重还是轮询模式后进行选择
- 禁止使用域名直接访问服务器接口,防止被追踪到域名和IP的关联关系

二、HTTP协议规范 (固件UOTA HTTP协议)
2.1 请求规范 (固件UOTA HTTP协议)
- Content-Type: text/plain(所有POST请求)
- 响应格式: text/plain
- 请求方式: 仅支持IP地址请求,禁止域名请求
- 加密要求: 除时间戳请求外,所有请求都需要加密,否则返回404错误
2.2 固件配置参数 (固件UOTA HTTP协议)
每个固件需要内置以下两个参数(需要混淆避免反编译):
sdkVersion(SDK版本号)
- 用途: 版本标识,对应HTTP请求头中的sdk-version
- 建议: 每次发布固件都使用不同版本号
- 测试环境示例:
- allwinner-202510-01
- amlogic-202510-01
- rockchip-202510-01
secretKey(加密密钥)
- 用途: AES对称加解密密钥
- 长度要求: 16、24或32字节(对应AES-128、AES-192、AES-256)
- 建议: 每次发布固件都使用不同密钥
- 测试环境密钥:
12345678123456781234567812345678
2.3 测试环境特殊配置 (固件UOTA HTTP协议)
测试环境支持以下特殊请求头:
ignore-verification-time: true- 忽略时间戳校验encrypt: false- 返回明文响应(便于调试)
三、通信流程 (固件UOTA HTTP协议)
3.1 获取时间戳 (固件UOTA HTTP协议)
客户端首先需要获取服务器当前时间戳。
请求信息:
- 请求地址:
/app-api/get8852/yygtnow - 请求方式: GET
- 响应格式: text/plain
请求示例:
curl -X GET http://192.168.1.87:8419/app-api/get8852/yygtnow
响应示例:
1760951487809
3.2 构造UOTA请求数据 (固件UOTA HTTP协议)
客户端收到时间戳后,构造UOTA信息请求数据。
请求数据结构(加密前): 请求数据结构(加密前):
{
"mac": "A8:20:06:B7:C8:B9",
"cpu": "02c0008145f0462078a3840038350b53",
"time": 1760951487809,
"platform": "allwinner"
}
字段说明:
mac: 设备MAC地址cpu: 设备CPU标识,获取不到则传nulltime: 服务端返回的时间戳platform: 固件平台,支持amlogic、rockchip、allwinner
3.3 UOTA信息请求 (固件UOTA HTTP协议)
客户端将请求数据加密后发送给服务器。
请求信息:
- 请求地址:
/app-api/get8852/uncheck - 请求方式: POST
- Content-Type: text/plain
- 请求头:
sdk-version: SDK版本号(如:allwinner-202510-01)ignore-verification-time: true(仅测试环境)
- 请求体: 加密后的请求数据
响应数据结构(解密后):
{
"url": "http://192.168.1.87:8742/app-api/infra/file/download/redirect/1757560816080/app-netservice-3.4.9-allwinner-20250911-debug.apk",
"size": 4747480,
"packageName": "com.android.netservice",
"versionCode": 49,
"md5": "c7d614b68b94ef0ca470fb30fc3a79ec"
}
3.4 固件下载 (固件UOTA HTTP协议)
根据响应信息下载固件文件。
下载特性:
- 支持HTTP Range请求(RFC 7233规范)
- 支持断点续传和分片下载
- 文件完整性通过MD5校验
Range请求示例:
Range: bytes=0-499- 请求前500个字节(0到499)Range: bytes=500-999- 请求第500到第999个字节Range: bytes=500-- 请求从第500个字节到文件末尾Range: bytes=-500- 请求文件的最后500个字节Range: bytes=0-0- 请求第一个字节(探测文件信息)
服务器响应:
- 状态码: 206 Partial Content
- 响应头: Content-Range: bytes <range-start>-<range-end>/<total-size>
注意事项:
- 下载完整文件时:
Range: bytes=0-(size-1) - 如果下载字节数超过size,说明文件损坏,需要清理缓存重新下载
四、加密算法 (固件UOTA HTTP协议)
4.1 加密参数 (固件UOTA HTTP协议)
- 算法: AES-CBC
- 填充: PKCS5Padding
- 密钥长度: 16、24或32字节(对应AES-128、AES-192、AES-256)
- IV: 16字节随机生成
- 编码: Base64
4.2 密钥配置 (固件UOTA HTTP协议)
每个固件版本都应配置独立的密钥:
- 测试环境密钥:
12345678123456781234567812345678 - 生产环境: 根据sdkVersion从服务端配置获取
4.3 加密流程 (固件UOTA HTTP协议)
- 生成16字节随机IV
- 使用AES-CBC模式加密明文
- 将IV和密文拼接(IV在前)
- 对拼接结果进行Base64编码
4.4 解密流程 (固件UOTA HTTP协议)
- Base64解码得到拼接数据
- 分离前16字节作为IV
- 剩余数据作为密文
- 使用AES-CBC模式解密
五、Java实现示例 (固件UOTA HTTP协议)
5.1 加密方法 (固件UOTA HTTP协议)
/**
* 加密方法
* @param plainText 明文
* @param secretKey 固件中的加解密密钥
* @return 密文
* @throws Exception
*/
public static String encrypt(String plainText, String secretKey) throws Exception {
// 将密钥转换为字节数组并验证长度
byte[] keyBytes = validateKey(secretKey);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
// 生成随机初始化向量(IV)
byte[] ivBytes = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(ivBytes);
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// 初始化加密器
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
// 执行加密
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
// 组合IV和密文(IV前置方案)
byte[] combined = new byte[ivBytes.length + encryptedBytes.length];
System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length);
System.arraycopy(encryptedBytes, 0, combined, ivBytes.length, encryptedBytes.length);
// 返回Base64编码的字符串
return Base64.getEncoder().encodeToString(combined);
}
2.4 设备端请求服务端获取固件 (固件UOTA HTTP协议)
> 请求地址:/app-api/get8852/uncheck
> 请求方式:POST
> 请求格式content-type:text/plain
> 请求头(2.1提到的sdkVersion):sdk-version= allwinner-202510-01
> 请求头(是否忽略时间戳校验,仅测试环境有效):ignore-verification-time=true
> 请求body:2.5中的加密结果
> 响应格式content-type:text/plain
> 响应结果示例(加密后的字符串):DC15hFmkezrskGUxijhd47DMxzS8IXKtuXAwJ6YC0C6mrBBxHJi0Cw6VVJxkIDxSeHU/nqd6yftwUFfEdy505HyNMXrZfKio7c/GQzQGJlM9/i7B+n1vOgc8wMfUsttdOI6U2d4v06VKKuPQd2nLIC7Eq3MQCSf+cCJdT8kKPN2Jd0A86v1PiwCI4cCmYhV5QHdqmvAw2+Wjqn51qNytOFQA4CBnD3qprDDEbm/qNHcvW8TBSIfg5iK4PfZ9MFog32QyNjN9vVGjugYZpPkf/nncnYoM+ZAIBr3O9Qc8PV9C3XCMjkXRlBGDNUkQqp3lSpdwoQY2wHorYpY0krS04K0TmcbQM8YS7Xz5HiEdcZLAjBNZcvxhsPG1N2rSkQcShFHsYBfYwWVCSWVa3hwlIARwgul7PK1rM/IuU5eCpbPxo635mCyUZWHrk3wARt6MQUP2hyUy9ddfAnCVkU0P5Q==
5.2 解密方法 (固件UOTA HTTP协议)
/**
* 解密方法
* @param encryptedText 密文
* @param secretKey 密钥
* @return 明文
* @throws Exception
*/
public static String decrypt(String encryptedText, String secretKey) throws Exception {
// 将密钥转换为字节数组并验证长度
byte[] keyBytes = validateKey(secretKey);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
// Base64解码
byte[] combined = Base64.getDecoder().decode(encryptedText);
// 分离IV和密文(前16字节是IV)
byte[] ivBytes = new byte[16];
byte[] encryptedBytes = new byte[combined.length - ivBytes.length];
System.arraycopy(combined, 0, ivBytes, 0, ivBytes.length);
System.arraycopy(combined, ivBytes.length, encryptedBytes, 0, encryptedBytes.length);
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// 初始化解密器
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// 执行解密并返回UTF-8字符串
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
5.3 密钥验证方法 (固件UOTA HTTP协议)
/**
* 密钥验证(支持128/192/256位)
* @param key 密钥字符串
* @return 密钥字节数组
*/
private static byte[] validateKey(String key) {
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
if (keyBytes.length != 16 && keyBytes.length != 24 && keyBytes.length != 32) {
throw new IllegalArgumentException("密钥长度必须是16、24或32字节(对应128/192/256位)");
}
return keyBytes;
}
六、测试示例 (固件UOTA HTTP协议)
> 说明:以下示例中的加解密密钥均使用测试环境密钥 12345678123456781234567812345678,便于不同编程语言的实现参考。
6.1 加密测试示例 (固件UOTA HTTP协议)
明文数据(未格式化无换行无空格的JSON):
{"mac":"A8:20:06:B7:C8:B9","cpu":"02c0008145f0462078a3840038350b53","time":1760951487809,"platform":"allwinner"}
加密结果:
z7YgclIhr4Zxe+l5ev3g8/H5Yb4QTNPy3IE8N624tWeeB5x8IUMtVV+tuOeuUrkzdxOFovXZdwH5yyaF/yu9uf0E1rpgVYwLH9x0fXqER0EmLl2TEopTP1rKUlHkkCC50pU/LN7C4h5/C1HBA1xRrphIF3++rhOnu7SxsM6Sns91Eht/seT+RvW5hBRsPZDQ
6.2 解密测试示例 (固件UOTA HTTP协议)
密文数据:
ccWGSPEPz4Cduc6Ey/Ixq8BbWCWPFqUfiydkf7SnIetKVi0lgZC/iQzGq7oMfJmWaD73akk3rsXJlUOEcQzA+s11IrdEOdbkqFnmaLH/8uSr6e9EuXZAzFwffa2WTgB4YTr/h8ZuX/zdiubMAkBiJazkjS0gTSMaw7EOaORj3ysBan500UFGxRe9XSxcP455/aNwBcdAVZikp7XqEB/L/cGZFUFsEZLj5KtiQRIskl/R3kkPYIpUae2QxvxmSgIe8ZQ/ADiD3VGNPSLQr7oPVRgyEUKqj7Bcq2p8TgYSpnY+BCTtNED9XVqclE5G3uO/v1KVWPFH6GcdtYqKLGP24BFlVYmYyjkcD4r3k/+9CaM=
解密结果(未格式化无换行无空格的JSON):
{"url":"http://192.168.1.87:8742/app-api/infra/file/download/redirect/1757560816080/app-netservice-3.4.9-allwinner-20250911-debug.apk","size":4747480,"packageName":"com.android.netservice","versionCode":49,"md5":"c7d614b68b94ef0ca470fb30fc3a79ec"}
6.3 cURL请求示例 (固件UOTA HTTP协议)
6.3.1 获取时间戳 (固件UOTA HTTP协议)
请求脚本:
curl --location --request GET 'http://192.168.1.87:8419/app-api/get8852/yygtnow' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Accept: */*' \
--header 'Host: 192.168.1.87:8419' \
--header 'Connection: keep-alive'
响应结果:
1761018914222
6.3.2 请求UOTA下载地址 (固件UOTA HTTP协议)
请求脚本:
curl --location --request POST 'http://192.168.1.87:8419/app-api/get8852/uncheck' \
--header 'sdk-version: allwinner-202510-01' \
--header 'ignore-verification-time: true' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Content-Type: text/plain' \
--header 'Accept: */*' \
--header 'Host: 192.168.1.87:8419' \
--header 'Connection: keep-alive' \
--data-raw 'z7YgclIhr4Zxe+l5ev3g8/H5Yb4QTNPy3IE8N624tWeeB5x8IUMtVV+tuOeuUrkzdxOFovXZdwH5yyaF/yu9uf0E1rpgVYwLH9x0fXqER0EmLl2TEopTP1rKUlHkkCC50pU/LN7C4h5/C1HBA1xRrphIF3++rhOnu7SxsM6Sns91Eht/seT+RvW5hBRsPZDQ'
响应结果:
GgUA9M6XpQRuMdpLh5xe6OCEwoe1BPf09OFOcgW+nWZKNMf7xri8sEDZy2Ek3zeexnZQbaXNj64NG7rBIeXPxCiAkbYaDZlsAZhzW1stQmS2k7CAjNsHnj79CzZM9xDGFDS9zULTOjJ+cXKy2EtJtt5t99wgt3QReKxWlc6ku4Revn7+syUmbQBPn7s5RF2dZAh8wUdiZ/Cg4gpoV2xW4te7z9tmTMbscAhdMLW7c7ZKdJM4deekZlYwp/mspLq3IF/yqOXnLY42KFAXeZVH0vVLbxKx64GeF1dsBq88HUJFccWLHzj9k6hFQbiG5/yO47VY69WEHW7l/u2n4Gk9a46rPpLh98c+v03OQsXyx0s=