{"id":13519158,"url":"https://github.com/paragonie/sapient","last_synced_at":"2025-04-13T00:49:12.917Z","repository":{"id":41293786,"uuid":"94958009","full_name":"paragonie/sapient","owner":"paragonie","description":"Secure API Toolkit","archived":false,"fork":false,"pushed_at":"2022-05-12T04:10:09.000Z","size":107,"stargazers_count":311,"open_issues_count":0,"forks_count":26,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-04-13T00:49:09.616Z","etag":null,"topics":["cryptography","guzzle","json","libsodium","sapient"],"latest_commit_sha":null,"homepage":"https://paragonie.com/blog/2017/06/hardening-your-php-powered-apis-with-sapient","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/paragonie.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-06-21T03:14:32.000Z","updated_at":"2025-01-18T17:06:52.000Z","dependencies_parsed_at":"2022-09-01T15:34:41.514Z","dependency_job_id":null,"html_url":"https://github.com/paragonie/sapient","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paragonie%2Fsapient","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paragonie%2Fsapient/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paragonie%2Fsapient/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paragonie%2Fsapient/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paragonie","download_url":"https://codeload.github.com/paragonie/sapient/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248650436,"owners_count":21139672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cryptography","guzzle","json","libsodium","sapient"],"created_at":"2024-08-01T05:01:54.791Z","updated_at":"2025-04-13T00:49:12.899Z","avatar_url":"https://github.com/paragonie.png","language":"PHP","funding_links":[],"categories":["PHP","身份验证( Authentication and Authorization )"],"sub_categories":[],"readme":"# Sapient: Secure API toolkit\n\n[![Build Status](https://github.com/paragonie/sapient/actions/workflows/ci.yml/badge.svg)](https://github.com/paragonie/sapient/actions)\n[![Latest Stable Version](https://poser.pugx.org/paragonie/sapient/v/stable)](https://packagist.org/packages/paragonie/sapient)\n[![Latest Unstable Version](https://poser.pugx.org/paragonie/sapient/v/unstable)](https://packagist.org/packages/paragonie/sapient)\n[![License](https://poser.pugx.org/paragonie/sapient/license)](https://packagist.org/packages/paragonie/sapient)\n\n**Sapient** secures your PHP applications' server-to-server HTTP(S) traffic even in the wake of a\nTLS security breakdown (compromised certificate authority, etc.).\n\nSapient allows you to quickly and easily add application-layer cryptography to your API requests\nand responses. **Requires PHP 7 or newer.**\n\nSapient was designed and implemented by [the PHP security and cryptography team at Paragon Initiative Enterprises](https://paragonie.com).\n\n\u003e See [our blog post about using Sapient to harden your PHP-powered APIs](https://paragonie.com/blog/2017/06/hardening-your-php-powered-apis-with-sapient)\n\u003e for more information about its design rationale and motivation.\n\nThe cryptography is provided by [sodium_compat](https://github.com/paragonie/sodium_compat) (which,\nin turn, will use the libsodium extension in PECL if it's installed).\n\nBecause sodium_compat operates on strings rather than resources (a.k.a. streams), Sapient is not\nsuitable for extremely large messages on systems with very low available memory.\nSapient [only encrypts or authenticates message bodies](https://github.com/paragonie/sapient/blob/master/docs/Internals/Sapient.md#important);\nif you need headers to be encrypted or authenticated, that's the job of Transport-Layer Security (TLS).\n\n## Features at a Glance\n\n* Works with both `Request` and `Response` objects (PSR-7)\n  * Includes a Guzzle adapter for HTTP clients\n* Secure APIs:\n  * Shared-key encryption\n    * XChaCha20-Poly1305\n  * Shared-key authentication\n    * HMAC-SHA512-256\n  * Anonymous public-key encryption\n    * X25519 + BLAKE2b + XChaCha20-Poly1305\n  * Public-key digital signatures\n    * Ed25519\n* Works with arrays\n  * i.e. the methods with \"Json\" in the name\n  * Sends/receives signed or encrypted JSON\n* Works with strings\n  * i.e. the methods without \"Json\" in the name\n* Digital signatures and authentication are backwards-compatible\n  with unsigned JSON API clients and servers\n  * The signaure and authentication tag will go into HTTP headers,\n    rather than the request/response body.\n\nAdditionally, Sapient is covered by both **unit tests** (provided by [PHPUnit](https://github.com/sebastianbergmann/phpunit)) and\n**automated static analysis** (provided by [Psalm](https://github.com/vimeo/psalm)).\n\n## Sapient Adapters\n\nIf you're looking to integrate Sapient into an existing framework:\n\n* **Guzzle**\n  * Adapter is included, but Guzzle itself is not a dependency. \n  * Add the suggested package if you want to use Guzzle (e.g. `composer require guzzlehttp/guzzle:^6`)\n* [Laravel Sapient Adapter](https://github.com/mcordingley/LaravelSapient)\n  * `composer require mcordingley/laravel-sapient`\n* [Slim Framework Sapient Adapter](https://github.com/paragonie/slim-sapient)\n  * `composer require paragonie/slim-sapient`\n* [Zend Framework Diactoros Sapient Adapter](https://github.com/paragonie/zend-diactoros-sapient)\n  * `composer require paragonie/zend-diactoros-sapient`\n* [Symfony bundle](https://github.com/lepiaf/sapient-bundle)\n  * `composer require lepiaf/sapient-bundle`\n\nIf your framework correctly implements PSR-7, you most likely do not need an adapter. However,\nsome adapters provide convenience methods that make rapid development easier.\n\nTo learn more about adapters, see [the documentation for `AdapterInterface`](docs/Internals/Adapter/AdapterInterface.md).\n\n## Sapient in Other Languages\n\n* [sapient.js](https://github.com/paragonie/sapient-js) (JavaScript, Node.js)\n\n## Example 1: Signed PSR-7 Responses\n\nThis demonstrats a minimal implementation that adds Ed25519 signatures to your\nexisting PSR-7 HTTP responses.\n\n### Server-Side: Signing an HTTP Response\n\n```php\n\u003c?php\nuse ParagonIE\\ConstantTime\\Base64UrlSafe;\nuse ParagonIE\\Sapient\\Sapient;\nuse ParagonIE\\Sapient\\CryptographyKeys\\SigningSecretKey;\nuse Psr\\Http\\Message\\ResponseInterface;\n\n/**\n * @var ResponseInterface $response\n *\n * Let's assume we have a valid ResponseInterface object already.\n * (Most likely, after doing normal framework things.)  \n */\n\n$sapient = new Sapient();\n$serverSignSecret = new SigningSecretKey(\n    Base64UrlSafe::decode(\n        'q6KSHArUnD0sEa-KWpBCYLka805gdA6lVG2mbeM9kq82_Cwg1n7XLQXXXHF538URRov8xV7CF2AX20xh_moQTA=='\n    )\n);\n\n$signedResponse = $sapient-\u003esignResponse($response, $serverSignSecret);\n```\n\n### Client-Side: Verifying the Signature\n\n```php\n\u003c?php\nuse ParagonIE\\ConstantTime\\Base64UrlSafe;\nuse ParagonIE\\Sapient\\Sapient;\nuse ParagonIE\\Sapient\\CryptographyKeys\\SigningPublicKey;\nuse ParagonIE\\Sapient\\Exception\\{\n    HeaderMissingException,\n    InvalidMessageException\n};\nuse Psr\\Http\\Message\\ResponseInterface;\n\n/**\n * @var ResponseInterface $response\n *\n * Let's assume we have a valid ResponseInterface object already.\n * (Most likely the result of an HTTP request to the server.)  \n */\n\n$sapient = new Sapient();\n$serverPublicKey = new SigningPublicKey(\n    Base64UrlSafe::decode(\n        'NvwsINZ-1y0F11xxed_FEUaL_MVewhdgF9tMYf5qEEw='\n    )\n);\n\ntry {\n    $verified = $sapient-\u003everifySignedResponse($response, $serverPublicKey);\n} catch (HeaderMissingException $ex) {\n    /* The server didn't provide a header. Discard and log the error! */\n} catch (InvalidMessageException $ex) {\n    /* Invalid signature for the message. Discard and log the error! */\n}\n```\n\n## Example 2: Mutually Signed JSON API with the Guzzle Adapter\n\nThis example takes advantage of an Adapter the provides the convenience methods\ndescribed in [`ConvenienceInterface`](docs/Internals/Adapter/ConvenienceInterface.md).\n\n### Client-Side: Sending a Signed Request, Verifying the Response\n\n```php\n\u003c?php\nuse GuzzleHttp\\Client;\nuse ParagonIE\\ConstantTime\\Base64UrlSafe;\nuse ParagonIE\\Sapient\\Adapter\\Guzzle as GuzzleAdapter;\nuse ParagonIE\\Sapient\\Sapient;\nuse ParagonIE\\Sapient\\CryptographyKeys\\SigningPublicKey;\nuse ParagonIE\\Sapient\\CryptographyKeys\\SigningSecretKey;\nuse ParagonIE\\Sapient\\Exception\\InvalidMessageException;\n\n$http = new Client([\n    'base_uri' =\u003e 'https://your-api.example.com'\n]);\n$sapient = new Sapient(new GuzzleAdapter($http));\n\n// Keys\n$clientSigningKey = new SigningSecretKey(\n    Base64UrlSafe::decode(\n        'AHxoibWhTylBMgFzJp6GGgYto24PVbQ-ognw9SPnvKppfti72R8By8XnIMTJ8HbDTks7jK5GmAnvtzaj3rbcTA=='\n    )\n);\n$serverPublicKey = new SigningPublicKey(\n    Base64UrlSafe::decode(\n        'NvwsINZ-1y0F11xxed_FEUaL_MVewhdgF9tMYf5qEEw='\n    )\n);\n\n// We use an array to define our message\n$myMessage = [\n    'date' =\u003e (new DateTime)-\u003eformat(DateTime::ATOM),\n    'body' =\u003e [\n        'test' =\u003e 'hello world!'        \n    ]\n];\n\n// Create the signed request:\n$request = $sapient-\u003ecreateSignedJsonRequest(\n    'POST',\n     '/my/api/endpoint',\n     $myMessage,\n     $clientSigningKey\n);\n\n$response = $http-\u003esend($request);\ntry {\n    /** @var array $verifiedResponse */\n    $verifiedResponse = $sapient-\u003edecodeSignedJsonResponse(\n        $response,\n        $serverPublicKey\n    );\n} catch (InvalidMessageException $ex) {\n    \\http_response_code(500);\n    exit;\n}\n\n```\n\n### Server-Side: Verifying a Signed Request, Signing a Response\n\n```php\n \u003c?php\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Psr7\\ServerRequest;\nuse ParagonIE\\ConstantTime\\Base64UrlSafe;\nuse ParagonIE\\Sapient\\Adapter\\Guzzle as GuzzleAdapter;\nuse ParagonIE\\Sapient\\Sapient;\nuse ParagonIE\\Sapient\\CryptographyKeys\\SigningPublicKey;\nuse ParagonIE\\Sapient\\CryptographyKeys\\SigningSecretKey;\nuse ParagonIE\\Sapient\\Exception\\InvalidMessageException;\n\n$http = new Client([\n    'base_uri' =\u003e 'https://your-api.example.com'\n]);\n$sapient = new Sapient(new GuzzleAdapter($http));\n \n$clientPublicKey = new SigningPublicKey(\n    Base64UrlSafe::decode(\n        'aX7Yu9kfAcvF5yDEyfB2w05LO4yuRpgJ77c2o9623Ew='\n    )\n);\n$request = ServerRequest::fromGlobals();\ntry {\n    /** @var array $decodedRequest */\n    $decodedRequest = $sapient-\u003edecodeSignedJsonRequest(\n        $request,\n        $clientPublicKey\n    );\n} catch (InvalidMessageException $ex) {\n    \\http_response_code(500);\n    exit;\n}\n\n/* Business logic goes here */\n\n// Signing a response:\n$serverSignSecret = new SigningSecretKey(\n    Base64UrlSafe::decode(\n        'q6KSHArUnD0sEa-KWpBCYLka805gdA6lVG2mbeM9kq82_Cwg1n7XLQXXXHF538URRov8xV7CF2AX20xh_moQTA=='\n    )\n);\n\n$responseMessage = [\n    'date' =\u003e (new DateTime)-\u003eformat(DateTime::ATOM),\n    'body' =\u003e [\n        'status' =\u003e 'OK',\n        'message' =\u003e 'We got your message loud and clear.'\n    ]\n];\n\n$response = $sapient-\u003ecreateSignedJsonResponse(\n    200,\n    $responseMessage,\n    $serverSignSecret\n);\n/* If your framework speaks PSR-7, just return the response object and let it\n   take care of the rest. */\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparagonie%2Fsapient","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparagonie%2Fsapient","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparagonie%2Fsapient/lists"}