Open API RSA 模式使用指南
本文档旨在详细指导用户如何使用 RSA 模式调用共绩算力 Open API,并提供基于 Python 的实现示例。
1. 前置条件
Section titled “1. 前置条件”在开始之前,请确保您已准备好以下环境和工具:
- Python 环境(可选):确保您的系统已安装 Python 3.6 或更高版本。
- 必要的 Python 库(可选):您需要安装 cryptography 库用于 RSA 加签,以及 requests 库用于发送 HTTP 请求。您可以使用 pip 进行安装:pip install cryptography requests
- OpenSSL 工具:通常用于在本地生成 RSA 密钥对。大多数 Linux/macOS 系统自带,Windows 用户可能需要单独安装。
- 您的 RSA 密钥文件:您需要一个 RSA 密钥文件(通常是 .key 或 .pem 格式),该密钥是您提供给平台所对应的密钥。
2. 获取并设置 RSA 密钥
Section titled “2. 获取并设置 RSA 密钥”使用 RSA 模式的第一步是生成 RSA 密钥对并在平台设置您的公钥。
2.1 获取 RSA 密钥对
Section titled “2.1 获取 RSA 密钥对”- 在本地生成 RSA 密钥对:
- 打开终端或命令行工具(如 PowerShell, CMD, Bash)。
- 使用 OpenSSL 生成私钥文件(例如 2048 位):
openssl genrsa -out private.key 2048
- 从私钥中提取公钥文件:
openssl rsa -pubout -in private.key -out public.pem
- 执行完成后,您将得到
private.key
(私钥,务必妥善保管,切勿泄露)和public.pem
(公钥,用于提供给平台)两个文件。

- 在平台设置您的公钥:
- 登录共绩算力平台。
- 进入 API 密钥管理页面
https://console.suanli.cn/settings/key
。 - 点击“新建密钥”,选择“RSA 加验签模式”。
- 填写备注并提供您的 RSA 公钥。 使用文本编辑器打开您本地生成的
public.pem
文件,复制从-----BEGIN PUBLIC KEY-----
到-----END PUBLIC KEY-----
的全部内容,粘贴到平台对应的输入框。 - 系统将生成并展示与您公钥匹配的 RSA 私钥 以及其他相关信息。
重要: 请务必立即妥善保管生成的私钥和相关信息,因为它们只会显示一次。一旦丢失,您将无法找回,只能重新生成新的密钥对。平台仅保存您的公钥用于验证签名。

2.2 三种 RSA 密钥格式介绍
Section titled “2.2 三种 RSA 密钥格式介绍”在平台设置密钥时,可能会涉及不同的密钥格式。理解这些格式有助于您正确处理密钥文件:
-
RsaPkcs8Pem
- 结构:符合 PKCS#8 标准的 RSA 密钥
- 格式:PEM 编码(含头部/尾部标签的文本文件,如 -----BEGIN PRIVATE KEY-----)
- 用途:通用性强,适用于配置文件、文件存储
-
RsaPkcs1Pem
- 结构:符合 PKCS#1 标准的 RSA 密钥(原始 RSA 参数)
- 格式:PEM 编码(含 RSA 专属标签,如 -----BEGIN RSA PRIVATE KEY----- )
- 用途:兼容需要 PKCS#1 格式的旧系统
-
RsaPkcs8Base64
- 结构:PKCS#8 标准密钥
- 格式:纯 Base64 字符串(无 PEM 标签)
- 用途:适合代码嵌入或 API 直接传输
2.3 RSA 密钥格式选型建议
Section titled “2.3 RSA 密钥格式选型建议”- 首选 RsaPkcs8Pem:平台通常默认此格式,通用性最佳,便于文件保管和配置。
- 特殊场景选其他:
- 需对接传统系统 → RsaPkcs1Pem
- 需密钥值直接嵌入代码 → RsaPkcs8Base64
注:平台在您选择并生成密钥后,密钥内容只会展示一次,请务必及时、妥善保管。
3. 使用 Python 脚本进行加签和调用
Section titled “3. 使用 Python 脚本进行加签和调用”我们已经为您准备了一个 Python 脚本 rsa_sign_util.py
来帮助您实现加签和 API 调用过程。
3.1 脚本代码 (rsa_sign_util.py
)
Section titled “3.1 脚本代码 (rsa_sign_util.py)”import base64import timeimport jsonimport http.clientfrom cryptography.hazmat.primitives import serialization, hashesfrom cryptography.hazmat.primitives.asymmetric import padding, rsafrom cryptography.exceptions import InvalidSignaturefrom cryptography.hazmat.primitives.asymmetric import ecfrom cryptography.hazmat.primitives import serializationfrom cryptography.hazmat.backends import default_backendfrom typing import Optional, Union, Tuple
def encrypt_data_with_public_key(public_key_path: str, data: bytes) -> Optional[str]: """ 使用 RSA 公钥加密数据并进行 Base64 编码。
Args: public_key_path: RSA 公钥文件路径。 data: 待加密的原始数据 (字节类型)。
Returns: 加密后并 Base64 编码的字符串,或 None 如果加密失败。 """ try: with open(public_key_path, "rb") as key_file: # 尝试加载 PEM 格式的公钥 public_key = serialization.load_pem_public_key( key_file.read(), backend=default_backend() ) except FileNotFoundError: print(f"错误:公钥文件未找到:{public_key_path}") return None except Exception as e: print(f"错误:加载公钥失败:{e}") return None
try: # 使用 RSA 公钥和 PKCS1v15 填充进行加密 ciphertext = public_key.encrypt( data, padding.PKCS1v15() ) # 将加密结果进行 Base64 编码 encrypted_base64 = base64.b64encode(ciphertext).decode('utf-8') return encrypted_base64 except Exception as e: print(f"错误:数据加密失败:{e}") return None
def sign_request(private_key_path: str, api_path: str, api_version: str, api_token: str, request_data: dict, method: str, public_key_path_for_encryption: Optional[str] = None) -> Tuple[Optional[str], Optional[int], Optional[str], Optional[str]]: """ 生成待加签字符串并计算 RSA-SHA256 签名,可选对请求体进行 RSA 公钥加密。
Args: private_key_path: RSA 私钥文件路径。 api_path: API 接口地址。 api_version: API 版本。 api_token: API 认证 Token。 request_data: 接口请求体 (Python 字典)。 method: HTTP 方法 (e.g., "GET", "POST"). public_key_path_for_encryption: 可选的 RSA 公钥文件路径,如果提供,则对 request_data 进行加密。
Returns: 包含签名字符串、时间戳、用于签名的 data_str 和实际发送的 payload 的元组。 如果签名或加密失败,返回 (None, None, None, None)。 """ try: # 读取私钥文件 with open(private_key_path, "rb") as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, # 如果私钥有密码,请在此提供 backend=default_backend() ) except FileNotFoundError: print(f"错误:私钥文件未找到:{private_key_path}") return None, None, None, None except Exception as e: print(f"错误:加载私钥失败:{e}") return None, None, None, None
timestamp_ms = int(time.time() * 1000) data_to_sign = "" request_payload = "" # 实际发送的请求体
if public_key_path_for_encryption: # 如果提供了公钥路径,先加密请求数据 request_data_bytes = json.dumps(request_data, separators=(',', ':')).encode('utf-8') encrypted_data_base64 = encrypt_data_with_public_key(public_key_path_for_encryption, request_data_bytes)
if encrypted_data_base64 is None: # 加密失败 return None, None, None, None
# 加密后的 Base64 字符串用于签名 data_to_sign = encrypted_data_base64 # 实际发送的请求体就是这个加密后的 Base64 字符串 request_payload = encrypted_data_base64
else: # 如果没有提供公钥路径,根据方法处理 request_data if method.upper() == "GET" and not request_data: data_to_sign = "" request_payload = "" # GET 请求体为空 else: # 使用 separators 参数生成紧凑 json,避免空格影响签名 data_str = json.dumps(request_data, separators=(',', ':')) data_to_sign = data_str # 实际发送的请求体也是这个 JSON 字符串 request_payload = data_str
# 生成待加签字符串:path\nversion\ntimestamp\ntoken\ndata string_to_sign = f"{api_path}\n{api_version}\n{timestamp_ms}\n{api_token}\n{data_to_sign}"
# 使用 RSA 私钥和 SHA-256 算法进行签名 try: # 待签名字符串需编码为字节类型 signature = private_key.sign( string_to_sign.encode('utf-8'), padding.PKCS1v15(), # 填充方式 hashes.SHA256() # 哈希算法 ) except Exception as e: print(f"错误:签名计算失败:{e}") return None, None, None, None
# 将签名结果进行 Base64 编码 sign_str_base64 = base64.b64encode(signature).decode('utf-8')
return sign_str_base64, timestamp_ms, data_to_sign, request_payload
YOUR_PRIVATE_KEY_FILE = "private.key" # 私钥文件路径YOUR_PUBLIC_KEY_FILE = "public.pem" # 公钥文件路径 (用于加密,如果需要)
API_GATEWAY_HOST = "https://openapi.suanli.cn" # API 网关域名
TARGET_API_PATH = "/api/deployment/resource/search" # 目标 API 路径
API_VERSION = "1.0.0" # API 版本
YOUR_API_TOKEN = "084b3e01-5faa-4c4c-bac6-433dbdd83150-20250609144703" # API Token
API_REQUEST_DATA = {} # 接口请求体数据
HTTP_METHOD = "GET" # HTTP 方法
NEED_ENCRYPTION = True # 根据您的需求设置
print("开始处理请求...")
public_key_path_for_encryption = YOUR_PUBLIC_KEY_FILE if NEED_ENCRYPTION else None
sign_string, timestamp, data_for_signing_str, actual_request_payload = sign_request( YOUR_PRIVATE_KEY_FILE, TARGET_API_PATH, API_VERSION, YOUR_API_TOKEN, API_REQUEST_DATA, HTTP_METHOD, public_key_path_for_encryption # 传入公钥路径)
if sign_string and timestamp is not None and data_for_signing_str is not None and actual_request_payload is not None: print("处理成功。") print(f"用于签名的 data_str: '{data_for_signing_str}'") print(f"计算得到的 Base64 签名字符串 (sign_str): {sign_string}") print(f"使用的时间戳 (timestamp): {timestamp}") print(f"构建的待加签原始字符串(供参考,请确保与平台逻辑一致):\n---BEGIN STRING TO SIGN---\n{TARGET_API_PATH}\n{API_VERSION}\n{timestamp}\n{YOUR_API_TOKEN}\n{data_for_signing_str}\n---END STRING TO SIGN---")
# 构建完整的请求 URL request_url = API_GATEWAY_HOST + TARGET_API_PATH
# 构建请求头 headers = { "version": API_VERSION, "timestamp": str(timestamp), # timestamp 在 Header 中需要是字符串类型 } # 添加 token 到 Header if YOUR_API_TOKEN: headers["token"] = YOUR_API_TOKEN # 如果有 token (非简易模式),则添加 sign_str if sign_string is not None: # 确保 sign_string 成功生成 headers["sign_str"] = sign_string
print(f"\n发送 API 请求至:{request_url}") print(f"请求方法:{HTTP_METHOD}") print(f"请求头:{json.dumps(headers, indent=2)}")
# 发送 HTTP 请求 try: # 从 API_GATEWAY_HOST 中提取 host host = API_GATEWAY_HOST.replace("https://", "") conn = http.client.HTTPSConnection(host)
# http.client 的 request 方法参数:method, url, body, headers # GET 请求 body 为 None 或空字符串 # 实际发送的请求体根据 actual_request_payload 确定 payload_bytes = actual_request_payload.encode('utf-8') if actual_request_payload else b''
conn.request(HTTP_METHOD, TARGET_API_PATH, payload_bytes, headers)
res = conn.getresponse() data = res.read()
print("\nAPI 响应:") # 响应体是 bytes,需解码 decoded_data = data.decode("utf-8") # 尝试解析并打印 JSON 响应 try: print(json.dumps(json.loads(decoded_data), indent=2, ensure_ascii=False)) except json.JSONDecodeError: print("响应体不是 JSON 格式:") print(decoded_data)
conn.close()
except Exception as e: print(f"\n发送请求时发生未知错误:{e}")
else: print("\n签名生成失败或构建待加签字符串出错,或者加密失败,无法发送 API 请求。请检查错误信息。")
3.2 脚本使用说明
Section titled “3.2 脚本使用说明”- 保存脚本: 确保您已将完整的 Python 代码保存为 rsa_sign_util.py 文件。
- 准备密钥文件:将您的 RSA 私钥文件(例如 private.key)和可选的公钥文件(例如 public.pem,如果需要加密)放在与 rsa_sign_util.py 相同的目录下,或者更新脚本中 YOUR_PRIVATE_KEY_FILE 和 YOUR_PUBLIC_KEY_FILE 的完整路径。
- 配置变量: 打开 rsa_sign_util.py 文件,找到 # --- 示例用法 --- 部分,根据您要调用的具体 API 修改以下变量的值:

YOUR_PRIVATE_KEY_FILE: 您的 RSA 私钥文件路径。 YOUR_PUBLIC_KEY_FILE: 可选:RSA 公钥文件路径 (用于加密请求体,如果需要)。 API_GATEWAY_HOST: API 网关域名。 TARGET_API_PATH: 您要调用的具体 API 接口路径。 API_VERSION: API 版本。 YOUR_API_TOKEN: 您的 API Token (如果不需要,设置为空字符串 "")。 API_REQUEST_DATA: 您要发送的接口请求体数据 (Python 字典格式)。 HTTP_METHOD: 您要使用的 HTTP 方法 (“GET” 或 “POST”)。 NEED_ENCRYPTION: 是否需要加密请求体 (True/False)
- 运行脚本: 打开终端或命令行,切换到 rsa_sign_util.py 文件所在的目录,然后运行:python rsa_sign_util.py
3.3 响应结果说明
Section titled “3.3 响应结果说明”开始处理请求...处理成功。用于签名的 data_str: '{}'计算得到的 Base64 签名字符串 (sign_str): ...使用的时间戳 (timestamp): ...构建的待加签原始字符串(供参考,请确保与平台逻辑一致):---BEGIN STRING TO SIGN---/api/deployment/resource/search1.0.0...//**实际 token 字符串**{}---END STRING TO SIGN---
发送 API 请求至: https://openapi.suanli.cn/api/deployment/resource/search请求方法: GET请求头: { "version": "1.0.0", "timestamp": "...", "token": "实际 token 字符串", "sign_str": "..."}
API 响应:{ "code": 0, "msg": "success", "data": { "device_categories": [ { "device_name": "H20", "region_map": { "xingjiangf-1": { "region": "xingjiangf-1", "region_name": "新疆一区", "mark": { "resource": { "device_name": "H20", "region": "xingjiangf-1", "gpu_name": "H20", "gpu_count": 1, "gpu_memory": 98304, "memory": 196608, "cpu_cores": 24 }, "region_name": null, "mark": "ha8GDGEAORN3a9Hhu7X+W4Vtce1MVTeWkoGuBrgRNV0VuvbQPOAijLHAl\nL3THz4zxz17CQ2xan6Q2UrnIQ\n6pUPcLqpoLUCh5MaAR9kIet8llTG2oulHTzTR6DoHqgQoSFvtGpGD4Rh7S1F32ACv6i9o7EFy0RPUnKv\nTMUFPSo1heC27vZCm+ab5SZnpZiQ==" }, "price": 2052, "inventory": 0 }, "hebeif-1": { "region": "hebeif-1", "region_name": "河北一区", "mark": { "resource": { "device_name": "H20", "region": "hebeif-1", "gpu_name": "H20", "gpu_count": 1, "gpu_memory": 98304, "memory": 196608, "cpu_cores": 24 }, "region_name": null, "mark": "ha8GDGEAORN3a9Hhu7X+W4Vtce1MVTeWkoGuBrgRNV0ZtvTTM6x9yfvPw\nu+KYjci3wc3UkKcenSQvVqwc1\nfUVOgLm41GH3chMf1Qrz8UotFVU3Hn7xqS3D53GpC30EtfCecJ8SO4UhLT1UfiU2Oj9tokF2akCb1wba\ns+QFIKvwEvJgaT2WcPA1YY" }, "price": 2052, "inventory": 0 } ], "regions": [ { "region": "xingjiangf-1", "region_name": "新疆一区", "mark": { "resource": { "device_name": "H20", "region": "xingjiangf-1", "gpu_name": "H20", "gpu_count": 1, "gpu_memory": 98304, "memory": 196608, "cpu_cores": 24 }, "region_name": null, "mark": "ha8GDGEAORN3a9Hhu7X+W4Vtce1MVTeWkoGuBrgRNV0VuvbQPOAijLHAl\nL3THz4zxz17CQ2xan6Q2UrnIQ\n6pUPcLqpoLUCh5MaAR9kIet8llTG2oulHTzTR6DoHqgQoSFvtGpGD4Rh7S1F32ACv6i9o7EFy0RPUnKv\nTMUFPSo1heC27vZCm+ab5SZnpZiQ==" }, "price": 2052, "inventory": 0 },
这个 JSON 数据是运行 rsa_sign_util.py
脚本后收到的 API 响应。
code
: 状态码。0
表示 API 请求成功。message
: 消息字符串。"success"
进一步确认了请求成功。result
: 包含实际数据的结果对象。total
: 表示找到的资源总数。resources
: 这是一个列表,包含了每种资源类型的详细信息。列表中的每个对象代表一种特定的设备配置(例如 “H20 x 8”, “4090 x 8” 等)。device_name
: 设备的名称或配置。region_map
和regions
: 提供该资源在不同区域的可用性和详细信息。regions
是一个列表,每个元素代表一个区域的详情。region
: 区域代码。region_name
: 区域的中文名称(例如 “浙江一区”, “福建一区”)。mark
: 包含资源规格和标记的内部对象。price
: 该资源在该区域的价格。inventory
: 该资源在该区域的库存数量。gpu_name
: 使用的 GPU 型号(例如 “H20”, “4090”)。gpu_memory
: GPU 显存大小。gpu_count
: GPU 数量。memory
: 系统内存大小。cpu_cores
: CPU 核心数。4. API 接口说明
Section titled “4. API 接口说明”4.1 公共请求头
Section titled “4.1 公共请求头”
列名
类型
是否必填
说明
实例值
version
String
是
API 版本
1.0.0
token
String
否
认证 token
sign_str
String(Base64)
否
签名字符串(参考加签流程)
timestamp
Integer
是
时间戳(毫秒)
1721299458423
curl —location —request POST ‘https://{gateway-host}/{api-path}’ \ —header ‘token: a0e13fe1-5626-4c05-926b-20f586c69102-20240821144204’ \ —header ‘timestamp: 1724222524375’ \ —header ‘version: 1.0.0’ \ —header ‘sign_str: EowIBAAKCAQEArbNcSNSLjHzqOzrYL+7afEh5TI4hn1BCxsuzY02c1RMn24a2YEvpqCCVDxmgN/dcAdcCcvhO/2wDG389LuEkw+QhVPzdAE29bbzz+Gb/FDusVNo6tl8mbfd/XA53h3sOCekEMP2QCoPAoUO94wUWK5RpjsONf9Bs0Q6YUmL4TWqvPGmWjc/Y8winDtFzKN2his5PhbWlRiSkENGXSma6lr66BA/SduAY/Fl8YxEWThVkYsAurg0rEd83DilN4zp7hZf82Msjgp8kPm/SMHTEF2V2cOo82m12HyRJKuKS0L8WPVIwQmXJ6VN55ue+b96sryUs/WZyfiXTh0thoa9vqQIDAQABAoIBAQCsOMDQSUTPl27aKR7+b5FbVrRF7kpx3i9HUeLcG7DbJrIHHAspcTsLgrqoDR1pQC2OeXMpMP+KirqOAdtU5tAAFenijRBGY83kx0sSSHSyx/O28eTyu2ar84/oY0OqJZ0mwE1ykYXGlxlgC31zYLC5pt3+Oe/LAYlSwmjOjuhoQDMZoiCByIw6oDAVLIlZwTl8A/HAR1yacDTh6lps1vTZ5lBfIDpbn1gHb2GWqHu8q9oD9G9IEyWwwTE1IsNXVwBKEifjLubd2WfTWDmROVZZ9T4AXm/40/Eb6ALApwY5s7lBMIiapmcJZzlyELEukUMmYN7vHBjdMDOPK3tjLSKNA’ \ —data-raw ’{“task_id”: 1}’
### 4.4 公共响应体 <table> <colgroup> <col/> <col/> <col/> <col/> <col/> </colgroup> <tbody> <tr><td><p>列名</p></td><td><p>类型</p></td><td><p>不是 null</p></td><td><p>说明</p></td><td><p>实例值</p></td></tr> <tr><td><p>code</p></td><td><p>String</p></td><td><p>是</p></td><td><p>响应 code</p></td><td><p>0000</p></td></tr> <tr><td><p>message</p></td><td><p>String</p></td><td><p>否</p></td><td><p>响应说明</p></td><td><p>success</p></td></tr> <tr><td><p>data</p></td><td><p>Object</p></td><td><p>否</p></td><td><p>数据体</p></td><td><p>{"task_name": "test_task1"}</p></td></tr> </tbody> </table> ### 4.5 加签流程 1. <b>生成待加签字符串</b>: 将以下参数按照顺序用换行符 `\n` 连接起来: > `path`: 接口地址。GET 请求需要包含完整的 URL(含 query 参数)。> `version`: 请求头中的接口版本。> `timestamp`: 请求头中的时间戳(毫秒)。> `token`: 请求头中的 token。> `data`: 接口请求体。如果接口需要加密,则为加密后的 Base64 字符串。 格式示例:`path\nversion\ntimestamp\ntoken\ndata` 例如: `/api/user/order/get_this_week_residue_withdrawal_count 1.0.0 1724222524375 a0e13fe1-5626-4c05-926b-20f586c69102-20240821144204 {"username":"test1","password":"password1"} ` 1. <b>计算签名</b>: 使用 RSA-SHA256 签名算法对待签名字符串进行计算,得到签名结果(字节数组)。 1. <b>Base64 编码</b>: 将签名结果(字节数组)转换为 Base64 字符串。 1. <b>设置请求头</b>: 将生成的 Base64 签名字符串设置到请求头的 `sign_str` 字段。 ### 4.6 加密流程 1. 根据 RSA 公钥对待发送的请求体进行 rsa_pubk_encrypt 算法加密。 2. 将加密后的字节数组转换为 Base64 字符串。 3. 将该 Base64 字符串作为实际的请求体发送。 4. 如果请求同时需要加密和加签 <b>必须先进行加密步骤,再进行加签步骤</b>(加签时使用的 data 即为加密后的 Base64 字符串)。