Implementing REST API digital signature

Question:

I’m currently trying to implement a minimal REST API with digital signature.

I understand that the underlying concept is that the sender sign the payload with its private key using HMAC and that the receiver verify this signature with the public key.

However I have some difficulties implementing a working example.

My API is implemented in PHP and the client in python.

I verify requests in PHP as follow

public function verify_request(Request $request)
{
    $method = $request->method();
    $timestamp = $request->header("timestamp");
    $public_key = $request->header("api-key");
    $signature = $request->header("signature");
    $url = $request->fullUrl();

    $data = $method.$timestamp.$url;

    $result = openssl_verify($data, $signature, $public_key, OPENSSL_ALGO_SHA256);
    Logger::info("Result : ".$result);
    return $result;
}

I already successfully signed requests on several other APIs using this minimal example in python

import requests
import hashlib
import hmac
import time

public_key = ""
private_key = ""

method = "GET"
base_url = "https://testing-api.com"
endpoint = "/private/test"
url = base_url + endpoint
timestamp = str(int(time.time()))

signature_data = method + timestamp + url

signature = hmac.new(private_key.encode("utf-8"), signature_data.encode("utf-8"), hashlib.sha256).hexdigest()

headers = {
    "api-key": public_key,
    "timestamp": timestamp,
    "signature": signature,
}

response = requests.request(method, url, headers=headers)
print(response)

However what I don’t understand is what kind of keys are used.

For example I generated RSA keys as follow

public_key = """-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL7Jz6mHX1cau3wlc6QrGW+kcVHO0HPj
iwUDxdM1B8KSLgkfle4+Snq6HhOp5VXdQXZkYF4u9ctWbx/sB2oiD1sCAwEAAQ==
-----END PUBLIC KEY-----"""

private_key = """-----BEGIN PRIVATE KEY-----
MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAvsnPqYdfVxq7fCVz
pCsZb6RxUc7Qc+OLBQPF0zUHwpIuCR+V7j5KeroeE6nlVd1BdmRgXi71y1ZvH+wH
aiIPWwIDAQABAkAnxyvkzLS0FH7Cg4x4zgOfo0l9JQGRJ//0K7UzM/tKNZOy881O
+qEDld7XMaUOmyGd/FxSskrJrzWTSqEZtQ8BAiEA7wsKUjGWFyTfuMkj/3/E0XI2
YYEaTRpROl4NN6FUttcCIQDMUnn7JDovxYlp17QhyOb41qc1I402QZ6zNiz33ytP
HQIgA2rr/drZo4ESdcjia9++x6PTZTd8Ucfji2sW00nKNUcCIFo7Oh9Mml2qcMrL
NYOOA2J0+RaggqYpSHqAPE+iwK+JAiB2YbMutRh5EDyTcI2ql/hYFLlAJVT/E1AN
/ItM1KCQQQ==
-----END PRIVATE KEY-----"""

But it doesn’t match the format of the API keys I tested on several other APIs, and when I try to pass the public key into the header I got the following error

requests.exceptions.InvalidHeader: Invalid return character or leading space in header: api-key

For example on an API I tested, the keys had the following format

public_key = "Tub3FH4CLjHezvRcSKdeE18a9VrtzL"
private_key = "tBssW3InKRq09OMLkveyuZ65LDRokjVX8FXO0CL5VpdaV73NWayUvJSSAtCs"

But when I try to use those API keys for signing into my python script I have the following error on the PHP

ErrorException: openssl_verify(): supplied key param cannot be coerced into a public key

So I have two issues here about the API key format.

First I would like to know what kind of keys are used by those APIs so I can have keys as simple strings instead of multi-line strings.

Secondly I’d like to understand why those API keys are not considered correct by openssl_verify, since I suppose that how the signature are verified.

Edit: As @Toppaco said in his answer, my issue came from the fact that I thought those APIs were using asymmetric keys and only keeping the public key when in fact they use two unrelated keys, one for authentication, and one for signing, thus my confusion.

Asked By: Arkaik

||

Answers:

You seem to confuse two signing concepts, HMAC and RSA. An HMAC uses the same secret key on both sides, RSA applies different keys on both sides, which form a keypair (private and public key). The private key is secret and is used for signing. The public is not secret and is passed to the verifying side where it is used for verification. For more information s. e.g. How to decide whether to use digital signature (RSA) or HMAC. Both mechanisms ensure data integrity, RSA additionally verifies the signer, s. e.g. JWT: Choosing between HMAC and RSA.

The key types of both are completely different. An HMAC uses a (random) byte sequence as key, RSA requires certain parameters (modulus, private exponent, public exponent etc.) which are provided in different key formats.
For example, your posted public key is a PEM encoded RSA public key in X.509/SPKI format and your posted private key a PEM encoded RSA private key in PKCS#8 format.
Note that both keys are too small with a (modulus) size of 512 bits. Nowadays, for security reasons, they must be at least 2048 bits in size.

Your PHP code uses RSA (verifying with the public key), your Python code applies an HMAC. Both are incompatible, you must also use RSA in the Python code (signing with the private key).

Without further information I can’t contextualize the keys Tub… and tBs…. But they cannot be used as HMAC keys because HMAC applies the same key on both sides. And they are obviously not RSA keys either.

Answered By: Topaco
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.