halite的使用_使用Halite进行电子邮件的隐私和双向加密

halite的使用

Cryptography is a complex matter. In fact, there is one golden rule:

密码学是一个复杂的问题。 实际上,有一个黄金法则:

* Don’t implement cryptography yourself *

*不要自己实施加密*

The reason for this is that so many things can go wrong while implementing it, the slightest error can generate a vulnerability and if you look away, your precious data can be read by someone else. Whilst this is not an exhaustive list, there are several important guidelines to follow when using cryptography:

这样做的原因是,实施它时可能会出错,丝毫的错误都会产生漏洞,如果您移开视线,其他人也可以读取您的宝贵数据。 虽然这不是一个详尽的清单,但是在使用密码学时要遵循一些重要的准则:

  • Don’t use the same key to encrypt everything

    不要使用相同的密钥来加密所有内容
  • Don’t use a generated key directly to encrypt

    不要直接使用生成的密钥进行加密
  • When generating values that you don’t want to be guessable, use a cryptographically secure pseudo random number generator (CSPRNG)

    当生成不想被猜测的值时,请使用加密安全的伪随机数生成器( CSPRNG )

  • Encrypt, then MAC (or the Cryptographic Doom Principle)

    先加密,然后加密MAC(或加密厄运原理 )

  • Kerckhoffs’s principle: A crypto system should be secure even if everything about the system, except the key, is public knowledge

    Kerckhoffs的原则:即使系统中除密钥之外的所有内容都是公共知识,加密系统也应是安全的
Picture of keys hanging on strings

Some of the cryptographic terms used in this article can be defined as follow:

本文中使用的一些加密术语可以定义如下:

  • Key: a piece of information that determines the functional output of a cryptographic algorithm.

    密钥 :确定加密算法功能输出的一条信息。

  • CSPRNG: also known as a deterministic random bit generator, is an algorithm for generating a sequence of numbers whose properties approximate the properties of sequences of random numbers (or bytes). To be cryptographically secure, a PRNG must:

    CSPRNG :也称为确定性随机位生成器,是一种用于生成数字序列的算法,该数字序列的属性近似于随机数(或字节)序列的属性。 为了保证密码安全,PRNG必须:

    • Pass statistical randomness tests

      通过统计随机性检验
    • Hold up well under serious attack, even when part of their initial or running state becomes available to an attacker.

      即使在攻击者可以使用部分初始或运行状态时,也要在严重的攻击下保持良好的状态。

    CSPRNG: also known as a deterministic random bit generator, is an algorithm for generating a sequence of numbers whose properties approximate the properties of sequences of random numbers (or bytes). To be cryptographically secure, a PRNG must:

    CSPRNG :也称为确定性随机位生成器,是一种用于生成数字序列的算法,该数字序列的属性近似于随机数(或字节)序列的属性。 为了保证密码安全,PRNG必须:

  • MAC: is a short piece of information used to confirm that the message came from the stated sender (its authenticity) and has not been changed in transit (its integrity). It accepts as input a secret key and an arbitrary-length message to be authenticated, and outputs a MAC.

    MAC :是一小段信息,用于确认消息来自指定的发件人(其真实性),并且在传输过程中未更改(其完整性)。 它接受要认证的密钥和任意长度的消息作为输入,并输出MAC。

To further read about cryptography and have a better understanding, you can take a look at the following pages:

为了进一步了解密码学并有更好的理解,您可以看一下以下页面:

Some libraries out there implement cryptography primitives and operations, and leave a lot of decisions to the developer. Examples of those are php’s own crypto library, or Defuse’s php-encryption. Some PHP frameworks implement their own crypto like Zend Framework’s zend-crypt or Laravel.

那里的一些库实现了加密原语和操作,并给开发人员留下了很多决定。 这些示例包括php自己的加密库或Defuse的php-encryption 。 一些PHP框架实现了自己的加密算法,例如Zend Framework的zend-cryptLaravel

Nevertheless, there is one library that stands out from the rest for its simplicity and takes a lot of responsibility from the developer on the best practices, in addition to using the libsodium library. In this article we are going to explore Halite.

但是,除了使用libsodium库之外,还有一个库因其简单性而在其他应用程序中脱颖而出,并承担了最佳实践方面的开发人员责任。 在本文中,我们将探讨Halite

halite

The assumption is that you already have PHP 7, a web server and a MySQL server installed and running. You might want to take a look at Homestead Improved for an environment like this. In order to be able to use Halite, our system must have the libsodium library, as well as the libsodium PHP extension. Those can be installed by using the following commands. As always, depending on your system configuration and installed packages, your mileage may vary:

假设您已经安装并运行了PHP 7,Web服务器和MySQL服务器。 您可能想看看针对此类环境的Homestead Improvement 。 为了能够使用Halite,我们的系统必须具有libsodium库以及libsodium PHP扩展。 可以使用以下命令进行安装。 与往常一样,根据您的系统配置和安装的软件包,您的里程可能会有所不同

在Ubuntu中安装 (Install in Ubuntu)

sudo apt-get install build-essential
wget https://github.com/jedisct1/libsodium/releases/download/1.0.10/libsodium-1.0.10.tar.gz
tar -xvf libsodium-1.0.10.tar.gz
cd libsodium-1.0.10/
sudo ./configure
sudo make
sudo make install
sudo apt-get install php-pear php7.0-dev
sudo pecl install libsodium

在CentOS中安装 (Install in CentOS)

sudo yum groupinstall "Development tools"
wget https://github.com/jedisct1/libsodium/releases/download/1.0.10/libsodium-1.0.10.tar.gz
tar -xvf libsodium-1.0.10.tar.gz
cd libsodium-1.0.10/
sudo ./configure
sudo make
sudo make install
yum install php-pear php-devel
sudo pecl install libsodium

You will have to edit your php.ini file to include the extension=libsodium.so line and restart your web server or php-fpm.

您将必须编辑php.ini文件以包含extension=libsodium.so行,然后重新启动Web服务器或php-fpm

应用程序 (The application)

To showcase some of Halite’s basic features, let’s create a set of RESTful services for a simple “e-mail like” messaging application, where we want to encrypt messages sent between users. Please take into consideration that this will be just a tiny and simple example, a lot of standard email features will be missing. Should you like to browse the full source code, please take a look at the github repo.

为了展示Halite的一些基本功能,让我们为一个简单的“类似于电子邮件”的消息传递应用程序创建一组RESTful服务,我们希望在其中加密用户之间发送的消息。 请注意,这只是一个很小的简单示例,很多标准电子邮件功能都将丢失。 如果您想浏览完整的源代码,请查看github repo

The database tables have the following schema:

数据库表具有以下架构:

Database schema

The most relevant fields are:

最相关的字段是:

tablefieldpurpose
userssaltholds a random binary string for each user
messagesusers_idthe user id to which the message is intended to
messagesfromUserIdthe user id from which the message is sent
领域 目的
使用者 为每个用户持有一个随机的二进制字符串
讯息 users_id 消息打算发送给的用户标识
讯息 fromUserId 从中发送消息的用户标识

Let’s begin by initializing a composer.json file, and then declaring the dependency of our project on the Halite library using Composer.

让我们首先初始化一个composer.json文件,然后使用Composer声明项目对Halite库的依赖关系。

composer init
composer require paragonie/halite

For this example, I’ll be using silex for routing requests into an MVC style application, phpdotenv to store and load environment variables and doctrine orm to interact with the database using php objects.

对于此示例,我将使用silex将请求路由到MVC风格的应用程序中, phpdotenv用来存储和加载环境变量,并使用orm来使用php对象与数据库进行交互。

composer require silex/silex:~2.0 vlucas/phpdotenv doctrine/orm

After successfully downloading the libraries, the composer.json file should be modified to look like the following:

成功下载库后,应将composer.json文件修改为如下所示:

{
    "name": "yourName/projectName",
    "description": "Sitepoint tutorial for Halite",
    "authors": [
        {
            "name": "yourName",
            "email": "yourEmail"
        }
    ],
    "require": {
        "silex/silex": "^1.3",
        "vlucas/phpdotenv": "^2.2",
        "paragonie/halite": "^2.1",
        "doctrine/orm": "^2.5"
    },
    "autoload": {
        "psr-4": {"Acme\\": "src/"}
    }
}

The Acme namespace will hold our custom classes.

Acme名称空间将保存我们的自定义类。

RESTful services

The RESTful services in our sample app have the following structure:

示例应用程序中的RESTful服务具有以下结构:

Methodurldescription
GET/usersGet a list of users
GET/users/{userid}Get the details of a given user
GET/users/{userid}/messagesGet a list of messages sent to a given user
POST/users/{userid}/messagesSend a message to a user
GET/users/{userid}/messages/{messageid}Retrieve a message from a given user
方法 网址 描述
得到 /用户 获取用户列表
得到 / users / {userid} 获取给定用户的详细信息
得到 / users / {userid} / messages 获取发送给给定用户的消息列表
开机自检 / users / {userid} / messages 向用户发送消息
得到 / users / {userid} / messages / {messageid} 检索给定用户的消息

For this application, we desire to encrypt the subject and the message, so the urls in bold are the ones that use Halite’s features.

对于此应用程序,我们希望对主题和消息进行加密,因此, 加粗的url是使用Halite功能的url。

For this article, we will be using symmetric encryption. That is, we use the same key to encrypt and decrypt a message.

对于本文,我们将使用对称加密。 也就是说,我们使用相同的密钥来加密和解密消息。

Given that we have the following users in the system (by invoking the users list service):

假设我们在系统中拥有以下用户(通过调用用户列表服务):

#Request
GET /users HTTP/1.1
Host: yourhost.dev
Cache-Control: no-cache

#Response
[
    {
        "id": 1,
        "name": "John Smith"
    },
    {
        "id": 2,
        "name": "Jane Doe"
    }
]

When we want to sent a message to John, from Jane, then we should send the following request:

当我们要从简向约翰发送消息时,我们应该发送以下请求:

POST /users/1/messages HTTP/1.1
Host: yourhost.dev
Cache-Control: no-cache

{
  "from": "2",
  "subject": "This is my subject",
  "message": "super secret information!!!"
}

The sample project uses Silex and Doctrine to do some heavy lifting, however, it is not the scope of this article to show how they work. For that, please see this intro to Silex and some of our Doctrine posts.

该示例项目使用Silex和Doctrine进行了一些繁重的工作,但是,本文并不显示它们如何工作。 为此,请参见Silex的简介我们的一些教义文章

Roughly speaking, Silex will forward HTTP requests to a controller. The controller will invoke a service, where things begin to get interesting. Let’s look at the code from the \Acme\Service\Message::save method.

粗略地讲,Silex会将HTTP请求转发到控制器。 控制器将调用一个服务,使事情开始变得有趣。 让我们看一下\Acme\Service\Message::save方法中的代码。

<?php
namespace Acme\Service;

use Acme\Model\Message as MessageModel;
use Acme\Model\Repository\Message as MessageRepository;
use Acme\Model\User as UserModel;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use ParagonIE\Halite\KeyFactory;
use ParagonIE\Halite\Symmetric\AuthenticationKey;
use ParagonIE\Halite\Symmetric\Crypto;
use ParagonIE\Halite\Symmetric\EncryptionKey;

/**
 * Class Message
 * @package Acme\Service
 */
class Message
{

// ... SOME CODE HERE ...

    /**
         * @param $from
         * @param $to
         * @param $subject
         * @param $message
         * @return string
         * @throws \ParagonIE\Halite\Alerts\InvalidSalt
         */
        public function save($from, $to, $subject, $message)
        {
            /*
             * create a new model object to hold the message
             */
            $messageModel = new MessageModel;

            /** @var EntityRepository $userRepository */
            $userRepository = $this->em->getRepository('Acme\Model\User');

            /*
             * search for the sender and recipient users
             */
            /** @var UserModel $fromUserModel */
            $fromUserModel = $userRepository->find($from);

            /** @var UserModel $toUserModel */
            $toUserModel = $userRepository->find($to);

            if (!$fromUserModel || !$toUserModel) {
                throw new \InvalidArgumentException('From or to user does not exist');
            }

            /*
             * create a placeholder for data, in order to generate a message id, used later to encrypt data.
             */
            $messageModel->setFromUser($fromUserModel)->setToUser($toUserModel);

            $this->em->persist($messageModel);
            $this->em->flush();

            /*
             * Retrieve the salts for both the sender and the recipient
             */
            $toUserSalt = $toUserModel->getSalt();

            /*
             * create encryption keys concatenating user's salt, a string representing the target field to be
             * encrypted, the message unique id, and a system wide salt.
             */
            $encryptionKeySubject = KeyFactory::deriveEncryptionKey(
                base64_decode($toUserSalt) . 'subject' . $messageModel->getId(),
                base64_decode(getenv('HALITE_ENCRYPTION_KEY_SALT')));
            $encryptionKeyMessage = KeyFactory::deriveEncryptionKey(
                base64_decode($toUserSalt) . 'message' . $messageModel->getId(),
                base64_decode(getenv('HALITE_ENCRYPTION_KEY_SALT')));

            /*
             * encrypt the subject and the message, each with their own encryption key
             */
            $cipherSubject = Crypto::encrypt($subject, $encryptionKeySubject, true);
            $cipherMessage = Crypto::encrypt($message, $encryptionKeyMessage, true);

            $messageModel->setSubject(base64_encode($cipherSubject))->setMessage(base64_encode($cipherMessage));

            $this->em->persist($messageModel);
            $this->em->flush();

            return $messageModel->getId();
        }
}

First, an object representing a record from the messages table is created to be used with Doctrine. This will make it easier to write data to the database. This object has attributes that are mapped 1:1 to the messages table.

首先,创建一个表示来自消息表的记录的对象,以便与Doctrine一起使用。 这将使将数据写入数据库更加容易。 该对象的属性被一对一映射到消息表。

Next, a Doctrine repository is created for the users table. The repository is used to easily retrieve data into model objects. The ease of retrieving data is shown with the statements $userRepository->find($from) and $userRepository->find($to) all without writing a single SQL command.

接下来,为用户表创建一个Doctrine存储库。 该存储库用于轻松地将数据检索到模型对象中。 使用语句$userRepository->find($from)$userRepository->find($to)轻松检索数据,而无需编写任何SQL命令。

The resulting models are then assigned to our message model, and then persisted to the database using Doctrine’s entity manager via the $this->em->persist($messageModel); and $this->em->flush(); statements. This is done so the primary key created for this record can be retrieved and used later on to encrypt the data. For more information about the dual nature of persist/flush, see this post.

然后将生成的模型分配给我们的消息模型,然后使用Doctrine的实体管理器通过$this->em->persist($messageModel);持久化至数据库$this->em->persist($messageModel);$this->em->flush(); 陈述。 这样做是为了为该记录创建的主密钥可以被检索并在以后用于加密数据。 有关persist / flush的双重性质的更多信息,请参阅本文

Each user in our database holds a unique random bytes string called ‘salt’. This salt, which is base64 encoded in order to be stored in the database as a series of printable characters, looks like this:

我们数据库中的每个用户都有一个唯一的随机字节字符串,称为“盐”。 这种盐经过base64编码,以便作为一系列可打印的字符存储在数据库中,如下所示:

users table data

We need to retrieve both the sender’s and recipient’s salt to be used to encrypt the message. As you may have already guessed, this is done easily via the models loaded above with the $toUserSalt = $toUserModel->getSalt(); and $fromUserSalt = $fromUserModel->getSalt();.

我们需要检索发送者和接收者的盐,以用于加密消息。 您可能已经猜到了,这可以通过上面加载的$toUserSalt = $toUserModel->getSalt();轻松完成$toUserSalt = $toUserModel->getSalt();$fromUserSalt = $fromUserModel->getSalt();

Finally, the real fun begins. It is time to use the Halite library. One of the rules of cryptography dictates to never directly use a pregenerated key to encrypt data, as well as to not use the same key multiple times. We will use the \ParagonIE\Halite\KeyFactory class to generate encryption keys. Please look at the following statement carefully:

最后,真正的乐趣开始了。 现在该使用Halite库了。 加密规则之一规定永远不要直接使用预生成的密钥来加密数据,也不要多次使用同一密钥。 我们将使用\ParagonIE\Halite\KeyFactory类生成加密密钥。 请仔细查看以下语句:

$encryptionKeySubject = KeyFactory::deriveEncryptionKey(base64_decode($toUserSalt) . 'subject' . $messageModel->getId(), base64_decode(getenv('HALITE_ENCRYPTION_KEY_SALT')));

The \ParagonIE\Halite\KeyFactory::deriveEncryptionKey method receives 2 parameters: the password to derive from, and a salt. Please notice that we are using the concatenation of the recipient’s salt (which has to be base64 decoded first), the string ‘subject’ and the message’s unique ID as the password parameter; this will ensure that the ‘password’ is unique for this recipient / this record / this field. The second parameter will hold an application wide salt set as an environment variable thanks to the dotenv library. This salt is stored in a .env file (along with the database credentials) encoded in base64. The .env file holds the salt as follows:

\ParagonIE\Halite\KeyFactory::deriveEncryptionKey方法接收两个参数:派生自密码和盐。 请注意,我们正在使用接收者的盐(必须首先对其进行base64解码),字符串“ subject”和消息的唯一ID的组合作为密码参数; 这样可以确保此收件人/记录/字段的“密码”是唯一的。 由于使用了dotenv库,第二个参数会将应用程序范围的盐集作为环境变量保存。 此盐存储在以base64编码的.env文件(以及数据库凭据)中。 .env文件包含以下内容:

HALITE_ENCRYPTION_KEY_SALT="/t4xwLjjkG0RoRHGkmOQVw=="

This salt is a 16 byte (as per requirements of argon2 algorithm used in Halite’s key derivation function) random binary string. This string can be generated with base64_encode(random_bytes(16)). For this approach to be secure, the application server and the database server should be on separate hardware.

该盐是一个16字节(根据Halite密钥派生函数中使用的argon2算法的要求)随机二进制字符串。 可以使用base64_encode(random_bytes(16))生成此字符串。 为了确保这种方法的安全,应用程序服务器和数据库服务器应位于单独的硬件上。

If both servers are on the same machine, and if an attacker gets access to it, they would have everything they need to decrypt the information. Another approach would be to encrypt the application’s salt using a key that would be provided at service boot time and/or persisted in memory. As long as this key is not intercepted, an attacker would have no way of decrypting anything even if both the web server and the database server are on the same hardware.

如果两个服务器都在同一台计算机上,并且攻击者可以访问它,则它们将拥有解密信息所需的一切。 另一种方法是使用在服务启动时提供和/或持久存储在内存中的密钥来加密应用程序的盐。 只要不截取该密钥,即使Web服务器和数据库服务器都在同一硬件上,攻击者也无法解密任何内容。

Now the actual encryption can take place

现在可以进行实际的加密了

$cipherSubject = Crypto::encrypt($subject, $encryptionKeySubject, true);
$cipherMessage = Crypto::encrypt($message, $encryptionKeyMessage, true);

The first parameter of \ParagonIE\Halite\Symmetric\Crypto::encrypt is the plain text, the second parameter is the encryption key derived above. The third parameter will tell Halite to return the raw binary string. The default behavior is to return a hex encoded representation. This, of course, depends on your preference.

\ParagonIE\Halite\Symmetric\Crypto::encrypt的第一个参数是纯文本,第二个参数是上面导出的加密密钥。 第三个参数将告诉Halite返回原始二进制字符串。 默认行为是返回十六进制编码的表示形式。 当然,这取决于您的偏好。

At the moment we have encrypted a message, so at first glance it won’t be known what the contents are. Halite already makes transparent the fact that it will add an authentication code to the cypher text in order to make sure that the contents of the message have not been tampered with. It will also use a different initialization vector to make sure that the same plain text encrypted with the same key will produce a different cypher text.

目前,我们已经加密了一条消息,因此乍一看它的内容是未知的。 Halite已经透明化了一个事实,即它将身份验证代码添加到密文,以确保消息的内容未被篡改。 它还将使用不同的初始化向量来确保使用相同密钥加密的相同纯文本将产生不同的密文。

Now we are ready to assign the cypher text to our model (base64 encoding them first) $messageModel->setSubject(base64_encode($cipherSubject))->setMessage(base64_encode($cipherMessage)); to be able to persist it to our database.

现在我们可以将密码文本分配给我们的模型了(首先对它们进行base64编码) $messageModel->setSubject(base64_encode($cipherSubject))->setMessage(base64_encode($cipherMessage)); 以便将其持久保存到我们的数据库中。

Let’s see the full request and response

让我们看看完整的请求和响应

Request

请求

POST /users/1/messages HTTP/1.1
Host: yourhost.dev
Cache-Control: no-cache

{
  "from": "2",
  "subject": "This is my subject",
  "message": "super secret information!!!"
}

Response

响应

{
    "messageId": 1
}

If we query the database directly, the record will look like the following:

如果我们直接查询数据库,记录将如下所示:

*************************** 1. row ***************************
        id: 1
  users_id: 1
fromUserId: 2
   subject: MUICAV13/brmlurlUIF6TOmhD6duztBI7vYzd4PGrJu8TinhAm69Kzv5QVFpNO55mssxsthPqmwb7l/py1iTCl0tSHUcB5Wsep0bcYDztIPhZ3g7VOcKKqu+YBTcuWprMvM22nvVdzcismGvCjkVW5hqCuNaCJPaUY+7VKRqCLg6FPY4WLMOdbY2yotQ4Q==
   message: MUICAaSGjcjwaDUORzctiK8rDla+w2Wm/6Z75EP7LJsRZbCk5zVHC/R6oxaJ6VrVbaB6WG1k9xtUcCt9fGN40r3zjFxp4M24QEdVu18t7A8N4wbiwd1W7LqbqjieziBchtKIJjE4oM68BlKxJMGO020GSnwNBuXIaz1b1MRVQDEsm1eT/oTx1oeLvwG1X94raKpHmK7D

If for any reason we were to send exactly the same request:

如果出于任何原因我们要发送完全相同的请求:

POST /users/1/messages HTTP/1.1
Host: yourhost.dev
Cache-Control: no-cache

{
  "from": "2",
  "subject": "This is my subject",
  "message": "super secret information!!!"
}
{
    "messageId": 2
}

And query the database again:

并再次查询数据库:

*************************** 1. row ***************************
        id: 1
  users_id: 1
fromUserId: 2
   subject: MUICAV13/brmlurlUIF6TOmhD6duztBI7vYzd4PGrJu8TinhAm69Kzv5QVFpNO55mssxsthPqmwb7l/py1iTCl0tSHUcB5Wsep0bcYDztIPhZ3g7VOcKKqu+YBTcuWprMvM22nvVdzcismGvCjkVW5hqCuNaCJPaUY+7VKRqCLg6FPY4WLMOdbY2yotQ4Q==
   message: MUICAaSGjcjwaDUORzctiK8rDla+w2Wm/6Z75EP7LJsRZbCk5zVHC/R6oxaJ6VrVbaB6WG1k9xtUcCt9fGN40r3zjFxp4M24QEdVu18t7A8N4wbiwd1W7LqbqjieziBchtKIJjE4oM68BlKxJMGO020GSnwNBuXIaz1b1MRVQDEsm1eT/oTx1oeLvwG1X94raKpHmK7D
*************************** 2. row ***************************
        id: 2
  users_id: 1
fromUserId: 2
   subject: MUICAeF/ci3fG7YWIhuLCU0pIxpbNDQ10KVWjSZF4G3K8lNVdV81LeveFyhAt/9PvJQy1ePLzl3EupJCROEQ+/L4nHUFRvCekEter2AQvnlyW03cxfjzZ6XwXBUkhzhZrT22JzxL7gSzpC6fPInAzsdDRIqhK/50wPhg/Wr29pH+PT/qwfuKr0rQWdyIfg==
   message: MUICAYOTzGO7DcpX7bVn4BMH4DKW+JCH1Eacj+lPx9I6eOHjNTA9jHT+u+VEz7cfdiitySU5UYYpBz+IBpV7b8hp/hwkqoLP7Durq/sPRJE/qMY9naKumaPXYW6XMhkysfwnwAIWQhjuiu/r9ARBwdTP3AgFNfVVc/jTlCuaPPZ8jw39xKCO5eNU3UyBrcXFhsGBnS6B
2 rows in set (0.00 sec)

Notice that while the plain text is actually the same in both messages, in the database they appear to be completely different, so even if an attacker had access to the database directly, without the encryption key they would not even be able to tell the messages are related, let alone identical, or find out their contents.

请注意,尽管两个消息中的纯文本实际上是相同的,但是在数据库中它们看起来完全不同,因此,即使攻击者无需加密密钥即可直接访问数据库,他们甚至也无法告诉消息。相关,更不用说完全相同,或者找出它们的内容。

Now it is decryption time!

现在是解密时间!

<?php
namespace Acme\Service;

use Acme\Model\Message as MessageModel;
use Acme\Model\Repository\Message as MessageRepository;
use Acme\Model\User as UserModel;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use ParagonIE\Halite\KeyFactory;
use ParagonIE\Halite\Symmetric\AuthenticationKey;
use ParagonIE\Halite\Symmetric\Crypto;
use ParagonIE\Halite\Symmetric\EncryptionKey;

/**
 * Class Message
 * @package Acme\Service
 */
class Message
{

// ... SOME CODE HERE ...

        /**
         * @param $userId
         * @param $messageId
         * @return array
         * @throws \InvalidArgumentException
         */
        public function get($userId, $messageId)
        {
            /** @var MessageRepository $repository */
            $repository = $this->em->getRepository('Acme\Model\Message');
            /** @var MessageModel $message */
            if (($message = $repository->find($messageId)) == true) {
                $toUser = $message->getToUser();

                /*
                 * Verify that the message belongs to the intended user
                 */
                if ($toUser->getId() == $userId) {
                    $toUserSalt = $toUser->getSalt();
                    $fromUser = $message->getFromUser();

                    $encryptionKeySubject = KeyFactory::deriveEncryptionKey(base64_decode($toUserSalt) . 'subject' . $message->getId(),
                        base64_decode(getenv('HALITE_ENCRYPTION_KEY_SALT')));

                    $encryptionKeyMessage = KeyFactory::deriveEncryptionKey(base64_decode($toUserSalt) . 'message' . $message->getId(),
                        base64_decode(getenv('HALITE_ENCRYPTION_KEY_SALT')));

                    $plainSubject = Crypto::decrypt(base64_decode($message->getSubject()), $encryptionKeySubject, true);
                    $plainMessage = Crypto::decrypt(base64_decode($message->getMessage()), $encryptionKeyMessage, true);

                    return [
                        'id' => $message->getId(),
                        'subject' => $plainSubject,
                        'message' => $plainMessage,
                        'name' => $fromUser->getName(),
                    ];

                }
            }

            throw new \InvalidArgumentException('Message not found');
        }
}

We get the message table repository with $repository = $this->em->getRepository('Acme\Model\Message');, and proceed to load the message with $message = $repository->find($messageId). To decrypt, we have to again derive the encryption key the same way we did when saving the message the first time, only this time we’ll use the \ParagonIE\Halite\Symmetric\Crypto::decrypt method:

我们使用$repository = $this->em->getRepository('Acme\Model\Message');获得消息表存储$repository = $this->em->getRepository('Acme\Model\Message'); ,然后继续使用$message = $repository->find($messageId)加载消息。 要解密,我们必须再次使用与首次保存消息时相同的方式来导出加密密钥,仅这次,我们将使用\ParagonIE\Halite\Symmetric\Crypto::decrypt方法:

$plainSubject = Crypto::decrypt(base64_decode($message->getSubject()), $encryptionKeySubject, true);
$plainMessage = Crypto::decrypt(base64_decode($message->getMessage()), $encryptionKeyMessage, true);

This method receives 3 parameters: the cypher text, the encryption key, and the flag to tell the method that it should expect raw binary bytes in the cypher text. (remember that we stored it base64 encoded in the database, which is why we need to base64 decode it). Now we are able to perform the request to retrieve a message by ID:

此方法接收3个参数:密文,加密密钥和标志,以告知该方法应在密文中包含原始二进制字节。 (请记住,我们将base64编码存储在数据库中,这就是为什么我们需要对它进行base64解码的原因) 。 现在,我们可以执行请求以按ID检索消息:

Request

请求

GET /users/1/messages/1 HTTP/1.1
Host: yourhost.dev
Cache-Control: no-cache

Response

响应

{
    "id": 1,
    "subject": "This is my subject",
    "message": "super secret information!!!",
    "name": "Jane Doe"
}

You can take a look at the full source code here.

您可以在此处查看完整的源代码。

结论 (Conclusion)

Encrypting and decrypting is dead simple with Halite. This does not mean that the above example is super secure. There is still a lot of potential for improvement, and Halite has other interesting features which might be showcased in a later article. Remember not to take the above implementation as production ready – it’s only for educational purposes.

使用Halite加密和解密非常简单。 这并不意味着上面的示例是超级安全的。 仍有很大的改进潜力,并且Halite具有其他有趣的功能,这些功能可能会在以后的文章中进行介绍。 请记住,不要将上述实现用于生产准备就绪–仅用于教育目的。

How do you do encryption / decryption in PHP? Have you used Halite? Let us know!

您如何在PHP中进行加密/解密? 您使用过Halite吗? 让我们知道!

翻译自: https://www.sitepoint.com/using-halite-for-privacy-and-two-way-encryption-of-emails/

halite的使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值