我弄清楚了。此函式並非用於一般加密和解密。對於該功能,您需要 openssl_seal() 和 openssl_open()。
(PHP 4 >= 4.0.6, PHP 5, PHP 7, PHP 8)
openssl_public_encrypt — 使用公鑰加密資料
$data
,&$encrypted_data
,$public_key
,$padding
= OPENSSL_PKCS1_PADDING
openssl_public_encrypt() 使用公鑰 public_key
加密 data
,並將結果儲存到 encrypted_data
中。加密的資料可以透過 openssl_private_decrypt() 解密。
此函式可用於加密訊息,然後只有私鑰擁有者才能讀取。它也可以用於將安全資料儲存在資料庫中。
data
encrypted_data
這將保存加密的結果。
public_key
public_key
必須是與將用於解密資料的私鑰對應的公鑰。
padding
padding
可以是 OPENSSL_PKCS1_PADDING
、OPENSSL_SSLV23_PADDING
、OPENSSL_PKCS1_OAEP_PADDING
、OPENSSL_NO_PADDING
其中之一。
版本 | 描述 |
---|---|
8.0.0 |
public_key 現在接受 OpenSSLAsymmetricKey 或 OpenSSLCertificate 實例;之前,接受類型為 OpenSSL key 或 OpenSSL X.509 的 資源。 |
我們無法保證 RSA 在 2016 年仍然會被信任其安全性,但這是目前 RSA 的最佳實務。世界其他地方正在轉向 ECDH 和 EdDSA(例如 Ed25519)。
也就是說,請確保您使用的是 OPENSSL_PKCS1_OAEP_PADDING,否則您容易受到選擇密文攻擊(Google:「Daniel Bleichenbacher 1998 RSA padding oracle」,您會找到大量相關資料。)
唯一的修復方法是使用 OAEP(最好使用 MGF1-SHA256,但此函式不允許您指定該細節,並預設為 MGF1+SHA1)。
Phpseclib 為加密提供 RSAES-OAEP + MGF1-SHA256,為簽名提供 RSASS-PSS + MGF1-SHA256。
http://phpseclib.sourceforge.net/rsa/examples.html#encrypt,enc1
如果您不想自己處理這些細節,請查看 https://github.com/paragonie/EasyRSA
chsnyder 寫道,在他的實作中,資料限制為 936 位元。
實際上,這與 RSA 是否是 CPU 密集型、RAM 或任何類似的東西無關。
基本上,當您使用 RSA 金鑰(無論是公鑰或私鑰)加密某些內容時,加密值必須小於金鑰(由於用於進行實際加密的數學運算)。因此,如果您有一個 1024 位元金鑰,理論上您可以使用該金鑰加密任何 1023 位元值(或小於金鑰的 1024 位元值)。
但是,OpenSSL 使用的 PKCS#1 標準指定了一種填充方案(因此您可以在不損失安全性的情況下加密較小的數量),並且該填充方案至少需要 11 個位元組(如果要加密的值較小,則會更長)。因此,由於此原因,您可以使用 1024 位元金鑰加密的最大位數是 936 位元(除非您透過新增 OPENSSL_NO_PADDING 標誌來停用填充,在這種情況下,您可以達到 1023-1024 位元)。對於 2048 位元金鑰,則為 1960 位元。
但是,正如 chsnyder 正確寫的那樣,公鑰加密演算法的正常應用是儲存您要分別加密或簽名的資料的密鑰或雜湊值。雜湊通常是 128-256 位元(PHP sha1() 函式傳回一個 160 位元雜湊)。而 AES 金鑰是 128 到 256 位元。因此,它們中的任何一個都可以輕鬆地放入單個 RSA 加密中。
T. Horsten 解釋了原始加密的大小限制。以下是兩個函式,當您無法使用封裝函式時,可用於加密/解密較大的資料
function ssl_encrypt($source,$type,$key){
//假設 1024 位元金鑰並以區塊加密。
$maxlength=117;
$output='';
while($source){
$input= substr($source,0,$maxlength);
$source=substr($source,$maxlength);
if($type=='private'){
$ok= openssl_private_encrypt($input,$encrypted,$key);
}else{
$ok= openssl_public_encrypt($input,$encrypted,$key);
}
$output.=$encrypted;
}
return $output;
}
function ssl_decrypt($source,$type,$key){
// 原生 PHP 解密函式似乎有效
// 在 128 位元組區塊上。因此,這會解密長文字
// 使用 ssl_encrypt() 加密的。
$maxlength=128;
$output='';
while($source){
$input= substr($source,0,$maxlength);
$source=substr($source,$maxlength);
if($type=='private'){
$ok= openssl_private_decrypt($input,$out,$key);
}else{
$ok= openssl_public_decrypt($input,$out,$key);
}
$output.=$out;
}
return $output;
}
非常重要的一點是要認識到 $data 字串的最大大小限制及其與 SSL 位元大小的關係,正如其他人所指出的。在我透過區塊處理資料來解決最大大小限制之前,我從 openssl_error_string() 收到以下類型的錯誤
- error:0906D06C:PEM routines:PEM_read_bio:no start line OR
- error:0E06D06C:configuration file routines:NCONF_get_string:no value
使用 2048 位元的金鑰 (sha512, OPENSSL_KEYTYPE_RSA) 時,我的最大訊息大小為 245 位元組,而 4096 位元時的最大大小則為 502 位元組。因此,如果您稍後更改金鑰大小,特別是如果您縮減其大小,請注意這會影響您的最大加密長度。
資料長度 < 私鑰長度...所以我將訊息分段處理,並在其中加入 ":::"。然後再次加密。請參考程式碼來了解這個概念。
<?php
class cry
{
# generate a 1024 bit rsa private key, ask for a passphrase to encrypt it and save to file
//openssl genrsa -des3 -out /path/to/privatekey 1024
# generate the public key for the private key and save to file
//openssl rsa -in /path/to/privatekey -pubout -out /path/to/publickey
//programatically using php-openssll
//This will call while registration
function gen_cert($userid)
{
$dn = array("countryName" => 'XX', "stateOrProvinceName" => 'State', "localityName" => 'SomewhereCity', "organizationName" =>'MySelf', "organizationalUnitName" => 'Whatever', "commonName" => 'mySelf', "emailAddress" => 'user@example.com');
//Passphrase can be taken during registration
//Here its initialized to 1234 for sample
$privkeypass = 'root';
$numberofdays = 365;
//RSA encryption and 1024 bits length
$privkey = openssl_pkey_new(array('private_key_bits' => 1024,'private_key_type' => OPENSSL_KEYTYPE_RSA));
$csr = openssl_csr_new($dn, $privkey);
$sscert = openssl_csr_sign($csr, null, $privkey, $numberofdays);
openssl_x509_export($sscert, $publickey);
openssl_pkey_export($privkey, $privatekey, $privkeypass);
openssl_csr_export($csr, $csrStr);
//Generated keys are stored into files
$fp=fopen("../PKI/private/$userid.key","w");
fwrite($fp,$privatekey);
fclose($fp);
$fp=fopen("../PKI/public/$userid.crt","w");
fwrite($fp,$publickey);
fclose($fp);
}
//Encryption with public key
function encrypt($source,$rc)
{
//path holds the certificate path present in the system
$path="../PKI/public/server.crt";
$fp=fopen($path,"r");
$pub_key=fread($fp,8192);
fclose($fp);
openssl_get_publickey($pub_key);
//$source='';
//$source="sumanth ahoiadodakjaksdsa;ldadkkllksdalkalsdl;asld;ls sumanthasddddddddddddddddddddddddddddddddfsdfsffdfsdfsumanth";
$j=0;
$x=strlen($source)/10;
$y=floor($x);
for($i=0;$i<$y;$i++)
{
$crypttext='';
openssl_public_encrypt(substr($source,$j,10),$crypttext,$pub_key);$j=$j+10;
$crt.=$crypttext;
$crt.=":::";
}
if((strlen($source)%10)>0)
{
openssl_public_encrypt(substr($source,$j),$crypttext,$pub_key);
$crt.=$crypttext;
}
return($crt);
}
//Decryption with private key
function decrypt($crypttext,$userid)
{
$passphrase="root";
$path="../PKI/private/server.key";
$fpp1=fopen($path,"r");
$priv_key=fread($fpp1,8192);
fclose($fpp1);
$res1= openssl_get_privatekey($priv_key,$passphrase);
$tt=explode(":::",$crypttext);
$cnt=count($tt);
$i=0;
while($i<$cnt)
{
openssl_private_decrypt($tt[$i],$str1,$res1);
$str.=$str1;
$i++;
}
return $str;
}
function sign($source,$rc)
{
$has=sha1($source);
$source.="::";
$source.=$has;
$path="../PKI/public/$rc.crt";
$fp=fopen($path,"r");
$pub_key=fread($fp,8192);
fclose($fp);
openssl_get_publickey($pub_key);
openssl_public_encrypt($source,$mese,$pub_key);
return $mese;
}
function verify($crypttext,$userid)
{
$passphrase="root";
$path="../PKI/private/$userid.key";
$fpp1=fopen($path,"r");
$priv_key=fread($fpp1,8192);
fclose($fpp1);
$res1= openssl_get_privatekey($priv_key,$passphrase);
openssl_private_decrypt($crypttext,$has1,$res1);
list($c1,$c2)=split("::",$has1);
$has=sha1($c1);
if($has==$c2)
{
$message=$c1;
return $message;
}
}
}
?>
大多數人的困惑似乎在於「混合 $key」。
$key 的解釋與 https://php.dev.org.tw/manual/en/function.openssl-pkey-get-public.php 的參數大致相同。
它可以接受從 openssl_pkey_get_public() 返回的資源 $key,或者找到值是文字,並將該文字傳遞給 openssl_pkey_get_public() 以取得有效的資源。
為了更好地分解 rstinnett 的範例
(以及缺陷所在)
<?php
function EncryptData($source)
{
$fp=fopen("/etc/httpd/conf/ssl.crt/server.crt","r");
$pub_key_string=fread($fp,8192);
fclose($fp);
openssl_get_publickey($pub_key);
openssl_public_encrypt($source,$crypttext,$pub_key_string);
/*這只是將 pub_key_string 的字串內容傳回以進行解碼*/
return(base64_encode($crypttext));
}
?>
更有效率
<?php
function EncryptData($source)
{
$fp=fopen("/etc/httpd/conf/ssl.crt/server.crt","r");
$pub_key_string=fread($fp,8192);
fclose($fp);
$key_resource = openssl_get_publickey($pub_key);
openssl_public_encrypt($source,$crypttext, $key_resource );
/*使用已存在的金鑰資源*/
return(base64_encode($crypttext));
}
?>
更簡短
<?php
function EncryptData($source)
{
$fp=fopen("/etc/httpd/conf/ssl.crt/server.crt","r");
$pub_key=fread($fp,8192);
fclose($fp);
openssl_public_encrypt($source,$crypttext, $pub_key );
return(base64_encode($crypttext));
}
?>
如果您需要訊息金鑰,請從 openssl_random_pseudo_bytes() 函數中取得。
請勿僅雜湊目前時間--攻擊者可以很容易地猜出任何此類金鑰 (他只需雜湊一堆可能的時間值並嘗試,直到找到正確的金鑰。攻擊者可以使用普通的 PC 每分鐘生成和測試數百萬個候選雜湊值)。
openssl_public_encrypt 和 openssl_private_encrypt 無法加密大型資料。所以我寫了一個類別。這個類別可以加密大型資料並解密。
請參考網址:http://pigo.pigo.idv.tw/opensslcrypt.phps
看來要加密的字串大小有限制:約 50 個字元。
這是因為實作會配置一個大小為 EVP_PKEY_size(pkey) 的輸出緩衝區,這完全是任意的,與輸入的大小無關。此外,它沒有使用密碼信封方法。它只是對輸入字串進行 RSA 加密。
簡單的方法
<?php
$publicKey = "file://path/to/public/key-crt.pem";
$plaintext = "要加密的字串";
openssl_public_encrypt($plaintext, $encrypted, $publicKey);
echo $encrypted; //加密後的字串
?>
這個範例對我有用
RedHat 7.2 / php 4.2.2 / Apache 1.3.7
// 步驟 1:使用公鑰加密 (您需要私鑰才能解密 - 請參閱步驟 2)。
$string="一些重要資料";
$fp=fopen ("cert.pem","r");
$pub_key=fread ($fp,8192);
fclose($fp);
$PK="";
$PK=openssl_get_publickey($pub_key);
if (!$PK) {
echo "無法取得公鑰";
}
$finaltext="";
openssl_public_encrypt($string,$finaltext,$PK);
if (!empty($finaltext)) {
openssl_free_key($PK);
echo "加密成功!";
}else{
echo "無法加密";
}
// 步驟 2:解密 (使用私鑰)
$fp=fopen ("pk.pem","r");
$priv_key2=fread ($fp,8192);
fclose($fp);
$PK2=openssl_get_privatekey($priv_key2);
$Crypted=openssl_private_decrypt($Data,$Decrypted,$PK2);
if (!$Crypted) {
$MSG.="<p class='error'>無法解密 ($CCID).</p>";
}else{
echo "解密資料: " . $Decrypted;
}
<?php
$value =<<<EOL
一些很長很長的文字
EOL;
$data = str_split($value, 214); // 最大值是 214
$result = '';
foreach($data as $d){
if(openssl_public_encrypt($d, $encrypted, $publickey, OPENSSL_PKCS1_OAEP_PADDING)){
$result .= $encrypted;
}
}
var_dump($result);
$result = base64_encode($result);
$data = str_split(base64_decode($result), 256); //每個 strlen($encrypted) == 256
$result = '';
foreach($data as $d){
if(openssl_private_decrypt($d, $decrypted, $privatekey, OPENSSL_PKCS1_OAEP_PADDING)){
$result .= $decrypted;
}
}
var_dump($result);
?>
由於演算法的性質,openssl_private_encrypt() 可以加密的資料長度有下限。
要加密更大的資料,您可以使用 openssl_encrypt() 和隨機密碼 (例如 sha1(microtime(true))),並使用 openssl_public_encrypt() 加密該密碼。
這樣就可以使用公鑰加密資料,並使用私鑰解密。
在下面的評論中,Jeff 說這個函數的輸入限制為「約 50 個字元」。在我的 PHP5 建置版本中,限制為 117 個字元 (936 位元,奇怪的數字)。
這是因為公鑰加密是 CPU 密集型的,旨在用於短值。其想法是使用此函數來加密一個秘密金鑰,該秘密金鑰又用於使用更有效率的演算法(例如 RC4 或 TripleDES)來加密資料。接收者使用其私鑰來解密秘密金鑰,然後可以解密資料。
openssl_seal() 和 openssl_open() 函數會在內部執行此操作,並且有詳細的文件記錄。您應該改用它們。
加密後的資料可以不使用 base64 儲存在 MySQL 中。必須使用 mysql_real_escape_string() 正確逸出,然後儲存到 BLOB 欄位。(事實上,每次在 MySQL 中插入二進位資料時都必須使用此函式)。
為了將加密資料儲存在 MySQL 資料庫中,您必須先對資料進行編碼,使其能安全寫入。您可以使用 blob 類型來做到這一點,但它會使 SELECT 語法變得非常複雜。我發現最簡單的方法是使用 base64_encode 和 base64_decode。以下範例使用了先前範例中的程式碼,並拆分為加密和解密函式。
function EncryptData($source)
{
$fp=fopen("/etc/httpd/conf/ssl.crt/server.crt","r");
$pub_key=fread($fp,8192);
fclose($fp);
openssl_get_publickey($pub_key);
/*
* 注意:這裡您使用 $pub_key 值(我猜是轉換過的)
*/
openssl_public_encrypt($source,$crypttext,$pub_key);
return(base64_encode($crypttext));
}
function DecryptData($source)
{
#print("number : $number");
$fp=fopen("/etc/httpd/conf/ssl.key/server.key","r");
$priv_key=fread($fp,8192);
fclose($fp);
// 如果您的金鑰已編碼(建議),則需要 $passphrase
$res = openssl_get_privatekey($priv_key,$passphrase);
/*
* 注意:這裡您使用返回的資源值
*/
$decoded_source = base64_decode($source);
openssl_private_decrypt($decoded_source,$newsource,$res);
return($newsource);
}
只需使用傳回值來儲存加密資料或顯示解密資料即可。