此包已被弃用且不再维护。作者建议使用 ncryptf/ncryptf-php 包代替。

ncryptf for PHP

0.2.1 2019-04-15 17:08 UTC

This package is not auto-updated.

Last update: 2022-02-01 13:14:20 UTC


README

Packagist Pre Release TravisCI Scrutinizer Code Quality License

ncryptf logo

这是一个库,用于简化基于散列的KDF签名认证和与兼容API的端到端加密通信。

HMAC+HKDF认证

HMAC+HKDF认证是一种认证方法,确保请求在传输过程中未被篡改。这不仅可以抵御网络层的操作,还可以抵御中间人攻击。

在较高层面,基于原始请求体、HTTP方法、URI(如果存在,则包含查询参数)和当前日期创建HMAC签名。除了确保请求在传输过程中不能被操作外,还确保请求有时间限制,有效地防止重放攻击。

此库通过composer提供

composer require ncryptf/ncryptf-php

支持API将返回以下有效负载,至少包含以下信息。

{
    "access_token": "7XF56VIP7ZQQOLGHM6MRIK56S2QS363ULNB5UKNFMJRQVYHQH7IA",
    "refresh_token": "MA2JX5FXWS57DHW4OIHHQDCJVGS3ZKKFCL7XM4GNOB567I6ER4LQ",
    "ikm": "bDEyECRvKKE8w81fX4hz/52cvHsFPMGeJ+a9fGaVvWM=",
    "signing": "7v/CdiGoEI7bcj7R2EyDPH5nrCd2+7rHYNACB+Kf2FMx405und2KenGjNpCBPv0jOiptfHJHiY3lldAQTGCdqw==",
    "expires_at": 1472678411
}

提取元素后,我们可以通过以下方式创建签名请求

use DateTime;
use ncryptf\Token;
use ncryptf\Authorization;

$date = new DateTime;
$token = new Token(
    $accessToken,
    $refreshToken,
    \base64_decode($ikm), // IKM must be in it's byte form, as oppose to the base64 representation returned by the server
    \base64_decode($signature), // Signature is the same,
    $expiresAt
);

$auth = new Authorization(
    $httpMethod,
    $uri,
    $token,
    new DateTime,
    $payload
);

$header = $auth->getHeader();

以下是一个简单的完整示例

use DateTime;
use ncryptf\Token;
use ncryptf\Authorization;

$date = new DateTime;
$token = new Token(
    '7XF56VIP7ZQQOLGHM6MRIK56S2QS363ULNB5UKNFMJRQVYHQH7IA',
    'MA2JX5FXWS57DHW4OIHHQDCJVGS3ZKKFCL7XM4GNOB567I6ER4LQ',
    \base64_decode('bDEyECRvKKE8w81fX4hz/52cvHsFPMGeJ+a9fGaVvWM='),
    \base64_decode('7v/CdiGoEI7bcj7R2EyDPH5nrCd2+7rHYNACB+Kf2FMx405und2KenGjNpCBPv0jOiptfHJHiY3lldAQTGCdqw=='),
    1472678411
);

$auth = new Authorization(
    'POST',
    '/api/v1/test',
    $token,
    new DateTime,
    [
        'foo' => 'bar'
    ]
);

$header = $auth->getHeader();

注意,在调用new Authorization时,应预先偏移$date参数以防止时间偏移。

Authorization::__construct中的$payload参数应该是一个可序列化的JSON数组,但也可以传递序列化的JSON字符串。

版本2 HMAC头

对于支持版本2 HMAC头的API,可以通过调用获取版本2 HMAC头

$header = $auth->getHeader();

版本1 HMAC头

对于使用版本1 HMAC头的API,请在调用new Authorization时,将可选的version参数设置为1作为第6个参数。

$auth = new Authorization(
    $httpMethod,
    $uri,
    $token,
    new DateTime,
    $payload,
    1
);

$auth->getHeader(),

此字符串可以用作Authorization

日期头

版本1 HMAC头需要额外的X-Date头。可以通过调用authorization.getDateString()获取X-Date头。

验证

此库还可以验证客户端生成的HMAC。以下提供了一个高级示例(伪代码)

use DateTime;
use ncryptf\Authorization;
use ncryptf\Token as NcryptfToken;

public function authenticate($user, $request, $response)
{
    // Extract the parameters from the header string
    $params = Authorization::extractParamsFromHeaderString($request->getHeaders()->get('Authorization'));

    if ($params) {
        // Your API should implement a method to fetch all token data from the access token
        // Typically this is stored in a cache of some kind, such as Redis
        if ($token = $this->getTokenFromAccessToken($params['access_token'])) {
            try {
                // Determine the appropriate date to use, depending upon the version
                $date = new DateTime($params['date'] ?? $request->getHeaders()->get('X-Date'));

                // Construct a new server side Authorization object
                $auth = new Authorization(
                    $request->getHttpMethod(), // GET, POST, PUT... etc
                    $request->getUrl(), // The URI with query parameters
                    $token->getNcryptfToken(), // Your token object should support data extraction to an ncryptf/Token type
                    $date,
                    $request->getRawBody(), // The raw JSON in the request. If you're using encrypted requests, this should be decrypted
                    $params['v'], // The version of the HMAC header to validate
                    \base64_decode($params['salt']) // The salt value from the parameters
                );

                // Verify the HMAC submitted against the newly generated auth object
                if ($auth->verify(\base64_decode($params['hmac']), $auth)) {
                    
                    // Do your login here
                    //
                    //
                }
            } catch (\Exception $e) {
                // Handle exceptions here
            }
        }
    }

   // Handle authentication failures
}

加密请求和响应

此库使PHP 7.1+的开发者能够在TLS层上建立信任的加密会话,同时(并且独立地)提供通过HMAC+HKDF样式认证验证和识别客户端的能力。

此功能的原因包括但不限于

  1. 需要额外的安全层
  2. 对网络或TLS本身缺乏信任(参见https://blog.cloudflare.com/incident-report-on-memory-leak-caused-by-cloudflare-parser-bug/
  3. 需要确保服务器提供的初始密钥材料(IKM)在HMAC+HKDF认证中的机密性
  4. 需要确保API认证中用户提交的凭证的机密性

您可能想要与API本身建立加密会话的主要原因是为了确保IKM的机密性,防止数据在不受信任的网络中泄露,以避免在类似Cloudflare事件(或任何中间人攻击)中信息被暴露。加密会话使您在内存泄漏再次发生时,有信心IKM和其他安全数据不会被暴露,从而可以利用像Cloudflare这样的服务。

加密请求数据

负载可以按照以下方式加密

use ncryptf\Request;
use ncryptf\Utils;
use ncryptf\exceptions\EncryptionFailedException;

try {
    // Generate your request keypair for your local device.
    $keypair = Utils::generateKeypair();
    $signatureKp = Utils::generateSigningKeypair()
    // Create a new request object with your private key
    // and the servers private key
    $request = new Request(
        $privateKeypair->getSecretKey(),
        $signatureKp->getSecretKey
    );

    // Encrypt JSON
    $encryptedRequest = $request->encrypt(
        '{ "foo": "bar" }',
        $remotePublicKey
    );
} catch (EncryptionFailedException $e) {
    // Encrypting the body failed
}

注意,这里只展示了v2加密。

注意,您需要有一个预引导的公钥来加密数据。对于v1 API,这通常是/api/v1/server/otk返回的内容。

解密响应

服务器的响应可以按照以下方式解密

use ncryptf\Response;
use ncryptf\exceptions\DecryptionFailedException;
use ncryptf\exceptions\InvalidChecksumException;
use ncryptf\exceptions\InvalidSignatureException;

// Represents the httpResponse
try {
    // Create a new request object with your private key
    // and the servers private key
    $response = new Response(
        \sodium_crypto_box_secretkey($privateKeypair['secret']),
    );

    // Extract the raw body from the response
    $rawBody = \base64_decode($httpResponse->getBody());
    $jsonResponse = $response->decrypt(
        $rawBody,
        $remotePublicKey
    );
} catch (DecryptionFailedException $e) {
    // Decryption failed
} catch (InvalidChecksumException $e) {
    // Request checksum failed
} catch (InvalidSignatureException $e) {
    // Signature verification failed
}

V2加密负载

版本2与版本1负载功能相同,不同之处在于所有解密消息所需的组件都包含在负载本身中,而不是分开到单独的标题中。这减轻了开发者需要管理多个标题的担忧。

版本2负载描述如下。每个组件连接在一起。

长度
4字节标题DE259002,二进制格式 4字节
随机数 24字节
与私钥关联的公钥 32字节
加密体 X字节 + 16字节MAC
签名公钥 32字节
签名或原始请求数据 64字节
先前元素连接在一起的总和校验和 64字节

PSR-15中间件

认证

Ncryptf通过ncryptf\middleware\AbstractAuthentication支持PSR-15中间件,只需扩展以提取令牌和检索用户即可。

use ncryptf\middleware\AbstractAuthentication;

final class Authentication extends AbstractAuthentication
{
    /**
     *  Given an access token, return an `ncryptf\Token` instance.
     */
    protected function getTokenFromAccessToken(string $accessToken) :? Token
    {
        // Search for token in database
        return \ncryptf\Token(...);
    }

    protected function getUserFromToken(Token $token)
    {
        // Convert a token to a user.
        return User::find()
            ->where(['access_token' => $token['access_token']])
            ->one();
    }
}

以下是一个简单的示例

use Authentication;
use Middlewares\Utils\Dispatcher;

$response = Dispatcher::run([
    new Authentication,
    function ($request, $next) {
        // This is your user, do whatever you need to do here.
        $user = $request->getAttribute('ncryptf-user');
        return $next->handle($request);
    }
], $request);

安全请求解析

提供了一个PSR-15中间件来解密使用application/vnd.ncryptf+json加密的请求。请求解密可以独立于认证请求执行,并在需要传输敏感数据但返回HTTP 204或非元数据泄露响应的情况下非常有用。

然而,理想情况下,这始终与认证请求和相应的加密响应相结合。

为了确保消息可以解密,需要三个组件

  1. 一个PSR-16缓存实例,其中存储您的加密密钥。本指南建议使用Redis或memcache等分布式缓存以方便长期存储。

  2. 一个表示可缓存的加密密钥的ncryptf\middleware\EncryptionKeyInterface类。

  3. 在请求数据操作之前,在派发器开头注入ncryptf\middleware\RequestParser

use ncryptf\middleware\RequestParser;
use Middlewares\Utils\Dispatcher;

$PSR16CacheInterface = new class implements \Psr\SimpleCache\CacheInterface {};

$response = Dispatcher::run([
    new RequestParser($PSR16CacheInterface),
    function ($request, $next) {
        // This is the plain-text decrypted body
        $decryptedBody = $request->getAttribute('ncryptf-decrypted-body');

        // The parsed body
        $params = $request->getParsedBody();
        return $next->handle($request);
    }
], $request);

安全响应格式化

当与认证的ncryptf请求结合时,ncryptf\middleware\ResponseFormatter可以将给定的响应格式化为application/vnd.ncryptf+json响应。格式化器目前只能处理JSON负载。

此实现必须与一个 ncryptf\middleware\AbstractAuthentication 实例一起使用,并建议与由 ncryptf\middleware\RequestParser 处理的安全请求一起使用,以确保消息的端到端加密。

ncryptf\middleware\ResponseFormatter 构造函数接收一个 Psr\SimpleCache\CacheInterface 实例来存储新生成的 ncryptf\middleware\EncryptionKeyInterface,以及一个 ncryptf\middleware\EncryptionKeyInterface 实例来构造一个新的密钥对,以确保前向安全。

use Authentication;
use ncryptf\middleware\EncryptionKeyInterface;
use ncryptf\middleware\ResponseFormatter;
use ncryptf\middleware\RequestParser;
use Middlewares\Utils\Dispatcher;

$PSR16CacheInterface = new class implements \Psr\SimpleCache\CacheInterface {};

$response = Dispatcher::run([
    new RequestParser($PSR16CacheInterface),
    new Authentication,
    new ResponseFormatter($PSR16CacheInterface, $EncryptionKeyInterface::class)
    function ($request, $next) {
        return new JsonResponse(['hello' => 'world'])
    }
], $request);

请参考 tests 目录以获取完整的端到端实现示例。