我明白了。此函数并非用于一般的加密和解密。为此,您需要 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
公钥。
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,否则您会容易受到选择明文攻击(谷歌搜索:“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 位而不是 936 位。
但正如 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" => '[email protected]');
//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;
}
}
}
?>
大多数人似乎对 "mixed $key" 感到困惑
对 $key 的解释与 https://php.net/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() 函数获取。
不要直接哈希当前时间——攻击者很容易猜到这种密钥(他们只需哈希一堆可能的时间值并尝试,直到找到正确的密钥。攻击者可以使用普通电脑每分钟生成和测试数百万个候选哈希值)。
openssl_public_encrypt 和 openssl_private_encrypt 无法加密大量数据。所以我写了一个类。这个类可以加密大量数据并解密。
查看 URL: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() 函数在内部执行此操作,并且有很好的文档说明。您可能应该使用它们而不是使用其他函数。
加密后的数据可以存储在 MySQL 中,无需使用 base64。它必须使用 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);
}
只需使用返回值来存储加密后的数据或显示解密后的数据。
使用公钥加密,使用私钥解密。
使用此方法将内容存储在您的数据库中:除非有人
拥有您的私钥,否则数据库内容毫无用处。
此外,使用此方法向特定个人发送消息:获取
他们的公钥,加密消息,只有他们可以使用
他们的私钥来解码。
<?php
echo "源: $source";
$fp=fopen ("/path/to/certificate.crt","r");
$pub_key=fread($fp,8192);
fclose($fp);
openssl_get_publickey($pub_key);
/*
* 注意:此处使用 $pub_key 值(已转换,我猜)
*/
openssl_public_encrypt($source,$crypttext,$pub_key);
echo "字符串加密: $crypttext";
$fp=fopen("/path/to/private.key","r");
$priv_key=fread($fp,8192);
fclose($fp);
// 如果您的密钥已编码(建议),则需要 $passphrase
$res = openssl_get_privatekey($priv_key,$passphrase);
/*
* 注意:此处使用返回的资源值
*/
openssl_private_decrypt($crypttext,$newsource,$res);
echo "字符串解密: $newsource";
?>
函数描述错误,您需要交换 crypttext 和 text 才能使其正常工作
这是正确的
bool openssl_public_encrypt ( string crypted, string data, mixed key [, int padding])