paragonie/sapient

安全API工具包

v1.1.2 2021-06-26 16:59 UTC

This package is auto-updated.

Last update: 2024-09-12 09:06:25 UTC


README

Build Status Latest Stable Version Latest Unstable Version License

Sapient 可确保您的PHP应用程序在TLS安全崩溃(受损害的证书机构等)之后也能保护服务器之间的HTTP(S)流量。

Sapient 允许您快速轻松地将应用层加密添加到API请求和响应中。 需要PHP 7或更高版本。

Sapient 由 Paragon Initiative Enterprises 的PHP安全和加密团队 设计和实现。

有关其设计理由和动机的更多信息,请参阅 我们的关于使用Sapient强化PHP应用程序API的博客文章

加密由 sodium_compat 提供(它将使用已安装的PECL中的libsodium扩展)。

由于sodium_compat在字符串上操作,而不是在资源(即流)上操作,因此Sapient不适用于内存非常低的系统上的极大规模消息。Sapient 仅加密或验证消息正文;如果需要加密或验证头部,那是传输层安全(TLS)的工作。

功能速览

  • RequestResponse 对象(PSR-7)兼容
    • 包含Guzzle客户端适配器
  • 安全API
    • 共享密钥加密
      • XChaCha20-Poly1305
    • 共享密钥认证
      • HMAC-SHA512-256
    • 匿名公钥加密
      • X25519 + BLAKE2b + XChaCha20-Poly1305
    • 公钥数字签名
      • Ed25519
  • 与数组兼容
    • 例如,名称中包含 "Json" 的方法
    • 发送/接收签名或加密JSON
  • 与字符串兼容
    • 例如,名称中不包含 "Json" 的方法
  • 数字签名和认证与未签名JSON API客户端和服务器向后兼容
    • 签名和认证标签将放入HTTP头部,而不是请求/响应正文。

此外,Sapient 由 单元测试(由 PHPUnit 提供)和 自动静态分析(由 Psalm 提供)覆盖。

Sapient适配器

如果您想将Sapient集成到现有框架中

如果您的框架正确实现了PSR-7,您可能不需要适配器。但是,一些适配器提供了方便的方法,使快速开发更容易。

有关适配器的更多信息,请参阅 AdapterInterface 文档

Sapient在其他语言中

示例1:签名PSR-7响应

本示例展示了一个最小化实现,将 Ed25519 签名添加到现有的 PSR-7 HTTP 响应中。

服务器端:签名 HTTP 响应

<?php
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\Sapient\Sapient;
use ParagonIE\Sapient\CryptographyKeys\SigningSecretKey;
use Psr\Http\Message\ResponseInterface;

/**
 * @var ResponseInterface $response
 *
 * Let's assume we have a valid ResponseInterface object already.
 * (Most likely, after doing normal framework things.)  
 */

$sapient = new Sapient();
$serverSignSecret = new SigningSecretKey(
    Base64UrlSafe::decode(
        'q6KSHArUnD0sEa-KWpBCYLka805gdA6lVG2mbeM9kq82_Cwg1n7XLQXXXHF538URRov8xV7CF2AX20xh_moQTA=='
    )
);

$signedResponse = $sapient->signResponse($response, $serverSignSecret);

客户端:验证签名

<?php
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\Sapient\Sapient;
use ParagonIE\Sapient\CryptographyKeys\SigningPublicKey;
use ParagonIE\Sapient\Exception\{
    HeaderMissingException,
    InvalidMessageException
};
use Psr\Http\Message\ResponseInterface;

/**
 * @var ResponseInterface $response
 *
 * Let's assume we have a valid ResponseInterface object already.
 * (Most likely the result of an HTTP request to the server.)  
 */

$sapient = new Sapient();
$serverPublicKey = new SigningPublicKey(
    Base64UrlSafe::decode(
        'NvwsINZ-1y0F11xxed_FEUaL_MVewhdgF9tMYf5qEEw='
    )
);

try {
    $verified = $sapient->verifySignedResponse($response, $serverPublicKey);
} catch (HeaderMissingException $ex) {
    /* The server didn't provide a header. Discard and log the error! */
} catch (InvalidMessageException $ex) {
    /* Invalid signature for the message. Discard and log the error! */
}

示例 2:使用 Guzzle 适配器的双向签名 JSON API

此示例利用了一个适配器,该适配器提供了在 ConvenienceInterface 中描述的便捷方法。

客户端:发送签名请求,验证响应

<?php
use GuzzleHttp\Client;
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\Sapient\Adapter\Guzzle as GuzzleAdapter;
use ParagonIE\Sapient\Sapient;
use ParagonIE\Sapient\CryptographyKeys\SigningPublicKey;
use ParagonIE\Sapient\CryptographyKeys\SigningSecretKey;
use ParagonIE\Sapient\Exception\InvalidMessageException;

$http = new Client([
    'base_uri' => 'https://your-api.example.com'
]);
$sapient = new Sapient(new GuzzleAdapter($http));

// Keys
$clientSigningKey = new SigningSecretKey(
    Base64UrlSafe::decode(
        'AHxoibWhTylBMgFzJp6GGgYto24PVbQ-ognw9SPnvKppfti72R8By8XnIMTJ8HbDTks7jK5GmAnvtzaj3rbcTA=='
    )
);
$serverPublicKey = new SigningPublicKey(
    Base64UrlSafe::decode(
        'NvwsINZ-1y0F11xxed_FEUaL_MVewhdgF9tMYf5qEEw='
    )
);

// We use an array to define our message
$myMessage = [
    'date' => (new DateTime)->format(DateTime::ATOM),
    'body' => [
        'test' => 'hello world!'        
    ]
];

// Create the signed request:
$request = $sapient->createSignedJsonRequest(
    'POST',
     '/my/api/endpoint',
     $myMessage,
     $clientSigningKey
);

$response = $http->send($request);
try {
    /** @var array $verifiedResponse */
    $verifiedResponse = $sapient->decodeSignedJsonResponse(
        $response,
        $serverPublicKey
    );
} catch (InvalidMessageException $ex) {
    \http_response_code(500);
    exit;
}

服务器端:验证签名请求,签名响应

 <?php
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\ServerRequest;
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\Sapient\Adapter\Guzzle as GuzzleAdapter;
use ParagonIE\Sapient\Sapient;
use ParagonIE\Sapient\CryptographyKeys\SigningPublicKey;
use ParagonIE\Sapient\CryptographyKeys\SigningSecretKey;
use ParagonIE\Sapient\Exception\InvalidMessageException;

$http = new Client([
    'base_uri' => 'https://your-api.example.com'
]);
$sapient = new Sapient(new GuzzleAdapter($http));
 
$clientPublicKey = new SigningPublicKey(
    Base64UrlSafe::decode(
        'aX7Yu9kfAcvF5yDEyfB2w05LO4yuRpgJ77c2o9623Ew='
    )
);
$request = ServerRequest::fromGlobals();
try {
    /** @var array $decodedRequest */
    $decodedRequest = $sapient->decodeSignedJsonRequest(
        $request,
        $clientPublicKey
    );
} catch (InvalidMessageException $ex) {
    \http_response_code(500);
    exit;
}

/* Business logic goes here */

// Signing a response:
$serverSignSecret = new SigningSecretKey(
    Base64UrlSafe::decode(
        'q6KSHArUnD0sEa-KWpBCYLka805gdA6lVG2mbeM9kq82_Cwg1n7XLQXXXHF538URRov8xV7CF2AX20xh_moQTA=='
    )
);

$responseMessage = [
    'date' => (new DateTime)->format(DateTime::ATOM),
    'body' => [
        'status' => 'OK',
        'message' => 'We got your message loud and clear.'
    ]
];

$response = $sapient->createSignedJsonResponse(
    200,
    $responseMessage,
    $serverSignSecret
);
/* If your framework speaks PSR-7, just return the response object and let it
   take care of the rest. */