Skip to content

Instantly share code, notes, and snippets.

@iyaozhen
Created July 12, 2019 04:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iyaozhen/4cd20a7fa9c7ecd2d2041ec5a831f328 to your computer and use it in GitHub Desktop.
Save iyaozhen/4cd20a7fa9c7ecd2d2041ec5a831f328 to your computer and use it in GitHub Desktop.
PHP Encrypter
<?php
/**
* 加解密类
*
* @author iyaozhen
* @date: 2016-11-12
*/
class Encrypter
{
private $method;
private $password;
private $padding;
private $iv;
private $lastErrorMessage; // 最后一次失败的信息
/**
* Encrypter constructor
*
* @param null|string $password 默认使用配置文件中的aes_key
* @param string $method 加密的模式
* @param string $padding 填充数据模式, 支持ZERO(默认)、PKCS7(兼容PKCS5)
* @param string $iv 初始向量, 默认使用AES-128-ECB初始向量为空
*/
function __construct($password = null, $method = 'AES-128-ECB', $padding = 'ZERO', $iv = '')
{
$this->password = $password;
$this->method = $method;
$this->padding = $padding;
$this->iv = $iv;
}
/**
* 加密
*
* @param string $data 需要加密的字符串
*
* @return false|string 失败时返回false
*/
public function encrypt($data)
{
/**
* openssl_encrypt ZERO_PADDING模式有个小问题
* 当设置为OPENSSL_ZERO_PADDING时其实是禁用padding
* EVP_CIPHER_CTX_set_padding(&cipher_ctx, 0);
* 因此需要构造合适长度的被加密串, 这样加密过程中就不需要padding了,不然会加密失败
* 不过需要注意的是因为padding了\0字符 解密完成后需要trim。显而易见,若是被加密串两端本身就包含\0等字符就会丢失信息
*
* 参考资料: http://php.net/manual/en/function.openssl-encrypt.php#117208
* http://php.net/manual/en/function.openssl-encrypt.php#117499
* http://www.cnblogs.com/solohac/p/4284424.html
* http://stackoverflow.com/questions/11838197/php-vs-java-aes-encryption-what-is-the-difference
*/
if ($this->padding === 'ZERO') {
$options = OPENSSL_ZERO_PADDING;
if ($blockSize = $this->getBlockSize($this->method)) {
$padLen = $blockSize - (strlen($data) % $blockSize);
$data .= str_repeat("\0", $padLen); // zero padding
}
else {
// 不支持的加密方法
$this->lastErrorMessage = 'Unknown cipher method';
return false;
}
}
else {
// 默认值, 使用PKCS7(兼容PKCS5) padding方式
$options = 0;
}
try {
$encryptedData = openssl_encrypt($data, $this->method, $this->password,
$options, $this->iv);
}
catch (Exception $e) {
$this->lastErrorMessage = $e;
return false;
}
return $encryptedData;
}
/**
* 解密
*
* @param string $data 需要解密的字符串
*
* @return string|false 失败时返回false
*/
public function decrypt($data)
{
// 预先 decode
// base64 url safe decode
$data = base64_decode(str_pad(
strtr($data, '-_', '+/'),
strlen($data) % 4,
'=',
STR_PAD_RIGHT
));
if ($this->padding === 'ZERO') {
$options = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
}
else {
// PKCS7(兼容PKCS5)padding,openssl默认方式
$options = OPENSSL_RAW_DATA;
}
try {
$decryptedData = openssl_decrypt($data, $this->method, $this->password,
$options, $this->iv);
}
catch (Exception $e) {
$this->lastErrorMessage = $e;
return false;
}
// 解密后的数据两端可能有padding的空字符, 需要调用方按需trim
return $decryptedData;
}
/**
* 获取某个加密方式的块大小
*
* @param string $method
*
* @return bool|int
*/
private function getBlockSize($method)
{
// 因为openssl没有现成方法获取block size, 只能写死几种常见加密模式
$methodBlockSize = [
'AES-128-CBC' => 16,
'AES-128-CFB' => 16,
'AES-128-ECB' => 16,
'AES-128-OFB' => 16,
'AES-192-CBC' => 24,
'AES-192-CFB' => 24,
'AES-192-ECB' => 24,
'AES-192-OFB' => 24,
'AES-256-CBC' => 32,
'AES-256-CFB' => 32,
'AES-256-ECB' => 32,
'AES-256-OFB' => 32,
];
return isset($methodBlockSize[$method]) ? $methodBlockSize[$method] : false;
}
/**
* 获取最后一次报错的信息
*
* @return string
*/
public function getLastErrorMessage()
{
return sprintf("last error: %s, openssl error: %s",
$this->lastErrorMessage, openssl_error_string()
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment