paragonie / sapient
安全API工具包
v1.1.2
2021-06-26 16:59 UTC
Requires
- php: ^7|^8
- guzzlehttp/psr7: ^1
- paragonie/constant_time_encoding: ^2
- paragonie/sodium_compat: >= 1.15.4
- psr/http-message: ^1
Requires (Dev)
- guzzlehttp/guzzle: ^6|^7
- phpunit/phpunit: ^6|^7|^8|^9
- vimeo/psalm: ^2|^3|^4
Suggests
- guzzlehttp/guzzle: >= 6.0 -- Sapient can be used a Guzzle adapter
README
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)的工作。
功能速览
- 与
Request
和Response
对象(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集成到现有框架中
- Guzzle
- 适配器已包含,但Guzzle本身不是依赖项。
- 如果您想使用Guzzle,请添加建议的包(例如,
composer require guzzlehttp/guzzle:^6
)
- Laravel Sapient适配器
composer require mcordingley/laravel-sapient
- Slim Framework Sapient适配器
composer require paragonie/slim-sapient
- Zend Framework Diactoros Sapient适配器
composer require paragonie/zend-diactoros-sapient
- Symfony捆绑包
composer require lepiaf/sapient-bundle
如果您的框架正确实现了PSR-7,您可能不需要适配器。但是,一些适配器提供了方便的方法,使快速开发更容易。
有关适配器的更多信息,请参阅 AdapterInterface
文档。
Sapient在其他语言中
- sapient.js(JavaScript,Node.js)
示例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. */