web-dev-qa-db-ja.com

アルゴトレーディング時にAPIキーを保存する方法は?

私はPythonクライアントが通貨/証券取引所のWebサイトで「アルゴトレード」するためのスクリプトを記述しています。クライアントは通常、従来のパーソナルデスクトップPCで私のスクリプトを実行しています。通常、これらのPCをWeb閲覧活動にも使用しています。 。環境は常に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キーを保存するために:

主な機能:

  • URLkey入力が与えられた場合:

  • secretをxclip付きのクリップボードに書き込みます

  • 10秒でクリップボードを自動消去

セキュリティ機能:

  • aES CBC暗号化パスワードJSONをテキストファイルに読み書きします

  • saltは16バイトのshake256で、os.urandomを使用して暗号化された安全な方法で生成されます

  • メインメニューに戻るたびに新しいソルトを終了し、辞書攻撃を防ぐために終了します

  • gPU/FPGA攻撃を防ぐためにマスターパスワードを400メガバイトに拡張

  • マスターパスワードは、ブルート攻撃を防ぐために、従来のソルト処理されたpbkdf sha512を介して1,000,000回繰り返しハッシュされました

  • サードパーティのモジュールのみが「pycryptodome」です。非推奨の「pycrypto」が見つかった場合、例外が発生します

  • スクリプトの編集に必要なSudoシステムパスワード

私の考えは、ユーザーがこのアプリを使用してキーを保護し、スクリプトの暗号化の意味で使用できるということでした...私は自分の点線を付け、自分のtを超えました...限目。私の暗号化スキームを破ることはできないと思います。

あなたは私のアプリを見つけることができます ここ またはグーグルで:litepresence/CypherVault

しかし、私はまだfacepalmです。私はReddit/r/securityスレッド here を開いてアプリについて話し合い、facepalmがすぐに検証されました。

結局のところ、api ["secret"]をどのように処理しても、RAM)になり、マルウェアによってダンプされ、攻撃者にアップロードされる可能性があります。

ユーザーが暗号化するsecretを入力すると... RAM上にあります。スクリプトがsecretを復号してトランザクションに署名すると、RAM上にあります。

それからhocus pocus ...「私のスクリプト」は「安全」ではなかったので誰かがお金を失いました。私には責任感があります。

これをどのように回避できますか? composing金融トランザクション-ボットによる-デスクトップマシン上でスクリプト言語で安全な方法でエクスチェンジAPIシークレットを安全に保存するには、ある時点でそれらを[〜#〜] ramに公開せずに[〜#〜]、そしてさらに悪いことに... [〜#〜] swap [〜#〜]

3
litepresence

私がapi ["secret"]をどのように処理しても、最終的にはRAM)になり、マルウェアによってダンプされ、攻撃者にアップロードされる可能性があります。

...

これをどのように回避できますか?

それはできません。コンピューターベースの認証手順でパスワードを使用する必要があるという要件と、コンピューターがパスワードにアクセスできないようにするという要件の間には、根本的な矛盾があります。第三の方法はありません。

使用していないときは暗号化できます(これは、これを行ったようです)。使用しないときはメモリに上書きできます。しかし、コンピュータがそれを使用する必要があるとき、それはメモリにあるでしょう。

脅威モデルによっては、これは大したことではないかもしれません。政府レベルの物理的な攻撃は、メモリの内容に対する最も一般的な懸念事項ですが、それに対する防御策を講じる必要がある人はほとんどいません。

3
gowenfawr

これはまさに、ハードウェアセキュリティモジュール(HSM)が解決しようとしている問題です。 HSMには独自の組み込みプロセッサがあり、HSMから出ることのない1つ以上の秘密鍵が含まれています。したがって、HSMの秘密鍵は、HSMが接続されているマシンで実行されているプログラムにアクセスできません。

HSMは、ユーザーの公開鍵に対応する秘密鍵を所有していることを証明することにより、サーバーでの認証に使用できます。 HSMは、デジタル署名や共有シークレットなどの秘密キーとそのオンボードプロセッサを使用して値を計算することにより、これを行います。デジタル署名または共有シークレットはサーバーによって提供される一時的な値に基づいているため、デジタル署名/共有シークレットは、これらを傍受できる攻撃者が再利用することはできません(つまり、ソリューションはリプレイ攻撃に耐性があります)。

もちろん、使用しているサービスがこのタイプの認証をサポートしている必要があります。

0
mti2935