在Symfony APIs内用OpenSSL进行请求信息签名和验证的实例

112 阅读2分钟

在这个例子中,我们的客户端将使用OpenSSL来签署数据,然后再将其发送到服务器。当服务器收到数据时,它将使用OpenSSL来检查它是否用给定的签名签署过。如果验证成功,那么客户端就被认为是值得信赖的。在这个过程中,客户使用他的 "私钥",服务器使用客户的 "公钥 "进行验证。请看更多关于签署HTTP消息openssl_signopenssl_verify的信息。

流程

类别

签名控制器(SignatureController

我在这里非常懒惰,在控制器中做了太多的事情,但这纯粹是因为我想让帖子尽可能的简短。你可以把逻辑移到一个服务类中--只是一个例子。

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]);
    }
}
services:
    ...

    App\Controller\SignatureController:
        arguments:
            $publicKey: '%kernel.project_dir%/config/ssl/public_key.pem'
            $privateKey: '%kernel.project_dir%/config/ssl/private_key.pem'
  • /client/create-keys - 使用这个端点,以编程方式创建客户的公钥和私钥。然后把私钥给客户,公钥给服务器。**注意:**我添加这个端点只是为了演示,所以你不应该在现实世界中这样做。相反,在终端创建这些密钥。

  • /client/create-signature - 使用这个端点来签署你的数据。这个端点的响应将包含签名,它将和实际数据一起被发送到服务器。注意:假设这个端点是在客户端API中。

  • /server/verify-signature - 当客户端发送请求时,它将在这里登陆,签名验证在这里进行。:假设这个端点是在服务器API中。

签章工具(SignatureUtil

请阅读代码中的注释:

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.');
    }
}

测试

创建客户端密钥

$ ls -l config/ssl/
total 0

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

$ 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

创建签名

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

"S3PRm0yePp3fIgEhtIYXq+oQrFkbkxSHiJHbyESu0pLRILg7N2AWrdsvkK6LzK9F2WB1DKXrAAXD3L+R0au2penzBCnX9ERtJXZq71h2qVZYbkrxbY4jQsYU/aUg3KxakVQxTi4gehj0zeGvTV6OxkjFbKq0/KLgWD9AjYZxeWnmFhh8wbbGtmFMZzBkGU7iXSC+/5kKguI28WWt1ALYHJJxSsJWmZnccnAELo6tVhqBKxavuB/E6TZTEiWGeTC0wAWcjuqfEIFoA5b1fx73L6vcCGqf17Ov42NIJL50BRlmRcHkmnNJXVUUbSLmI+Cu+uzwE3KN9z/pVv8dfLIFAA=="

消耗API

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!"
> }'

{"valid":true}