2024年PHP开发者大会日本站

mcrypt_encrypt

(PHP 4 >= 4.0.2, PHP 5, PHP 7 < 7.2.0, PECL mcrypt >= 1.0.0)

mcrypt_encrypt使用给定参数加密明文

警告

此函数自PHP 7.1.0起已弃用,自PHP 7.2.0起已移除。强烈建议不要依赖此函数。

描述

mcrypt_encrypt(
    字符串 $cipher,
    字符串 $key,
    字符串 $data,
    字符串 $mode,
    字符串 $iv = ?
): 字符串|false

加密数据并返回。

参数

cipher

一个**MCRYPT_ciphername**常量,或算法名称字符串。

key

用于加密数据的密钥。如果提供的密钥大小不受密码支持,则函数将发出警告并返回false

data

将使用给定的ciphermode加密的数据。如果数据大小不是n * 块大小,则数据将用'\0'填充。

返回的密文可能大于data给定的数据大小。

mode

一个**MCRYPT_MODE_modename**常量,或以下字符串之一:“ecb”、“cbc”、“cfb”、“ofb”、“nofb”或“stream”。

iv

用于CBC、CFB、OFB模式的初始化,以及某些算法的STREAM模式。如果提供的IV大小不受链式模式支持,或者没有提供IV,但链式模式需要一个IV,则函数将发出警告并返回false

返回值

返回加密后的数据字符串,或在失败时返回false

范例

示例 #1 mcrypt_encrypt() 示例

<?php
# --- 加密 ---

# 密钥应为随机二进制数据,使用scrypt、bcrypt或PBKDF2将
# 字符串转换为密钥
# 密钥使用十六进制指定
$key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");

# 显示密钥大小,对于AES-128、192和256分别使用16、24或32字节的密钥
$key_size = strlen($key);
echo
"密钥大小: " . $key_size . "\n";

$plaintext = "This string was AES-256 / CBC / ZeroBytePadding encrypted.";

# 创建一个随机IV与CBC编码一起使用
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

# 创建与AES(Rijndael块大小=128)兼容的密文
# 保持文本机密
# 只适用于编码输入永不以00h结尾的输入
# (因为默认零填充)
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key,
$plaintext, MCRYPT_MODE_CBC, $iv);

# 将IV添加到前面,以便在解密时可用
$ciphertext = $iv . $ciphertext;

# 编码生成的密文,以便可以用字符串表示
$ciphertext_base64 = base64_encode($ciphertext);

echo
$ciphertext_base64 . "\n";

# === 警告 ===

# 生成的密文没有添加完整性或真实性
# 并且不受填充预言机攻击的保护。

# --- 解密 ---

$ciphertext_dec = base64_decode($ciphertext_base64);

# 获取IV,iv_size应使用mcrypt_get_iv_size()创建
$iv_dec = substr($ciphertext_dec, 0, $iv_size);

# 获取密文(除了前面的$iv_size之外的所有内容)
$ciphertext_dec = substr($ciphertext_dec, $iv_size);

# 可以删除明文末尾的00h值字符
$plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key,
$ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);

echo
$plaintext_dec . "\n";
?>

以上示例将输出

Key size: 32
ENJW8mS2KaJoNB5E5CoSAAu0xARgsR1bdzFWpEn+poYw45q+73az5kYi4j+0haevext1dGrcW8Qi59txfCBV8BBj3bzRP3dFCp3CPQSJ8eU=
This string was AES-256 / CBC / ZeroBytePadding encrypted.

参见

添加注释

用户贡献的注释 14条注释

scott at paragonie dot com
9年前
如果您在2015年编写用于加密/解密数据的代码,则应使用openssl_encrypt()和openssl_decrypt()。底层库(libmcrypt)自2007年以来已被放弃,其性能远不如OpenSSL(它在现代处理器上利用AES-NI,并且是缓存定时安全的)。

此外,MCRYPT_RIJNDAEL_256不是AES-256,它是Rijndael分组密码的不同变体。如果您想要mcrypt中的AES-256,则必须使用具有32字节密钥的MCRYPT_RIJNDAEL_128。OpenSSL更清晰地显示您正在使用哪种模式(即'aes-128-cbc'与'aes-256-ctr')。

OpenSSL 使用 PKCS7 填充和 CBC 模式,而不是 mcrypt 的 NULL 字节填充。因此,与 OpenSSL 相比,mcrypt 更容易使您的代码容易受到填充预言机攻击。

最后,如果您没有对密文进行身份验证(先加密后 MAC),那么您的做法是错误的。

进一步阅读

https://paragonie.com/blog/2015/05/using-encryption-and-authentication-correctly

https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong
jesse at pctest dot com
19 年前
解决 .NET 的 TripleDESCryptoServiceProvider 与 3DES 的不兼容性

mcrypt 的 3DES 只接受 192 位密钥,但 Microsoft 的 .NET 和许多其他工具都接受 128 位和 192 位密钥。
如果您的密钥太短,mcrypt 会“贴心地”在末尾填充空字符,但 .NET 拒绝使用最后一个三分之一全是空字符的密钥(这是一个无效密钥)。这阻止您在 .NET 中模拟 mcrypt 的“短密钥”行为。

如何解决这个问题?需要一些 DES 理论知识
3DES 三次运行 DES 算法,使用 192 位密钥的每一部分作为 64 位 DES 密钥

加密 Key1 -> 解密 Key2 -> 加密 Key3

并且 .NET 和 PHP 的 mcrypt 的方式相同。
问题出现在 .NET 的短密钥模式中,因为 128 位只有两个 64 位 DES 密钥
他们使用的算法是

加密 Key1 -> 解密 Key2 -> 加密 Key1

mcrypt 本身没有这种操作模式。
但在您开始自己运行三次 DES 之前,这里有一个快速修复方法
<?php
$my_key
= "12345678abcdefgh"; // 一个 128 位 (16 字节) 密钥
$my_key .= substr($my_key,0,8); // 将前 8 个字节附加到末尾
$secret = mcrypt_encrypt(MCRYPT_3DES, $my_key, $data, MCRYPT_MODE_CBC, $iv); // CBC 是 .NET 中的默认模式
?>

就像变魔术一样,它起作用了。

还有一个需要注意的地方:数据填充
mcrypt 总是使用空字符填充数据
但 .NET 有两种填充模式:“零”和“PKCS7”
“零”与 mcrypt 方案相同,但 PKCS7 是默认值。
不过,PKCS7 并没有复杂多少
它不是使用空字符,而是附加填充字节的总数(这意味着,对于 3DES,它可以是 0x01 到 0x07 之间的任何值)
如果您的明文是“ABC”,它将被填充为
0x41 0x42 0x43 0x05 0x05 0x05 0x05 0x05

您可以通过计算最后一个字符出现的次数来从 PHP 中的解密字符串中删除这些字符,如果它与它的序数值匹配,则将字符串截断这么多字符
<?php
$block
= mcrypt_get_block_size('tripledes', 'cbc');
$packing = ord($text{strlen($text) - 1});
if(
$packing and ($packing < $block)){
for(
$P = strlen($text) - 1; $P >= strlen($text) - $packing; $P--){
if(
ord($text{$P}) != $packing){
$packing = 0;
}
}
}
$text = substr($text,0,strlen($text) - $packing);
?>

要填充打算用 .NET 解密的字符串,只需添加填充字节数量的 chr() 值即可
<?php
$block
= mcrypt_get_block_size('tripledes', 'cbc');
$len = strlen($dat);
$padding = $block - ($len % $block);
$dat .= str_repeat(chr($padding),$padding);
?>

这就是全部内容。
了解这一点,您就可以在 PHP 中精确地加密、解密和复制任何 .NET 3DES 行为。
your dot brother dot t at hotmail dot com
9年前
加密没有身份验证检查。可以使用三种方法实现,在http://en.wikipedia.org/wiki/Authenticated_encryption#Approaches_to_Authenticated_Encryption 中进行了描述
先加密后 MAC (EtM)、加密并 MAC (E&M)、先 MAC 后加密 (MtE)。

以下是 MtE 的建议

<?php
public static function getMacAlgoBlockSize($algorithm = 'sha1')
{
switch(
$algorithm)
{
case
'sha1':
{
return
160;
}
default:
{
return
false;
break;
}
}
}

public static function
decrypt($message, $key, $mac_algorithm = 'sha1',
$enc_algorithm = MCRYPT_RIJNDAEL_256, $enc_mode = MCRYPT_MODE_CBC)
{
$message= base64_decode($message);
$iv_size = mcrypt_get_iv_size($enc_algorithm, $enc_mode);

$iv_dec = substr($message, 0, $iv_size);
$message= substr($message, $iv_size);

$message= mcrypt_decrypt($enc_algorithm, $key, $message, $enc_mode, $iv_dec);

$mac_block_size = ceil(static::getMacAlgoBlockSize($mac_algorithm)/8);
$mac_dec = substr($message, 0, $mac_block_size);
$message= substr($message, $mac_block_size);

$mac = hash_hmac($mac_algorithm, $message, $key, true);

if(
$mac_dec == $mac)
{
return
$password;
}
else
{
return
false;
}
}

public static function
encrypt($message, $key, $mac_algorithm = 'sha1',
$enc_algorithm = MCRYPT_RIJNDAEL_256, $enc_mode = MCRYPT_MODE_CBC)
{

$mac = hash_hmac($mac_algorithm, $message, $key, true);
$mac = substr($mac, 0, ceil(static::getMacAlgoBlockSize($mac_algorithm)/8));
$message= $mac . $message;

$iv_size = mcrypt_get_iv_size($enc_algorithm, $enc_mode);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

$ciphertext = mcrypt_encrypt($enc_algorithm, $key,
$message, $enc_mode, $iv);

return
base64_encode($iv . $ciphertext);
}
?>
匿名用户
13年前
其他笔记中对加密和IV有一些误解,尤其是在CBC模式下。

最重要的一点:加密本身**根本不提供**任何数据完整性或身份验证的证明。如果您需要确保数据是秘密且未被篡改的,您需要先加密,然后使用带密钥的HMAC。

对于CBC模式,IV**不需要**保密。它可以与明文一起发送。它需要是**唯一**且**随机**的。这样每条消息都使用不同的IV进行加密。

生成IV的最佳方法是使用`mcrypt_create_iv()`。

密钥必须是二进制的,而不是ASCII码。要从密码创建密钥:

<?php
$password
= "MyPassword!1!";
$aes256Key = hash("SHA256", $password, true); //我们需要一个32字节的二进制数据块
?>
Scott.a.Herbert at googlemail.com
12年前
最好始终使用标准的加密密码,而不是“自己编写”,首先,标准密码已经过世界一流的密码分析测试,而除非您是世界一流的密码分析师(如果您是,为什么还要考虑自己编写?!),否则您甚至没有能力测试它(例如,如果您只是用密钥对每个字符进行异或运算,它看起来可能很安全(文本将不同),但是如果您计算字符重复出现的次数,您会发现加密后的“E”出现的次数比加密后的“Z”更多(假设是英文文本)

其次,您可能认为您密码的隐藏特性使其更安全,但事实是,您的密码可能*只*是因为它是秘密的才安全,如果有人能够侵入您的网站并窃取您的代码(但不是您的密钥),他们也许能够破解您加密的数据,如果有人闯入并发现您正在使用Blowfish(例如),这将无济于事。
MadMass
11年前
请注意,IV对于`mcrypt_encrypt`和`mcrypt_decrypt`必须相同,否则解密后数据将损坏。
匿名用户
13年前
我注意到有些人使用a-z、A-Z和0-9作为密钥,并声明诸如“16个字符是128位密钥”之类的话。这是错误的。仅使用这些字符,每个字符最多只能获得6位的熵。

log2(26 + 26 + 10) = 5.954196310386876

因此,您实际上只获得了95位的熵,占您使用全范围密钥空间的0.0000000117%。

为了从仅使用a-z、A-Z和0-9的密钥中获得完整的熵,您应该将密钥长度乘以1.3333,以考虑每个字节丢失的2位熵。
匿名用户
17年前
我应该提到ECB模式忽略了IV,因此使用MCRYPT_MODE_ECB和IV的示例具有误导性(手册中的示例也显示了相同的内容)。此外,重要的是要知道ECB适用于随机数据,但结构化数据应该使用更强大的模式,如MCRYPT_MODE_CBC。

此外,`rtrim($decryptedtext, "\0")` 将比我懒惰的`trim()`更好地删除空填充……
Robin Leffmann
14年前
与`mcrypt_encrypt()`手册页以及关于CBC与CFB模式的信息中暗示的相反,`mcrypt_encrypt()`也可以很好地用于加密二进制数据。

一个简单的例子验证了,一旦截断到原始长度,解密后的输出在二进制上是相同的。

<?php

// 448位密钥(56字节) - mcrypt/php只为Blowfish密码使用此大小
// (使用较小的密钥也可以正常工作,因为mcrypt会追加\0以达到正确的密钥大小)
$key = 'SADFo92jzVnzSj39IUYGvi6eL8v6RvJH8Cytuiouh547vCytdyUFl76R';

// Blowfish/CBC使用8字节的IV
$iv = substr( md5(mt_rand(),true), 0, 8 );

// 对一些随机数据进行50次加密/解密操作,并使用md5()验证完整性
for( $i = 0; $i < 50; $i++ )
{
// 创建一个长度随机的随机二进制字符串
$size = mt_rand( 25000, 500000 );
$c = 0; $data = null;
while(
$c++ * 16 < $size )
$data .= md5( mt_rand(), true );
$data = substr( $data, 0, $size );
$cksum = md5( $data );

// 使用Blowfish/CBC加密
$enc = mcrypt_encrypt( MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_CBC, $iv );

echo
$size . ' -> ' . strlen( $enc ) . ' -> ';

// 解密(使用相同的IV - CBC模式必须如此)
$dec = mcrypt_decrypt( MCRYPT_BLOWFISH, $key, $enc, MCRYPT_MODE_CBC, $iv );

// 使用substr()截取输出,而不是像一些mcrypt手册页面中建议的那样使用rtrim() - 这是二进制数据,而不是纯文本
echo ( md5(substr($dec, 0, $size)) == $cksum ? 'ok' : 'bad' ) . PHP_EOL;
}

?>
stefan at katic dot me dot rs
10年前
我尝试(并成功)在JAVA中进行加密和解密,将其传递给php,然后再次进行操作,在我注意到一些有趣的事情时,不会损坏数据。所以,我的代码是这样的
$data = 'one';
$key = '1234567890123456';

function encrypt($data, $key){
return base64_encode(
mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
$key,
$data,
MCRYPT_MODE_CBC,
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
)
);
}
function decrypt($data, $key){
$decode = base64_decode($data);
return mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
$key,
$decode,
MCRYPT_MODE_CBC,
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
);


}
$encrypted = encrypt($data, $key);
$decrypted= decrypt($encrypted, $key);

//一开始,我以为有什么问题,因为我用'==='比较了$decrypted和$data。它不起作用(但后来又开始工作了,不知道为什么……)所以我转储了这两个
var_dump($data);
var_dump($decrypted);
//结果
string(16) "one"
string(16) "one"
//显然,两者的长度都是3,而不是16。只是想让你知道可能会发生什么,我真的不知道这是否是错误……
谢谢,
S.
匿名
6年前
<?php
# Vernam Cipher (One-time pad)
$k1 = '1.key';
$k2 = '2.key';
$d = 'The quick brown fox jumped over the lazy dog';
VernamCipher::createTestKeyFile($k1, 1024);
copy($k1, $k2);
$c1 = new VernamCipher($k1);
$eD = $c1->encryptWithHMAC($d);
echo
'Encrypted: ', bin2hex($eD);
$c2 = new VernamCipher($k2);
echo
PHP_EOL, 'Decrypted: ', $c2->decryptWithHMAC($eD);

class
VernamCipher
{
const
DEFAULT_HMAC_ALGO = 'sha3-256';
const
DEFAULT_HMAC_KEY_LENGTH = 16;
const
DEFAULT_HMAC_HASH_LENGTH = 32;
private
$keyFilePath;
private
$keyFileHandler;
private
$deferredFtruncate = false;
private
$deferredFtruncatePos;
private
$hmacAlgo = self::DEFAULT_HMAC_ALGO;
private
$hmacKeyLength = self::DEFAULT_HMAC_KEY_LENGTH;
private
$hmacHashLength = self::DEFAULT_HMAC_HASH_LENGTH;

function
__construct(string $keyFilePath, string $hmacAlgo = self::DEFAULT_HMAC_ALGO, int $hmacKeyLength = self::DEFAULT_HMAC_KEY_LENGTH)
{
$this->keyFilePath = $keyFilePath;
$this->openKeyFile();

if(
$hmacAlgo !== self::DEFAULT_HMAC_ALGO) {
if(
in_array($hmacAlgo, hash_algos())) {
$this->hmacAlgo = $hmacAlgo;
$this->hmacHashLength = strlen(hash($this->hmacAlgo, '', true));
}
else {
$this->hmacAlgo = self::DEFAULT_HMAC_ALGO;
$this->hmacHashLength = self::DEFAULT_HMAC_HASH_LENGTH;
}
}

if(
$hmacKeyLength !== self::DEFAULT_HMAC_KEY_LENGTH) {
$this->hmacKeyLength = $hmacKeyLength;
}
}
public function
encryptWithHMAC(string $data)
{
$hmacKey = $this->getBytes($this->hmacKeyLength);
$encData = $this->encrypt($data);
$dataHmac = $this->hashHmac($encData, $hmacKey);

return
$dataHmac.$encData;
}
public function
decryptWithHMAC(string $data)
{
$dataLength = strlen($data);

if(
$dataLength < $this->hmacHashLength)
throw new
Exception('data length '.$dataLength.' < hmac length '. $this->hmacHashLength);

$dataHmacRemote = substr($data, 0, $this->hmacHashLength);
$dataOnly = substr($data, $this->hmacHashLength);

$hmacKey = $this->getBytes($this->hmacKeyLength, false);
$dataHmacLocal = $this->hashHmac($dataOnly, $hmacKey);

if(
hash_equals($dataHmacLocal, $dataHmacRemote) === false)
throw new
Exception('Hashes not equals, remote: '.bin2hex($dataHmacRemote).' local:'. bin2hex($dataHmacLocal));

$this->deferredFtruncate();

return
$this->encrypt($dataOnly);
}
public function
encrypt(string $data) : string
{
$dataLength = strlen($data);
$bytes = $this->getBytes($dataLength);
for(
$i=0;$i<$dataLength;$i++)
$data{$i} = $data{$i} ^ $bytes{$i};

return
$data;
}
public function
decrypt(string $data) : string
{
return
$this->encrypt($data);
}
private function
hashHmac($data, $key)
{
return
hash_hmac($this->hmacAlgo, $data, $key, true);
}
# Don't use in production. You must use true random number generator.
public static function createTestKeyFile(string $filePath, int $size)
{
file_put_contents($filePath, random_bytes($size));
}
private function
deferredFtruncate()
{
if(!
$this->deferredFtruncate)
return;

ftruncate($this->keyFileHandler, $this->deferredFtruncatePos);
$this->deferredFtruncate = false;
}
public function
getBytes(int $length, $truncateNow = true) : string
{
fseek($this->keyFileHandler, 0, SEEK_END);
$currentPos = ftell($this->keyFileHandler);

if(
$currentPos < $length)
throw new
Exception('Not enough key materials, key size: '. $currentPos. ' needed: '.$length);

fseek($this->keyFileHandler, -$length, SEEK_END);
$bytes = fread($this->keyFileHandler, $length);

if(
$truncateNow)
ftruncate($this->keyFileHandler, $currentPos - $length);
else {
$this->deferredFtruncate = true;
$this->deferredFtruncatePos = $currentPos - $length;
}

return
$bytes;
}
private function
openKeyFile()
{
if(
$this->keyFileHandler)
return;

if((
$this->keyFileHandler = fopen($this->keyFilePath, 'rb+')) === false)
throw new
Exception('Cant open key file: '.$this->keyFilePath);

if(!
flock($this->keyFileHandler, LOCK_EX | LOCK_NB))
throw new
Exception('Cant lock key file: '.$this->keyFilePath);
}
}
?>
stonecypher at gmail dot com
18年前
这里大多数用户编写的密码示例都严重损坏,并且有一些情况下,手册中说的是完全错误的,例如“可以安全地以明文传输初始化向量”(这是错误的:参见Ciphers By Ritter,http://www.ciphersbyritter.com/GLOSSARY.HTM#IV,了解详细信息。)

mcrypt本身是完全安全的,但是正确且安全的用法并不明显。正确使用加密库很重要;即使简单的使用错误,即使它产生的结果可以在另一端解包,也可能使强大的算法完全失效。

必须使用可恢复的噪声源(可以接受任意的md5哈希,因为它只是一个伪OTP,其原始内容完全不重要)来置换初始化向量。

密码应该使用加盐的单向哈希重新创建(即使md5已被损坏,也可以接受,因为从破解的md5哈希中唯一可以恢复的是生成密码的源数据,这是无用的)。

使用合理的块模式很重要(OFB对几乎所有算法都不安全;永远不要使用它。在所有情况下都优先使用CBC,除非您需要处理退化的信号并且无法重新传输)。

正确的使用方法示例实际上很长,需要很多解释,所以我开发了一个安全的包装库,它不会限制使用,并且会对其自身进行大量注释。它适合使用或学习。请参阅我的博客了解有关Stone PHP SafeCrypt的详细信息

http://blog.sc.tri-bit.com/archives/101
leilond at hotmail dot com
8年前
我一直使用这种方法来防止很多错误

function encrypt( $string ) {
$algorithm = 'rijndael-128'; // 你可以使用任何可用的
$key = md5( "mypassword", true); // 二进制原始16字节维度。
$iv_length = mcrypt_get_iv_size( $algorithm, MCRYPT_MODE_CBC );
$iv = mcrypt_create_iv( $iv_length, MCRYPT_RAND );
$encrypted = mcrypt_encrypt( $algorithm, $key, $string, MCRYPT_MODE_CBC, $iv );
$result = base64_encode( $iv . $encrypted );
return $result;
}
function decrypt( $string ) {
$algorithm = 'rijndael-128';
$key = md5( "mypassword", true );
$iv_length = mcrypt_get_iv_size( $algorithm, MCRYPT_MODE_CBC );
$string = base64_decode( $string );
$iv = substr( $string, 0, $iv_length );
$encrypted = substr( $string, $iv_length );
$result = mcrypt_decrypt( $algoritmo, $key, $encrypted, MCRYPT_MODE_CBC, $iv );
return $result;
}
gm dot outside+php at gmail dot com
10年前
请注意,文档的以下部分不再正确(提交后:http://git.php.net/?p=php-src.git;a=commit;h=a861a3a93d89a50ce58e1ab1abef1eb501f97483)

> key
> 将用于加密数据的密钥。如果它小于所需的密钥大小,则用'\0'填充。最好不要使用ASCII字符串作为密钥。

该提交将行为更改为严格的,如果密钥大小小于所需大小,则会发出如下警告

警告:mcrypt_encrypt(): 此算法不支持大小为 10 的密钥。脚本 script.php 第 5 行中仅支持大小为 16 的密钥

并且 mcrypt_encode() 将返回失败。
To Top