在 PHP 中使用 RSA 加密和解密文本

Posted

技术标签:

【中文标题】在 PHP 中使用 RSA 加密和解密文本【英文标题】:Encrypt and Decrypt text with RSA in PHP 【发布时间】:2011-05-27 22:07:42 【问题描述】:

是否有任何 php 5.3 类提供 RSA 加密/解密而无需填充?

我有私钥和公钥、p、q 和模数。

【问题讨论】:

【参考方案1】:

你可以使用phpseclib, a pure PHP RSA implementation:

<?php
include('Crypt/RSA.php');

$privatekey = file_get_contents('private.key');

$rsa = new Crypt_RSA();
$rsa->loadKey($privatekey);

$plaintext = new Math_BigInteger('aaaaaa');
echo $rsa->_exponentiate($plaintext)->toBytes();
?>

【讨论】:

@ScottArciszewski - 我能想象的唯一让你说的是 phpseclib 1.0 和 2.0 使用 sha1 作为默认哈希。 OAEP / PSS 是所有版本的 phpseclib 的默认模式,它们非常安全。如果您想更改哈希值,您可以通过 $rsa-&gt;setHash('sha256') 或其他方式来实现。此外,主分支(据我了解,未来将是 3.0)将 sha256 作为默认哈希。 1.0 和 2.0 的默认设置为 sha1 永远不会改变,因为这会构成 BC 中断。 "OAEP / PSS 是所有 phpseclib 版本的默认模式" 好吧,我在这一点上弄错了。 :)【参考方案2】:

安全警告:此代码 sn-p 易受Bleichenbacher's 1998 padding oracle attack 的攻击。请参阅this answer 以获得更好的安全性。

class MyEncryption


    public $pubkey = '...public key here...';
    public $privkey = '...private key here...';

    public function encrypt($data)
    
        if (openssl_public_encrypt($data, $encrypted, $this->pubkey))
            $data = base64_encode($encrypted);
        else
            throw new Exception('Unable to encrypt data. Perhaps it is bigger than the key size?');

        return $data;
    

    public function decrypt($data)
    
        if (openssl_private_decrypt(base64_decode($data), $decrypted, $this->privkey))
            $data = $decrypted;
        else
            $data = '';

        return $data;
    

【讨论】:

我也试过了,但我的公钥是:(添加空格以避免网站混乱)109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110 6039072308886100726558188253585034290 57592827629436413108566029093628 2126359538366865626758497206207862794310902180176810615217550567108238764764442605581471797071 19674283982419152118103759076030616683978566631413 它说:error:0906D06c:PEMroutines:PEM_read_bio:no start line 它在我这边运行良好,你可以将这个 base64 _decode 更改为 base32_decode 并尝试。 唯一接受的版本。为什么人们自己做的这么难。【参考方案3】:

在 2017 年(或之后)编写的任何打算加入严格加密技术的应用程序都不应再使用 RSA。有better options for PHP public-key cryptography。

人们在决定使用 RSA 加密时会犯两个大错误:

    开发者选择the wrong padding mode。 由于 RSA 本身不能加密很长的字符串,开发人员通常会将字符串分成小块并独立加密每个块。有点像ECB mode。

最佳选择:sodium_crypto_box_seal() (libsodium)

$keypair = sodium_crypto_box_keypair();
$publicKey = sodium_crypto_box_publickey($keypair);
// ...
$encrypted = sodium_crypto_box_seal(
    $plaintextMessage,
    $publicKey
);
// ...
$decrypted = sodium_crypto_box_seal_open(
    $encrypted,
    $keypair
);

简单且安全。 Libsodium 将在 PHP 7.2 中提供,或者通过 PECL 提供早期版本的 PHP。如果您需要纯 PHP 的 polyfill,请获取 paragonie/sodium_compat

勉强:正确使用 RSA

2017 年使用 RSA 的唯一原因是,“我被禁止安装 PECL 扩展,因此不能use libsodium,并且由于某种原因也不能使用 paragonie/sodium_compat。” p>

您的协议应如下所示:

    生成随机 AES 密钥。 使用 AES 密钥加密您的纯文本消息,使用 AEAD 加密模式,如果失败,则使用 CBC 然后 HMAC-SHA256。 使用您的 RSA 公钥加密您的 AES 密钥(步骤 1),使用 RSAES-OAEP + MGF1-SHA256 连接您的 RSA 加密的 AES 密钥(第 3 步)和 AES 加密的消息(第 2 步)。

与其自己实现,不如查看EasyRSA。

延伸阅读:Doing RSA in PHP correctly.

【讨论】:

感谢 Scott 与社区分享您的宝贵知识!【参考方案4】:

如果您使用的是 PHP >= 7.2,请考虑使用内置的钠核心扩展进行加密。

它更现代且更安全。您可以在此处找到更多信息 - http://php.net/manual/en/intro.sodium.php。在这里 - https://paragonie.com/book/pecl-libsodium/read/00-intro.md

示例 PHP 7.2 钠加密类 -

<?php

/**
 * Simple sodium crypto class for PHP >= 7.2
 * @author MRK
 */
class crypto 

    /**
     * 
     * @return type
     */
    static public function create_encryption_key() 
        return base64_encode(sodium_crypto_secretbox_keygen());
    

    /**
     * Encrypt a message
     * 
     * @param string $message - message to encrypt
     * @param string $key - encryption key created using create_encryption_key()
     * @return string
     */
    static function encrypt($message, $key) 
        $key_decoded = base64_decode($key);
        $nonce = random_bytes(
                SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
        );

        $cipher = base64_encode(
                $nonce .
                sodium_crypto_secretbox(
                        $message, $nonce, $key_decoded
                )
        );
        sodium_memzero($message);
        sodium_memzero($key_decoded);
        return $cipher;
    

    /**
     * Decrypt a message
     * @param string $encrypted - message encrypted with safeEncrypt()
     * @param string $key - key used for encryption
     * @return string
     */
    static function decrypt($encrypted, $key) 
        $decoded = base64_decode($encrypted);
        $key_decoded = base64_decode($key);
        if ($decoded === false) 
            throw new Exception('Decryption error : the encoding failed');
        
        if (mb_strlen($decoded, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) 
            throw new Exception('Decryption error : the message was truncated');
        
        $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
        $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');

        $plain = sodium_crypto_secretbox_open(
                $ciphertext, $nonce, $key_decoded
        );
        if ($plain === false) 
            throw new Exception('Decryption error : the message was tampered with in transit');
        
        sodium_memzero($ciphertext);
        sodium_memzero($key_decoded);
        return $plain;
    


示例用法 -

<?php 

$key = crypto::create_encryption_key();

$string = 'Sri Lanka is a beautiful country !';

echo $enc = crypto::encrypt($string, $key); 
echo crypto::decrypt($enc, $key);

【讨论】:

很好的答案,但它不提供 RSA 加密。它没有公钥/私钥对,如“示例用法”部分所示,它使用与加密部分产生的相同密钥解密消息。所以不幸的是,这不是问题的正确答案。【参考方案5】:

是的。看http://jerrywickey.com/test/testJerrysLibrary.php

它给出了PHP中的RSA加密和解密以及javascript中的RSA加密的示例代码示例。

如果您想加密文本而不仅仅是以 10 为基数的数字,您还需要一个基数到基数的转换。那就是将文本转换为一个非常大的数字。文本实际上只是以 63 为基数书写。26 个小写字母加上 26 个大写字母 + 10 个数字 + 空格字符。代码也在下面。

$GETn 参数是保存加密功能密钥的文件名。如果你不明白,问。我会帮忙的。

实际上我昨天发布了整个加密库,但是 Brad Larson 是一个模组,杀死了它并说这种东西并不是 Stack Overflow 的真正意义所在。但是您仍然可以在上面的链接中找到所有代码示例和整个函数库来为 AJAX 进行客户端/服务器加密解密。

function RSAencrypt( $num, $GETn)
    if ( file_exists( 'temp/bigprimes'.hash( 'sha256', $GETn).'.php'))
        $t= explode( '>,', file_get_contents('temp/bigprimes'.hash( 'sha256', $GETn).'.php'));
        return JL_powmod( $num, $t[4], $t[10]); 
    else
        return false;
    


function RSAdecrypt( $num, $GETn)
    if ( file_exists( 'temp/bigprimes'.hash( 'sha256', $GETn).'.php'))
        $t= explode( '>,', file_get_contents('temp/bigprimes'.hash( 'sha256', $GETn).'.php'));
        return JL_powmod( $num, $t[8], $t[10]);     
    else
        return false;
    


function JL_powmod( $num, $pow, $mod) 
    if ( function_exists('bcpowmod')) 
        return bcpowmod( $num, $pow, $mod);
    
    $result= '1';
    do 
        if ( !bccomp( bcmod( $pow, '2'), '1')) 
            $result = bcmod( bcmul( $result, $num), $mod);
        
       $num = bcmod( bcpow( $num, '2'), $mod);

       $pow = bcdiv( $pow, '2');
     while ( bccomp( $pow, '0'));
    return $result;


function baseToBase ($message, $fromBase, $toBase)
    $from= strlen( $fromBase);
    $b[$from]= $fromBase; 
    $to= strlen( $toBase);
    $b[$to]= $toBase; 

    $result= substr( $b[$to], 0, 1);

    $f= substr( $b[$to], 1, 1);

    $tf= digit( $from, $b[$to]);

    for ($i=strlen($message)-1; $i>=0; $i--)
        $result= badd( $result, bmul( digit( strpos( $b[$from], substr( $message, $i, 1)), $b[$to]), $f, $b[$to]), $b[$to]);
        $f= bmul($f, $tf, $b[$to]);
    
    return $result;
 

function digit( $from, $bto)   
    $to= strlen( $bto);
    $b[$to]= $bto; 

    $t[0]= intval( $from);
    $i= 0;
    while ( $t[$i] >= intval( $to))
        if ( !isset( $t[$i+1])) 
            $t[$i+1]= 0;
        
        while ( $t[$i] >= intval( $to))
            $t[$i]= $t[$i] - intval( $to);
            $t[$i+1]++;
        
        $i++;
    

    $res= '';
    for ( $i=count( $t)-1; $i>=0; $i--) 
        $res.= substr( $b[$to], $t[$i], 1);
    
    return $res;
   

function badd( $n1, $n2, $nbase)
    $base= strlen( $nbase);
    $b[$base]= $nbase; 

    while ( strlen( $n1) < strlen( $n2))
        $n1= substr( $b[$base], 0, 1) . $n1;
    
    while ( strlen( $n1) > strlen( $n2))
        $n2= substr( $b[$base], 0, 1) . $n2;
    
    $n1= substr( $b[$base], 0, 1) . $n1;    
    $n2= substr( $b[$base], 0, 1) . $n2;
    $m1= array();
    for ( $i=0; $i<strlen( $n1); $i++)
        $m1[$i]= strpos( $b[$base], substr( $n1, (strlen( $n1)-$i-1), 1));
       
    $res= array();
    $m2= array();
    for ($i=0; $i<strlen( $n1); $i++)
        $m2[$i]= strpos( $b[$base], substr( $n2, (strlen( $n1)-$i-1), 1));
        $res[$i]= 0;
               
    for ($i=0; $i<strlen( $n1)  ; $i++)
        $res[$i]= $m1[$i] + $m2[$i] + $res[$i];
        if ($res[$i] >= $base)
            $res[$i]= $res[$i] - $base;
            $res[$i+1]++;
        
    
    $o= '';
    for ($i=0; $i<strlen( $n1); $i++)
        $o= substr( $b[$base], $res[$i], 1).$o;
       
    $t= false;
    $o= '';
    for ($i=strlen( $n1)-1; $i>=0; $i--)
        if ($res[$i] > 0 || $t)    
            $o.= substr( $b[$base], $res[$i], 1);
            $t= true;
        
    
    return $o;

function bmul( $n1, $n2, $nbase)
    $base= strlen( $nbase);
    $b[$base]= $nbase; 

    $m1= array();
    for ($i=0; $i<strlen( $n1); $i++)
        $m1[$i]= strpos( $b[$base], substr($n1, (strlen( $n1)-$i-1), 1));
       
    $m2= array();
    for ($i=0; $i<strlen( $n2); $i++)
        $m2[$i]= strpos( $b[$base], substr($n2, (strlen( $n2)-$i-1), 1));
               
    $res= array();
    for ($i=0; $i<strlen( $n1)+strlen( $n2)+2; $i++)
        $res[$i]= 0;
    
    for ($i=0; $i<strlen( $n1)  ; $i++)
        for ($j=0; $j<strlen( $n2)  ; $j++)
            $res[$i+$j]= ($m1[$i] * $m2[$j]) + $res[$i+$j];
            while ( $res[$i+$j] >= $base)
                $res[$i+$j]= $res[$i+$j] - $base;
                $res[$i+$j+1]++;
            
        
    
    $t= false;
    $o= '';
    for ($i=count( $res)-1; $i>=0; $i--)
        if ($res[$i]>0 || $t)  
            $o.= substr( $b[$base], $res[$i], 1);
            $t= true;
        
    
    return $o;

【讨论】:

整个 $GETn 和 file_get~~ 看起来是个坏主意.. 你为什么要这样做?如果你真的需要在临时文件中放一些东西,tmpfile() 和一个静态数组,其中 sha256 是关键,也许? 我使用文件是因为使用 session 会阻止质数生成器的并发迭代,并且 tmpfile 不会通过多次调用而持续存在。 "tmpfile 不会通过多次调用保持"确定它可以 -> static $tmpfiles=array(); if(空($tmpfiles[hash('sha256',$GETn)])) $tmpfiles[hash('sha256')]=tmpfile(); 您能否添加一个安全免责声明,告诉人们不要使用它?没有填充的教科书 RSA 在语义上是不安全的。【参考方案6】:

我很难解密在 python 中加密的长字符串。这里是python加密函数:

def RSA_encrypt(public_key, msg, chunk_size=214):
    """
    Encrypt the message by the provided RSA public key.

    :param public_key: RSA public key in PEM format.
    :type public_key: binary
    :param msg: message that to be encrypted
    :type msg: string
    :param chunk_size: the chunk size used for PKCS1_OAEP decryption, it is determined by \
    the private key length used in bytes - 42 bytes.
    :type chunk_size: int
    :return: Base 64 encryption of the encrypted message
    :rtype: binray
    """
    rsa_key = RSA.importKey(public_key)
    rsa_key = PKCS1_OAEP.new(rsa_key)

    encrypted = b''
    offset = 0
    end_loop = False

    while not end_loop:
        chunk = msg[offset:offset + chunk_size]

        if len(chunk) % chunk_size != 0:
            chunk += " " * (chunk_size - len(chunk))
            end_loop = True

        encrypted += rsa_key.encrypt(chunk.encode())
        offset += chunk_size

    return base64.b64encode(encrypted)

PHP中的解密:

/**
 * @param  base64_encoded string holds the encrypted message.
 * @param  Resource your private key loaded using openssl_pkey_get_private
 * @param  integer Chunking by bytes to feed to the decryptor algorithm.
 * @return String decrypted message.
 */
public function RSADecyrpt($encrypted_msg, $ppk, $chunk_size=256)
    if(is_null($ppk))
        throw new Exception("Returned message is encrypted while you did not provide private key!");
    $encrypted_msg = base64_decode($encrypted_msg);

    $offset = 0;
    $chunk_size = 256;

    $decrypted = "";
    while($offset < strlen($encrypted_msg))
        $decrypted_chunk = "";
        $chunk = substr($encrypted_msg, $offset, $chunk_size);

        if(openssl_private_decrypt($chunk, $decrypted_chunk, $ppk, OPENSSL_PKCS1_OAEP_PADDING))
            $decrypted .= $decrypted_chunk;
        else 
            throw new exception("Problem decrypting the message");
        $offset += $chunk_size;
    
    return $decrypted;

【讨论】:

这个仅包含代码的答案中的散文实际上使它看起来更像是一个糟糕的问题。 “我有一个问题。代码。”请让回答部分更明显,并考虑解释代码的核心。格式化以便不需要水平滚动将是一个不错的选择。

以上是关于在 PHP 中使用 RSA 加密和解密文本的主要内容,如果未能解决你的问题,请参考以下文章

php使用openssl进行Rsa长数据加密(117)解密(128) 和 DES 加密解密

Python中的RSA加密和解密

php中rsa加密解密验证

RSA加密/解密

PHP开发接口使用RSA进行加密解密方法

PHP rsa加密解密使用方法