我为客户编写 Python 脚本以在货币/证券交易所网站上进行“算法交易”。我的客户通常在传统的个人台式电脑上运行我的脚本。通常也将这些 PC 用于网络浏览活动。环境始终是 Linux;通常是 Debian。在行业内;以这种方式进行算法交易,Python 是相当标准的;无论是体制上还是私下。
但是,我不禁看到了安全模型中的一个缺陷。
每个交易所的认证方式略有不同,但总之有:
USER INPUTS:
api['secret'] # private key from exchange
USER CONFIG FILE CONTAINS:
api['key'] # public key from exchange
api['exchange'] # name of exchange; ie "binance"
api['symbol'] # market pair symbol in format BTC:USD
api['uri'] # the url up to .com/
FROM USER INPUTS SCRIPT BUILDS REQUEST SPECIFIC:
api['nonce'] # time.time() at beginning of request
api['endpoint'] # path/to/server/resource
api['url'] # uri + endpoint
api['method'] # GET, POST, or DELETE
api['params'] # dict with request specific parameters
api['data'] # str with request specific parameters
api['headers'] # authentication signature specific to the request
这些请求是这样的:
POST BUY/SELL
DELETE BUY/SELL (CANCEL)
GET ACCOUNT BALANCES
GET OPEN ORDERS
WITHDRAW FUNDS
以下是我写给一些货币交易所的认证方法的一些例子;它们在外汇/股票/加密交易行业都是相当标准的。主要是通用格式:
api["header"] = {"signature": HMAC(SHA256(the_request_parameters))}
例子:
def signed_request(api, signal):
"""
Remote procedure call for authenticated exchange operations
api : dict with keys for building external request
signal : multiprocessing completion relay
"""
api = lookup_url(api)
api["data"] = ""
if api["exchange"] == "coinbase":
api["data"] = json_dumps(api["params"]) if api["params"] else ""
api["params"] = None
message = (
str(api["nonce"]) + api["method"] + api["endpoint"] + api["data"]
).encode("ascii")
secret = b64decode(api["secret"])
signature = hmac.new(secret, message, hashlib.sha256).digest()
signature = b64encode(signature).decode("utf-8")
api["headers"] = {
"Content-Type": "Application/JSON",
"CB-ACCESS-SIGN": signature,
"CB-ACCESS-TIMESTAMP": str(api["nonce"]),
"CB-ACCESS-KEY": api["key"],
"CB-ACCESS-PASSPHRASE": api["passphrase"],
}
elif api["exchange"] == "poloniex":
api["params"]["nonce"] = int(api["nonce"] * 1000)
message = urlencode(api["params"]).encode("utf-8")
secret = api["secret"].encode("utf-8")
signature = hmac.new(secret, message, hashlib.sha512).hexdigest()
api["headers"] = {
"Content-Type": "application/x-www-form-urlencoded",
"Key": api["key"],
"Sign": signature,
}
elif api["exchange"] == "binance":
api["params"]["timestamp"] = int(api["nonce"] * 1000)
api["params"]["signature"] = signature
message = urlencode(api["params"].items()).encode("utf-8")
secret = bytes(api["secret"].encode("utf-8"))
signature = hmac.new(secret, message, hashlib.sha256).hexdigest()
api["headers"] = {"X-MBX-APIKEY": api["key"]}
elif api["exchange"] == "bittrex":
api["params"]["apikey"] = api["key"]
api["params"]["nonce"] = int(api["nonce"] * 1000)
message = api["url"] + api["endpoint"] + urlencode(api["params"])
message = bytearray(message, "ascii")
secret = bytearray(api["secret"], "ascii")
signature = hmac.new(secret, message, hashlib.sha512).hexdigest()
api["headers"] = {}
elif api["exchange"] == "kraken":
api["data"] = api["params"][:]
api["params"] = {}
data["nonce"] = int(1000 * api["nonce"])
api["endpoint"] = "/2.1.0/private/" + api["endpoint"]
message = (str(data["nonce"]) + urlencode(data)).encode("ascii")
message = api["endpoint"].encode("ascii") + hashlib.sha256(message).digest()
secret = b64decode(api["secret"])
signature = b64encode(hmac.new(secret, message, hashlib.sha512).digest())
api["headers"] = {
"User-Agent": "krakenex/2.1.0",
"API-Key": api["key"],
"API-Sign": signature,
}
elif api["exchange"] == "bitfinex":
nonce = str(int(api["nonce"] * 1000))
api["endpoint"] = path = "v2/auth/r/orders"
api["data"] = json.dumps(api["params"])
api["params"] = {}
message = ("/api/" + api["endpoint"] + nonce + api["data"]).encode("utf8")
secret = api["secret"].encode("utf8")
signature = hmac.new(secret, message, hashlib.sha384).hexdigest()
api["headers"] = {
"bfx-nonce": nonce,
"bfx-apikey": api["key"],
"bfx-signature": signature,
"content-type": "application/json",
}
url = api["url"] + api["endpoint"]
ret = requests.request(
method=api["method"],
url=url,
data=api["data"],
params=api["params"],
headers=api["headers"],
)
response = ret.json()
我的客户经常问我,在哪里存储api["secret"]. 在配置文件中?在环境变量中?每次重新启动时手动输入并将其物理存储在纸上?我没有好的答案和任何建议……我赶紧捂脸。
我开始编写一个应用程序 - 在 python 中 - 来存储 API 密钥:
主要特点:
给定
URL和key输入:使用
secretxclip写入剪贴板在 10 秒内自动清除剪贴板
安全特性:
读取/写入 AES CBC 加密密码 JSON 到文本文件
salt 是 16 字节的shake256,以加密安全方式生成,带有 os.urandom
每次返回主菜单并退出后添加新盐以防止字典攻击
主密码延长至 400 兆字节以防止 GPU/FPGA 攻击
主密码通过传统的盐渍 pbkdf sha512 迭代 1,000,000 次以防止暴力攻击
只有第 3 方模块是“pycryptodome”;如果发现已弃用的“pycrypto”,则引发异常
编辑脚本需要 sudo 系统密码
我的想法是我的用户可以使用这个应用程序来保护他们的密钥,并且在脚本加密意义上......我已经点缀了我的 I's 并越过了我的 t's......当它们被存储时,它们被存储了。时期。我很相信你不能破解我的加密方案。
你可以在这里或通过谷歌搜索找到我的应用程序:litepresence/CypherVault
但我还是捂脸。我在这里打开了一个Reddit/r/security帖子来讨论我的应用程序,我的 facepalm 很快就得到了验证。
归根结底……无论我如何处理 api["secret"],它最终都会在 RAM 上被恶意软件转储并上传给攻击者。
当用户输入secret要加密的...它在 RAM 上。当脚本解密secret以签署交易时......它在RAM上。
然后恶作剧……“我的脚本”导致某人赔钱,因为它不“安全”;我有责任感。
如何避免这种情况?一个人如何安全地存储交换 API 机密以组成金融交易 - 由机器人 - 在脚本语言中,在台式机上,而不在某些时候将它们暴露给你的RAM,甚至可能更糟......你的SWAP?