amegatron/cryptoapi

提供用于实现与客户端软件加密API的类

v1.0.4 2014-05-25 09:18 UTC

This package is auto-updated.

Last update: 2024-09-15 16:45:16 UTC


README

提供用于实现与客户端应用加密交互的类

想象一下,你有一个客户端应用程序,例如用C#编写的,它需要与服务器API进行安全(加密)的交互。

这正是CryptoApi能帮到的地方。

密码学基础

我先说,我不擅长密码学,但我理解了基础知识,这帮助我编写了这个包。

加密交互应该如何实现?

该算法基于两种密码学算法:对称和非对称。在这种情况下,AES用于对称,RSA用于非对称。如果你不知道这是什么意思 - 你应该在Google上搜索 :) 互联网上有大量关于这个主题的文章。

  1. 首先,服务器有其私钥。客户端有其对应的公钥(或证书)
  2. 客户端为对称算法生成一个随机的会话密钥。
  3. 然后客户端使用非对称算法加密此密钥并发送给服务器。
  4. 服务器收到密钥后,会向客户端响应OK或ERROR,告诉客户端是否成功接收密钥。服务器还将此密钥存储在会话中,以便未来与客户端通信。
  5. 以后,客户端发送给服务器的任何数据,都使用对称算法和该生成的密钥进行加密。
  6. 来自服务器的所有响应也对客户端进行了加密,同时使用服务器私钥进行了签名,这样客户端就可以验证数据确实来自预期的服务器。

服务器端实现

包安装

在Laravel项目的composer.json中的"require"部分添加以下内容:"amegatron/cryptoapi": "1.0.*"

之后,在控制台运行composer update,确保已下载该包。

如果你使用rtablada/package-installer,可以运行php artisan package:install amegatron/cryptoapi以自动将包的Service Provider添加到应用配置中。

如果你不使用上述包,应手动注册Service Provider。要做到这一点,在app/config/app.php中的"providers"部分添加以下元素:'Amegatron\Cryptoapi\CryptoapiServiceProvider',

此外,您还必须“注册”一个由包提供的输出加密过滤器。在app/filters.php中添加以下内容:

Route::filter('cryptOut', 'Amegatron\Cryptoapi\Filters\OutgoingCryptFilter');

生成密钥对

接下来,你需要为非对称加密生成一个密钥对。要做到这一点,请在控制台运行以下命令:php artisan cryptoapi:generatekeys

默认情况下,将在app/keys文件夹中创建两个文件:private.keypublic.crtpublic.crt仅由客户端需要,因此您可以在将其嵌入到客户端软件后将其删除。但private.key应保持安全。由于您的服务器指向网站的public文件夹,因此此文件不可通过互联网访问。而且不应该。永远不要。保密此文件。除了您的服务器之外,没有人应该知道这个密钥。

默认情况下,密钥大小为1024,但您可以通过指定上面 artisan 命令的--keySize=XXXX选项来覆盖默认值。但1024对于大多数情况来说已经足够了。此外,我没有成功生成2048大小的密钥 - 它花费了我太长时间,所以我取消了进程 :)

实现服务器端API

现在一切准备就绪,可以编写您的API了。

为了简化,我将建议所有的服务器API逻辑都放在一个名为ApiController的单个控制器中。因此,在app/controllers中创建ApiController.php文件,并在app/routes.php中为它创建一个路由。

Route::controller('api', 'ApiController');

正如你可能猜到的,所有我们的加密交互都将指向http://youdomain.com/api

现在,让我们开始实现控制器。

初始化

首先,我们需要实现前面列出的算法中的第3点和第4点。AES算法的密钥由两部分组成:keyiv。两者都可以通过POST方式发送到/api/init

为此,我建议以下代码

public function postInit() {
    if (!(Input::has('key') && Input::has('iv'))) {
        return 'ERROR 1';
    }

    $crypt = App::make('CryptographyInterface');

    extract(Input::only('key', 'iv'));
    $key = $crypt->asymmetricDecrypt($key);
    $iv = $crypt->asymmetricDecrypt($iv);

    if (!($key && $iv)) {
        return 'ERROR 2';
    }

    $crypt->initSymmetric(array(
        'key'   => $key,
        'iv'    => $iv,
    ));

    return 'OK';
}

这里需要注意什么?首先,通过Laravel IoC创建$crypt对象。目前,它创建了一个实现接口Amegatron\Cryptoapi\Cryptography\CryptographyInterfaceAmegatron\Cryptoapi\Cryptography\RsaAesCryptography对象。此对象包含我们将需要的所有加密、解密和签名方法。

其次,是对称“驱动”的初始化($crypt->initSymmetric(...);)。它将AES密钥存储在会话中,以供以后使用。

另外,请注意,从/api/init返回的响应不是加密的(以防此初始化失败)。此外,响应消息不是“对话式的”。客户端应该知道例如“ERROR 1”代表什么。这是为了不向可能的黑客透露API的内部结构。

最后,如果您喜欢以下代码,您可以直接使用预先制作的特质,Amegatron\Cryptoapi\Traits\RsaAesControllerTrait,它已经具有此postInit方法。

use Amegatron\Cryptoapi\Traits\RsaAesControllerTrait;

或者,如果您太懒或您的IDE没有自动插入特质的全名,您可以直接

use RsaAesControllerTrait;

特质的别名已在包服务提供者中注册。

特定应用的API

为了演示目的,让我们假设我们的服务器应用的目的是为了验证客户端的许可证:客户端向服务器发送许可证密钥,服务器响应,告诉客户端该许可证密钥是否有效,以及客户端软件的进一步使用是否允许。

在我们继续编码之前,我应该提醒,所有来自客户端的数据都是加密的(使用AES)。为了方便解密传入的数据,我实现了DecryptedInput Facade,它几乎与Laravel的Input Facade相同,只是它允许从请求中获取解密值。您不需要担心为DecryptedInput添加别名 - 它已在包服务提供者中自动添加。

让我们为api/checklicense路由创建一个方法

public function postChecklicense() {

    $licenseKey = DecryptedInput::get('licenseKey');

    // Perform some logic to determine whether the received license key is valid or has not expired for example
    // Most probably retreiving this info from database.
    $licenseIsValid     = true;
    $licenseExpiresAt   = '2014-12-31 23:59:59';

    $response = array(
        'isValid'   => $licenseIsValid,
        'liceseExpiresAt'   => $licenseExpiresAt;
    );

    return json_encode($response);
}

请注意,postChecklicense不关心输出数据的加密。为此,我们将使用之前提到的预制cryptOut过滤器。为此,让我们为我们的ApiController创建一个构造函数

public function __construct() {
    $this->afterFilter(
        'cryptOut',
        array(
            'except'   => array('postInit'),
        )
    );
}

cryptOut过滤器执行两项操作:首先,它加密输出数据;其次,它签名数据。总结来说,它向客户端发送一个JSON编码的对象,包含两个字段

  1. data - 加密数据
  2. sign - 加密数据的签名

请注意,此过滤器应用于除postInit之外的所有控制器方法,正如我之前所述——此方法的输出不应加密。

如果对加密和签名的方式感兴趣,请参阅Amegatron\Cryptoapi\Filters\OutgoingCryptFilter.php

客户端示例

以下是一个C#客户端示例:https://github.com/Amegatron/CryptoApiExample

测试您的CryptoApi

您可以使用CodeCeption测试CryptoApi。我提供了一个示例测试,它测试了一个简单的“echo”API方法:测试向服务器发送加密的message,并期望在响应中收到它,它也是加密和签名的。

要运行此测试,您需要做以下操作

先决条件

您需要将以下内容添加到您的composer.json

    "require-dev": {
        "codeception/codeception": "1.8.5",
        "guzzle/plugin": "3.9.1"
    },

之后在控制台运行 composer update 命令。这将安装 CodeCeption 本身以及额外的 guzzle/plugin,这是进行测试所需的。

服务器API

确保您有一个如之前所述的 ApiController。对于这个测试,这个控制器必须有两个方法:postInitpostTestEcho。为此,您可以使用预制的 Traits。

class ApiController extends BaseController {

    use \Amegatron\Cryptoapi\Traits\RsaAesControllerTrait;
    use \Amegatron\Cryptoapi\Traits\TestsControllerTrait;

}

同时确保您有一个对应于该控制器的 api 路由,以及一个 cryptOut 过滤器(如之前所述)。

密钥

如果您还没有这样做,请生成密钥对:php artisan cryptoapi:generatekeys

运行测试

启动服务器

首先确保您位于项目的根目录中(其中包含 composer.jsonartisan)。

之后启动服务器:php artisan serve &。它将在端口 8000 上启动服务器。可能需要一些时间,请等待显示服务器已启动的消息。

测试

现在切换到包目录:cd vendor/amegatron/cryptoapi

现在您可以运行测试了。执行以下命令:../..bin/codecapt run 并等待它完成。

如果您一切都做对了,最后应该看到绿色的消息

OK (1 个测试,6 个断言)

编写自己的测试

如果您对 CodeCeption 不是很熟悉,请访问其官方网站:http://codeception.com/

目前我无法提供任何用于测试的 API,但您应该调查现有的测试 tests/cryptoapi/CheckEchoCept.php 以及它使用的辅助类:tests/_helpers/CryptoApiHelper.php。在那里,您可以找到使用 phpseclib(Laravel 附带)实现的加密算法的实现。