Zend Framework 2.x 之 Zend\Crypt

Zend\Crypt组件封装了大部分细节,让加解密非常容易。

Introduction to Zend\Crypt

Zend\Crypt provides support of some cryptographic用密码写的 tools. The available features are:
*encrypt-then-authenticate加密然后验证 using symmetric对称 ciphers密码 (the authentication step is provided using HMAC);
*encrypt/decrypt using symmetric and public key algorithm(对称和公钥算法) (e.g. RSA algorithm);
*generate digital sign数字签名 using public key algorithm (e.g. RSA algorithm);
*key exchange using the Diffie-Hellman method;
*Key derivation function (e.g. using PBKDF2 algorithm);
*Secure password hash 安全密码哈希(e.g. using Bcrypt algorithm);
*generate Hash values;
*generate HMAC values;

The main scope of this component is to offer an easy and secure way to protect and authenticate鉴定 sensitive data in PHP. Because the use of cryptography密码系统 is not so easy we recommend to use the Zend\Crypt component only if you have a minimum background on this topic. For an introduction to cryptography we suggest the following references:推荐课程
*Dan Boneh “Cryptography course” Stanford University, Coursera – free online course
*N.Ferguson, B.Schneier, and T.Kohno, “Cryptography Engineering”, John Wiley & Sons (2010)
*B.Schneier “Applied Cryptography”, John Wiley & Sons (1996)

PHP-CryptLib—————
Most of the ideas behind the Zend\Crypt component have been inspired启发 by the PHP-CryptLib project of Anthony Ferrara. PHP-CryptLib is an all-inclusive pure PHP cryptographic library for all cryptographic needs. It is meant to be easy to install and use, yet extensible可展开的 and powerful enough for even the most experienced developer.

Encrypt/decrypt using block ciphers 分组密码加解密
Zend\Crypt\BlockCipher implements the encrypt-then-authenticate mode using HMAC to provide authentication.

The symmetric cipher对应秘钥 can be chosen with a specific adapter that implements the Zend\Crypt\Symmetric\SymmetricInterface. We support the standard algorithms of the Mcrypt extension. The adapter that implements the Mcrypt is Zend\Crypt\Symmetric\Mcrypt.

In the following code we reported an example on how to use the BlockCipher class to encrypt-then-authenticate a string using the AES block cipher (with a key of 256 bit) and the HMAC algorithm (using the SHA-256 hash function).

use Zend\Crypt\BlockCipher;
// 目前对称分组密码中只有mcrypt实现
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey('encryption key');

// 每次输出均不一样
$result = $blockCipher->encrypt('this is a secret message');
echo "Encrypted text: $result \n";

// 都得到原始内容
echo "Decrypted text:".$blockCipher->decrypt($result)."\n";

The BlockCipher is initialized using a factory method with the name of the cipher adapter to use (mcrypt) and the parameters to pass to the adapter (the AES algorithm). In order to encrypt a string we need to specify an encryption key and we used the setKey() method for that scope. The encryption is provided by the encrypt() method.

The output of the encryption is a string, encoded in Base64 (default), that contains the HMAC value, the IV vector, and the encrypted text. The encryption mode used is the CBC (with a random IV by default) and SHA256 as default hash algorithm of the HMAC. The Mcrypt adapter encrypts using the PKCS#7 padding mechanism by default. You can specify a different padding method using a special adapter for that (Zend\Crypt\Symmetric\Padding). The encryption and authentication keys used by the BlockCipher are generated with the PBKDF2 algorithm, used as key derivation function from the user’s key specified using the setKey() method.

Key size
BlockCipher try to use always the longest size of the key for the specified cipher. For instance, for the AES algorithm it uses 256 bits and for the Blowfish algorithm it uses 448 bits.

You can change all the default settings passing the values to the factory parameters. For instance, if you want to use the Blowfish algorithm, with the CFB mode and the SHA512 hash function for HMAC you have to initialize the class as follow:

use Zend\Crypt\BlockCipher;

$blockCipher = BlockCipher::factory('mcrypt', array(
                                'algo' => 'blowfish',
                                'mode' => 'cfb',
                                'hash' => 'sha512'
                            ));

Recommendation
If you are not familiar with symmetric encryption techniques we strongly suggest to use the default values of the BlockCipher class. The default values are: AES algorithm, CBC mode, HMAC with SHA256, PKCS#7 padding.

To decrypt a string we can use the decrypt() method. In order to successfully decrypt a string we have to configure the BlockCipher with the same parameters of the encryption.解密是要跟加密时配置一样

We can also initialize the BlockCipher manually without use the factory method. We can inject the symmetric cipher adapter directly to the constructor of the BlockCipher class. For instance, we can rewrite the previous example as follow:

use Zend\Crypt\BlockCipher;
use Zend\Crypt\Symmetric\Mcrypt;

$blockCipher = new BlockCipher(new Mcrypt(array('algo' => 'aes')));
$blockCipher->setKey('encryption key');
$result = $blockCipher->encrypt('this is a secret message');
echo "Encrypted text: $result \n";

这里描述的是对称加解密,实际使用非常简单(有默认值)。

Encrypt/decrypt a file 加解密文件
Zend\Crypt\FileCipher implements the encryption of decryption of a file using a symmetric cipher in CBC mode with the encrypt-then-authenticate approach, using HMAC to provide authentication (the same solution used by Zend\Crypt\BlockCipher component).

Encrypt and decrypt a file is not an easy task, especially a big file(对大文件加解密不是一个容易的任务). For instance, in CBC mode you must be sure to handle the IV correctly for each block. That means, if you are reading a big file you need to use a buffer and be sure to use the last block of the buffer as new IV for the next encryption step.

The FileCipher uses a symmetric cipher, with the Zend\Crypt\Symmetric\Mcrypt component.

The usage of this component is very simple, you just need to create an instance of FileCipher and specify the key, and you are ready to encrypt/decrypt any file:

use Zend\Crypt\FileCipher;

$fileCipher = new FileCipher;
$fileCipher->setKey('encryption key');
// encryption
if ($fileCipher->encrypt('path/to/file_to_encrypt', 'path/to/output')) {
    echo "The file has been encrypted successfully\n";
}
// decryption
if ($fileCipher->decrypt('path/to/file_to_decrypt', 'path/to/output')) {
    echo "The file has been decrypted successfully\n";
}

By default FileCipher uses the AES encryption algorithm (with a key of 256 bit) and the SHA-256 hash algorithm to authenticate the data using the HMAC function. This component uses the PBKDF2 key derivation algorithm to generate the encryption key and the authentication key, for the HMAC, based on the key specified using the method setKey().

If you want to change the encryption algorithm, you can use the setCipherAlgorithm() function, for instance you can specity to use the Blowfish encryption algorihtm using setCipherAlgorithm(‘blowfish’). You can retrieve the list of all the supported encryption algorithm in your environment using the function getCipherSupportedAlgorithms(), it will return an array of all the algorithm name.

If you need to customize the cipher algorithm, for instance changing the Padding mode, you can inject your Mcrypt object in the FileCipher using the setCipher() method. The only parameter of the cipher that you cannot change is the cipher mode, that will be CBC in any case.

Output format
The output of the encryption file is in binary format(加密输出是二进制格式). We used this format to do not impact on the output size. If you encrypt a file using the FileCipher component, you will notice that the output file size is almost the same of the input size, just some bytes more to store the HMAC and the IV vector. The format of the output is the concatenation of the HMAC, the IV and the encrypted file.
这里使用的对称加密文件和加密字符串几乎一样。

Key derivation function
这个主题大概就是在对称加解密中使用的随机key的产生(内部使用,实现每次输出不一样)….

Password
In the Zend\Crypt\Password namespace you can find all the password formats supported by Zend Framework. We currently support the following passwords:
bcrypt;
Apache (htpasswd).

If you need to choose a password format to store the user’s password we suggest to use the bcrypt algorithm that is considered secure against brute forcing attacks (see the details below).

Bcrypt
The bcrypt algorithm is an hashing algorithm that is widely used and suggested by the security community to store user’s passwords in a secure way.

Classic hashing mechanisms like MD5 or SHA, with or without a salt value(加盐), are not considered secure anymore (read this post to know why).

The security of bcrypt is related to the speed of the algorithm. Bcrypt is very slow, it can request even a second to generate an hash value. That means a brute force attack is impossible to execute, due to the amount of time that its need.

Bcrypt uses a cost parameter that specify the number of cycles to use in the algorithm. Increasing this number the algorithm will spend more time to generate the hash output. The cost parameter is represented by an integer value between 4 to 31. The default cost value of the Zend\Crypt\Password\Bcrypt component is 10, that means about 0.07 second using a CPU Intel i5 at 3.3Ghz (the cost parameter is a relative value according to the speed of the CPU used). We changed the default value of the cost parameter from 14 to 10, starting from Zend Framework 2.3.0, due to high computational time to prevent potential denial-of-service attacks (you can read this article Aggressive password stretching for more information).

If you want to change the cost parameter of the bcrypt algorithm you can use the setCost() method. Please note, if you change the cost parameter, the resulting hash will be different. This will not affect the verification process of the algorithm, therefore not breaking the password hashes you already have stored. Bcrypt reads the cost parameter from the hash value, during the password authentication. All of the parts needed to verify the hash are all together, separated with $’s, first the algorithm, then the cost, the salt, and then finally the hash.

The example below shows how to use the bcrypt algorithm to store a user’s password:

use Zend\Crypt\Password\Bcrypt;

$bcrypt = new Bcrypt();
$securePass = $bcrypt->create('user password');

The output of the create() method is the hash of the password. This value can then be stored in a repository like a database (the output is a string of 60 bytes).

Bcrypt truncates input > 72 bytes
The input string of the bcrypt algorithm is limited to 72 bytes. If you use a string with a length more than this limit, bcrypt will consider only the first 72 bytes. If you need to use a longer string, you should pre-hash it using SHA256 prior to passing it to the bcrypt algorithm: $hashedPassword = \Zend\Crypt\Hash::compute(‘sha256’, $password);

To verify if a given password is valid against a bcrypt value you can use the verify() method. An example is reported below:

use Zend\Crypt\Password\Bcrypt;

$bcrypt = new Bcrypt();
$securePass = 'the stored bcrypt value';
$password = 'the password to check';

if ($bcrypt->verify($password, $securePass)) {
    echo "The password is correct! \n";
} else {
    echo "The password is NOT correct.\n";
}

In the bcrypt uses also a salt value to improve the randomness of the algorithm. By default, the Zend\Crypt\Password\Bcrypt component generates a random salt for each hash. If you want to specify a preselected salt you can use the setSalt() method.

We provide also a getSalt() method to retrieve the salt specified by the user. The salt and the cost parameter can be also specified during the constructor of the class, below is reported an example:

use Zend\Crypt\Password\Bcrypt;

$bcrypt = new Bcrypt(array(
    'salt' => 'random value',
    'cost' => 11
));

…….

Public key cryptography(公钥,也叫非对称)
Public key cryptography¶

Public-key cryptography refers to a cryptographic system requiring two separate keys, one of which is secret and one of which is public. Although different, the two parts of the key pair are mathematically linked. One key locks or encrypts the plaintext, and the other unlocks or decrypts the cyphertext. Neither key can perform both functions. One of these keys is published or public, while the other is kept private.

In Zend Framework we implemented two public key algorithms: Diffie-Hellman key exchange and RSA.(提供两种公钥算法)

Diffie-Hellman
这个跳过。

RSA
RSA is an algorithm for public-key cryptography that is based on the presumed difficulty of factoring large integers, the factoring problem. A user of RSA creates and then publishes the product of two large prime numbers大素数, along with an auxiliary value, as their public key. The prime factors must be kept secret. Anyone can use the public key to encrypt a message, but with currently published methods, if the public key is large enough, only someone with knowledge of the prime factors can feasibly decode the message. Whether breaking RSA encryption is as hard as factoring is an open question known as the RSA problem.

The RSA algorithm can be used to encrypt/decrypt message and also to provide authenticity and integrity generating a digital signature of a message. Suppose that Alice wants to send an encrypted message to Bob. Alice must use the public key of Bob to encrypt the message. Bob can decrypt the message using his private key. Because Bob he is the only one that can access to his private key, he is the only one that can decrypt the message. If Alice wants to provide authenticity and integrity of a message to Bob she can use her private key to sign the message. Bob can check the correctness of the digital signature using the public key of Alice. Alice can provide encryption, authenticity and integrity of a message to Bob using the previous schemas in sequence, applying the encryption first and the digital signature after.

以上描述了RSA基本实现与应用。

Below we reported some examples of usage of the Zend\Crypt\PublicKey\Rsa class in order to:
*generate a public key and a private key;
*encrypt/decrypt a string;
*generate a digital signature of a file.

Generate a public key and a private key
In order to generate a public and private key you can use the following code:

use Zend\Crypt\PublicKey\RsaOptions;

$rsaOptions = new RsaOptions(array(
    'pass_phrase' => 'test' //密码
));

$rsaOptions->generateKeys(array(
    'private_key_bits' => 2048,
));

file_put_contents('private_key.pem', $rsaOptions->getPrivateKey());
file_put_contents('public_key.pub', $rsaOptions->getPublicKey());

This example generates a public and private key of 2048 bit storing the keys in two separate files, the private_key.pem for the private key and the public_key.pub for the public key. You can also generate the public and private key using OpenSSL from the command line (Unix style syntax):

ssh-keygen -t rsa

Encrypt and decrypt a string
Below is reported an example on how to encrypt and decrypt a string using the RSA algorithm. You can encrypt only small strings. The maximum size of encryption is given by the length of the public/private key – 88 bits. For instance, if we use a size of 2048 bit you can encrypt string with a maximum size of 1960 bit (245 characters). This limitation is related to the OpenSSL implementation for a security reason related to the nature of the RSA algorithm.

The normal application of a public key encryption algorithm is to store a key or a hash of the data you want to respectively encrypt or sign. A hash is typically 128-256 bits (the PHP sha1() function returns a 160 bit hash). An AES encryption key is 128 to 256 bits. So either of those will comfortably fit inside a single RSA encryption.

use Zend\Crypt\PublicKey\Rsa;

$rsa = Rsa::factory(array(
    'public_key'    => 'public_key.pub',
    'private_key'   => 'private_key.pem',
    'pass_phrase'   => 'test',
    'binary_output' => false
));

$text = 'This is the message to encrypt';

$encrypt = $rsa->encrypt($text);
printf("Encrypted message:\n%s\n", $encrypt);

$decrypt = $rsa->decrypt($encrypt);

if ($text !== $decrypt) {
    echo "ERROR\n";
} else {
    echo "Encryption and decryption performed successfully!\n";
}

注意:加密一个字符串有长度限制(加密大数据不是其应用)。

Generate a digital signature of a file
Below is reported an example of how to generate a digital signature of a file.

use Zend\Crypt\PublicKey\Rsa;

$rsa = Rsa::factory(array(
    'private_key'   => 'path/to/private_key',
    'pass_phrase'   => 'passphrase of the private key',
    'binary_output' => false
));

$file = file_get_contents('path/file/to/sign');

$signature = $rsa->sign($file, $rsa->getOptions()->getPrivateKey());
$verify    = $rsa->verify($file, $signature, $rsa->getOptions()->getPublicKey());

if ($verify) {
    echo "The signature is OK\n";
    file_put_contents($filename . '.sig', $signature);
    echo "Signature save in $filename.sig\n";
} else {
     echo "The signature is not valid!\n";
}

数据签名,私钥加密,公钥可验证,主要的应用场景之一。在认证中也大量应用(加密小字符串,私钥解密,识别对方)。