Hello everyone!

We have been investing plenty of personal time and energy for many years to share our knowledge with you all. However, we now need your help to keep this blog running. All you have to do is just click one of the adverts on the site, otherwise it will sadly be taken down due to hosting etc. costs. Thank you.

In this example, our client is going to use OpenSSL to sign the data before sending it to the server. When the server receives the data it will use OpenSSL to check if it was signed with the given signature or not. If the verification was successful then the client is considered as trustworthy. In this process client uses his "private key" and the server uses client's "public key" for verification. See more info for Signing HTTP Messages, openssl_sign and openssl_verify.




I am being very lazy here and doing too much in controller but it is purely because I wanted to keep the post as short as possible. You can move the logic to a service class - just an example.

namespace App\Controller;

use App\Util\SignatureUtil;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

* @Route("/signatures")
class SignatureController
private $signatureUtil;
private $publicKey;
private $privateKey;

public function __construct(
SignatureUtil $signatureUtil,
string $publicKey,
string $privateKey
) {
$this->signatureUtil = $signatureUtil;
$this->publicKey = $publicKey;
$this->privateKey = $privateKey;

* @Route("/client/create-keys", methods={"GET"})
public function createKeys(): Response
$keys = $this->signatureUtil->createKeys();

file_put_contents($this->publicKey, $keys['public_key']);
file_put_contents($this->privateKey, $keys['private_key']);

return new JsonResponse('Done!');

* @Route("/client/create-signature", methods={"POST"})
public function createSignature(Request $request): Response
$privateKey = file_get_contents($this->privateKey);
$data = json_decode($request->getContent(), true)['data'];
$signature = $this->signatureUtil->createSignature($data, $privateKey);

return new JsonResponse($signature);

* @Route("/server/verify-signature", methods={"POST"})
public function verifySignature(Request $request): Response
$publicKey = file_get_contents($this->publicKey);
$data = json_decode($request->getContent(), true)['data'];
$signature = $request->headers->get('X-Signature');

$valid = $this->signatureUtil->verifySignature($data, $signature, $publicKey);

return new JsonResponse(['valid' => $valid]);


$publicKey: '%kernel.project_dir%/config/ssl/public_key.pem'
$privateKey: '%kernel.project_dir%/config/ssl/private_key.pem'


Read the comments in the code please.

namespace App\Util;

use RuntimeException;

class SignatureUtil
* This is only relevant to server API.
* The "public_key" is shared with server.
* The "private_key" is kept securely by the client only.
public function createKeys(): array
$keys = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);

openssl_pkey_export($keys, $privateKey);

$keyDetails = openssl_pkey_get_details($keys);
$publicKey = $keyDetails['key'];

return [
'public_key' => $publicKey,
'private_key' => $privateKey,

* This is only relevant to client API.
* The client calls this method to create a "signature" based on the "data".
* The client then sends the "signature" and the "data" to the server.
public function createSignature(string $data, string $privateKey): string
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256);

return base64_encode($signature);

* This is only relevant to server API.
* The server checks "data" against the "signature" to see if the client is genuine or not.
public function verifySignature(string $data, string $signature, string $publicKey): bool
$verify = openssl_verify($data, base64_decode($signature), $publicKey, 'sha256WithRSAEncryption');

if (1 == $verify) {
return true;

if (0 === $verify) {
return false;

throw new RuntimeException('An error occurred while verifying the signature.');


Create client keys

$ ls -l config/ssl/
total 0

$ curl -X GET \
> http://localhost/signatures/client/create-keys

$ ls -l config/ssl/
-rw-rw-rw- 1 501 dialout 1704 Jan 13 2019 private_key.pem
-rw-rw-rw- 1 501 dialout 451 Jan 13 2019 public_key.pem

Create signature

$ curl -X POST \
http://localhost/signatures/client/create-signature \
-H 'Content-Type: application/json' \
-d '{
"data": "Hello World!"


Consume API

Use signature in X-Signature header.

$ curl -X POST \
> http://localhost/signatures/server/verify-signature \
> -H 'Content-Type: application/json' \
> -H 'X-Signature: S3PRm0yePp3fIgEhtIYXq+oQrFkbkxSHiJHbyESu0pLRILg7N2AWrdsvkK6LzK9F2WB1DKXrAAXD3L+R0au2penzBCnX9ERtJXZq71h2qVZYbkrxbY4jQsYU/aUg3KxakVQxTi4gehj0zeGvTV6OxkjFbKq0/KLgWD9AjYZxeWnmFhh8wbbGtmFMZzBkGU7iXSC+/5kKguI28WWt1ALYHJJxSsJWmZnccnAELo6tVhqBKxavuB/E6TZTEiWGeTC0wAWcjuqfEIFoA5b1fx73L6vcCGqf17Ov42NIJL50BRlmRcHkmnNJXVUUbSLmI+Cu+uzwE3KN9z/pVv8dfLIFAA==' \
> -d '{
> "data": "Hello World!"
> }'