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.
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.
Edit: According to the documentation linked by the OP in the comments, the two posted keys public_key = "Tub..."
and private_key = "tBs..."
are an API key and API secret for authenticating requests to the Delta Exchange API. The API secret is used for signing and verifying the requests with an HMAC, as shown in the sample code of the documentation. In particular, API secret and API key do not form a related key pair consisting of a private and public key as used e.g. in the context of RSA.