PHP 大会日本 2024

openssl_encrypt

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

openssl_encrypt加密数据

描述

openssl_encrypt(
    #[\SensitiveParameter] 字符串 $data,
    字符串 $cipher_algo,
    #[\SensitiveParameter] 字符串 $passphrase,
    整数 $options = 0,
    字符串 $iv = "",
    字符串 &$tag = NULL,
    字符串 $aad = "",
    整数 $tag_length = 16
): 字符串|false

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

参数

data

要加密的明文消息数据。

cipher_algo

密码方法。有关可用密码方法的列表,请使用 openssl_get_cipher_methods()

passphrase

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

注意

对于 passphrase 没有使用密钥派生函数,正如其名称可能暗示的那样。唯一使用的操作是,如果长度与预期不同,则使用 NUL 字符进行填充或截断。

options

options 是标志 OPENSSL_RAW_DATAOPENSSL_ZERO_PADDINGOPENSSL_DONT_ZERO_PAD_KEY 的按位或。

iv

NULL 初始化向量。如果 IV 短于预期,则会用 NUL 字符填充并发出警告;如果密码短语长于预期,则会将其截断并发出警告。

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+ 中 GCM 模式下的 AES 认证加密示例

<?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";
}
?>

另请参阅

添加注释

用户贡献的注释 21 条注释

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;
}
?>
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 作为第四个参数,然后在 openssl 命令行中添加 iv 的十六进制表示作为参数

# 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 = '它有效!';

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

echo
"\niv 的十六进制表示:".strtohex ($iv);
echo
"\nkey 的十六进制表示:".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
'执行:'.$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 编码的文件进行解码。该命令将回显“它有效...”

: /
Nick
8 年前
关于 openssl 库,这里有很多困惑,也有一些错误的指导。

基本技巧是

截至 2016 年,aes-256-ctr 可以说是最佳的密码算法选择。这避免了潜在的安全问题(所谓的填充预言机攻击)以及来自将数据填充到特定块大小的算法的膨胀。aes-256-gcm 更好,但直到 openssl 库得到增强才能使用,这将在 PHP 7.1 中实现。

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

将 iv 数据加入到加密结果中,并在解密时再次提取 iv 数据。

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

使用 openssl_digest() 和 sha256 等哈希函数对选定的加密密钥(密码参数)进行哈希,并将哈希值用于密码参数。

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

希望它能帮助任何想要开始使用这个强大库的人。
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 文档无法达到质量预期时,您可以查看幕后实际发生的情况。
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 编码(原始数据)
Jean-Luc
7 年前
重要提示:密钥的长度应与您使用的密码完全相同。例如,如果您使用 AES-256,则应提供长度为 32 字节的 $key(256 位 == 32 字节)。$key 中的任何额外字节都将被截断,根本不会使用。
TheNorthMemory
3 年前
我看到有一个文档错误(#80236)提到 $tag 的用法。以下是一个示例,希望这些对某些人有所帮助。

<?php

/**
* 使用给定的密钥、初始化向量和附加认证数据加密给定的数据,返回一个 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);
}

/**
* 使用给定的密钥、初始化向量和附加认证数据解密一个 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);
naitsirch at e dot mail dot de
8 年前
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/
匿名用户
9 年前
关于参数的一些说明

data - 被解释为二进制字符串
method - 普通字符串,确保检查 openssl_get_cipher_methods() 以获取服务器上可用加密算法的列表*
password - 正如 biohazard 之前提到的,这实际上是密钥!它应该以十六进制格式表示。
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 的安装和编译选项。
desmatic at gmail dot com
3 年前
升级了 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);

?>
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);
?>

/!\ 使用此选项时请注意,确保您提供的数据已经填充过,或者已经包含了完整的块大小。
denis at bitrix dot ru
7 年前
如何从 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);
?>
输出结果

string(32) "SWBMedXJIxuA9FcMOqCqomk0E5nFq6wv"
string(24) "my secret message\000\000\000\000\000\000\000"
ralf at exphpert dot de
2 年前
我想指出,命令描述并没有很好地说明命令对于经验不足的用户是如何真正工作的。

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

为了能够使用 openssl_decrypt 解密加密的密钥,您需要提供(至少)密钥、密码、初始化向量和标签。
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 ]

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

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

要修复此示例,HMAC 应按如下方式计算

<?php
$hmac
= hash_hmac('sha256', $iv.$ciphertext_raw, $key, $as_binary=true);
?>
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
handsomedmm at 126 dot com
5 年前
如果使用 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 和密钥
$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);
darek334 at gazeta dot pl
7 年前
要检查密码是否使用 IV,可以使用 openssl_cipher_iv_length,如果存在则返回长度,不存在则返回 0,如果密码未知则返回 false。
Kruthers
8 年前
关于此函数的“密码”参数似乎仍然存在一些混淆。它接受密钥的二进制字符串(即,未编码),至少对于我尝试过的密码方法(AES-128-CTR 和 AES-256-CTR)是这样的。其中一篇帖子说你应该对密钥进行十六进制编码(这是错误的),有些帖子说你应该对密钥进行哈希处理,但没有清楚说明如何正确传递哈希后的密钥。

匿名用户发布的帖子应该更正为以下关于参数的更准确信息

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

这不仅来自我的测试,而且还得到了 https://github.com/defuse/php-encryption 对此函数的使用所支持。
public at grik dot net
14 年前
可以使用 openssl_get_cipher_methods(); 获取此函数的方法列表。
密码可以使用 openssl_private/public_encrypt() 进行加密。
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;
}

// 零填充 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\n反转\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";
To Top