分类 PHP 下的文章

在 PHP 中实现与 OpenSSL 兼容的 ‌AES 加密(含 Salted__ 头)‌,需手动处理盐值生成和密钥派生。以下是完整实现代码及分步说明:

一、加密实现代码

<?php
/**
 * AES 加密(兼容 OpenSSL 的 "Salted__" 格式)
 * @param string $data 明文数据
 * @param string $password 加密密码
 * @param string $method 加密算法,如 'AES-256-CBC'
 * @return string Base64 编码的加密数据(含 Salted__ 头)
 */
function opensslEncryptWithSalt($data, $password, $method = 'AES-256-CBC') {
    // 生成 8 字节随机盐
    $salt = openssl_random_pseudo_bytes(8);
    
    // 通过盐和密码派生密钥与 IV
    $keyIv = deriveKeyAndIV($password, $salt, $method);
    $key = $keyIv['key'];
    $iv = $keyIv['iv'];
    
    // 加密数据
    $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
    
    // 拼接 Salted__ 头 + 盐 + 密文
    $result = "Salted__" . $salt . $encrypted;
    
    // Base64 编码便于传输
    return base64_encode($result);
}

/**
 * 派生密钥和 IV(模拟 OpenSSL 的 EVP_BytesToKey)
 */
function deriveKeyAndIV($password, $salt, $method) {
    $digest = 'md5'; // OpenSSL 默认使用 MD5
    $keySize = 32;   // AES-256 需要 32 字节密钥
    $ivSize = 16;    // CBC 模式需要 16 字节 IV
    
    $derived = '';
    $data = '';
    $length = 0;
    
    while ($length < $keySize + $ivSize) {
        $data = $data . $password . $salt;
        $md5 = hash($digest, $data, true);
        $data = $md5;
        $derived .= $md5;
        $length += strlen($md5);
    }
    
    return [
        'key' => substr($derived, 0, $keySize),
        'iv'  => substr($derived, $keySize, $ivSize)
    ];
}

二、解密实现代码

<?php
/**
 * AES 解密(兼容 OpenSSL 的 "Salted__" 格式)
 * @param string $encryptedData Base64 编码的加密数据
 * @param string $password 解密密码
 * @param string $method 加密算法,如 'AES-256-CBC'
 * @return string 解密后的明文
 */
function opensslDecryptWithSalt($encryptedData, $password, $method = 'AES-256-CBC') {
    // 解码 Base64
    $data = base64_decode($encryptedData);
    
    // 提取盐(前 8 字节,跳过 "Salted__" 头)
    $salt = substr($data, 8, 8);
    
    // 提取密文(去除头部的 16 字节:Salted__ + 盐)
    $encrypted = substr($data, 16);
    
    // 派生密钥和 IV
    $keyIv = deriveKeyAndIV($password, $salt, $method);
    $key = $keyIv['key'];
    $iv = $keyIv['iv'];
    
    // 解密数据
    return openssl_decrypt($encrypted, $method, $key, OPENSSL_RAW_DATA, $iv);
}

三、使用示例

<?php
// 加密示例
$plaintext = "Hello, World!";
$password = "mySecretPassword";
$encrypted = opensslEncryptWithSalt($plaintext, $password);
echo "加密结果: " . $encrypted . "\n";

// 解密示例
$decrypted = opensslDecryptWithSalt($encrypted, $password);
echo "解密结果: " . $decrypted . "\n";

四、核心逻辑说明

‌1. 盐值生成‌

- 阅读剩余部分 -

介绍

BosForTypecho 插件基于 AliOssForTypecho 开发。

为保证正确记录日志,请赋予以下目录写权限:/usr/uploads/,并定期查阅日志处理事件错误。开启SELinux的用户注意合理配置权限。

当文件成功上传到BOS,但保存到服务器失败时,总体进度会显示失败。在BOS中的文件不会自动删除,请根据错误日志自行处理。

运行在云应用引擎上的站点 “在服务器保留备份” 选项无效,且无法记录日志。

旧版本Typecho存在无法上传大写扩展名文件的bug,请更新Typecho程序。

下载地址

https://gitee.com/jeaxnew/bos-for-typecho

程序开发过程中总是有一些自增ID信息会展现给前端,这样就给一些人可乘之机,通过遍历ID等方式获取到数据信息。

为了解决这个问题,想了很多办法,今天自己写了一个对称加密算法分享给大家。ID加密后展示到前端,后端先解密再处理。

代码如下,欢迎指正。

<?php
/**
 * 整型数字对称加密,使用场景:对ID加密后展示
 * 只适用整型数字
 * 使用时请重新生成密钥
 */
class IntCode{
    // 密钥(0-9A-Za-z)打乱,可使用str_shuffle()函数重新生成
    private $key = 'PTvNKJdjOyB3niF891XCspl7rHMQIkELVqYbm20ZGUWhfze5txSRowg4uDA6ac';
    
    public function encode($int){
        //判断是否为整型
        if (! is_int($int)) {
            return '不是整型';
        }
        //将传入数字转换成十六进制分割成数组
        $hexArr = str_split(dechex($int));
        //将密钥分割成数组
        $keyArr = str_split($this->key);
        //密钥长度,推荐62
        $keyLen = count($keyArr);
        //随机数字
        $rand = mt_rand(0, $keyLen - 1);
        //将随机值压入结果开头
        $str = $keyArr[$rand];
        //验证码
        $verfy = $keyArr[($keyLen - $rand + strlen($int)) % $keyLen];
        //循环十六进制每一位数字,替换成密钥里的值
        foreach ($hexArr as $v) {
            $offset = hexdec($v) + $rand;
            $str .= $keyArr[$offset % $keyLen];
        }
        //将验证码压入结果末尾并返回
        return $str . $verfy;
    }
    
    public function decode($str){
        //验证$str是否合法
        if (! preg_match('/^[0-9a-zA-Z]{2,10}$/', $str)) {
            return '字符不合法';
        }
        //将传入字符串分割成数组
        $strArr = str_split($str);
        //密钥
        $key = $this->key;
        //将密钥分割成数组
        $keyArr = str_split($this->key);
        //密钥长度
        $keyLen = count($keyArr);
        //十六进制数值
        $hex = '';
        //获取随机数
        $rand = strpos($key, array_shift($strArr));
        //获取验证码
        $verfy = array_pop($strArr);
        //循环每一个字串并转换成十六进制
        foreach ($strArr as $k => $v) {
            if (strpos($key, $v) >= $rand) {
                $hex .= dechex(strpos($key, $v) - $rand);
            } else {
                $hex .= dechex($keyLen - $rand + strpos($key, $v));
            }
        }
        //十六进制转换成十进制
        $dec = hexdec($hex);
        //判断验证码是否正确
        if ($verfy !== $keyArr[($keyLen - $rand + strlen($dec)) % $keyLen]) {
            return '校验错误,给定字符串不合法'; 
        }
        return $dec;
    }
}

- 阅读剩余部分 -

本文来源网络:

  • 1、码农何苦为难穷逼
  • 2、删了就无法运行,我也不知道为什么
  • 3、早点睡吧,别改代码了
  • 4、系统终止运行了
  • 5、感觉念了一首诗
  • 6、我刚写这段代码时,我和上帝知道这段代码的意思,现在只有上帝知道了
  • 7、这样写还是生动形象的
  • 8、客户需求怎么做
  • 9、这个太狠了吧
  • 10、老实的程序员

1、码农何苦为难穷逼

1530171051-5e818c9125304_articlex.png

2、删了就无法运行,我也不知道为什么

// 写完这段就辞职
// 这段代码不知道谁写的
// 看起来没用
// 但是删完之后程序就跑不起来了
// 我也不知道为什么

- 阅读剩余部分 -

20191112215429.png

原因分析

这个问题在 64 位的 PHP 版本中并不存在,因为是在 32 位版本中,以秒计算 PHP 只支持到 2147483648,即:2^31,到 2038-01-19 03:14:08。

有效的时间戳通常从 Fri, 13 Dec 1901 20:45:54 GMT 到 Tue, 19 Jan 2038 03:14:07 GMT(对应于 32 位有符号整数的最小值和最大值)。不是所有的平台都支持负的时间戳,那么日记范围就被限制为不能早于 Unix 纪元。这意味着在 1970 年 1 月 1 日之前的日期将不能用在 Windows,一些 Linux 版本,以及几个其它的操作系统中。不过 PHP 5.1.0 及更新的版本克服了此限制。 —— [ PHP手册 ]

解决办法

如果在32系统PHP 5.1.0之后的版本,可以使用new DateTime解决。代码如下:

将时间戳转为年月日:

$d = new DateTime("@21474836490");
$d->setTimezone(new DateTimeZone("PRC"));
echo $d->format("Y-m-d H:i:s");

将年月日转为时间戳:

$d = new DateTime('2650-07-06 16:21:30');
echo $d->format('U');

如果运行时,遇到如下报错:

WARNING: It is not safe to rely on the system’s timezone settings

- 阅读剩余部分 -

php.jpg

使用 COOKIEJAR 方法

PHP 中 CURL 类在做请求时非常好用,对于COOKIE,CURL类也有很不错的支持。

获得 COOKIE 并存为文件:

// 把 COOKIE 保存至 cookie.txt
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookie.txt');

// COOKIE 文件存放在至 temp 文件夹下的随机文件
$cookie_file = tempnam('./temp','cookie');
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);

读取文件并携带 COOKIE:

curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookie.txt');

先把 COOKIE 保存文件,调用的时候再读取文件。

使用正则表达式

使用 COOKIEJAR 方法,会有两次 IO 操作,对效率有一定的损失。且运行在 BAE/SAE/GAE 等云计算平台时,不支持本地文件写入。使用正则表达式可以解决这些问题。

- 阅读剩余部分 -

JetBrainsLicenseServer 授权服务类

代码如下:

<?php

/**
 * JetBrains许可服务器
 */
class JetBrainsLicenseServer
{
    // 授权给谁
    public $licensee = 'jxx';
    // license 有效时间(单位:毫秒),默认约为7天多(607875500),原厂 server 传递的数值。
    public $prolongationPeriod = 607875500;
    // RSA 私钥
    public $privateKey = <<<Eof
-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBALecq3BwAI4YJZwhJ+snnDFj3lF3DMqNPorV6y5ZKXCiCMqj8OeO
mxk4YZW9aaV9ckl/zlAOI0mpB3pDT+Xlj2sCAwEAAQJAW6/aVD05qbsZHMvZuS2A
a5FpNNj0BDlf38hOtkhDzz/hkYb+EBYLLvldhgsD0OvRNy8yhz7EjaUqLCB0juIN
4QIhAMsJQ3xiJemnJ2pD65iRNCC/Kr7jtxbbBwa6ZFLjp12pAiEA54JCn41fF8GZ
90b9L5dtFQB2/yIcGX4Xo7bCvl8DaPMCIBgOZ+2T33QYtwXTOFXiVm/O1qy5ZFcT
6ng0m3BqwsjJAiEAqna/l7wAyP1E4U7kHqbhKxWsiTAUgLDXtzRbMNHFMQECIQCA
xlpXEPqnC3P8if0G9xHomqJ531rOJuzB8fNtRFmxnA==
-----END RSA PRIVATE KEY-----
Eof;

    public function ping($salt, $isAnswer = false)
    {
        $str = '<PingResponse><message></message><responseCode>OK</responseCode><salt>%s</salt></PingResponse>';
        $out = sprintf($str, $salt);
        return $isAnswer ? $this->writeAnswer($out) : $out;
    }

    public function obtainTicket($salt, $isAnswer = false)
    {
        $ticketId = 1;
        $str      = "<ObtainTicketResponse><message></message><prolongationPeriod>%u</prolongationPeriod><responseCode>OK</responseCode><salt>%s</salt><ticketId>%d</ticketId><ticketProperties>licensee=%s\tlicenseType=0\t</ticketProperties></ObtainTicketResponse>";
        $out      = sprintf($str, $this->prolongationPeriod, $salt, $ticketId, $this->licensee);
        return $isAnswer ? $this->writeAnswer($out) : $out;
    }

    public function prolongTicket($salt, $isAnswer = false)
    {
        $ticketId = 1;
        $str      = '<ProlongTicketResponse><message></message><responseCode>OK</responseCode><salt>%s</salt><ticketId>%d</ticketId></ProlongTicketResponse>';
        $out      = sprintf($str, $salt, $ticketId);
        return $isAnswer ? $this->writeAnswer($out) : $out;
    }

    public function releaseTicket($salt, $isAnswer = false)
    {
        $str = '<ReleaseTicketResponse><message></message><responseCode>OK</responseCode><salt>%s</salt></ReleaseTicketResponse>';
        $out = sprintf($str, $salt);
        return $isAnswer ? $this->writeAnswer($out) : $out;
    }

    public function writeAnswer($str)
    {
        $signature    = $this->sign($str);
        $signatureHex = bin2hex($signature);
        $out          = sprintf("<!-- %s -->\n%s", $signatureHex, $str);
        return $out;
    }

    private function sign($data)
    {
        if (empty ($data)) {
            return null;
        }
        $pkeyid = openssl_get_privatekey($this->privateKey);
        if (empty ($pkeyid)) {
            return null;
        }
        $signature = '';
        $verify    = openssl_sign($data, $signature, $pkeyid, OPENSSL_ALGO_MD5);
        openssl_free_key($pkeyid);
        return $verify ? $signature : null;
    }

}

index.php 文件

代码如下:

- 阅读剩余部分 -

下载代码:

composer require medivh/oauth dev-master

GitHub 项目地址:https://github.com/medivh-jay/oauth

QQ 登录

<?php
require 'vendor/autoload.php';

// 配置信息
$config = [
    'appid' => '申请的appid',
    'secret' => '申请的appKey',
    'redirect_uri' => '跳转地址',
    'response_type' => 'code',
    'display' => 'default', // 分 default 和 mobile
    'scope' => 'get_user_info,add_share,list_album,add_album,upload_pic,add_topic,add_one_blog,add_weibo,check_page_fans,add_t,add_pic_t,del_t,get_repost_list,get_info,get_other_info,get_fanslist,get_idolist,add_idol,del_idol,get_tenpay_addr' // 这里可以固定成这个
];

得到认证对象

Driver命名空间下提供了部分认证驱动类,亦可以自己实现,只要继承了OAuthInterface接口, 都可以使用OAuth来调用

$oAuth = \medivh\OAuth\OAuth::register(new \medivh\OAuth\Driver\QQ, $config);

生成登录地址

$oAuth->getAuthorizeURL();

获取access_token

$oAuth->getAccessToken();

获取用户信息

这个方法可以传入两个参数,openid 和 access_token
当服务器保存了用户的 openid 和 access_token 时,可以在用户登录时直接调用这个方法获取用户信息

- 阅读剩余部分 -

代码如下:

$t1 = microtime(true);
// ... 执行代码 ...
$t2 = microtime(true);
echo '耗时 '.round($t2-$t1, 3).' 秒';

microtime() 如果带有 true 参数,将返回一个浮点类型。

这样 t1 和 t2 得到的就是两个浮点数,相减之后得到之间的差。

由于浮点的位数很长,或者说不确定,所以再用 round() 取出小数点后 3 位。

代码如下:

// 设置HTTP状态码
function http_header($num) {
    $http = array(
        100 => "HTTP/1.1 100 Continue",
        101 => "HTTP/1.1 101 Switching Protocols",
        200 => "HTTP/1.1 200 OK",
        201 => "HTTP/1.1 201 Created",
        202 => "HTTP/1.1 202 Accepted",
        203 => "HTTP/1.1 203 Non-Authoritative Information",
        204 => "HTTP/1.1 204 No Content",
        205 => "HTTP/1.1 205 Reset Content",
        206 => "HTTP/1.1 206 Partial Content",
        300 => "HTTP/1.1 300 Multiple Choices",
        301 => "HTTP/1.1 301 Moved Permanently",
        302 => "HTTP/1.1 302 Found",
        303 => "HTTP/1.1 303 See Other",
        304 => "HTTP/1.1 304 Not Modified",
        305 => "HTTP/1.1 305 Use Proxy",
        307 => "HTTP/1.1 307 Temporary Redirect",
        400 => "HTTP/1.1 400 Bad Request",
        401 => "HTTP/1.1 401 Unauthorized",
        402 => "HTTP/1.1 402 Payment Required",
        403 => "HTTP/1.1 403 Forbidden",
        404 => "HTTP/1.1 404 Not Found",
        405 => "HTTP/1.1 405 Method Not Allowed",
        406 => "HTTP/1.1 406 Not Acceptable",
        407 => "HTTP/1.1 407 Proxy Authentication Required",
        408 => "HTTP/1.1 408 Request Time-out",
        409 => "HTTP/1.1 409 Conflict",
        410 => "HTTP/1.1 410 Gone",
        411 => "HTTP/1.1 411 Length Required",
        412 => "HTTP/1.1 412 Precondition Failed",
        413 => "HTTP/1.1 413 Request Entity Too Large",
        414 => "HTTP/1.1 414 Request-URI Too Large",
        415 => "HTTP/1.1 415 Unsupported Media Type",
        416 => "HTTP/1.1 416 Requested range not satisfiable",
        417 => "HTTP/1.1 417 Expectation Failed",
        500 => "HTTP/1.1 500 Internal Server Error",
        501 => "HTTP/1.1 501 Not Implemented",
        502 => "HTTP/1.1 502 Bad Gateway",
        503 => "HTTP/1.1 503 Service Unavailable",
        504 => "HTTP/1.1 504 Gateway Time-out"
    );
    header($http[$num]);
}