PHP Conference Japan 2024

openssl_encrypt

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

openssl_encrypt加密資料

描述

openssl_encrypt(
    #[\SensitiveParameter] string $data,
    string $cipher_algo,
    #[\SensitiveParameter] string $passphrase,
    int $options = 0,
    string $iv = "",
    string &$tag = null,
    string $aad = "",
    int $tag_length = 16
): string|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
= 'It works ? Or not it works ?';
$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 cmd 工具用於檔案加密解密的 [-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 和金鑰參數必須是字串的十六進位表示法。

正確的解密命令是

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

由於它沒有鹽值,也沒有填充,而且透過設定函式的第三個參數,我們不再需要解碼 base64 編碼的檔案。該命令將會輸出 "it works..."

: /
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 上有一個名為 php-openssl-cryptor 的簡單 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 編碼 (RAW DATA)
options = 2
-> 無填充,Base64 編碼
options = 3 ( 1 或 2 )
-> 無填充,無 Base64 編碼 (RAW DATA)
Jean-Luc
7 年前
重要:金鑰的長度應與您正在使用的密碼完全相同。例如,如果您使用 AES-256,則您應該提供一個 32 個位元組長的 $key(256 位元 == 32 個位元組)。$key 中的任何其他位元組都會被截斷,完全不會使用。
TheNorthMemory
3 年前
我看到文件錯誤(#80236)提到 $tag 的用法。這裡有一些範例,希望對某些人有所幫助。

<?php

/**
* 使用給定的金鑰、初始化向量 (IV) 和額外認證資料 (AAD) 加密給定的資料,並回傳 base64 編碼的字串。
*
* @param string $plaintext - 要編碼的文字。
* @param string $key - 秘密金鑰,32 位元組的字串。
* @param string $iv - 初始化向量 (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 - 初始化向量 (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)
$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/
Anonymous
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;

簡單來說,緩衝區必須填充至區塊大小。如果緩衝區已經是區塊大小的倍數,您會新增一整個新的區塊大小位元組作為填充。

填充位元組的值 *必須* 是作為位元組的填充位元組數...

因此,5 個位元組的填充會在密文的結尾新增以下位元組
[ 0x05 ][ 0x05 ][ 0x05 ][ 0x05 ][ 0x05 ]

希望這能為其他人省下幾個小時的生命。
gcleaves at gmail dot com
5 年前
請注意,在撰寫本文時,「範例 #2 PHP 5.6+ 的 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 是由此 commit 引入的
https://github.com/php/php-src/commit/9e7ae3b2d0e942b816e3836025456544d6288ac3

在此之前,選項參數稱為 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 命令透過密碼和 salt 加密資料,則也可以透過 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 和密碼設定
$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);
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 Jun 22 '13 at 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";
To Top