amegatron / cryptoapi
提供用于实现与客户端软件加密API的类
Requires
- php: >=5.3.7
- illuminate/support: 4.1.*
README
提供用于实现与客户端应用加密交互的类
想象一下,你有一个客户端应用程序,例如用C#编写的,它需要与服务器API进行安全(加密)的交互。
这正是CryptoApi能帮到的地方。
密码学基础
我先说,我不擅长密码学,但我理解了基础知识,这帮助我编写了这个包。
加密交互应该如何实现?
该算法基于两种密码学算法:对称和非对称。在这种情况下,AES用于对称,RSA用于非对称。如果你不知道这是什么意思 - 你应该在Google上搜索 :) 互联网上有大量关于这个主题的文章。
- 首先,服务器有其私钥。客户端有其对应的公钥(或证书)
- 客户端为对称算法生成一个随机的会话密钥。
- 然后客户端使用非对称算法加密此密钥并发送给服务器。
- 服务器收到密钥后,会向客户端响应OK或ERROR,告诉客户端是否成功接收密钥。服务器还将此密钥存储在会话中,以便未来与客户端通信。
- 以后,客户端发送给服务器的任何数据,都使用对称算法和该生成的密钥进行加密。
- 来自服务器的所有响应也对客户端进行了加密,同时使用服务器私钥进行了签名,这样客户端就可以验证数据确实来自预期的服务器。
服务器端实现
包安装
在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.key
和public.crt
。 public.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算法的密钥由两部分组成:key
和iv
。两者都可以通过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\CryptographyInterface
的Amegatron\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编码的对象,包含两个字段
- data - 加密数据
- 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
。对于这个测试,这个控制器必须有两个方法:postInit
和 postTestEcho
。为此,您可以使用预制的 Traits。
class ApiController extends BaseController {
use \Amegatron\Cryptoapi\Traits\RsaAesControllerTrait;
use \Amegatron\Cryptoapi\Traits\TestsControllerTrait;
}
同时确保您有一个对应于该控制器的 api
路由,以及一个 cryptOut
过滤器(如之前所述)。
密钥
如果您还没有这样做,请生成密钥对:php artisan cryptoapi:generatekeys
。
运行测试
启动服务器
首先确保您位于项目的根目录中(其中包含 composer.json
和 artisan
)。
之后启动服务器: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 附带)实现的加密算法的实现。