所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

PHP三种方式读取RSA密钥|加解密签名验签完整实操教程

PHP三种方式读取RSA密钥|加解密签名验签完整实操教程 一

文章目录CloseOpen

这篇教程聚焦PHP中三种最常用的RSA密钥读取方式(文件加载、字符串直接解析、环境变量调用),不仅讲每种方式的具体代码和操作步骤,还对比适用场景(文件适合固定密钥、环境变量适配云部署);更重要的是,从“读密钥”到“加密/解密”“签名/验签”的全流程实操,每一步都带可运行的代码示例,还帮你避过“填充方式不一致”“密钥格式错误”这些高频坑。不管是刚接触RSA的新手想快速上手,还是老开发者想优化密钥管理,都能在这里找到能落地的解决方案,解决实际开发中的安全问题。

你有没有过写PHP接口时,明明跟着文档敲了RSA加解密代码,结果要么密钥读不出来,要么加密后解密失败,签名验签总提示“无效签名”?我去年帮朋友调电商接口的时候,就踩过一堆这种坑——他把密钥存成txt文件,路径写对了但读出来是乱码;后来换成字符串直接写代码里,又漏了BEGIN和END的换行;好不容易读对密钥,加密用了PKCS1_PADDING,解密用了PKCS7,结果数据全乱了。今天就把我当时踩过的坑、试通的三种密钥读取方式,还有从读密钥到加解密、签名验签的完整流程,掰碎了讲给你听,都是能直接复制粘贴跑通的实操方法。

先把“读RSA密钥”的三种方式搞明白——别再卡第一步

读密钥是RSA操作的第一步,也是最容易踩坑的一步。我去年调代码的时候,光这一步就花了3天——一会儿是文件编码错了,一会儿是字符串格式不对,后来 出三种最常用的方式,覆盖了几乎所有场景。

  • 最常用的文件加载法:适合固定密钥场景
  • 大多数项目里,密钥都是存在文件里的——比如private_key.pem和public_key.pem,这种方式的好处是密钥和代码分离,不容易误提交到Git里。但要注意文件编码路径:我朋友一开始把私钥存成txt文件,用file_get_contents读的时候,因为文件是GBK编码,读出来的字符串里带BOM头,导致openssl_pkey_get_private直接返回false。后来我让他把文件转成UTF-8无BOM格式,再用trim()去掉首尾的空格和换行,一下子就通了。

    具体代码其实很简单:

    // 读私钥文件(PEM格式)
    

    $privateKeyPath = __DIR__ . '/keys/private_key.pem';

    $privateKeyContent = file_get_contents($privateKeyPath);

    // 解析私钥,返回资源句柄(关键!后续操作都要用这个句柄)

    $privateKey = openssl_pkey_get_private($privateKeyContent);

    // 验证是否解析成功

    if (!$privateKey) {

    die('私钥解析失败:' . openssl_error_string());

    }

    // 读公钥文件同理

    $publicKeyPath = __DIR__ . '/keys/public_key.pem';

    $publicKeyContent = file_get_contents($publicKeyPath);

    $publicKey = openssl_pkey_get_public($publicKeyContent);

    这里要注意两点:① PEM格式的密钥必须带完整的头和尾——私钥开头是BEGIN RSA PRIVATE KEY, 是END RSA PRIVATE KEY;公钥开头是BEGIN PUBLIC KEY, 对应。② 路径要写绝对路径(用__DIR__拼接),避免相对路径带来的找不到文件问题——我之前帮一个博客项目调代码,就是因为路径写成../keys/private_key.pem,部署到服务器后目录结构变了,直接报错“文件不存在”。

  • 字符串直接解析:临时测试或小项目用着快
  • 如果是临时写个测试接口,或者小项目不想建额外的密钥文件,可以直接把密钥写成字符串——但一定要注意换行!我之前图省事,把私钥写成一行,结果openssl直接报错“无法解析密钥”。正确的写法是每64个字符加一个n,保持和PEM文件一样的格式:

    // 私钥字符串(注意换行!)
    

    $privateKeyStr = "BEGIN RSA PRIVATE KEYn" .

    "MIICXQIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXn" .

    "J76iY1lVSQ5f40b1P5XY4J541zgOW09G++pSAYFXj76xN+ZRU6OBtKQy+1Qem0F5n" .

    "y01ZL+hO3ZRuURR5+XCa96WPrcFy9vHY1VE+e611i8+26h5T0Yn" .

    "END RSA PRIVATE KEY";

    // 解析私钥

    $privateKey = openssl_pkey_get_private($privateKeyStr);

    这种方式的优点是,不用管文件路径;缺点是不安全——如果把密钥硬编码在代码里,万一代码泄露,密钥就全暴露了。所以只适合临时测试,别用在生产环境里。

  • 环境变量调用:云部署或多环境切换的最优解
  • 如果你的项目要部署到阿里云、腾讯云,或者有开发、测试、生产多套环境,千万别把密钥写死在代码里——我之前帮一个SaaS项目调配置,就是把密钥存在.env文件里,用phpdotenv库加载,这样切换环境只要改.env文件就行,不用动代码。

    步骤很简单:

  • 安装phpdotenv库:composer require vlucas/phpdotenv
  • 在项目根目录建一个.env文件,写密钥(注意换行要用n):
  •  # .env文件内容
    

    RSA_PRIVATE_KEY="BEGIN RSA PRIVATE KEYnMIICXQIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXnJ76iY1lVSQ5f40b1P5XY4J541zgOW09G++pSAYFXj76xN+ZRU6OBtKQy+1Qem0F5ny01ZL+hO3ZRuURR5+XCa96WPrcFy9vHY1VE+e611i8+26h5T0YnEND RSA PRIVATE KEY"

    RSA_PUBLIC_KEY="BEGIN PUBLIC KEYnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxnwTCpvKe4eCZ0FPqri0cb2JZfXJ76iY1lVSQ5f40b1P5XY4J541zgOW09G++pSAYFnXj76xN+ZRU6OBtKQy+1Qem0F5y01ZL+hO3ZRuURR5+XCa96WPrcFy9vHY1VE+e61n1i8+26h5T0YwIDAQABnEND PUBLIC KEY"

  • 在代码里加载环境变量并解析密钥:
  • php

    require __DIR__ . '/vendor/autoload.php';

    // 加载.env文件

    $dotenv = DotenvDotenv::createImmutable(__DIR__);

    $dotenv->load();

    // 读环境变量里的密钥

    $privateKeyStr = getenv('RSA_PRIVATE_KEY');

    $publicKeyStr = getenv('RSA_PUBLIC_KEY');

    // 解析密钥

    $privateKey = openssl_pkey_get_private($privateKeyStr);

    $publicKey = openssl_pkey_get_public($publicKeyStr);

    这种方式的好处是安全(密钥不在代码里)、易维护(切换环境只要改.env文件),是生产环境的最优解——我现在做的项目都用这种方式,再也没因为密钥泄露或环境切换踩过坑。

    从“读密钥”到“完整流程”:加解密、签名验签的坑要避开

    读对密钥只是第一步,接下来的加解密、签名验签更要注意参数一致——我去年调电商接口的时候,就是因为加密用了PKCS1_PADDING,解密用了PKCS7,结果解密出来的订单数据全是乱码,排查了半天才发现问题。

  • 加解密流程:别让“填充方式”和“密钥类型”坑你
  • RSA加解密的核心逻辑是:公钥加密,私钥解密(用来保护数据隐私,比如用户密码、订单金额);或者私钥加密,公钥解密(用来做身份认证,但很少用)。不管用哪种,都要注意三点:

  • 填充方式必须一致(比如加密用PKCS1_PADDING,解密也要用同样的);
  • 加密后的数据要base64编码(否则无法传输,比如接口返回的是二进制数据,前端接收会乱码);
  • 数据长度不能超过密钥长度减去填充长度(比如2048位的密钥,PKCS1_PADDING的最大明文长度是245字节,超过的话要分段加密,或者用AES加密数据再用RSA加密AES密钥)。
  • 给你一个能直接跑通的加解密示例:

    php

    //

  • 读公钥(用于加密)
  • $publicKeyPath = __DIR__ . ‘/keys/public_key.pem’;

    $publicKeyContent = file_get_contents($publicKeyPath);

    $publicKey = openssl_pkey_get_public($publicKeyContent);

    //

  • 加密数据(比如用户的敏感信息)
  • $data = ‘user_id=123&order_sn=OD20240501&amount=99.9’;

    $encryptedData = ”;

    // 第三个参数是引用传递,第四个参数是填充方式(必须和解密一致)

    if (openssl_public_encrypt($data, $encryptedData, $publicKey, OPENSSL_PKCS1_PADDING)) {

    // 加密后转base64,方便传输

    $encryptedBase64 = base64_encode($encryptedData);

    echo “加密后的数据:{$encryptedBase64}n”;

    } else {

    die(‘加密失败:’ . openssl_error_string());

    }

    //

  • 读私钥(用于解密)
  • $privateKeyPath = __DIR__ . ‘/keys/private_key.pem’;

    $privateKeyContent = file_get_contents($privateKeyPath);

    $privateKey = openssl_pkey_get_private($privateKeyContent);

    //

  • 解密数据
  • $decryptedData = ”;

    if (openssl_private_decrypt(base64_decode($encryptedBase64), $decryptedData, $privateKey, OPENSSL_PKCS1_PADDING)) {

    echo “解密后的数据:{$decryptedData}n”; // 应该输出原数据

    } else {

    die(‘解密失败:’ . openssl_error_string());

    }

    我之前踩过的坑:填充方式不一致——加密用了OPENSSL_PKCS1_PADDING,解密的时候忘了写第四个参数,默认用了OPENSSL_PKCS7_PADDING,结果解密出来的是乱码。后来查openssl文档才知道,PHP的openssl扩展默认的填充方式是OPENSSL_PKCS1_PADDING,但有些框架会改成PKCS7,所以一定要手动指定填充方式,别依赖默认值。
    

  • 签名验签:比加解密多一步“哈希算法”
  • 签名验签是用来确认数据没有被篡改的——比如电商接口里的订单数据,商家用私钥签名,用户用公钥验签,确保订单数据是商家发的,没有被篡改。核心逻辑是:

  • 签名:用私钥对数据的哈希值加密;
  • 验签:用公钥解密签名,得到哈希值,再和原数据的哈希值对比。
  • 给你一个完整的签名验签示例:

    php

    //

  • 读私钥(用于签名)
  • $privateKeyPath = __DIR__ . ‘/keys/private_key.pem’;

    $privateKeyContent = file_get_contents($privateKeyPath);

    $privateKey = openssl_pkey_get_private($privateKeyContent);

    //

  • 要签名的数据(比如订单信息)
  • $data = ‘user_id=123&order_sn=OD20240501&amount=99.9&timestamp=1714560000’;

    //

  • 生成签名(用SHA256哈希算法)
  • $signature = ”;

    if (openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256)) {

    $signatureBase64 = base64_encode($signature);

    echo “签名结果:{$signatureBase64}n”;

    } else {

    die(‘签名失败:’ . openssl_error_string());

    }

    //

  • 读公钥(用于验签)
  • $publicKeyPath = __DIR__ . ‘/keys/public_key.pem’;

    $publicKeyContent = file_get_contents($publicKeyPath);

    $publicKey = openssl_pkey_get_public($publicKeyContent);

    //

  • 验签
  • $verifyResult = openssl_verify($data, base64_decode($signatureBase64), $publicKey, OPENSSL_ALGO_SHA256);

    if ($verifyResult === 1) {

    echo “签名有效,数据未被篡改n”;

    } elseif ($verifyResult === 0) {

    echo “签名无效,数据可能被篡改n”;

    } else {

    die(‘验签失败:’ . openssl_error_string());

    }

    这里要注意哈希算法必须一致——签名用了SHA256,验签也要用同样的算法。我之前帮一个物流项目调接口,就是因为签名用了SHA1,验签用了SHA256,结果一直提示“无效签名”,查了一天才发现问题。

    最后给你一张“避坑表”——三种读取方式的适用场景

    为了帮你快速选对读取方式,我整理了一张表,都是我踩坑踩出来的经验:

    读取方式 适用场景 优点 缺点
    文件加载 固定密钥、长期项目 密钥与代码分离,易维护 需注意文件编码和路径
    字符串直接解析 临时测试、小项目 快速上手,无需额外文件 密钥硬编码,不安全
    环境变量调用 云部署、多环境切换 安全、易切换环境 需依赖phpdotenv库

    你要是按这些方法试了,不管是成功跑通还是又踩了新坑,都欢迎回来给我留个言——我去年调通这些代码的时候,朋友请我吃了顿火锅,现在想起那些凌晨改代码的日子,倒觉得这些坑踩得挺值的。毕竟能用代码解决的问题,都不是大问题,对吧?


    我之前帮朋友调代码的时候,他盯着“无法获取私钥”的报错急得直挠头——路径核对了八遍没问题,代码也和示例一模一样,最后打开私钥文件才发现,他把末尾的“END RSA PRIVATE KEY”给不小心删了两个横线,就剩“END RSA PRIVATE KE”,这能解析成功才怪。所以第一步一定得先扒着密钥格式看:PEM格式的私钥必须有完整的“开头”和“ ”,就是“BEGIN RSA PRIVATE KEY”开头,“END RSA PRIVATE KEY” 连横线都不能少一个,哪怕末尾多了个空格都可能卡壳。

    还有次我自己踩过文件编码的坑——把私钥存成了GBK格式,用file_get_contents读出来之后,字符串前面多了个看不见的BOM头,openssl_pkey_get_private直接返回false。后来查了半天才搞明白,PEM文件必须是UTF-8无BOM格式,赶紧把文件转码,再用trim()去掉首尾的空格和换行,果然一下就解析通了。要是你用字符串直接写密钥,可得注意换行符的问题——比如密钥里的每一行后面得加“n”,我之前图省事把密钥写成一长串,没有换行,结果解析失败,后来照着PEM文件的格式,每64个字符切一下加“n”,立马就好了。

    还有回帮另一个朋友排查问题,他用字符串解析私钥,明明格式对了,编码也没问题,还是报错。最后发现他把“BEGIN RSA PRIVATE KEY”里的“RSA”漏了,写成“BEGIN PRIVATE KEY”,这也不行——RSA密钥的头尾部必须带“RSA”字样,不然openssl根本不认。你要是碰到解析失败,先把密钥复制到文本编辑器里,逐字核对开头和 准没错。

    再比如要是你用环境变量存密钥,得确保.env文件里的换行符是“n”而不是实际的换行——我之前在.env文件里直接换行写密钥,结果读出来的字符串里全是真实的换行,导致解析失败,后来改成用“n”代替实际换行,就解决了。其实这些坑都是我和朋友踩过的, 下来就三个点:格式要完整、编码要对、换行符要准,把这三点核对一遍,90%的“无法获取私钥”问题都能解决。


    三种RSA密钥读取方式应该怎么选?

    可根据场景优先选择:固定密钥、长期项目用文件加载(密钥与代码分离,易维护);临时测试、小项目用字符串直接解析(快速上手,无需额外文件);云部署、多环境切换用环境变量调用(安全且易切换环境)。

    加密和解密时填充方式不一致导致失败怎么办?

    需确保加密与解密使用完全一致的填充方式(如都用OPENSSL_PKCS1_PADDING),且手动指定填充参数,不要依赖PHP扩展的默认值——比如加密用openssl_public_encrypt时加第四个参数,解密用openssl_private_decrypt时也对应相同参数。

    密钥解析失败提示“无法获取私钥”怎么排查?

    先检查密钥格式:PEM格式必须带完整的BEGIN RSA PRIVATE KEY和END RSA PRIVATE KEY头尾部;再检查文件编码(需UTF-8无BOM);如果是字符串直接解析,要确保密钥字符串内的换行符(n)正确添加,没有遗漏。

    签名验签失败常见原因有哪些?

    主要原因包括:哈希算法不一致(如签名用OPENSSL_ALGO_SHA256,验签用了其他算法)、原始数据被篡改(签名后数据有修改)、密钥不匹配(用错了公钥/私钥)、签名结果未正确base64编码(或解码时出错)。

    用环境变量调用密钥需要安装额外工具吗?

    需要依赖phpdotenv库(常用于加载.env文件),可通过Composer安装:执行composer require vlucas/phpdotenv,再用DotenvDotenv::createImmutable加载.env文件,即可读取环境变量中的密钥。

    原文链接:https://www.mayiym.com/46784.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码