openssl_encrypt

(PHP 5 >= 5.3.0, PHP 7, PHP 8)

openssl_encrypt加密数据

说明

openssl_encrypt(
    字符串 $data,
    字符串 $cipher_algo,
    字符串 $passphrase,
    整数 $options = 0,
    字符串 $iv = "",
    字符串 &$tag = null,
    字符串 $aad = "",
    整数 $tag_length = 16
): 字符串|false

使用给定的方法和密钥加密给定的数据,返回原始或 base64 编码的字符串

参数

data

要加密的明文消息数据。

cipher_algo

密码方法。要查看可用的密码方法列表,请使用 openssl_get_cipher_methods().

passphrase

密码短语。如果密码短语短于预期,则会静默用 NUL 字符填充;如果密码短语长于预期,则会静默截断。

options

optionsOPENSSL_RAW_DATAOPENSSL_ZERO_PADDINGOPENSSL_DONT_ZERO_PAD_KEY 标志的按位或运算。

iv

非空初始化向量。

tag

使用 AEAD 密码模式 (GCM 或 CCM) 时,按引用传递的身份验证标记。

aad

附加身份验证数据。

tag_length

身份验证 tag 的长度。它的值对于 GCM 模式可以介于 4 到 16 之间。

返回值

成功时返回加密字符串,失败时返回 false

错误/异常

如果通过 cipher_algo 参数传递了未知的密码算法,则会发出 E_WARNING 级别的错误。

如果通过 iv 参数传递了空值,则会发出 E_WARNING 级别的错误。

变更日志

版本 说明
7.1.0 添加了 tagaadtag_length 参数。

范例

示例 #1 PHP 7.1+ 中的 AES 身份验证加密 GCM 模式示例

<?php
//$key 应该之前以加密安全的方式生成,比如 openssl_random_pseudo_bytes
$plaintext = "要加密的消息";
$cipher = "aes-128-gcm";
if (
in_array($cipher, openssl_get_cipher_methods()))
{
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag);
// 存储 $cipher、$iv 和 $tag 以便稍后解密
$original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options=0, $iv, $tag);
echo
$original_plaintext."\n";
}
?>

示例 #2 PHP 7.1 之前版本的 AES 身份验证加密示例

<?php
//$key 之前安全生成,例如:openssl_random_pseudo_bytes
$plaintext = "要加密的消息";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );

//稍后解密....
$c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
if (
hash_equals($hmac, $calcmac))// 定时攻击安全比较
{
echo
$original_plaintext."\n";
}
?>

另请参阅

添加笔记

用户贡献笔记 26 notes

57
omidbahrami1990 at gmail dot com
6 年前
这是加密和解密数据的最安全方式,
几乎不可能破解您的加密。
--------------------------------------------------------
--- 创建两个随机密钥并将其保存在您的配置文件中 ---
<?php
// 创建第一个密钥
echo base64_encode(openssl_random_pseudo_bytes(32));

// 创建第二个密钥
echo base64_encode(openssl_random_pseudo_bytes(64));
?>
--------------------------------------------------------
<?php
// 将密钥保存在您的配置文件中
define('FIRSTKEY','Lk5Uz3slx3BrAghS1aaW5AYgWZRV0tIX5eI0yPchFz4=');
define('SECONDKEY','EZ44mFi3TlAey1b2w4Y7lVDuqO+SRxGXsa7nctnr/JmMrA2vN6EJhrvdVZbxaQs5jpSe34X3ejFK/o9+Y5c83w==');
?>
--------------------------------------------------------
<?php
function secured_encrypt($data)
{
$first_key = base64_decode(FIRSTKEY);
$second_key = base64_decode(SECONDKEY);

$method = "aes-256-cbc";
$iv_length = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($iv_length);

$first_encrypted = openssl_encrypt($data,$method,$first_key, OPENSSL_RAW_DATA ,$iv);
$second_encrypted = hash_hmac('sha3-512', $first_encrypted, $second_key, TRUE);

$output = base64_encode($iv.$second_encrypted.$first_encrypted);
return
$output;
}
?>
--------------------------------------------------------
<?php
function secured_decrypt($input)
{
$first_key = base64_decode(FIRSTKEY);
$second_key = base64_decode(SECONDKEY);
$mix = base64_decode($input);

$method = "aes-256-cbc";
$iv_length = openssl_cipher_iv_length($method);

$iv = substr($mix,0,$iv_length);
$second_encrypted = substr($mix,$iv_length,64);
$first_encrypted = substr($mix,$iv_length+64);

$data = openssl_decrypt($first_encrypted,$method,$first_key,OPENSSL_RAW_DATA,$iv);
$second_encrypted_new = hash_hmac('sha3-512', $first_encrypted, $second_key, TRUE);

if (
hash_equals($second_encrypted,$second_encrypted_new))
return
$data;

return
false;
}
?>
48
biohazard dot ge at gmail dot com
13 年前
当 openssl 命令行工具无法解密使用 openssl_encrypt 函数加密的 php openssl 加密文件时,许多用户放弃了处理问题。

例如,初学者如何加密数据

<?php

$string
= '它起作用了吗?还是它不起作用?';
$pass = '1234';
$method = 'aes128';

file_put_contents ('./file.encrypted', openssl_encrypt ($string, $method, $pass));

?>

然后初学者如何尝试从命令行解密数据

# openssl enc -aes-128-cbc -d -in file.encrypted -pass pass:123

即使他/她确定 openssl_encrypt 的输出是 base64 并尝试

# openssl enc -aes-128-cbc -d -in file.encrypted -base64 -pass pass:123

即使他确定 base64 编码的文件在一行中表示并尝试

# openssl enc -aes-128-cbc -d -in file.encrypted -base64 -A -pass pass:123

即使他确定需要 IV,并将一些字符串 iv 添加为加密函数的第四个参数,然后将 iv 的十六进制表示形式作为参数添加到 openssl 命令行中

# openssl enc -aes-128-cbc -d -in file.encrypted -base64 -pass pass:123 -iv -iv 31323334353637383132333435363738

即使他确定 aes-128 密码必须是 128 位,因此为 16 字节,并将 $pass 设置为 '1234567812345678' 并尝试

# openssl enc -aes-128-cbc -d -in file.encrypted -base64 -pass pass:1234567812345678 -iv -iv 31323334353637383132333435363738

所有这些麻烦在任何情况下都不会有任何结果。

因为这里记录的密码参数不是密码。

这意味着函数的密码参数与使用 openssl 命令行工具进行文件加密解密的 [-pass pass:] 参数使用的字符串不同。

这就是关键!

现在,如何使用 php openssl_encrypt 正确加密数据,以及如何使用 openssl 命令行工具正确解密数据。

<?php

function strtohex($x)
{
$s='';
foreach (
str_split($x) as $c) $s.=sprintf("%02X",ord($c));
return(
$s);
}

$source = 'It works !';

$iv = "1234567812345678";
$pass = '1234567812345678';
$method = 'aes-128-cbc';

echo
"\niv in hex to use: ".strtohex ($iv);
echo
"\nkey in hex to use: ".strtohex ($pass);
echo
"\n";

file_put_contents ('./file.encrypted',openssl_encrypt ($source, $method, $pass, true, $iv));

$exec = "openssl enc -".$method." -d -in file.encrypted -nosalt -nopad -K ".strtohex($pass)." -iv ".strtohex($iv);

echo
'executing: '.$exec."\n\n";
echo
exec ($exec);
echo
"\n";

?>

传递给 openssl 命令行的 IV 和 Key 参数必须是字符串的十六进制表示形式。

解密的正确命令是

# openssl enc -aes-128-cbc -d -in file.encrypted -nosalt -nopad -K 31323334353637383132333435363738 -iv 31323334353637383132333435363738

由于它没有盐也没有填充,并且通过设置函数的第三个参数,我们不再需要对文件进行 Base64 解码。命令将输出“它有效……”

: /
44
Nick
8 年前
关于 openssl 库,这里有很多混淆,还有一些错误的指导。

基本技巧是

截至 2016 年,aes-256-ctr 可说是最佳的密码算法选择。这避免了潜在的安全问题(所谓的填充预言机攻击),以及填充数据的算法带来的膨胀。

每次使用相同的密钥进行加密时,都要使用不同的随机数据作为初始向量。mcrypt_create_iv() 是用于随机数据的选项之一。AES 使用 16 字节的块,因此需要 16 字节的 iv。

将 iv 数据与加密结果连接起来,并在解密时再次提取 iv 数据。

对于标志,请传递 OPENSSL_RAW_DATA,并在必要时对结果进行编码(在添加 iv 数据之后)。

使用 openssl_digest() 使用 sha256 等哈希函数对选定的加密密钥(密码参数)进行哈希处理,并使用哈希值作为密码参数。

GitHub 上有一个简单的 Cryptor 类,名为 php-openssl-cryptor,它演示了如何使用 openssl 进行加密/解密和哈希处理,以及如何以 Base64 和十六进制以及二进制格式生成和使用数据。它应该为更好地理解 openssl 与 PHP 的结合以及更有效地使用 openssl 打下基础。

希望这能帮助任何想要开始使用这个强大库的人。
32
openssl at mailismagic dot com
9 年前
由于 $options 没有文档,我将在下面的注释中阐明它们的意思。在后台,在 /ext/openssl/openssl.c 的源代码中

EVP_EncryptInit_ex(&cipher_ctx, NULL, NULL, key, (unsigned char *)iv);
if (options & OPENSSL_ZERO_PADDING) {
EVP_CIPHER_CTX_set_padding(&cipher_ctx, 0);
}

稍后

if (options & OPENSSL_RAW_DATA) {
outbuf[outlen] = '\0';
RETVAL_STRINGL((char *)outbuf, outlen, 0);
} else {
int base64_str_len;
char *base64_str;

base64_str = (char*)php_base64_encode(outbuf, outlen, &base64_str_len);
efree(outbuf);
RETVAL_STRINGL(base64_str, base64_str_len, 0);
}

因此,正如我们在这里看到的,OPENSSL_ZERO_PADDING 对 OpenSSL 上下文有直接影响。EVP_CIPHER_CTX_set_padding() 启用或禁用填充(默认情况下启用)。因此,OPENSSL_ZERO_PADDING 会禁用上下文的填充,这意味着您必须手动将填充应用到块大小。如果不使用 OPENSSL_ZERO_PADDING,您将自动获得 PKCS#7 填充。

OPENSSL_RAW_DATA 不会影响 OpenSSL 上下文,但会影响返回给调用者的数据格式。当指定 OPENSSL_RAW_DATA 时,返回的数据将按原样返回。如果没有指定,则将 Base64 编码数据返回给调用者。

希望这能避免有人去 PHP 源代码中查找 $options 的作用。专业开发人员提示:下载 PHP 源代码并将其保存在本地,以便在 PHP 文档无法满足质量期望时,您可以看到幕后实际发生的事情。
6
Shin
3 年前
关于“options”参数的简洁描述!

http://phpcoderweb.com/manual/function-openssl-encrypt_5698.html

> OPENSSL_ZERO_PADDING 对 OpenSSL 上下文有直接影响。EVP_CIPHER_CTX_set_padding() 启用或禁用填充(默认情况下启用)。因此,OPENSSL_ZERO_PADDING 会禁用上下文的填充,这意味着您必须手动将填充应用到块大小。如果不使用 OPENSSL_ZERO_PADDING,您将自动获得 PKCS#7 填充。

> OPENSSL_RAW_DATA 不会影响 OpenSSL 上下文,但会影响返回给调用者的数据格式。当指定 OPENSSL_RAW_DATA 时,返回的数据将按原样返回。如果没有指定,则将 Base64 编码数据返回给调用者。

其中
- OPENSSL_RAW_DATA=1
- OPENSSL_ZERO_PADDING=2

因此
options = 0
-> PKCS#7 填充,Base64 编码
options = 1
-> PKCS#7 填充,不进行 Base64 编码(原始数据)
options = 2
-> 不进行填充,Base64 编码
options = 3 ( 1 OR 2 )
-> 不进行填充,不进行 Base64 编码(原始数据)
7
Jean-Luc
6 年前
重要:密钥的长度应与您使用的密码完全相同。例如,如果您使用 AES-256,那么您应该提供一个长度为 32 字节的 $key(256 位 == 32 字节)。$key 中的任何额外字节都将被截断,不会被使用。
8
TheNorthMemory
3 年前
我看到有一个文档错误(#80236)提到 $tag 的使用。以下是一些示例,希望这些示例能帮助到其他人。

<?php

/**
* 使用给定的密钥、iv 和 aad 加密给定的数据,返回一个 base64 编码的字符串。
*
* @param string $plaintext - 要编码的文本。
* @param string $key - 密钥,32 字节字符串。
* @param string $iv - 初始化向量,16 字节字符串。
* @param string $aad - 附加认证数据,可以为空字符串。
*
* @return string - base64 编码的密文。
*/
function encrypt(string $plaintext, string $key, string $iv = '', string $aad = ''): string
{
$ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, $aad, 16);

if (
false === $ciphertext) {
throw new
UnexpectedValueException('加密输入 $plaintext 失败,请检查 $key 和 $iv 是否正确。');
}

return
base64_encode($ciphertext . $tag);
}

/**
* 使用给定的密钥、iv 和 aad 解密一个 base64 编码的字符串。
*
* @param string $ciphertext - base64 编码的密文。
* @param string $key - 密钥,32 字节字符串。
* @param string $iv - 初始化向量,16 字节字符串。
* @param string $aad - 附加认证数据,可以为空字符串。
*
* @return string - utf-8 明文。
*/
function decrypt(string $ciphertext, string $key, string $iv = '', string $aad = ''): string
{
$ciphertext = base64_decode($ciphertext);
$authTag = substr($ciphertext, -16);
$tagLength = strlen($authTag);

/* 手动检查标签的长度,因为 `openssl_decrypt` 在这里被提及,这是调用者的责任。 */
if ($tagLength > 16 || ($tagLength < 12 && $tagLength !== 8 && $tagLength !== 4)) {
throw new
RuntimeException('输入 `$ciphertext` 不完整,字节长度必须是 16、15、14、13、12、8 或 4 之一。');
}

$plaintext = openssl_decrypt(substr($ciphertext, 0, -16), 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $authTag, $aad);

if (
false === $plaintext) {
throw new
UnexpectedValueException('解密输入 $ciphertext 失败,请检查 $key 和 $iv 是否正确。');
}

return
$plaintext;
}

// 使用示例
$aesKey = random_bytes(32);
$aesIv = random_bytes(16);
$ciphertext = encrypt('thing', $aesKey, $aesIv);
$plaintext = decrypt($ciphertext, $aesKey, $aesIv);

var_dump($ciphertext);
var_dump($plaintext);
16
naitsirch at e dot mail dot de
7 years ago
PHP 缺乏内置函数来加密和解密大型文件。`openssl_encrypt()` 可以用来加密字符串,但将一个巨大的文件加载到内存中是一个不好的做法。

所以我们必须编写一个用户级函数来完成这项工作。这个例子使用对称 AES-128-CBC 算法来加密大型文件中的较小块,并将它们写入另一个文件。

# 加密文件

<?php
/**
* 定义从源文件中读取的每个块的块数。
* 对于 'AES-128-CBC',每个块包含 16 字节。
* 所以如果我们读取 10,000 个块,我们就会加载 160kb 到内存中。您可以调整此值
* 来读取/写入更短或更长的块。
*/
define('FILE_ENCRYPTION_BLOCKS', 10000);

/**
* 加密传入的文件并将结果保存到一个新文件,后缀为 ".enc"。
*
* @param string $source 要加密的文件路径
* @param string $key 用于加密的密钥
* @param string $dest 要写入加密文件的文件名。
* @return string|false 返回已创建的文件名或错误时返回 FALSE
*/
function encryptFile($source, $key, $dest)
{
$key = substr(sha1($key, true), 0, 16);
$iv = openssl_random_pseudo_bytes(16);

$error = false;
if (
$fpOut = fopen($dest, 'w')) {
// 将初始向量放在文件开头
fwrite($fpOut, $iv);
if (
$fpIn = fopen($source, 'rb')) {
while (!
feof($fpIn)) {
$plaintext = fread($fpIn, 16 * FILE_ENCRYPTION_BLOCKS);
$ciphertext = openssl_encrypt($plaintext, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
// 使用密文的前 16 个字节作为下一个初始化向量
$iv = substr($ciphertext, 0, 16);
fwrite($fpOut, $ciphertext);
}
fclose($fpIn);
} else {
$error = true;
}
fclose($fpOut);
} else {
$error = true;
}

return
$error ? false : $dest;
}
?>

# 解密文件

要解密使用上面函数加密的文件,可以使用这个函数。

<?php
/**
* 解密传入的文件,并将结果保存到新文件,删除文件名最后 4 个字符。
*
* @param string $source 要解密的文件路径
* @param string $key 用于解密的密钥(必须与加密时使用的密钥相同)
* @param string $dest 要写入解密文件的文件名。
* @return string|false 返回已创建的文件名,如果发生错误则返回 FALSE
*/
function decryptFile($source, $key, $dest)
{
$key = substr(sha1($key, true), 0, 16);

$error = false;
if (
$fpOut = fopen($dest, 'w')) {
if (
$fpIn = fopen($source, 'rb')) {
// 从文件开头获取初始化向量
$iv = fread($fpIn, 16);
while (!
feof($fpIn)) {
// 解密时需要读取比加密时多一个块
$ciphertext = fread($fpIn, 16 * (FILE_ENCRYPTION_BLOCKS + 1));
$plaintext = openssl_decrypt($ciphertext, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
// 使用密文的第一个 16 字节作为下一个初始化向量
$iv = substr($ciphertext, 0, 16);
fwrite($fpOut, $plaintext);
}
fclose($fpIn);
} else {
$error = true;
}
fclose($fpOut);
} else {
$error = true;
}

return
$error ? false : $dest;
}
?>

来源:http://stackoverflow.com/documentation/php/5794/cryptography/25499/
3
desmatic at gmail dot com
2 年前
升级了 PHP,需要用什么东西来替换不安全的遗留 mcrypt 库,但仍然支持经典的用户、密码接口。

<?php
function encrypt($plaintext, $key, $cipher = "aes-256-gcm") {
if (!
in_array($cipher, openssl_get_cipher_methods())) {
return
false;
}
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
$tag = null;
$ciphertext = openssl_encrypt(
gzcompress($plaintext),
$cipher,
base64_decode($key),
$options=0,
$iv,
$tag,
);
return
json_encode(
array(
"ciphertext" => base64_encode($ciphertext),
"cipher" => $cipher,
"iv" => base64_encode($iv),
"tag" => base64_encode($tag),
)
);
}

function
decrypt($cipherjson, $key) {
try {
$json = json_decode($cipherjson, true, 2, JSON_THROW_ON_ERROR);
} catch (
Exception $e) {
return
false;
}
return
gzuncompress(
openssl_decrypt(
base64_decode($json['ciphertext']),
$json['cipher'],
base64_decode($key),
$options=0,
base64_decode($json['iv']),
base64_decode($json['tag'])
)
);
}

$secret = "MySecRet@123";
$cipherjson = encrypt("Hello world!\n", $secret);
echo
decrypt($cipherjson, $secret);

?>
11
Anonymous
9 年前
关于参数的几个注意事项

data - 被解释为二进制字符串
method - 普通的字符串,确保您检查 openssl_get_cipher_methods() 以获取服务器中可用密码的列表*
password - 正如 biohazard 之前提到的,这实际上是 THE KEY!它应该是十六进制格式。
options - 如“参数”部分所述
iv - 初始化向量。与 biohazard 之前提到的不同,这应该是二进制字符串。您应该检查您的特定实现。

要验证您的 IV 的长度/格式,您可以提供不同长度的字符串并检查错误日志。例如,在 PHP 5.5.9(Ubuntu 14.04 LTS)中,提供 32 字节的十六进制字符串(代表 16 字节的二进制 IV)会引发错误。
“传入的 IV 长度为 32 字节,超过所选密码的预期长度 16 字节”(所选密码为“aes-256-cbc”,使用 128 位的 IV,即它的块大小)。
或者,您可以使用 openssl_cipher_iv_length()。

从安全角度来看,请确保您了解您的 IV 是否需要是随机的、秘密的或加密的。很多时候 IV 可以是非秘密的,但它必须是密码学安全的随机数。请确保您使用适当的函数(如 openssl_random_pseudo_bytes(),而不是 mt_rand())来生成它。

*请注意,可用的密码方法在开发服务器和生产服务器之间可能有所不同!它们将取决于您机器上的 OpenSSL 安装和编译选项。
12
Raphael
9 年前
注意此方法添加的填充!

<?php
$encryption_key
= openssl_random_pseudo_bytes(32);
$iv = openssl_random_pseudo_bytes(16);
$data = openssl_random_pseudo_bytes(32);

for (
$i = 0; $i < 5; $i++) {
$data = openssl_encrypt($data, 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, $iv);
echo
strlen($data) . "\n";
}
?>

使用此示例,输出将为
48
64
80
96
112

这是因为我们的 $data 已经占用所有块大小,因此该方法添加了一个新块,该块将只包含填充字节。

我想到的唯一避免这种情况的解决方案是在第一个选项中添加 OPENSSL_ZERO_PADDING 选项
<?php
$data
= openssl_encrypt($data, 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv);
?>

/!\ 使用此选项时要小心,确保您提供的 data 已经过填充或已经占用所有块大小。
10
denis at bitrix dot ru
6 年前
如何从 mcrypt 迁移到 openssl 并保持向后兼容性。

在我的情况下,我使用了 ECB 模式下的 Blowfish。任务是使用 openssl_decrypt 解密由 mcrypt_encrypt 加密的数据,反之亦然。乍一看很明显。但实际上,openssl_encrypt 和 mcrypt_encript 在大多数情况下会给出不同的结果。

在网上调查后,我发现原因在于不同的填充方法。由于某种原因,openssl_encrypt 在使用 OPENSSL_ZERO_PADDING 和 OPENSSL_NO_PADDING 选项时表现出相同的不寻常行为:如果加密字符串不能被块大小整除,它将返回 FALSE。要解决这个问题,您必须自己用 NUL 填充字符串。

第二个问题是密钥长度。如果密钥长度在 16 到 56 字节之间,这两个函数都会给出相同的结果。而且我设法发现,如果您的密钥长度小于 16 字节,您只需要重复它适当的次数即可。

最后,以下代码在 openssl 和 mcrypt 库上以相同的方式工作。

<?php
function encrypt($data, $key)
{
$l = strlen($key);
if (
$l < 16)
$key = str_repeat($key, ceil(16/$l));

if (
$m = strlen($data)%8)
$data .= str_repeat("\x00", 8 - $m);
if (
function_exists('mcrypt_encrypt'))
$val = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
else
$val = openssl_encrypt($data, 'BF-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);

return
$val;
}

function
decrypt($data, $key)
{
$l = strlen($key);
if (
$l < 16)
$key = str_repeat($key, ceil(16/$l));

if (
function_exists('mcrypt_encrypt'))
$val = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
else
$val = openssl_decrypt($data, 'BF-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
return
$val;
}

$data = 'my secret message';
$key = 'dontsay';

$c = encrypt($data, $key);
$d = decrypt($c, $key);
var_dump($c);
var_dump($d);
?>
Gives

string(32) "SWBMedXJIxuA9FcMOqCqomk0E5nFq6wv"
string(24) "my secret message\000\000\000\000\000\000\000"
1
ralf at exphpert dot de
2 年前
我想指出,对于经验较少的用户来说,该命令描述没有很好地指出该命令的实际工作原理。

一个重要的点是,您不需要将标签传递给 openssl_encrypt。标签变量中的任何值都将被 openssl_encrypt 覆盖。它本身会创建一个需要存储的标签。

要使用 openssl_decrypt 解密加密的密钥,您需要提供(至少)密钥、密码、初始化向量和标签。
7
max
12 年前
对于尝试使用 'aes-256-cbc' 密码(以及其他 cbc 密码)与其他 AES 实现(例如 C 库)协同工作的人来说,这可能很有用,因为 openssl 扩展对填充字节有严格的实现。我只有通过手动查看 openssl 源代码才找到了解决方案。

在 C 中,您需要以下方式对明文进行填充(假设所有内存分配都是正确的)

nPadding = ( 16 - ( bufferSize % 16 ) ) ? ( 16 - ( bufferSize % 16 ) ) : 16;
for( index = bufferSize; index < bufferSize + nPadding; index++ )
{
plaintext[ index ] = (char)nPadding;
}

而解密的验证方式为

isSuccess = TRUE;
for( index = bufferSize - 1; index > ( bufferSize - nPadding ); index-- )
{
if( plaintext[ index ] != nPadding )
{
isSuccess = FALSE;
break;
}
}
decryptedSize = bufferSize - nPadding;

简单来说,缓冲区必须填充到 blockSize。如果缓冲区已经是 blockSize 的倍数,则添加一个完整的 blockSize 字节作为填充。

填充字节的值必须是填充字节数的字节数...

因此,5 个字节的填充将在密文末尾添加以下字节
[ 0x05 ][ 0x05 ][ 0x05 ][ 0x05 ][ 0x05 ]

希望这能为其他人节省几小时的时间。
2
gcleaves at gmail dot com
4 年前
请注意,在撰写本文时,“示例#2 AES 认证加密示例(适用于 PHP 5.6+)”中存在一个重要且天真的安全漏洞。

在计算 HMAC 时,您必须包含 IV。否则,有人可以在传输过程中更改 IV,从而更改解密消息,同时保持 HMAC 的完整性。这是一个绝对的灾难。

要修复示例,HMAC 应该这样计算

<?php
$hmac
= hash_hmac('sha256', $iv.$ciphertext_raw, $key, $as_binary=true);
?>
1
Jess Portnoy
6 年前
请注意,OPENSSL_RAW_DATA 和 OPENSSL_ZERO_PADDING 是由以下提交引入的
https://github.com/php/php-src/commit/9e7ae3b2d0e942b816e3836025456544d6288ac3

在此之前,options 参数称为 raw_output,是一个布尔值,因此如果您考虑将此方法作为 mcrypt_encrypt() 的替代方法,它只适用于 PHP 5.5 及更高版本。

有关使用 OpenSSL 替代 Mcrypt 加密/解密方法的良好指南,请点击此处
http://thefsb.tumblr.com/post/110749271235/using-opensslendecrypt-in-php-instead-of
1
handsomedmm at 126 dot com
4 年前
如果使用 openssl enc 命令并带密码和盐对数据进行加密,也可以使用 openssl_decrypt 进行解密。

例如。

加密命令

# echo -n test123 | openssl enc -aes-128-cbc -pass pass:"pass123" -a -md md5

解密命令
# echo -n U2FsdGVkX19349P4LpeP5Sbi4lpCx6lLwFQ2t9xs2AQ= | base64 -d| openssl enc -aes-128-cbc -pass pass:"pass123" -md md5 -d -p
salt=77E3D3F82E978FE5
key=9CA70521F78B9909BF73BAE9233D6258
iv =04BCCB509EC9E6F5AF7E822CA58EA557
test123

使用 php 代码
<?php
// 编码数据
$encodeData = "U2FsdGVkX19349P4LpeP5Sbi4lpCx6lLwFQ2t9xs2AQ=";

// base64 解码
$data = base64_decode($encodeData);
$data = substr($data, 16); // 如果加盐,则删除 Salted__ 前缀

// 盐和密码配置
$salt = hex2bin("77E3D3F82E978FE5");
$pass = "pass123";
$method = "AES-128-CBC";

// 生成 iv 和 key
$hash1 = md5($pass . $salt);
$hash2 = md5(hex2bin($hash1) . $pass . $salt);
$key = hex2bin($hash1);
$iv = hex2bin($hash2);

$decodeData = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);

var_dump($decodeData);
0
darek334 at gazeta dot pl
7 years ago
要检查密码是否使用 IV,请使用 openssl_cipher_iv_length,如果存在,它将返回长度,如果不存在,将返回 0,如果密码未知,将返回 false。
0
Kruthers
7 years ago
对于该函数的“password”参数,似乎仍然存在一些困惑。它接受密钥的二进制字符串(即,不是编码的),至少对于我尝试的密码方法(AES-128-CTR 和 AES-256-CTR)来说是如此。其中一篇帖子说您应该对密钥进行十六进制编码(这是错误的),还有一些帖子说您应该对密钥进行散列,但没有明确说明如何正确传递散列密钥。

除了匿名发布的帖子之外,以下应该是有关参数的更准确信息

data - 二进制字符串
method - 普通字符串,来自 openssl_get_cipher_methods()
password - 二进制字符串(即,密钥的二进制形式)
options - 整数(使用提供的常量)
iv - 二进制字符串

这不仅来自我的测试,而且得到了 https://github.com/defuse/php-encryption 对该函数的使用所支持。
-1
public at grik dot net
14 年前
可以使用 openssl_get_cipher_methods(); 获取该函数的方法列表。
可以使用 openssl_private/public_encrypt() 对密码进行加密。
-3
waltzie
5 年前
在使用 MCRYPT_RIJNDAEL_128 CBC 的情况下,在 mcrypt 和 openssl 之间实现 1:1 的加密/解密之间存在一些问题,因为 AES-256 与 RIJNDAEL-256 不同。
AES 中的 256 指的是密钥大小,而 RIJNDAEL 中的 256 指的是块大小。
当使用 256 位密钥时,AES-256 等同于 RIJNDAEL-128。
(https://stackoverflow.com/questions/6770370/aes-256-encryption-in-php ircmaxell 2013 年 6 月 22 日 11:50)

示例

<?php

function encrypt_openssl($msg, $key, $iv) {
$encryptedMessage = openssl_encrypt($msg, 'AES-256-CBC', $key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING , $iv);
return
$iv . $encryptedMessage;
}

function
decrypt_openssl($data, $key) {
$iv_size = openssl_cipher_iv_length('AES-256-CBC');
$iv = substr($data, 0, $iv_size);
$data = substr($data, $iv_size);
return
openssl_decrypt($data, 'AES-256-CBC', $key,OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING , $iv);

}

function
decrypt_data($data,$key) {
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = substr($data, 0, $iv_size);
$data = substr($data, $iv_size);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);
$decrypted = rtrim($decrypted, chr(0));
return(
$decrypted);
}

function
encrypt_data($data,$key,$iv) {
$encrypted = $iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);
return
$encrypted;
}

// ZERO Padding ISO/IEC 9797-1, ISO/IEC 10118-1
function pad_zero($data) {
$len = mcrypt_get_block_size (MCRYPT_RIJNDAEL_128,MCRYPT_MODE_CBC);
if (
strlen($data) % $len) {
$padLength = $len - strlen($data) % $len;
$data .= str_repeat("\0", $padLength);
}
return
$data;
}

$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$data = "Hello World!";
$key = hash('sha256',"secret",true);

echo
"\n\n$data\n\n";

$enc = base64_encode(encrypt_data($data,$key,$iv));
echo
"\nEnc: $enc";
$dec = decrypt_data(base64_decode($enc),$key);
echo
"\nDec: $dec";
$dec2=decrypt_openssl(base64_decode($enc),$key);
echo
"\nDec: $dec2";

echo
"\n\nreverse\n";

$enc2 = base64_encode(encrypt_openssl(pad_zero($data),$key,$iv));
echo
"\nEnc: $enc2";
$dec = decrypt_data(base64_decode($enc2),$key);
echo
"\nDec: $dec";
$dec2=decrypt_openssl(base64_decode($enc2),$key);
echo
"\nDec: $dec2";
-6
David
8 年前
注意,如果您没有指定 ...RAW_DATA 选项,则会得到一个 base64 编码的结果。我浪费了几个小时,因为我的 PHP 没有 OPENSSL_RAW_DATA 常量,而且在我仔细地对结果进行 base64 编码后,它就是无法解码...
-6
me at bobste dot in
6 年前
<?php
string openssl_encrypt
(
string $data ,
string $method ,
string $key,
int $options = 0, // 可选
string $iv = "", // 可选
string &$tag = NULL, // 可选
string $aad = "", // 可选
int $tag_length = 16 // 可选
)
// (一个更友好的函数头部)
?>
-16
Kukulkan
12 年前
PHP OpenSSL 函数 openssl_encrypt() 和 openssl_decrypt() 似乎对所有对称密码使用 PKCS5/7 样式填充。因此,您无法使用它们来使用空字节填充进行加密,也无法解密使用空字节填充的数据。

包装器的开发者忘记了填充方案标志... :(
-16
kazaaknet at yahoo dot com
12 年前
请注意,此函数存在内存泄漏:https://bugs.php.net/bug.php?id=54060. 我相信这个问题在 5.3.6 中得到了修复,但在运行 5.3.5 且流量适中的生产 Web 服务器上,这会导致内存大量泄漏,从而导致我的网站崩溃。请改用 mcrypt_encrypt。
-14
Geoff
8 年前
与这里其他一些评论相反,我不确定密码是否真的被不正确地当作直接密钥处理。我这么说,是因为我一直将随机文本值传递给这个参数,而这些值作为十六进制输入是无效的。它似乎正在对提供的密码进行哈希处理,使用的是我所不知道的算法,否则我预计它会抛出异常,而不是按预期工作。

也就是说,我正在使用 openssl_decrypt() 解密只用 openssl_encrypt() 加密的数据。我还没有尝试解密我知道确切直接密钥的数据,以确定我是否在遇到错误的填充块或其他异常,这些异常会表明密钥不匹配。
To Top