PHP 使用 OPENSSL 分段加密与解密

最近在做一个接口对接的任务时,遇到 RSA 加密与解密的问题,对方的语言是 Java,约定数据需要经 RSA 加密后传输,解密后使用。

生成密钥

生成私钥

openssl genrsa -out app_private_key.pem 2048

生成公钥

openssl rsa -in app_private_key.pem -pubout -out app_public_key.pem

公钥无效问题

刚开始我直接使用 file_get_contents() 来获取对方发来的密钥文件里的密钥内容,然后使用得到的密钥进行加密时遇到下面这样的错误:

openssl_public_encrypt (): key parameter is not a valid public key

经过查看,对方将公钥的内容合并成了一行导致的,而 PHP 里公钥需要满足以下条件:

  1. 公钥中必须有 -----BEGIN PUBLIC KEY----------END PUBLIC KEY-----
  2. 公钥必须换行,一般每行 64 个长度。

最终把公钥内容按以上规则整理之后顺利过关。

分段加密与分段解密问题

一开始没有采取分段加解密,导致有些接口无法解密(其实一开始对方的开发人员还好心提醒过我这一点,但我急着见到效果,打算之后再做分段处理,然后,然后就给忘了)。

openssl_private_encrypt() 一次最多可以加密 117 个字节长度,这个长度依赖于生成的 KEY 的长度。

如果生成的是 1024 位的 KEY,那最大的可加密字节数为 1024 / 8 - 11 = 117 个字节,其中的 11 是被填充用了。

同样可以算得 2048 位的 KEY 的最大可加密字节数为 245 个字节长度。

加密后的字节串的长度总是 129 个字节,如果使用 base64_encode() 编码加密后的字节串,长度总是为 172 个字节。

这依赖于 KEY 的长度,1024 位长度的 KEY,RSA 设计的加密原始字节数始终是 128 字节(1024 位)的块,而 2048 位长度的 KEY,原始字节数始终是 256 字节。

如果使用 base64_encode() 编码字符串,字符串的长度会增长 33% 左右,128 字节被编码后长度会变成 170 字节左右,256 字节被编码后长度会变成 340 字节左右。

所以对于 2048 位的 KEY,加密时分块的大小应该为:256 / 1.33 = 192 个字节左右。保险起见,应该使用 190 作为块的大小。

而解密的时候不需要考虑字节膨胀的问题,因为解密字符串本身就已经是 base64_encode() 之后的了,所以应该使用 256 作为块的大小。

所以以 2048 位的 KEY 为例,加密解密代码如下:

<?php

/**
 * Class Rsa
 */
class Rsa
{
    /**
     * 加密时块的大小
     *
     * @var int
     */
    private $encryptBlockSize = 190;

    /**
     * 解密时块的大小
     *
     * @var int
     */
    private $decryptBlockSize = 256;

    /**
     * 私钥
     *
     * @var string
     */
    private $privateKey = 'xxx';

    /**
     * 公钥
     *
     * @var string
     */
    private $publicKey = 'xxx';

    /**
     * 加密
     *
     * @param  string  $string  欲加密的字符串
     *
     * @return string 加密后的字符串
     */
    public function encrypt(string $string)
    {
        $blocks = str_split($string, $this->encryptBlockSize);

        $encryptedString = '';
        foreach ($blocks as $block) {
            openssl_private_encrypt($block, $encrypted, $this->privateKey);
            $encryptedString .= $encrypted;
        }

        return base64_encode($encryptedString);
    }

    /**
     * 解密
     *
     * @param  string  $string  欲解密的字符串
     *
     * @return string 解密后的字符串
     */
    public function decrypt(string $string)
    {
        $decryptedString = '';

        $blocks = str_split(base64_decode($string), $this->decryptBlockSize);

        foreach ($blocks as $block) {
            openssl_public_decrypt($block, $decrypted, $this->publicKey);

            $decryptedString .= $decrypted;
        }

        return $decryptedString;
    }
}

 本篇
PHP 使用 OPENSSL 分段加密与解密 PHP 使用 OPENSSL 分段加密与解密
最近在做一个接口对接的任务时,遇到 RSA 加密与解密的问题,对方的语言是 Java,约定数据需要经 RSA 加密后传输,解密后使用。 生成密钥生成私钥 openssl genrsa -out app_private_key.pem 204
2019-06-04
下一篇 
解决 Laravel 无法下载中文名称的文件问题 解决 Laravel 无法下载中文名称的文件问题
本文介绍如何在 Laravel 中不用修改源码解决下载中文文件名称的文件问题。 近日在使用 Laravel 添加下载功能时,遇到了无法下载中文名称的文件的问题。 Laravel 会给出报错信息:The filename fallback
2019-06-02
  目录