飞牛api逻辑
根据浏览器抓包,飞牛大部分接口都是websocket协议
上传用的http协议,但是要使用ws获取一个文件名,叫做checkUpload
飞牛网页一共建立三个ws,分别是main, file,timer,实际都是一个服务响应
只是网页版建立了三个连接,用python调用api,使用type=main也可以checkUpload
ws接口地址
ws://192.168.1.4:5666/websocket?type=main # 这里的main就是类型区分
请求参数结构
json格式的的请求体
| 参数 | 
类型 | 
示例 | 
用途 | 
| req | 
str | 
user.login | 
指定调用的api | 
| reqid | 
str | 
6819dfb56819dfb500000002000d | 
请求id,和响应对应 | 
| 其他业务参数 | 
... | 
... | 
... | 
reqid生成代码
python生成,自增
def _get_reqid():
    index = 1
    def func(backId='0000000000000000'):
        nonlocal index
        t = format(int(time.time()), 'x').zfill(8)
        e = format(index, 'x').zfill(4)
        index += 1
        return f"{t}{backId}{e}"
    return func
get_reqid = _get_reqid()
接口加密
连接ws后先发送获取RSA公钥的请求到服务器,用于登录和其他参数加密
{reqid: "6819e1ca00000000000000000001", req: "util.crypto.getRSAPub"}
返回示例:
{"pub":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzWICqab2gcSuRzhguXqH\nKOzS0irokLS9pvT488UIv1581RcfuqUKV/CpvBvbzrLEM1kQtbSAXjOSAYrmOW+V\nN9Nwb8XhJSZHuPdAmqDzm9hu+06QDkIE9TLkNnIZcQKW6gG9Pbo5vID7BWYjzJVU\n7rP+lX5lUrCbpgsXxs5UjEb+4E5St1RaKFCOMiapy40wXgMh4rVyfbfkT752RSsj\nvnNWyYEHuFDJZ7Z2JHJZBhZnIRXOt0k4bTzpqxBbq/2llZ2Z60pm+Ad/h+xpLjvU\nlITh9ddocFpYddYO7MIGQ6dDO4KBObPkqvOJuZ+9sKw1PE6pm4C/ArR1lVHh4L3j\n8wIDAQAB\n-----END PUBLIC KEY-----\n","si":"21474836501","result":"succ","reqid":"6819e1ca00000000000000000001"}
登录接口
原始数据
| 参数 | 
类型 | 
示例值 | 
备注 | 
| req | 
str | 
user.login | 
 | 
| reqid | 
str | 
6819e1ce00000000000000000003 | 
自增id,自己维护,可以是uuid | 
| user | 
str | 
admin | 
需要登录的账号 | 
| password | 
str | 
password | 
账号的密码 | 
| deviceType | 
str | 
Browser | 
这里可以固定,也可以随便填,我认为不重要 | 
| deviceName | 
str | 
Windows-Google Chrome | 
同上 | 
| stay | 
bool | 
True | 
勾选“保持登录”时为True | 
| si | 
str | 
21474836501 | 
util.crypto.getRSAPub返回的si | 
 | 
 | 
 | 
 | 
加密后
| 参数 | 
类型 | 
示例 | 
备注 | 
| aes | 
str | 
5aA/cocgii6UWK.... | 
把原始请求数据使用aes加密 | 
| iv | 
str | 
aHr5yKmqaVU8gJ... | 
随机os.urandom(16) | 
| req | 
str | 
encrypted | 
固定 | 
| rsa | 
str | 
aTlI**Z8HOjhV0... | 
aes加密key使用rsa加密 | 
失败返回
{"errno":131072,"result":"fail","reqid":"6819e1ce00000000000000000003"}
成功响应
{
    "uid": 1000, // uid
    "admin": true, // 是否管理
    "token": "TvPxZlDmGWj+Zhoj3ePKAXhNwEoRg20sOTC0+j/Yof8=", // 会话token
    "secret": "bWH/dMzpTM2c498hzpW5FXic9ap5wPHhFiMqXnFBqs4=", // 后面签名密钥
    "backId": "6819e65000000004", // 替换reqid的8-24位的0, 参考reqid生成代码
    "machineId": "744d46cccc6b4ababf2ffbe273a55cc620fee21f", // 没搞懂
    "result": "succ", // 接口响应状态
    "reqid": "6819e65000000000000000000004" // 和请求id保持一致
}
加密函数
# AES 加密
def aes_encrypt(data, key, iv):
    cipher = AES.new(key.encode(), AES.MODE_CBC, iv)
    ciphertext = cipher.encrypt(pad(data.encode('utf-8'), AES.block_size))
    return base64.b64encode(ciphertext).decode('utf-8')
# RSA 加密
def rsa_encrypt(public_key_str, plaintext):
    key = RSA.import_key(public_key_str)
    # 注释和不注释都能过,就很奇怪
    # MAX_ENCRYPT_BLOCK = 117
    # ciphertext = b''
    # for i in range(0, len(plaintext), MAX_ENCRYPT_BLOCK):
    #     chunk = plaintext[i:i + MAX_ENCRYPT_BLOCK]
    #     cipher = PKCS1_v1_5.new(key)
    #     encrypted_chunk = cipher.encrypt(chunk)
    #     ciphertext += encrypted_chunk
    cipher = PKCS1_v1_5.new(key)
    ciphertext = cipher.encrypt(plaintext.encode())
    ciphertext = base64.b64encode(ciphertext).decode()
    return ciphertext
def login_encrypt(data, public_key_str, key, iv):
    # RSA 加密
    rsa_encrypted = rsa_encrypt(public_key_str, key)
    # AES 加密
    # data = json.dumps(t, separators=(',', ':'))
    aes_encrypted = aes_encrypt(data, key, iv)
    # 返回加密数据
    return {
        'req': 'encrypted',
        'iv': base64.b64encode(iv).decode('utf-8'),  # 正常
        'rsa': rsa_encrypted,
        'aes': aes_encrypted  # 正常
    }
写不下去了
去github看代码吧,我实在不会写文档,所有代码使用python写
仓库地址:https://github.com/k23223/fnnas-api?tab=readme-ov-file
免责声明
如果此仓库违反了飞牛私有云用户协议,请联系我删除仓库。