我弄明白了。此函数并非用于一般的加密和解密。为此,您需要使用 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() 收到了以下类型的错误
- 错误:0906D06C:PEM 例程:PEM_read_bio:无起始行 OR
- 错误:0E06D06C:配置文件例程:NCONF_get_string:无值
使用具有 2048 位的密钥(sha512,OPENSSL_KEYTYPE_RSA),我的最大消息大小为 245 字节,而 4096 位的最大大小为 502 字节。因此,如果您稍后更改密钥大小,特别是如果减小密钥大小,请注意它会影响最大加密长度。
数据的长度 < 私钥的长度..所以我在获取消息时将其分割,放入“:::”。然后再次加密它。查看 pgm 以了解这一点..
<?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;
}
}
}
?>
大多数人似乎对“混合 $key”感到困惑
在 https://php.net/manual/en/function.openssl-pkey-get-public.php 中解释了 $key,并且它与该参数基本相同
它可以获取从 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 无法加密大量数据。所以我写了一个类。这个类可以加密和解密大量数据。
查看网址: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);
}
只需使用返回值来存储加密数据或显示解密数据。