web-dev-qa-db-fra.com

Comment stocker les clés API lors du trading algo?

J'écris des scripts Python pour les clients à "algo trade" sur les sites Web de change/bourse. Mes clients exécutent généralement mes scripts sur des PC de bureau personnels traditionnels. Habituellement, ils utilisent également ces PC pour des activités de navigation sur le Web. L'environnement est toujours Linux, généralement Debian. Dans l'industrie; Python est tout à fait standard pour le trading d'algo de cette manière; à la fois institutionnellement et en privé.

Cependant, je ne peux m'empêcher de voir une faille dans le modèle de sécurité.

Chaque échange a une méthode d'authentification légèrement différente, mais en bref, il y a:

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

Ces demandes sont du type:

POST BUY/SELL
DELETE BUY/SELL (CANCEL)
GET ACCOUNT BALANCES
GET OPEN ORDERS
WITHDRAW FUNDS

Voici quelques exemples des méthodes d'authentification que j'ai écrites sur certains bureaux de change; ils sont tous assez standard dans l'industrie du trading forex/stock/crypto. Surtout au format généralisé:

api["header"] = {"signature": HMAC(SHA256(the_request_parameters))}

exemples:

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()

Mes clients me demandent souvent où stocker les api["secret"]. Dans le fichier de configuration? Dans une variable environnementale? Le saisir manuellement à chaque redémarrage et le stocker physiquement sur papier? Je n'ai pas de bonne réponse et quoi que ce soit suggéré ... J'ai vite fait facepalm.

Je me suis mis à écrire une application - en python - pour stocker les clés API:

caractéristique principale:

  • étant donné URL et key entrée:

  • écrit secret dans le presse-papiers avec xclip

  • efface automatiquement le presse-papiers en 10 secondes

fonctions de sécurité:

  • lit/écrit le mot de passe JSON crypté AES CBC dans un fichier texte

  • le sel est de 16 octets shake256 et généré de manière crypto sécurisée avec os.urandom

  • nouveau sel après chaque retour au menu principal et sortie pour empêcher une attaque par dictionnaire

  • mot de passe principal étendu à 400 mégaoctets pour empêcher les attaques GPU/FPGA

  • le mot de passe principal a été haché de manière itérative 1 000 000 fois via pbkdf salé traditionnel sha512 pour empêcher une attaque brutale

  • seul le module tiers est "pycryptodome"; lève une exception si "pycrypto" obsolète est trouvé

  • Mot de passe du système Sudo requis pour éditer le script

Je pensais que mes utilisateurs pouvaient utiliser cette application pour sécuriser leurs clés et dans un sens cryptographique de script ... J'ai pointillé mes I et croisé mes t ... quand ils sont stockés, ils sont stockés. Période. Je suis tout à fait convaincu que vous ne pouvez pas casser mon schéma de cryptage.

Vous pouvez trouver mon application ici ou en recherchant sur Google: litepresence/CypherVault

Mais je reste facepalm. J'ai ouvert un Reddit/r/security thread ici pour discuter de mon application et mon facepalm a été rapidement validé.

À la fin des choses ... peu importe la façon dont je gère l'api ["secret"], il finit sur RAM qui peut être vidé par un malware et téléchargé sur un attaquant.

Lorsque l'utilisateur entre le secret à chiffrer ... c'est sur la RAM. Lorsque le script déchiffre le secret pour signer une transaction ... c'est sur la RAM.

Puis hocus pocus ... "mon script" a fait perdre de l'argent à quelqu'un parce qu'il n'était pas "sécurisé"; J'ai un sens des responsabilités.

Comment éviter cela? Comment stocker en toute sécurité des secrets d'API d'échange pour composer des transactions financières - par un bot - dans des langages de script, sur des machines de bureau, sans à un moment donné les exposer à votre [~ # ~] bélier [~ # ~] , et potentiellement pire ... votre [~ # ~] échanger [~ # ~] ?

3
litepresence

peu importe la façon dont je gère l'api ["secret"], il se termine sur RAM qui peut être vidé par un logiciel malveillant et téléchargé sur un attaquant.

...

Comment éviter cela?

Ça ne peut pas l'être. Il existe une contradiction fondamentale entre l'obligation d'utiliser le mot de passe pour une étape d'authentification sur ordinateur et le fait de ne pas vouloir que l'ordinateur n'ait pas accès au mot de passe. Il n'y a pas de troisième voie.

Vous pouvez le crypter lorsqu'il n'est pas utilisé (on dirait que vous l'avez fait). Vous pouvez l'écraser en mémoire lorsqu'il n'est pas utilisé. Mais lorsque l'ordinateur doit l'utiliser, il sera en mémoire.

En fonction de votre modèle de menace, ce n'est peut-être pas un gros problème. L'attaque physique au niveau du gouvernement est la préoccupation la plus courante pour le contenu de la mémoire, mais peu de gens doivent adapter leurs défenses à cela.

3
gowenfawr

C'est exactement le problème que les modules de sécurité matériels (HSM) visent à résoudre. Un HSM possède son propre processeur intégré et contient une ou plusieurs clés privées qui ne quittent jamais le HSM. Par conséquent, les clés privées du HSM sont inaccessibles aux programmes exécutés sur la machine à laquelle le HSM est connecté.

Le HSM peut être utilisé pour s'authentifier auprès d'un serveur, en prouvant qu'il possède la clé privée correspondant à la clé publique de l'utilisateur. Le HSM le fait en calculant une valeur à l'aide de la clé privée et de son processeur intégré, comme une signature numérique ou un secret partagé. La signature numérique ou le secret partagé sont basés sur des valeurs éphémères fournies par le serveur, de sorte que la signature numérique/le secret partagé ne peuvent pas être réutilisés par un attaquant qui est capable de les intercepter (c'est-à-dire que la solution est résistante aux attaques de réexécution).

Bien entendu, le service que vous utilisez doit prendre en charge ce type d'authentification.

0
mti2935