chore(addon/huawiepay): 增强huaweipay 的预下单及单元测试

This commit is contained in:
2025-12-05 15:05:20 +08:00
parent bbb0271a5e
commit ee2785f972
380 changed files with 65253 additions and 311 deletions

View File

@@ -0,0 +1,16 @@
# 华为支付插件
## 参考文档
1. [证书准备](https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/certificate-preparation-0000001596094962#section1557292604420)
## 华为支付服务(非虚拟类)
### H5 支付
商户支付接入流程:
![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20250904134016.26094001741235049643433979198784:50001231000000:2800:3BB133019EBF404FC77A3BEB5B76E5AAF4809C6D4E86C47BB6C59EB5BF362599.png)

View File

@@ -6,16 +6,26 @@
namespace addon\huaweipay\data\sdk;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Crypt\RSA;
use app\exception\ApiException;
use think\facade\Log;
// 引入工具类
use addon\huaweipay\data\sdk\Utils;
// 定义常量
// 使用SM2算法验证签名使用SM3哈希算法
// 注意OPENSSL_ALGO_SM3常量在PHP 8.0.0及以上版本可用
// 对于PHP 7.4及以下版本使用数字值18OpenSSL内部SM3算法编号
define('OPENSSL_ALGO_SM3', 18);
class HuaweiPayClient
{
// 华为支付网关地址
private $gatewayUrl = 'https://pay.cloud.huawei.com/gateway/api/pay';
private $gatewayUrl = 'https://petalpay.cloud.huawei.com.cn';
// 华为支付配置
private $config = [];
@@ -26,6 +36,9 @@ class HuaweiPayClient
// 商户应用私有证书实例
private $private_key_certificate_instance;
// 商户应用公钥证书实例
private $public_key_certificate_instance;
// 华为平台支付证书实例
private $huawei_public_key_certificate_instance;
@@ -35,7 +48,8 @@ class HuaweiPayClient
// 是否加载了华为平台支付服务加密证书
private $has_huawei_public_key_certificate_instance_encrypt = false;
// 是否显示详细的curl请求信息
private $show_curl_detail = false;
/**
* 构造函数
@@ -77,76 +91,9 @@ class HuaweiPayClient
}
}
// 加载证书
try {
// 加载商户应用私有证书
$private_key_content = '';
if (!empty($this->config['private_key_text'])) {
// 文本模式,需要格式化
$private_key_content = Utils::formatCertificateContent($this->config['private_key_text'], 'private_key');
} elseif (!empty($this->config['private_key'])) {
// 文件模式
$private_key_path = realpath($cert_base_path . $this->config['private_key']);
if (!$private_key_path) {
throw new \Exception('商户应用私有证书文件不存在: ' . $this->config['private_key']);
}
$private_key_content = file_get_contents($private_key_path);
} else {
throw new \Exception('缺少必要配置private_key或private_key_text');
}
$this->private_key_certificate_instance = openssl_pkey_get_private($private_key_content);
if (!$this->private_key_certificate_instance) {
// 输出详细的openssl错误信息
$error = '';
while ($err = openssl_error_string()) {
$error .= $err . ' ';
}
throw new \Exception('加载商户应用私有证书失败请检查证书格式是否正确。OpenSSL错误: ' . $error . ' 证书内容开头: ' . substr($private_key_content, 0, 100));
}
// 加载华为平台支付证书
$huawei_public_key_content = '';
if (!empty($this->config['huawei_public_key_text'])) {
// 文本模式,需要格式化
$huawei_public_key_content = Utils::formatCertificateContent($this->config['huawei_public_key_text'], 'public_key');
} elseif (!empty($this->config['huawei_public_key'])) {
// 文件模式
$huawei_public_key_path = realpath($cert_base_path . $this->config['huawei_public_key']);
if (!$huawei_public_key_path) {
throw new \Exception('华为平台支付证书文件不存在: ' . $this->config['huawei_public_key']);
}
$huawei_public_key_content = file_get_contents($huawei_public_key_path);
} else {
throw new \Exception('缺少必要配置huawei_public_key或huawei_public_key_text');
}
$this->huawei_public_key_certificate_instance = openssl_pkey_get_public($huawei_public_key_content);
if (!$this->huawei_public_key_certificate_instance) {
throw new \Exception('加载华为平台支付证书失败,请检查证书格式是否正确');
}
// 加载华为平台支付服务加密证书(可选)
if (!empty($this->config['huawei_public_key_for_sessionkey_text'])) {
// 文本模式,需要格式化
$huawei_public_key_encrypt_content = Utils::formatCertificateContent($this->config['huawei_public_key_for_sessionkey_text'], 'public_key');
$this->huawei_public_key_certificate_instance_encrypt = openssl_pkey_get_public($huawei_public_key_encrypt_content);
if (!$this->huawei_public_key_certificate_instance_encrypt) {
throw new \Exception('加载华为平台支付服务加密证书失败');
}
$this->has_huawei_public_key_certificate_instance_encrypt = true;
} elseif (!empty($this->config['huawei_public_key_for_sessionkey'])) {
// 文件模式
$huawei_public_key_encrypt_path = realpath($cert_base_path . $this->config['huawei_public_key_for_sessionkey']);
if (!$huawei_public_key_encrypt_path) {
throw new \Exception('华为平台支付服务加密证书文件不存在: ' . $this->config['huawei_public_key_for_sessionkey']);
}
$huawei_public_key_encrypt_content = file_get_contents($huawei_public_key_encrypt_path);
$this->huawei_public_key_certificate_instance_encrypt = openssl_pkey_get_public($huawei_public_key_encrypt_content);
if (!$this->huawei_public_key_certificate_instance_encrypt) {
throw new \Exception('加载华为平台支付服务加密证书失败');
}
$this->has_huawei_public_key_certificate_instance_encrypt = true;
}
$this->loadCertificates($cert_base_path);
} catch (\Exception $e) {
// 释放已加载的证书资源
$this->__destruct();
@@ -155,7 +102,106 @@ class HuaweiPayClient
// 根据配置设置网关地址
if (isset($config['sandbox']) && $config['sandbox']) {
$this->gatewayUrl = 'https://petalpay-developer-sandbox.cloud.huawei.com.cn/gateway/api/pay';
$this->gatewayUrl = 'https://petalpay-developer.cloud.huawei.com.cn';
}
}
/**
* 加载证书
* @param string $cert_base_path 证书基础路径
* @throws \Exception
*/
private function loadCertificates($cert_base_path)
{
// 加载商户应用私有证书
$private_key_content = '';
if (!empty($this->config['private_key_text'])) {
// 文本模式,需要格式化
$private_key_content = Utils::formatCertificateContent($this->config['private_key_text'], 'private_key');
} elseif (!empty($this->config['private_key'])) {
// 文件模式
$private_key_path = realpath($cert_base_path . $this->config['private_key']);
if (!$private_key_path) {
throw new \Exception('商户应用私有证书文件不存在: ' . $this->config['private_key']);
}
$private_key_content = file_get_contents($private_key_path);
} else {
throw new \Exception('缺少必要配置private_key或private_key_text');
}
$this->private_key_certificate_instance = openssl_pkey_get_private($private_key_content);
if (!$this->private_key_certificate_instance) {
// 输出详细的openssl错误信息
$error = '';
while ($err = openssl_error_string()) {
$error .= $err . ' ';
}
throw new \Exception('加载商户应用私有证书失败请检查证书格式是否正确。OpenSSL错误: ' . $error . ' 证书内容开头: ' . substr($private_key_content, 0, 100));
}
// 加载商户应用公钥证书
$public_key_content = '';
if (!empty($this->config['public_key_text'])) {
// 文本模式,需要格式化
$public_key_content = Utils::formatCertificateContent($this->config['public_key_text'], 'public_key');
} elseif (!empty($this->config['public_key'])) {
// 文件模式
$public_key_path = realpath($cert_base_path . $this->config['public_key']);
if (!$public_key_path) {
throw new \Exception('商户应用公钥证书文件不存在: ' . $this->config['public_key']);
}
$public_key_content = file_get_contents($public_key_path);
} else {
throw new \Exception('缺少必要配置public_key或public_key_text');
}
$this->public_key_certificate_instance = openssl_pkey_get_public($public_key_content);
if (!$this->public_key_certificate_instance) {
throw new \Exception('加载商户应用公钥证书失败,请检查证书格式是否正确');
}
// 加载华为平台支付证书
$huawei_public_key_content = '';
if (!empty($this->config['huawei_public_key_text'])) {
// 文本模式,需要格式化
$huawei_public_key_content = Utils::formatCertificateContent($this->config['huawei_public_key_text'], 'public_key');
} elseif (!empty($this->config['huawei_public_key'])) {
// 文件模式
$huawei_public_key_path = realpath($cert_base_path . $this->config['huawei_public_key']);
if (!$huawei_public_key_path) {
throw new \Exception('华为平台支付证书文件不存在: ' . $this->config['huawei_public_key']);
}
$huawei_public_key_content = file_get_contents($huawei_public_key_path);
} else {
throw new \Exception('缺少必要配置huawei_public_key或huawei_public_key_text');
}
$this->huawei_public_key_certificate_instance = openssl_pkey_get_public($huawei_public_key_content);
if (!$this->huawei_public_key_certificate_instance) {
throw new \Exception('加载华为平台支付证书失败,请检查证书格式是否正确');
}
// 加载华为平台支付服务加密证书(可选)
if (!empty($this->config['huawei_public_key_for_sessionkey_text'])) {
// 文本模式,需要格式化
$huawei_public_key_encrypt_content = Utils::formatCertificateContent($this->config['huawei_public_key_for_sessionkey_text'], 'public_key');
$this->huawei_public_key_certificate_instance_encrypt = openssl_pkey_get_public($huawei_public_key_encrypt_content);
if (!$this->huawei_public_key_certificate_instance_encrypt) {
throw new \Exception('加载华为平台支付服务加密证书失败');
}
$this->has_huawei_public_key_certificate_instance_encrypt = true;
} elseif (!empty($this->config['huawei_public_key_for_sessionkey'])) {
// 文件模式
$huawei_public_key_encrypt_path = realpath($cert_base_path . $this->config['huawei_public_key_for_sessionkey']);
if (!$huawei_public_key_encrypt_path) {
throw new \Exception('华为平台支付服务加密证书文件不存在: ' . $this->config['huawei_public_key_for_sessionkey']);
}
$huawei_public_key_encrypt_content = file_get_contents($huawei_public_key_encrypt_path);
$this->huawei_public_key_certificate_instance_encrypt = openssl_pkey_get_public($huawei_public_key_encrypt_content);
if (!$this->huawei_public_key_certificate_instance_encrypt) {
throw new \Exception('加载华为平台支付服务加密证书失败');
}
$this->has_huawei_public_key_certificate_instance_encrypt = true;
}
}
@@ -183,22 +229,36 @@ class HuaweiPayClient
}
/**
* 生成签名
* 通用生成签名
* @param array $params 请求参数
* @param resource $private_key_certificate_instance 商户应用私钥证书实例
* @param array $excludeParams 要排除的参数键名数组
* @param string $signType 签名类型默认RSA2
*
* @return string 签名结果
* @notes
* 1签名类型根据$signType参数确定默认RSA2
* 2RSA2使用SHA256WithRSA算法RSA使用SHA1WithRSA算法
* 3SM2使用SM3WithSM2算法
*
* 签名使用的私钥是商户私有,商户公钥已经上传到华为支付平台
*/
private function generateSign($params)
private function generateSign($params, $private_key_certificate_instance, $excludeParams = [], $signType = 'RSA2')
{
// 深拷贝参数,避免修改原参数
$signParams = $params;
// 移除空值和签名参数
$signParams = array_filter($signParams, function($value) {
$signParams = array_filter($signParams, function ($value) {
return $value !== null && $value !== '';
});
unset($signParams['sign']);
unset($signParams['sign_type']);
unset($signParams['headerSign']); // 移除headerSign参数
// 移除指定的参数
foreach ($excludeParams as $key) {
if (array_key_exists($key, $signParams)) {
unset($signParams[$key]);
}
}
// 按键名排序
ksort($signParams);
@@ -207,7 +267,7 @@ class HuaweiPayClient
$stringToSign = '';
foreach ($signParams as $key => $value) {
// 确保值为字符串类型
$value = (string)$value;
$value = (string) $value;
$stringToSign .= $key . '=' . $value . '&';
}
$stringToSign = rtrim($stringToSign, '&');
@@ -217,19 +277,27 @@ class HuaweiPayClient
$signatureAlgo = '';
// 根据签名类型生成签名
switch ($this->signType) {
switch ($signType) {
case 'RSA2':
$signatureAlgo = OPENSSL_ALGO_SHA256;
break;
case 'RSA':
$signatureAlgo = OPENSSL_ALGO_SHA1;
break;
case 'SM2':
$signatureAlgo = OPENSSL_ALGO_SM3;
break;
default:
throw new \Exception('不支持的签名类型: ' . $this->signType);
throw new \Exception('不支持的签名类型: ' . $signType);
}
// 检查私钥证书实例是否有效
if (!is_resource($private_key_certificate_instance)) {
throw new \Exception('私钥证书实例无效');
}
// 生成签名
if (!is_resource($this->private_key_certificate_instance) || !openssl_sign($stringToSign, $sign, $this->private_key_certificate_instance, $signatureAlgo)) {
if (!openssl_sign($stringToSign, $sign, $private_key_certificate_instance, $signatureAlgo)) {
$error = openssl_error_string();
throw new \Exception('签名生成失败: ' . $error);
}
@@ -239,11 +307,14 @@ class HuaweiPayClient
}
/**
* 验证签名
* 通用验证签名
* @param array $params 响应参数
* @param resource $publicKeyInstance 公钥证书实例
* @param array $excludeParams 要排除的参数键名数组
* @param string $signType 签名类型默认RSA2
* @return bool 验证结果
*/
public function verifySign($params)
public function verifySign($params, $publicKeyInstance, $excludeParams = ['sign'], $signType = 'RSA2')
{
// 保存签名
$sign = $params['sign'] ?? '';
@@ -254,11 +325,20 @@ class HuaweiPayClient
// 深拷贝参数,避免修改原参数
$verifyParams = $params;
unset($verifyParams['sign']);
unset($verifyParams['sign_type']);
// 移除指定的参数
foreach ($excludeParams as $key) {
if (array_key_exists($key, $verifyParams)) {
unset($verifyParams[$key]);
}
}
// 如果有sign_type参数使用它覆盖默认值
if (isset($verifyParams['sign_type'])) {
$signType = $verifyParams['sign_type'];
}
// 移除空值
$verifyParams = array_filter($verifyParams, function($value) {
$verifyParams = array_filter($verifyParams, function ($value) {
return $value !== null && $value !== '';
});
@@ -272,47 +352,104 @@ class HuaweiPayClient
}
$stringToSign = rtrim($stringToSign, '&');
// 验证公钥证书实例是否有效
if (!is_resource($publicKeyInstance)) {
throw new \Exception('公钥证书实例无效');
}
// 检查公钥类型是否和签名类型匹配
$keyDetails = openssl_pkey_get_details($publicKeyInstance);
if ($signType === 'SM2' && ($keyDetails['type'] !== OPENSSL_KEYTYPE_EC || $keyDetails['ec']['curve_name'] !== 'sm2p256v1')) {
throw new \Exception('SM2签名类型要求使用SM2公钥');
} elseif ($signType === 'RSA2' && $keyDetails['type'] !== OPENSSL_KEYTYPE_RSA) {
throw new \Exception('RSA2签名类型要求使用RSA公钥');
} elseif ($signType === 'RSA' && $keyDetails['type'] !== OPENSSL_KEYTYPE_RSA) {
throw new \Exception('RSA签名类型要求使用RSA公钥');
}
// 验证签名
$result = 0;
switch ($this->signType) {
// 先进行url解码再进行base64解码因为generateSign方法中先base64编码再url编码
$decoded_sign = base64_decode(urldecode($sign));
// 根据签名类型选择不同的验证方法
switch ($signType) {
case 'SM2':
// 使用SM2算法验证签名
$result = openssl_verify($stringToSign, $decoded_sign, $publicKeyInstance, OPENSSL_ALGO_SM3);
break;
case 'RSA2':
$result = openssl_verify($stringToSign, base64_decode($sign), $this->huawei_public_key_certificate_instance, OPENSSL_ALGO_SHA256);
// 使用RSA2算法验证签名
$result = openssl_verify($stringToSign, $decoded_sign, $publicKeyInstance, OPENSSL_ALGO_SHA256);
break;
case 'RSA':
$result = openssl_verify($stringToSign, base64_decode($sign), $this->huawei_public_key_certificate_instance, OPENSSL_ALGO_SHA1);
// 使用RSA算法验证签名
$result = openssl_verify($stringToSign, $decoded_sign, $publicKeyInstance, OPENSSL_ALGO_SHA1);
break;
default:
throw new \Exception('不支持的签名类型');
// 默认使用RSA2算法
$result = openssl_verify($stringToSign, $decoded_sign, $publicKeyInstance, OPENSSL_ALGO_SHA256);
break;
}
return $result === 1;
}
/**
* 验证华为支付中心服务器返回的签名
* @param array $params 响应参数
* @return bool 验证结果
*/
private function verifySignFromHuawei($params)
{
// 验证签名
return $this->verifySign($params, $this->huawei_public_key_certificate_instance, ['sign'], $params['sign_type'] ?? 'SM2');
}
/**
* 发送HTTP请求
* @param string $url 请求地址
* @param array $params 请求参数
* @param string $method 请求方法
* @param $param["out_trade_no"] 订单号,商户订单号
* @param $param["subject"] 订单标题,商品简称
* @param $param["total_amount"] 订单金额(单位:分)
* @param $param["bizType"] 业务类型默认值100001
* @param $param["notify_url"] 回调URL, 异步通知商户服务器的URL用于接收支付结果通知。
* @param string $method 请求方法 可选值POST、GET
* @return array 响应结果
* @throws \Exception 异常信息
* @doc
* - [API 参考](https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-android-pre-pay-order-0000001589121249)
*/
private function httpRequest($url, $params, $method = 'POST')
{
$ch = curl_init();
// 根据华为支付文档,添加必要的公共参数
$params['app_id'] = $this->config['app_id'];
$params['merc_no'] = $this->config['merc_no'];
// 根据场景方法验证必要参数
$scene_method = $params['method'] ?? ''; // 场景方法h5pay.createPayment
$requiredParams = []; // 场景方法必填参数
// 根据方法类型验证必要参数
$method = $params['method'] ?? '';
$requiredParams = [];
// 根据不同的API方法设置不同的必填参数
switch ($method) {
// 根据不同的场景方法设置不同的必填参数, 同时设置参数映射转义
switch ($scene_method) {
case 'h5pay.createPayment':
case 'quick_app_pay.createPayment':
case 'fa_pay.createPayment':
case 'apppay.createPayment':
$requiredParams = ['out_trade_no', 'subject', 'total_amount', 'notify_url'];
// 应用支付必填参数
$requiredParams = ['out_trade_no', 'subject', 'total_amount', 'notify_url', 'bizType'];
// 参数映射转义
$params['mercOrderNo'] = $params['out_trade_no']; // 订单号,商户订单号
$params['tradeSummary'] = $params['subject']; // 订单标题,商品简称
$params['totalAmount'] = $params['total_amount'] * 100; // 订单金额(单位:分)
$params['callbackUrl'] = $params['notify_url']; // 回调URL
$params['currency'] = $params['currency'] ?? "CNY"; // 货币类型默认值CNY
$params['bizType'] = $params['bizType'] ?? "100001"; // (100001虚拟商品购买,100002实物商品购买,100003预付类账号充值,100004航旅交通服务,100005活动票务订购,100006商业服务消费,100007生活服务消费,100008租金缴纳,100009会员费缴纳,100011其他商家消费,100037公共便民服务)
// 可选参数
$params['payload'] = $params['payload'] ?? ""; // 业务扩展参数JSON格式字符串用于传递业务相关信息。
break;
case 'unifiedorder.query':
// 查询订单只需要out_trade_no或trade_no中的一个
@@ -330,33 +467,50 @@ class HuaweiPayClient
}
}
// 根据华为支付文档,添加必要的公共参数
$params['appId'] = $this->config['app_id']; // 应用ID商户在华为支付平台注册的应用ID
$params['mercNo'] = $this->config['merc_no']; // 商户号,商户在华为支付平台注册的商户号
// 生成签名
$params['sign_type'] = $this->signType;
$params['sign'] = $this->generateSign($params);
$params['sign'] = $this->generateSign($params, $this->private_key_certificate_instance, ['sign'], $this->signType);
// 设置请求方式
if ($method === 'POST') {
// 根据$method参数设置请求方式
$method = strtoupper($method);
if ($method == 'POST') {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
} elseif ($method == 'GET') {
// GET请求时将参数拼接到URL中
$url = $url . '?' . http_build_query($params);
curl_setopt($ch, CURLOPT_HTTPGET, 1);
} else {
$url .= '?' . http_build_query($params);
// 支持其他HTTP方法
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
}
// 添加调试信息
curl_setopt($ch, CURLOPT_VERBOSE, true);
$verbose = fopen('php://temp', 'rw+');
curl_setopt($ch, CURLOPT_STDERR, $verbose);
// 参考华为支付文档设置PayMercAuth头
// 注意华为支付SDK v2.0及以上版本需要此头部
if (isset($this->config['auth_id']) && !empty($this->config['auth_id'])) {
// 参考https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/payment-prepay-V5
if (isset($this->config['mch_auth_id']) && !empty($this->config['mch_auth_id'])) {
$traceId = isset($params['traceId']) ? $params['traceId'] : uniqid();
$pay_merc_auth_part = [
'callerId' => $this->config['merc_no'],
'traceId' => $traceId,
'time' => date('YmdHis'),
'authId' => $this->config['auth_id'],
'bodySign' => $params['sign'] // 直接使用已生成的签名
'callerId' => $this->config['merc_no'], // 商户号,商户在华为支付平台注册的商户号
'traceId' => $traceId, // 调用方请求ID用于跟踪请求建议使用UUID格式
'time' => intval(microtime(true) * 1000), // 时间戳,格式:毫秒级时间戳
'authId' => $this->config['mch_auth_id'], // 商户认证ID商户在华为支付平台注册的商户认证ID
'bodySign' => $params['sign'] // 直接使用已生成的签名
];
// 对PayMercAuthPart进行签名
$pay_merc_auth_part['headerSign'] = $this->generateSign($pay_merc_auth_part);
$pay_merc_auth_part['headerSign'] = $this->generateSign($pay_merc_auth_part, $this->private_key_certificate_instance, ['headerSign'], $this->signType);
// 设置请求头
$headers = [
@@ -385,8 +539,25 @@ class HuaweiPayClient
throw new \Exception('HTTP请求错误: ' . curl_error($ch));
}
// 获取调试信息
$verboseLog = '';
if ($this->show_curl_detail) {
rewind($verbose);
$verboseLog = stream_get_contents($verbose);
fclose($verbose);
}
curl_close($ch);
// 添加调试日志
if (!empty($verboseLog) && $this->show_curl_detail) {
error_log('cURL Debug Log: ' . $verboseLog);
// 也可以将调试日志输出到控制台,方便测试
echo "\n - cURL Debug Log:\n";
echo " " . str_replace("\n", "\n ", $verboseLog) . "\n";
}
// 解析响应
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
@@ -401,13 +572,119 @@ class HuaweiPayClient
}
/**
* H5支付
* 元服务支付
* @param array $params 支付参数
* @param string $returnUrl 同步回调地址
* @param string $notifyUrl 异步回调地址
* @return array 支付结果
*/
public function h5Pay($params, $returnUrl, $notifyUrl)
public function faPay($params, $notifyUrl)
{
// 检查必要参数
if (empty($params['out_trade_no'])) {
throw new \Exception('缺少必要参数out_trade_no');
}
if (empty($params['subject'])) {
throw new \Exception('缺少必要参数subject');
}
if (!isset($params['total_amount']) || $params['total_amount'] <= 0) {
throw new \Exception('缺少或无效的参数total_amount');
}
if (empty($notifyUrl)) {
throw new \Exception('缺少必要参数notify_url');
}
$requestParams = [
'method' => 'fa_pay.createPayment',
'version' => '1.0',
'timestamp' => date('Y-m-d H:i:s'),
'out_trade_no' => $params['out_trade_no'],
'subject' => mb_substr($params['subject'], 0, 128, 'UTF-8'), // 限制长度
'body' => isset($params['body']) ? mb_substr($params['body'], 0, 512, 'UTF-8') : mb_substr($params['subject'], 0, 512, 'UTF-8'),
'total_amount' => number_format($params['total_amount'], 2, '.', ''), // 确保格式正确
'scene' => 'quick_app',
'notify_url' => $notifyUrl,
];
// 可选参数,根据文档添加
if (isset($params['currency'])) {
$requestParams['currency'] = $params['currency'];
}
if (isset($params['client_ip'])) {
$requestParams['client_ip'] = $params['client_ip'];
}
if (isset($params['timeout_express'])) {
$requestParams['timeout_express'] = $params['timeout_express'];
}
if (isset($params['attach'])) {
$requestParams['attach'] = mb_substr($params['attach'], 0, 128, 'UTF-8');
}
$url = $this->gatewayUrl . '/api/v2/aggr/preorder/create/fa';
return $this->httpRequest($url, $requestParams);
}
/**
* 快应用支付
* @param array $params 支付参数
* @param string $notifyUrl 异步回调地址
* @return array 支付结果
*/
public function quickAppPay($params, $notifyUrl)
{
// 检查必要参数
if (empty($params['out_trade_no'])) {
throw new \Exception('缺少必要参数out_trade_no');
}
if (empty($params['subject'])) {
throw new \Exception('缺少必要参数subject');
}
if (!isset($params['total_amount']) || $params['total_amount'] <= 0) {
throw new \Exception('缺少或无效的参数total_amount');
}
if (empty($notifyUrl)) {
throw new \Exception('缺少必要参数notify_url');
}
$requestParams = [
'method' => 'quick_app_pay.createPayment',
'version' => '1.0',
'timestamp' => date('Y-m-d H:i:s'),
'out_trade_no' => $params['out_trade_no'],
'subject' => mb_substr($params['subject'], 0, 128, 'UTF-8'), // 限制长度
'body' => isset($params['body']) ? mb_substr($params['body'], 0, 512, 'UTF-8') : mb_substr($params['subject'], 0, 512, 'UTF-8'),
'total_amount' => number_format($params['total_amount'], 2, '.', ''), // 确保格式正确
'scene' => 'quick_app',
'notify_url' => $notifyUrl,
];
// 可选参数,根据文档添加
if (isset($params['currency'])) {
$requestParams['currency'] = $params['currency'];
}
if (isset($params['client_ip'])) {
$requestParams['client_ip'] = $params['client_ip'];
}
if (isset($params['timeout_express'])) {
$requestParams['timeout_express'] = $params['timeout_express'];
}
if (isset($params['attach'])) {
$requestParams['attach'] = mb_substr($params['attach'], 0, 128, 'UTF-8');
}
$url = $this->gatewayUrl . '/api/v2/aggr/preorder/create/quick-app';
return $this->httpRequest($url, $requestParams);
}
/**
* H5支付
* @param array $params 支付参数
* @param string $notifyUrl 异步回调地址,用于华为支付服务器调用,将用户支付成功的消息通知给商户服务器。
* @param string $returnUrl 可选参数支付成功后跳转地址用于用户支付收银台支付完成后重定向的URL地址。
* @return array 支付结果
* @doc
* - [H5支付说明文档](https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/h5-dev-0000001537334464#section28465232319)
*/
public function h5Pay($params, $notifyUrl, $returnUrl = '')
{
// 检查必要参数
if (empty($params['out_trade_no'])) {
@@ -430,27 +707,27 @@ class HuaweiPayClient
'out_trade_no' => $params['out_trade_no'],
'subject' => mb_substr($params['subject'], 0, 128, 'UTF-8'), // 限制长度
'body' => isset($params['body']) ? mb_substr($params['body'], 0, 512, 'UTF-8') : mb_substr($params['subject'], 0, 512, 'UTF-8'),
'total_amount' => number_format($params['total_amount'], 2, '.', ''), // 确保格式正确
'total_amount' => $params['total_amount'], // 确保格式正确
'scene' => 'h5',
'return_url' => $returnUrl,
'notify_url' => $notifyUrl,
];
// 可选参数,根据文档添加
if (isset($params['currency'])) {
$requestParams['currency'] = $params['currency'];
}
if (isset($params['client_ip'])) {
$requestParams['client_ip'] = $params['client_ip'];
}
if (isset($params['timeout_express'])) {
$requestParams['timeout_express'] = $params['timeout_express'];
}
if (isset($params['attach'])) {
$requestParams['attach'] = mb_substr($params['attach'], 0, 128, 'UTF-8');
// 判断是否有return_url参数
if (!empty($returnUrl)) {
$requestParams['return_url'] = $returnUrl;
}
return $this->httpRequest($this->gatewayUrl, $requestParams);
// 可选参数,根据文档添加
// 参照https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-h5-pre-pay-order-0000001538376420
if (isset($params['currency']) && $params['currency'] !== 'CNY') {
$requestParams['currency'] = $params['currency'];
}
if (isset($params['expireTime'])) {
$requestParams['expire_time'] = $params['expireTime'];
}
$url = $this->gatewayUrl . '/api/v2/aggr/preorder/create/h5';
return $this->httpRequest($url, $requestParams);
}
@@ -462,14 +739,28 @@ class HuaweiPayClient
*/
public function appPay($params, $notifyUrl)
{
// 检查必要参数
if (empty($params['out_trade_no'])) {
throw new \Exception('缺少必要参数out_trade_no');
}
if (empty($params['subject'])) {
throw new \Exception('缺少必要参数subject');
}
if (!isset($params['total_amount']) || $params['total_amount'] <= 0) {
throw new \Exception('缺少或无效的参数total_amount');
}
if (empty($notifyUrl)) {
throw new \Exception('缺少必要参数notify_url');
}
$requestParams = [
'method' => 'apppay.createPayment',
'version' => '1.0',
'timestamp' => date('Y-m-d H:i:s'),
'out_trade_no' => $params['out_trade_no'],
'subject' => $params['subject'],
'body' => $params['body'],
'total_amount' => $params['total_amount'],
'subject' => mb_substr($params['subject'], 0, 128, 'UTF-8'), // 限制长度
'body' => isset($params['body']) ? mb_substr($params['body'], 0, 512, 'UTF-8') : mb_substr($params['subject'], 0, 512, 'UTF-8'),
'total_amount' => number_format($params['total_amount'], 2, '.', ''), // 确保格式正确
'notify_url' => $notifyUrl,
];
@@ -484,7 +775,8 @@ class HuaweiPayClient
$requestParams['attach'] = $params['attach'];
}
return $this->httpRequest($this->gatewayUrl, $requestParams);
$url = $this->gatewayUrl . '/api/v2/aggr/preorder/create/app';
return $this->httpRequest($url, $requestParams);
}
/**
@@ -574,6 +866,6 @@ class HuaweiPayClient
*/
public function verifyNotify($params)
{
return $this->verifySign($params);
return $this->verifySignFromHuawei($params);
}
}

View File

@@ -69,7 +69,8 @@ class Pay extends BaseModel
* @param $param["out_trade_no"] 订单号
* @param $param["pay_body"] 支付描述
* @param $param["pay_money"] 支付金额
* @param $param["notify_url"] 支付回调地址
* @param $param["notify_url"] 支付回调地址必须是https协议用于华为支付服务器调用回调地址通知商户服务器
* @param $param["return_url"] 可选参数支付成功后跳转地址必须是https协议用于用户支付收银台支付完成后重定向的URL地址
* @param $param["client_ip"] 客户端IP
* @param $param["attach"] 附加数据
* @param $param["app_type"] 应用类型
@@ -119,50 +120,50 @@ class Pay extends BaseModel
// 根据不同的应用类型调用不同的华为支付API
switch ($param["app_type"]) {
case "weapp":
// 微信小程序支付
$result = $this->hwpay_client->weappPay($parameter, $param["notify_url"]);
// 记录响应日志
Log::info('华为微信小程序支付响应: ' . json_encode($result));
if (isset($result['code']) && $result['code'] == '0') {
return $this->success([
'type' => 'params',
'data' => $result
]);
} else {
$errorMsg = $result['msg'] ?? ($result['message'] ?? '华为微信小程序支付请求失败');
Log::error('华为微信小程序支付失败: ' . $errorMsg);
return $this->error('', $errorMsg);
}
break;
case "h5":
// H5支付
if (empty($param["return_url"])) {
throw new \Exception('H5支付缺少必要参数return_url');
}
$result = $this->hwpay_client->h5Pay($parameter, $param["return_url"], $param["notify_url"]);
// 参见华为H5支付文档https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-h5-pre-pay-order-0000001538376420
// 参见公共错误码说明https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-error-code-description-0000001589053741#section1187515498410
$result = $this->hwpay_client->h5Pay($parameter, $param["notify_url"], $param["return_url"]);
// 记录响应日志
Log::info('华为H5支付响应: ' . json_encode($result));
if (isset($result['code']) && $result['code'] == '0') {
// 检查是否有pay_url
if (isset($result['pay_url'])) {
return $this->success([
'type' => 'url',
'data' => $result['pay_url']
]);
} else {
throw new \Exception('华为支付响应缺少pay_url');
// 定义H5支付响应字段
$check_result_def = [
'resultCode' => '返回码,"000000"表示成功,其他表示见错误码',
'resultDesc' => '返回消息,结果描述',
// 'subCode' => '业务错误码',
// 'subDesc' => '业务错误码描述',
'sign' => '签名,用于验证响应的完整性和真实性',
'prepayId' => '预支付订单ID用于后续查询订单状态',
'h5Url' => 'H5支付URL用户在浏览器中打开该URL进行支付',
// 'mercOrderNo' => '商户订单号,用于商户查询订单状态',
];
// 验证H5返回的结果要包括必要字段
foreach ($check_result_def as $key => $desc) {
if (!isset($result[$key])) {
$errorMsg = "华为H5支付失败响应缺少必要字段{$key} , 字段含义: {$desc}";
Log::error($errorMsg);
return error(500000, $errorMsg);
}
} else {
$errorMsg = $result['msg'] ?? ($result['message'] ?? '华为支付请求失败');
Log::error('华为H5支付失败: ' . $errorMsg);
return $this->error('', $errorMsg);
}
return success(0, '', [
'type' => 'url',
'url' => $result['h5Url'],
]);
case "hwapp":
// 华为快应用
if (empty($param["return_url"])) {
throw new \Exception('华为快应用支付缺少必要参数return_url');
}
$result = $this->hwpay_client->quickAppPay($parameter, $param["return_url"]);
// 记录响应日志
Log::info('华为快应用支付响应: ' . json_encode($result));
break;
case "app":
// APP支付
@@ -302,8 +303,11 @@ class Pay extends BaseModel
/**
* 异步完成支付
* 处理华为服务器通过callback_url支付回调验证签名并更新订单状态
* 接口访问:华为支付服务器 -> 开发者服务器
* 接口文档https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-notify-pay-result-0000001538536148
* @param $param 回调参数
* @return string 响应给华为服务器的结果
* @return array 响应给华为服务器的结果
*/
public function notify($param)
{
@@ -314,45 +318,85 @@ class Pay extends BaseModel
// 验证华为支付回调签名
$is_valid = $this->hwpay_client->verifyNotify($param);
if ($is_valid) { // 验证成功
// 获取订单号和交易状态
$out_trade_no = $param['out_trade_no'] ?? '';
$trade_no = $param['trade_no'] ?? '';
$trade_status = $param['trade_status'] ?? '';
// 验证必要参数
if (empty($out_trade_no)) {
Log::error('华为支付回调缺少out_trade_no');
echo "fail";
return;
}
// 根据华为支付文档,成功状态可能是'SUCCESS'或'TRADE_SUCCESS'
if ($trade_status == "SUCCESS" || $trade_status == "TRADE_SUCCESS") {
$pay_common = new PayCommon();
$retval = $pay_common->onlinePay($out_trade_no, "huaweipay", $trade_no, "huaweipay");
// 记录支付结果
if ($retval['code'] >= 0) {
Log::info('华为支付回调处理成功,订单号: ' . $out_trade_no);
} else {
Log::error('华为支付回调处理失败,订单号: ' . $out_trade_no . ',错误信息: ' . ($retval['message'] ?? '未知错误'));
}
} else {
Log::info('华为支付回调状态非成功: ' . $trade_status . ',订单号: ' . $out_trade_no);
}
// 无论支付是否成功只要签名验证通过就返回success
// 避免华为服务器重复推送
echo "success";
} else {
// 验证失败
if (!$is_valid) { // 验证失败
Log::error('华为支付回调签名验证失败: ' . json_encode($param));
echo "fail";
return [
'resultCode' => '300000',
'resultDesc' => '签名验证失败',
];
}
// 参照文档https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-notify-pay-result-0000001538536148
$check_request_body = [
'callbackId' => '回调通知的唯一ID',
'callbackTime' => '回调通知时间',
'dataType' => '数据加密类型标识: encrypt加密, plain明文',
'sign' => '回调通知结果签名除“sign”字段以外的其他字段参与签名',
'signType' => '签名类型。华为支付生成签名字符串使用的算法当前为SM2算法',
'certNo' => '签名所使用的证书编号',
'mercNo' => '商户号',
'appId' => '应用ID',
'sysTransOrderNo' => '华为支付系统订单号',
'mercOrderNo' => '商户订单号由商户自己生成商户需保证订单信息唯一性。最大长度46',
'orderStatus' => '订单状态.TRX_SUCCESS交易成功, TRX_FAILED交易失败',
'payload' => '预留信息,如商户请求时传递该参数,此时会原样返回',
'currency' => '交易币种单位最大长度为3. 例如CNY',
'totalAmount' => '订单总金额,单位:分',
'payerAmount' => '买家实付金额,单位:分',
'promotionAmount' => '优惠金额,单位:分',
'finishTime' => "支付完成时间UTC时间格式yyyy-MM-dd'T'HH:mm:ss.SSSZ",
'paymentTools' => '支付工具: 支付渠道WECHAT_MICROPAY微信小程序支付 AGMT快捷 ACCT账户余额',
'promotionDetail' => '营销活动信息。',
'payer' => '买家信息。用户支付时客户端信息',
];
// 检查回调参数是否包含必要字段
foreach (['sign', 'signType', 'sysTransOrderNo', 'mercOrderNo', 'orderStatus', 'payload', 'currency', 'totalAmount', 'payerAmount', 'promotionAmount', 'finishTime', 'paymentTools'] as $key) {
if (!isset($param[$key])) {
Log::error('华为支付回调缺少必要字段: ' . $key);
return [
'resultCode' => '200000',
'resultDesc' => '缺少必要字段: ' . $key,
];
}
}
// 获取订单号和交易状态
$out_trade_no = $param['mercOrderNo'] ?? ''; // 商户订单号
$trade_no = $param['sysTransOrderNo'] ?? ''; // 华为支付系统订单号
$trade_status = $param['orderStatus'] ?? ''; // 订单状态
// 根据华为支付文档,成功状态可能是'SUCCESS'或'TRX_SUCCESS'
if ($trade_status == "SUCCESS" || $trade_status == "TRX_SUCCESS") {
$pay_common = new PayCommon();
$retval = $pay_common->onlinePay($out_trade_no, "huaweipay", $trade_no, "huaweipay");
// 记录支付结果
if ($retval['code'] >= 0) {
Log::info('华为支付回调处理成功,订单号: ' . $out_trade_no);
return [
'resultCode' => '000000',
'resultDesc' => '成功',
];
} else {
Log::error('华为支付回调处理失败,订单号: ' . $out_trade_no . ',错误信息: ' . ($retval['message'] ?? '未知错误'));
return [
'resultCode' => '100000',
'resultDesc' => '处理失败: ' . ($retval['message'] ?? '未知错误'),
];
}
} else {
Log::info('华为支付回调状态非成功: ' . $trade_status . ',订单号: ' . $out_trade_no);
return [
'resultCode' => '400000',
'resultDesc' => '订单状态非成功: ' . $trade_status,
];
}
} catch (\Exception $e) {
Log::error('华为支付回调处理异常: ' . $e->getMessage() . ',错误堆栈: ' . $e->getTraceAsString());
echo "fail";
return [
'resultCode' => '500000',
'resultDesc' => '处理异常: ' . $e->getMessage(),
];
}
}

View File

@@ -26,6 +26,15 @@ function app() {
};
}
// 模拟log类解决框架依赖问题
if (!class_exists('log')) {
class log {
public static function error($msg) {
echo " - 模拟日志错误: {$msg}\n";
}
}
}
class PayModelTest
{
protected $config = [];
@@ -122,30 +131,35 @@ class PayModelTest
echo "开始运行华为支付模型测试...\n";
try {
// 1. 测试支付方法
echo "1. 测试支付方法...\n";
// 1. 测试签名生成和验签方法
echo "1. 测试签名生成和验签方法...\n";
$this->testSignAndVerify();
echo "1. 签名生成和验签方法测试通过\n";
// 2. 测试支付方法
echo "2. 测试支付方法...\n";
$this->testPayMethod();
echo "1. 支付方法测试通过\n";
echo "2. 支付方法测试通过\n";
// 2. 测试关闭订单方法
echo "2. 测试关闭订单方法...\n";
$this->testCloseOrderMethod();
echo "2. 关闭订单方法测试通过\n";
// 3. 测试退款方法
echo "3. 测试退款方法...\n";
$this->testRefundMethod();
echo "3. 退款方法测试通过\n";
// 4. 测试查询订单方法
echo "4. 测试查询订单方法...\n";
$this->testQueryOrderMethod();
echo "4. 查询订单方法测试通过\n";
// 5. 测试回调验证方法
echo "5. 测试回调验证方法...\n";
// 3. 测试回调验证方法
echo "3. 测试回调验证方法...\n";
$this->testCallbackVerification();
echo "5. 回调验证方法测试通过\n";
echo "3. 回调验证方法测试通过\n";
// 4. 测试关闭订单方法
echo "4. 测试关闭订单方法...\n";
$this->testCloseOrderMethod();
echo "4. 关闭订单方法测试通过\n";
// 5. 测试退款方法
echo "5. 测试退款方法...\n";
$this->testRefundMethod();
echo "5. 退款方法测试通过\n";
// 6. 测试查询订单方法
echo "6. 测试查询订单方法...\n";
$this->testQueryOrderMethod();
echo "6. 查询订单方法测试通过\n";
echo "\n所有测试通过!\n";
} catch (Exception $e) {
@@ -191,10 +205,13 @@ class PayModelTest
];
// 调用真实的H5支付方法
$h5_result = $huawei_pay_client->h5Pay($h5_params, $config_data['returnUrl'], $config_data['notifyUrl']);
$h5_result = $huawei_pay_client->h5Pay($h5_params, $config_data['notifyUrl']);
echo " - H5支付方法调用成功\n";
echo " 结果: " . json_encode($h5_result, JSON_UNESCAPED_UNICODE) . "\n";
// 检查H5支付结果是否包含错误
$this->checkResultForErrors($h5_result, 'H5支付');
// 测试APP支付
$app_params = [
'out_trade_no' => 'TEST_APP_' . time(),
@@ -208,6 +225,9 @@ class PayModelTest
echo " - APP支付方法调用成功\n";
echo " 结果: " . json_encode($app_result, JSON_UNESCAPED_UNICODE) . "\n";
// 检查APP支付结果是否包含错误
$this->checkResultForErrors($app_result, 'APP支付');
} catch (Exception $e) {
echo " - 支付测试失败: " . $e->getMessage() . "\n";
throw $e;
@@ -246,6 +266,9 @@ class PayModelTest
echo " - 关闭订单方法调用成功\n";
echo " 结果: " . json_encode($close_result, JSON_UNESCAPED_UNICODE) . "\n";
// 检查关闭订单结果是否包含错误
$this->checkResultForErrors($close_result, '关闭订单');
} catch (Exception $e) {
echo " - 关闭订单测试失败: " . $e->getMessage() . "\n";
throw $e;
@@ -287,6 +310,9 @@ class PayModelTest
echo " - 退款方法调用成功\n";
echo " 结果: " . json_encode($refund_result, JSON_UNESCAPED_UNICODE) . "\n";
// 检查退款结果是否包含错误
$this->checkResultForErrors($refund_result, '退款');
} catch (Exception $e) {
echo " - 退款测试失败: " . $e->getMessage() . "\n";
throw $e;
@@ -325,6 +351,9 @@ class PayModelTest
echo " - 查询订单方法调用成功\n";
echo " 结果: " . json_encode($query_result, JSON_UNESCAPED_UNICODE) . "\n";
// 检查查询订单结果是否包含错误
$this->checkResultForErrors($query_result, '查询订单');
} catch (Exception $e) {
echo " - 查询订单测试失败: " . $e->getMessage() . "\n";
throw $e;
@@ -366,38 +395,28 @@ class PayModelTest
'sign_type' => 'RSA2'
];
// 使用华为支付客户端的generateSign方法生成签名与实际使用场景一致
$reflection = new ReflectionClass($huawei_pay_client);
$generateSignMethod = $reflection->getMethod('generateSign');
$generateSignMethod->setAccessible(true);
// 通过反射获取商户应用私钥证书实例
$privateKeyInstanceProperty = $reflection->getProperty('private_key_certificate_instance');
$privateKeyInstanceProperty->setAccessible(true);
$private_key_certificate_instance = $privateKeyInstanceProperty->getValue($huawei_pay_client);
// 生成签名
$sign = '';
$signParams = $callback_params;
unset($signParams['sign']);
unset($signParams['sign_type']);
ksort($signParams);
$sign = $generateSignMethod->invoke($huawei_pay_client, $callback_params, $private_key_certificate_instance, [], 'RSA2');
$callback_params['sign'] = $sign;
$stringToSign = '';
foreach ($signParams as $key => $value) {
$stringToSign .= $key . '=' . $value . '&';
}
$stringToSign = rtrim($stringToSign, '&');
// 通过反射获取华为平台支付证书实例
$reflection = new ReflectionClass($huawei_pay_client);
$huaweiPublicKeyInstanceProperty = $reflection->getProperty('huawei_public_key_certificate_instance');
$huaweiPublicKeyInstanceProperty->setAccessible(true);
$huawei_public_key_certificate_instance = $huaweiPublicKeyInstanceProperty->getValue($huawei_pay_client);
// 使用文本模式的私钥生成签名
$private_key_content = $config_data['private_key_text'];
// 对私钥进行格式化确保openssl_sign能够正确处理
$cleanContent = preg_replace('/\s+/', '', $private_key_content);
$cleanContent = preg_replace('/-----BEGIN.*?-----/', '', $cleanContent);
$cleanContent = preg_replace('/-----END.*?-----/', '', $cleanContent);
$formattedKey = "-----BEGIN PRIVATE KEY-----\n";
$length = strlen($cleanContent);
for ($i = 0; $i < $length; $i += 64) {
$formattedKey .= substr($cleanContent, $i, 64) . "\n";
}
$formattedKey .= "-----END PRIVATE KEY-----";
openssl_sign($stringToSign, $sign, $formattedKey, OPENSSL_ALGO_SHA256);
$callback_params['sign'] = base64_encode($sign);
// 使用华为支付客户端验证签名
$verify_result = $huawei_pay_client->verifySign($callback_params);
// 使用华为支付客户端验证签名 - 使用华为平台支付证书实例
$verify_result = $huawei_pay_client->verifySign($callback_params, $huawei_public_key_certificate_instance);
if ($verify_result) {
echo " - 回调签名验证通过\n";
@@ -406,12 +425,220 @@ class PayModelTest
} else {
throw new Exception("回调签名验证失败");
}
} catch (Exception $e) {
echo " - 回调验证测试失败: " . $e->getMessage() . "\n";
throw $e;
}
}
/**
* 测试签名生成和验签方法
* 特指,用于测试商户应用公钥和私钥的签名生成和验签
*/
protected function testSignAndVerify()
{
require_once dirname(__DIR__) . '/data/sdk/utils.php';
require_once dirname(__DIR__) . '/data/sdk/HuaweiPayClient.php';
// 添加必要的配置
$site_id = '1';
$config_data = $this->config;
$config_data['site_id'] = $site_id;
// 使用文本模式的密钥,而不是证书文件
unset($config_data['private_key']);
unset($config_data['public_key']);
unset($config_data['huawei_public_key']);
try {
// 创建实际的华为支付客户端实例
$project_root = dirname(__DIR__, 4); // 获取项目根目录
$huawei_pay_client = new \addon\huaweipay\data\sdk\HuaweiPayClient($config_data, $project_root);
echo " - 华为支付客户端实例创建成功\n";
// 测试数据
$test_params = [
'out_trade_no' => 'TEST_SIGN_' . time(),
'subject' => '测试签名商品',
'body' => '测试签名商品描述',
'total_amount' => 0.01,
'client_ip' => '127.0.0.1',
'timestamp' => date('Y-m-d H:i:s'),
'app_id' => $config_data['app_id'],
'merc_no' => $config_data['merc_no'],
'sign_type' => 'RSA2'
];
// 直接调用签名生成方法注意generateSign是私有方法这里需要通过反射来调用
$reflection = new ReflectionClass($huawei_pay_client);
$generateSignMethod = $reflection->getMethod('generateSign');
$generateSignMethod->setAccessible(true);
// 通过反射获取商户应用私钥证书实例
$privateKeyInstanceProperty = $reflection->getProperty('private_key_certificate_instance');
$privateKeyInstanceProperty->setAccessible(true);
$private_key_certificate_instance = $privateKeyInstanceProperty->getValue($huawei_pay_client);
// 生成签名
$sign = $generateSignMethod->invoke($huawei_pay_client, $test_params, $private_key_certificate_instance, [], 'RSA2');
echo " - 签名生成成功:{$sign}\n";
// 添加签名到参数中用于验签
$test_params_with_sign = $test_params;
$test_params_with_sign['sign'] = $sign;
// 通过反射获取商户应用公钥证书实例
$reflection = new ReflectionClass($huawei_pay_client);
$publicKeyInstanceProperty = $reflection->getProperty('public_key_certificate_instance');
$publicKeyInstanceProperty->setAccessible(true);
$public_key_certificate_instance = $publicKeyInstanceProperty->getValue($huawei_pay_client);
// 测试验签 - 使用商户应用公钥证书实例
$verify_result = $huawei_pay_client->verifySign($test_params_with_sign, $public_key_certificate_instance);
if ($verify_result) {
echo " - 签名验证通过\n";
} else {
throw new Exception("签名验证失败");
}
// 测试修改参数后的验签(应该失败)
$test_params_modified = $test_params_with_sign;
$test_params_modified['total_amount'] = 0.02; // 修改金额
$verify_result_modified = $huawei_pay_client->verifySign($test_params_modified, $public_key_certificate_instance);
if (!$verify_result_modified) {
echo " - 修改参数后的验签失败(符合预期)\n";
} else {
throw new Exception("修改参数后的验签不应该通过");
}
// 测试错误签名的验签(应该失败)
$test_params_wrong_sign = $test_params;
$test_params_wrong_sign['sign'] = 'wrong_sign_value';
$verify_result_wrong = $huawei_pay_client->verifySign($test_params_wrong_sign, $public_key_certificate_instance);
if (!$verify_result_wrong) {
echo " - 错误签名的验签失败(符合预期)\n";
} else {
throw new Exception("错误签名的验签不应该通过");
}
} catch (ReflectionException $e) {
echo " - 反射调用签名方法失败: " . $e->getMessage() . "\n";
throw new Exception("签名测试失败:" . $e->getMessage());
} catch (Exception $e) {
echo " - 签名和验签测试失败: " . $e->getMessage() . "\n";
throw $e;
}
}
/**
* 检查API返回结果是否包含错误
* @param array $result API返回结果
* @param string $operation_name 操作名称(用于错误提示)
* @throws Exception 如果结果包含错误
*/
protected function checkResultForErrors($result, $operation_name)
{
// 检查是否是数组
if (!is_array($result)) {
throw new Exception("{$operation_name}响应格式错误不是有效的JSON或URL编码格式");
}
// 定义常见的错误字段
$error_fields = [
'code', 'error_code', 'return_code', 'result_code',
'sub_code', 'err_code', 'respCode', 'status'
];
// 定义常见的错误信息字段
$error_message_fields = [
'message', 'error_msg', 'return_msg', 'result_msg',
'sub_msg', 'err_msg', 'respMsg', 'error_description'
];
// 检查是否包含错误码字段
foreach ($error_fields as $error_field) {
if (isset($result[$error_field])) {
$error_value = $result[$error_field];
// 检查错误码是否表示失败
if (is_string($error_value) && in_array(strtolower($error_value), ['error', 'fail', 'failed', 'failure', 'error_code'])) {
// 获取错误信息
$error_message = "未知错误";
foreach ($error_message_fields as $msg_field) {
if (isset($result[$msg_field])) {
$error_message = $result[$msg_field];
break;
}
}
throw new Exception("{$operation_name}失败:{$error_message} (错误码: {$error_value})");
}
// 检查错误码是否是数字且不等于0或200通常表示成功
if (is_numeric($error_value) && $error_value != 0 && $error_value != 200) {
// 获取错误信息
$error_message = "未知错误";
foreach ($error_message_fields as $msg_field) {
if (isset($result[$msg_field])) {
$error_message = $result[$msg_field];
break;
}
}
throw new Exception("{$operation_name}失败:{$error_message} (错误码: {$error_value})");
}
}
}
// 检查是否包含明确的错误信息字段
foreach ($error_message_fields as $msg_field) {
if (isset($result[$msg_field])) {
$message_value = $result[$msg_field];
if (is_string($message_value) &&
(strpos(strtolower($message_value), 'error') !== false ||
strpos(strtolower($message_value), 'fail') !== false ||
strpos(strtolower($message_value), '无效') !== false ||
strpos(strtolower($message_value), '错误') !== false ||
strpos(strtolower($message_value), 'failed') !== false ||
strpos(strtolower($message_value), 'invalid') !== false ||
strpos(strtolower($message_value), 'not found') !== false)) {
// 尝试获取错误码
$error_code = "未知错误码";
foreach ($error_fields as $error_field) {
if (isset($result[$error_field])) {
$error_code = $result[$error_field];
break;
}
}
throw new Exception("{$operation_name}失败:{$message_value} (错误码: {$error_code})");
}
}
}
// 检查是否包含404、500等HTTP错误状态码或错误信息
if (isset($result['http_status'])) {
if ($result['http_status'] >= 400) {
throw new Exception("{$operation_name}请求失败HTTP状态码 {$result['http_status']}");
}
}
// 检查响应是否是HTML错误页面
if (isset($result[0]) && is_string($result[0])) {
if (strpos(strtolower($result[0]), '<html') !== false ||
strpos(strtolower($result[0]), '<head') !== false ||
strpos(strtolower($result[0]), '<body') !== false) {
// 简单提取错误信息
$error_message = "HTML错误页面";
if (preg_match('/<title>(.*?)<\/title>/i', $result[0], $matches)) {
$error_message = $matches[1];
}
throw new Exception("{$operation_name}请求失败返回了HTML错误页面 - {$error_message}");
}
}
echo " - {$operation_name}结果验证通过,未发现错误\n";
}
}
// 运行测试

View File

@@ -1,3 +1,40 @@
-----BEGIN PRIVATE KEY-----
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQD2DX5Byq8Lh/WlSLAnW6bWIFSU2fqFEMms12/nMHvuXkM2jpLqETjDtXKGQyeQiTz2LyRN6PWbUHKNGmjsHk4+pp82oj0kFDJDboDVmnvXNJ979SJwAWIy0XiVY2cAcFgfTqxRO/JvBiWUNXJumLqUvaLxUAYg3xiCgrfqU9pizFZn3XWU8J6DLsHeQOPXGEF7jVw0eC7zBiaOotTueOV5jk+x6F6vH4pvnxpC8HnFCvzDsqADIyux9EffQG2mD9lZGfyo5d38Nbp4SVrd2NqHi6DyMp2LSpSQDbgUsN05lJPi9vmqGBANVemomFiYEhTXhg4ewJf06W7pvsMONCj9285pje/NZE59X6xXrR9RUimzxXLUSlL9Rd4dTGDqvftTxYDFRtJXx3p7hXKUu6IiBtfkmlILWzuOr9zBTBd9ImYrNoKP4L4sn3nwgzXsVjEjPnFYm1kTNaw8AftZUI/dvWbgwdT9TAa4y78Z93O6yev5C1qfpaqf0CpTIk/8l+sCAwEAAQKCAYAVlJFiS9iWdlJBMOLiUNONLEC+3W9vhE1r72lNKZ91BKd4fYC9Ls1/vMZSqEksEB1cqj3Q54HDIYcqgQp6yx2puQt1yzz5kRvndiWulmIOOftS7+kZUcW/F0gwMguyqifQdyH97fgRbMSW/ykOMi8LJKbJ627eKzMHH1fqIXih+bIKYg4SBhihANTYHXDeSK5Vm8xefbwAbKWtFPMAB3J4+tZakDrduTJ3H8k53cWQVqpcr6oBHHCUpww2tHvpeLI3a/FXyHYBqrx8ErnXCjkVBHBtwQf+43H+buyDjrYUwJUi3RSJgeefcyyJoO0I9GwGb6nY8kK5kZ0aeIIkfiVOexMYS9w11FVl96LjC7HjJQrNY4jOLI/X76xyEwBFy9LNogRTafjZVZmVRj/9Kembx6+/eDxvyEv5FnelsHqfFbv1KPkR6e7FvwpDbYgJBpfKTE6SICqo52bHxewpSL7KHRlTpp0IfMM/IOOs8CCwI37ixKQun6W4en457j/tP4ECgcEA+0pa3omCKJfiVR4PpyXY+t9FEqdcZkdYEDtCSX9vqu6yE4sdt7rNnZ6cIiD6ViC2xvQ5VLry1y57JvP57svt5OJYhmILuj3jJ7pdcfKRAVaV4W7C8vGMit6ssckEOnOSj/bvEazfHorjXKU9cn9uCymczRQC9azaRkN+1LLk2mBbczghxd7rSJyXtbt9NVNUi9Gr11AzJsrWN/Bqqna5BizBLIaGYthJ/04LYTl1AjWPEJXjXI72/WvMdQ/w6itxAoHBAPqqAi5mbvjRDitmLQchc/R3Yl329OaB3GSz3YtFqfk9lLJIyl91+MzxBg1ChHnRFC8MB1s1Z05ZkFmcy8AVqvJ6MSZhUCLNjq8HyVgp24CqQTr8ciomF03ncXnKKSsH7tidInkQ8R3e7qRRGQMnDvV+Hs3FjlOk8vJyZCcGkUCVA0kkwkk/95qIfrZsthtewAxJR/rY63FnG+MUayEBB1lwyJZ7xDi/GyX6SAnPBl6bLRA1BdBeTV8K0MEwwIqzGwKBwBqo+9UKT7XQz2FqbAy2tjt/fouJGAN95DjsoI69p3JCGsB6DPAWMIRddIEmcIi8tceL151GrEbqFoS+c7DDD/0timjPdCEROc1YN1vEeV/j+MjPAH3X5KpDD51ZD0rIQi9l6l08svtBjvegTFGedWVXx9v2GI5KBWpY9NbKF/+XI3yo4uRkTyAIBQxx1MnYimq/FvUj/BlMgceziQ2GxQCDtQbtSsqn2cntVMW+28wdNI106YdDX67pRerRgyTE8QKBwHAwLxG9XuWWC5V5AaYzXsaHuEr+ANY6QP4BUqLG5zBaU3cIBSt8jYKMTX0ZzFkJLtNvussjt7zlcSnqd3bdO8mSzvSykT9CaR4FiiQfd9K6YL+ZxS8AJWYEtFEiHhLYVho1Gfy9jG0mHgEFGwDCNnvBmt/WD8F4DhRdBl5BHjmdd/8AqMRIEPXlKXFUbp0JZ0MYeVLYS2hSEbUsqlX3M+bgB6bydfw/7FKvFhbtxZgKM70RPizoSBDFsnEE9OgfCQKBwQD4M4f2f678YScvrwQnV2J8SSnJhQbZqht1Rlb34zU0JIcfpwOKpoNvRyZz1BxvzPSm5S3TLDytK16uvAsMFCHVByyCajPozlWnLzeEqvE4DZXZcWeY+/+MZ4nsajLTTeJWos6UrazZcd8/2c301x4OUKfgZWW1tS65MA48X0+Y4uPyOuH365wQ0obBfg4aB85sZl71v7Rveq+COHBstp/BdDVAsdufWCr7Nkbeu/3qh886ZumpYe+89So1oDIuoys=
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDVJViQP+5vjQoj
NaVZ+9rckiSXcsdwKL2EAPnY/2L39/sD0swWIAZu5pZL/Msjd6Phq249Fr3nC3pL
zJl4L2kx5BrqUA2IrQiaV+LNO2OgBm2UKxbYOIe+Q0gF6D+Qujh79OD7xPdnX2ym
sLBqhfMy4Q6/ywx/wy+330+Skwc74TduLUZijazdSNYEOipFafcm4Z4Zhz4P52be
kajsdFctiwfI46e9bggKnTOi5P83ne8WtutnJwKUm9riRa3q/EHFOZ/M0nM1tZSv
YGeoC7m89IoCsvuMATFrFkEUbJwg0ksc7FDh4jhhXEZq1JH+z1VZIYbm0Fdn6N1D
1fIDmdInaOa/8kImfKKfQjgSN7h0cKckxONOWf1zfC75Fy+8ba6f1j+ZI8IEfHkr
nwBJc0hm3oWnew7HFILlI6hshGMLFtuxp3FeYEZNZqKb8GtyhwXNd9YqkMHq5R/y
w4YYw7t4ccMFA97J3YyN37y0wxOUE2o8u+UOPOPSSNB39oKzK3ECAwEAAQKCAYAB
DlBOq/e9wbsdIDTo1Is/BdJR6xWl2UMnFkImmw1IdogIetDcAzNE9WDP5jleEZz+
5InamkmSjXIA/Msoge5Jevx50K+1z6BNSMYs3MXKPdM+eqrVo2IOGpM5bu8cWRp9
MrddR+/Z0+qGi4re3DmviiyNMq+l1Q0KzNETbtBdsZNEDnayIkrgRcNu/e1xA+y0
2pqdPG2dnTiF6Gl9J7kO1rN71p+MHAbE1rEyILmgSD4DpMsBwHsOIBTeLhjbIyjF
79X6VCnR1SM24i42iBCSuv/KkTWWoCk9bp2FBLjVovm1WgXuo8eLhH0RRWSd/2Zm
U3d2XKllaY3xquCnymImp2QdFHvEhUcM/UJvPL6DOxieVfhrQ5/c18hJCRE91WEs
wceMdiXEDyewV8KO7KZ0sfyuPQnxdOMg3y7Mlk4RS7kmBe151GdjlTeVKN7RiKL3
pjRYBkMhWnG2+gzUAJHZ7Tun6ye4T6Nc7S6RznXrGS4sIxT3p7XIow3RaXM9tmEC
gcEA8c5k5M6elY1h3JNi6bTaBTB6yX9gCcg43rfkognFnDYSo8eksd6c7mfQzG17
R6ARhZvn0l+k73BnJzEUvD8h6r0tNLyGV+OBjaYjpNKT4n9JlP8PtJ36eZvrZ2qY
tilCtHqVJaE4KMlF3a/J+0Y8NK2udG4cyCDVAZ4W7Xt/0knyzNG7ZH9+swi+ZHye
3Obb2/b0QJHN6W+SKepMkPWygL5L8IiNobCeJFzN81M13FVBeDre92UxPb1hUqcO
vsXFAoHBAOGoRldYWJJCtSwdoeHdoAK/cVbmvMGACr3lO55aBH5zvChm8hqwZ/ZY
ZpBfSrYep+tISQFrGwUgIZE4UoBbyS1DCZl1Mld/tZ9LlNL4huO60Jrfg8boVJyW
Tn0obdZ3quRMLjQkylovdOIU4BT6BVpRZgDOxfmQ6w5hjoYMk6Kf4K7k0xRBl8cY
orydWBflKCQYR7aHPeb8VXgUx2teLvMqmFcgqV0/li41AW0LJ6iu9td9hkvtKq84
BR6HLzoVvQKBwQCGYDBphvlSGtO7hJx/S+Ws5JgXyhVQ2lfgTUldUxjiCpqpMZUw
04laY48BTAYqc6XJHi7iFgzOkyR//yKKynrKvH6Ww/3UcbXZRe6g9fUA9cOqbWQI
95HuatuTCQX8lUlgio6/7qwK3m9ZFwuj/BARJkPAqECOZtB4qaYMK4mJD1dCw3rk
CoYacE1+AlM2Z4Te5IowJ9bKVxpe+Lm8BQ5BxX7mGLy8ki4P/xLkmeVgoHBVpEGs
Miv/J3MiP3xOQBECgcBJh5acIYqC5j+4x1MXkSrxmkYpRfhdqQZXCFob6EMsnTPD
in/7WF4X9Ig/JP3oSCb5UM2RK8BbNDQb6NlhQAdWu1Bmc7zarB+Xb35oHFl/3UDa
Jlj/OHdRqtyahdD8oHPL/lLT5OUW6/3PKXQ3AZ065+kv/DCgeKtPPVS+s1xsEmKZ
OrDZGeKGGq30feTVrHMfB9ZeUMVH/hMvlL3Ca6cqz0dipJ7rZqveFKGJUagznAeN
Ajb/iKFvBPcOEoi+CZUCgcAvlG2GBWh50e7QjT99WEZdehJpmYH9QDUwWYrAUAf2
Tl8OI+9fLAGLEe2kQ7jcdawt7vo7DuVzpdeqzMY38sHjGDrnIbWectzECGQto/dv
YNU1e+41ZFOK07t4vXxsbgIYRNm8v4mq133+qb/rP8l+Cc520vIcKXbeV2JMFnJk
2gdx4y2RHebItrlZ9zhb/1aP497PVpD2vSA+iD8ROG5qq0E9pxa2QDFpltFemdwK
45JeOYbQ9SAlEqNSMAzBKEE=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA1SVYkD/ub40KIzWlWfva
3JIkl3LHcCi9hAD52P9i9/f7A9LMFiAGbuaWS/zLI3ej4atuPRa95wt6S8yZeC9p
MeQa6lANiK0ImlfizTtjoAZtlCsW2DiHvkNIBeg/kLo4e/Tg+8T3Z19sprCwaoXz
MuEOv8sMf8Mvt99PkpMHO+E3bi1GYo2s3UjWBDoqRWn3JuGeGYc+D+dm3pGo7HRX
LYsHyOOnvW4ICp0zouT/N53vFrbrZycClJva4kWt6vxBxTmfzNJzNbWUr2BnqAu5
vPSKArL7jAExaxZBFGycINJLHOxQ4eI4YVxGatSR/s9VWSGG5tBXZ+jdQ9XyA5nS
J2jmv/JCJnyin0I4Eje4dHCnJMTjTln9c3wu+RcvvG2un9Y/mSPCBHx5K58ASXNI
Zt6Fp3sOxxSC5SOobIRjCxbbsadxXmBGTWaim/BrcocFzXfWKpDB6uUf8sOGGMO7
eHHDBQPeyd2Mjd+8tMMTlBNqPLvlDjzj0kjQd/aCsytxAgMBAAE=
-----END PUBLIC KEY-----

View File

@@ -6,6 +6,13 @@ sandbox: true
# 商户应用ID例如快应用
app_id: 115644647
# 应用名称
app_name: 秸秆粉碎机
# 应用包名
app_package_name: com.jieganfsj.fivegshop
# 商户号
merc_no: 102751500028
@@ -18,17 +25,71 @@ private_key: mock/cert/merchant_private_key.pem
# 商户应用私有密钥(支付私钥) - 文本内容
private_key_text: |
-----BEGIN PRIVATE KEY-----
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQD2DX5Byq8Lh/WlSLAnW6bWIFSU2fqFEMms12/nMHvuXkM2jpLqETjDtXKGQyeQiTz2LyRN6PWbUHKNGmjsHk4+pp82oj0kFDJDboDVmnvXNJ979SJwAWIy0XiVY2cAcFgfTqxRO/JvBiWUNXJumLqUvaLxUAYg3xiCgrfqU9pizFZn3XWU8J6DLsHeQOPXGEF7jVw0eC7zBiaOotTueOV5jk+x6F6vH4pvnxpC8HnFCvzDsqADIyux9EffQG2mD9lZGfyo5d38Nbp4SVrd2NqHi6DyMp2LSpSQDbgUsN05lJPi9vmqGBANVemomFiYEhTXhg4ewJf06W7pvsMONCj9285pje/NZE59X6xXrR9RUimzxXLUSlL9Rd4dTGDqvftTxYDFRtJXx3p7hXKUu6IiBtfkmlILWzuOr9zBTBd9ImYrNoKP4L4sn3nwgzXsVjEjPnFYm1kTNaw8AftZUI/dvWbgwdT9TAa4y78Z93O6yev5C1qfpaqf0CpTIk/8l+sCAwEAAQKCAYAVlJFiS9iWdlJBMOLiUNONLEC+3W9vhE1r72lNKZ91BKd4fYC9Ls1/vMZSqEksEB1cqj3Q54HDIYcqgQp6yx2puQt1yzz5kRvndiWulmIOOftS7+kZUcW/F0gwMguyqifQdyH97fgRbMSW/ykOMi8LJKbJ627eKzMHH1fqIXih+bIKYg4SBhihANTYHXDeSK5Vm8xefbwAbKWtFPMAB3J4+tZakDrduTJ3H8k53cWQVqpcr6oBHHCUpww2tHvpeLI3a/FXyHYBqrx8ErnXCjkVBHBtwQf+43H+buyDjrYUwJUi3RSJgeefcyyJoO0I9GwGb6nY8kK5kZ0aeIIkfiVOexMYS9w11FVl96LjC7HjJQrNY4jOLI/X76xyEwBFy9LNogRTafjZVZmVRj/9Kembx6+/eDxvyEv5FnelsHqfFbv1KPkR6e7FvwpDbYgJBpfKTE6SICqo52bHxewpSL7KHRlTpp0IfMM/IOOs8CCwI37ixKQun6W4en457j/tP4ECgcEA+0pa3omCKJfiVR4PpyXY+t9FEqdcZkdYEDtCSX9vqu6yE4sdt7rNnZ6cIiD6ViC2xvQ5VLry1y57JvP57svt5OJYhmILuj3jJ7pdcfKRAVaV4W7C8vGMit6ssckEOnOSj/bvEazfHorjXKU9cn9uCymczRQC9azaRkN+1LLk2mBbczghxd7rSJyXtbt9NVNUi9Gr11AzJsrWN/Bqqna5BizBLIaGYthJ/04LYTl1AjWPEJXjXI72/WvMdQ/w6itxAoHBAPqqAi5mbvjRDitmLQchc/R3Yl329OaB3GSz3YtFqfk9lLJIyl91+MzxBg1ChHnRFC8MB1s1Z05ZkFmcy8AVqvJ6MSZhUCLNjq8HyVgp24CqQTr8ciomF03ncXnKKSsH7tidInkQ8R3e7qRRGQMnDvV+Hs3FjlOk8vJyZCcGkUCVA0kkwkk/95qIfrZsthtewAxJR/rY63FnG+MUayEBB1lwyJZ7xDi/GyX6SAnPBl6bLRA1BdBeTV8K0MEwwIqzGwKBwBqo+9UKT7XQz2FqbAy2tjt/fouJGAN95DjsoI69p3JCGsB6DPAWMIRddIEmcIi8tceL151GrEbqFoS+c7DDD/0timjPdCEROc1YN1vEeV/j+MjPAH3X5KpDD51ZD0rIQi9l6l08svtBjvegTFGedWVXx9v2GI5KBWpY9NbKF/+XI3yo4uRkTyAIBQxx1MnYimq/FvUj/BlMgceziQ2GxQCDtQbtSsqn2cntVMW+28wdNI106YdDX67pRerRgyTE8QKBwHAwLxG9XuWWC5V5AaYzXsaHuEr+ANY6QP4BUqLG5zBaU3cIBSt8jYKMTX0ZzFkJLtNvussjt7zlcSnqd3bdO8mSzvSykT9CaR4FiiQfd9K6YL+ZxS8AJWYEtFEiHhLYVho1Gfy9jG0mHgEFGwDCNnvBmt/WD8F4DhRdBl5BHjmdd/8AqMRIEPXlKXFUbp0JZ0MYeVLYS2hSEbUsqlX3M+bgB6bydfw/7FKvFhbtxZgKM70RPizoSBDFsnEE9OgfCQKBwQD4M4f2f678YScvrwQnV2J8SSnJhQbZqht1Rlb34zU0JIcfpwOKpoNvRyZz1BxvzPSm5S3TLDytK16uvAsMFCHVByyCajPozlWnLzeEqvE4DZXZcWeY+/+MZ4nsajLTTeJWos6UrazZcd8/2c301x4OUKfgZWW1tS65MA48X0+Y4uPyOuH365wQ0obBfg4aB85sZl71v7Rveq+COHBstp/BdDVAsdufWCr7Nkbeu/3qh886ZumpYe+89So1oDIuoys=
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDVJViQP+5vjQoj
NaVZ+9rckiSXcsdwKL2EAPnY/2L39/sD0swWIAZu5pZL/Msjd6Phq249Fr3nC3pL
zJl4L2kx5BrqUA2IrQiaV+LNO2OgBm2UKxbYOIe+Q0gF6D+Qujh79OD7xPdnX2ym
sLBqhfMy4Q6/ywx/wy+330+Skwc74TduLUZijazdSNYEOipFafcm4Z4Zhz4P52be
kajsdFctiwfI46e9bggKnTOi5P83ne8WtutnJwKUm9riRa3q/EHFOZ/M0nM1tZSv
YGeoC7m89IoCsvuMATFrFkEUbJwg0ksc7FDh4jhhXEZq1JH+z1VZIYbm0Fdn6N1D
1fIDmdInaOa/8kImfKKfQjgSN7h0cKckxONOWf1zfC75Fy+8ba6f1j+ZI8IEfHkr
nwBJc0hm3oWnew7HFILlI6hshGMLFtuxp3FeYEZNZqKb8GtyhwXNd9YqkMHq5R/y
w4YYw7t4ccMFA97J3YyN37y0wxOUE2o8u+UOPOPSSNB39oKzK3ECAwEAAQKCAYAB
DlBOq/e9wbsdIDTo1Is/BdJR6xWl2UMnFkImmw1IdogIetDcAzNE9WDP5jleEZz+
5InamkmSjXIA/Msoge5Jevx50K+1z6BNSMYs3MXKPdM+eqrVo2IOGpM5bu8cWRp9
MrddR+/Z0+qGi4re3DmviiyNMq+l1Q0KzNETbtBdsZNEDnayIkrgRcNu/e1xA+y0
2pqdPG2dnTiF6Gl9J7kO1rN71p+MHAbE1rEyILmgSD4DpMsBwHsOIBTeLhjbIyjF
79X6VCnR1SM24i42iBCSuv/KkTWWoCk9bp2FBLjVovm1WgXuo8eLhH0RRWSd/2Zm
U3d2XKllaY3xquCnymImp2QdFHvEhUcM/UJvPL6DOxieVfhrQ5/c18hJCRE91WEs
wceMdiXEDyewV8KO7KZ0sfyuPQnxdOMg3y7Mlk4RS7kmBe151GdjlTeVKN7RiKL3
pjRYBkMhWnG2+gzUAJHZ7Tun6ye4T6Nc7S6RznXrGS4sIxT3p7XIow3RaXM9tmEC
gcEA8c5k5M6elY1h3JNi6bTaBTB6yX9gCcg43rfkognFnDYSo8eksd6c7mfQzG17
R6ARhZvn0l+k73BnJzEUvD8h6r0tNLyGV+OBjaYjpNKT4n9JlP8PtJ36eZvrZ2qY
tilCtHqVJaE4KMlF3a/J+0Y8NK2udG4cyCDVAZ4W7Xt/0knyzNG7ZH9+swi+ZHye
3Obb2/b0QJHN6W+SKepMkPWygL5L8IiNobCeJFzN81M13FVBeDre92UxPb1hUqcO
vsXFAoHBAOGoRldYWJJCtSwdoeHdoAK/cVbmvMGACr3lO55aBH5zvChm8hqwZ/ZY
ZpBfSrYep+tISQFrGwUgIZE4UoBbyS1DCZl1Mld/tZ9LlNL4huO60Jrfg8boVJyW
Tn0obdZ3quRMLjQkylovdOIU4BT6BVpRZgDOxfmQ6w5hjoYMk6Kf4K7k0xRBl8cY
orydWBflKCQYR7aHPeb8VXgUx2teLvMqmFcgqV0/li41AW0LJ6iu9td9hkvtKq84
BR6HLzoVvQKBwQCGYDBphvlSGtO7hJx/S+Ws5JgXyhVQ2lfgTUldUxjiCpqpMZUw
04laY48BTAYqc6XJHi7iFgzOkyR//yKKynrKvH6Ww/3UcbXZRe6g9fUA9cOqbWQI
95HuatuTCQX8lUlgio6/7qwK3m9ZFwuj/BARJkPAqECOZtB4qaYMK4mJD1dCw3rk
CoYacE1+AlM2Z4Te5IowJ9bKVxpe+Lm8BQ5BxX7mGLy8ki4P/xLkmeVgoHBVpEGs
Miv/J3MiP3xOQBECgcBJh5acIYqC5j+4x1MXkSrxmkYpRfhdqQZXCFob6EMsnTPD
in/7WF4X9Ig/JP3oSCb5UM2RK8BbNDQb6NlhQAdWu1Bmc7zarB+Xb35oHFl/3UDa
Jlj/OHdRqtyahdD8oHPL/lLT5OUW6/3PKXQ3AZ065+kv/DCgeKtPPVS+s1xsEmKZ
OrDZGeKGGq30feTVrHMfB9ZeUMVH/hMvlL3Ca6cqz0dipJ7rZqveFKGJUagznAeN
Ajb/iKFvBPcOEoi+CZUCgcAvlG2GBWh50e7QjT99WEZdehJpmYH9QDUwWYrAUAf2
Tl8OI+9fLAGLEe2kQ7jcdawt7vo7DuVzpdeqzMY38sHjGDrnIbWectzECGQto/dv
YNU1e+41ZFOK07t4vXxsbgIYRNm8v4mq133+qb/rP8l+Cc520vIcKXbeV2JMFnJk
2gdx4y2RHebItrlZ9zhb/1aP497PVpD2vSA+iD8ROG5qq0E9pxa2QDFpltFemdwK
45JeOYbQ9SAlEqNSMAzBKEE=
-----END PRIVATE KEY-----
# 商户应用公钥(支付公钥) - 文件路径
public_key: mock/cert/merchant_public_key.pem
# 商户应用公钥(支付公钥) - 文本内容
public_key_text: |
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA1SVYkD/ub40KIzWlWfva
3JIkl3LHcCi9hAD52P9i9/f7A9LMFiAGbuaWS/zLI3ej4atuPRa95wt6S8yZeC9p
MeQa6lANiK0ImlfizTtjoAZtlCsW2DiHvkNIBeg/kLo4e/Tg+8T3Z19sprCwaoXz
MuEOv8sMf8Mvt99PkpMHO+E3bi1GYo2s3UjWBDoqRWn3JuGeGYc+D+dm3pGo7HRX
LYsHyOOnvW4ICp0zouT/N53vFrbrZycClJva4kWt6vxBxTmfzNJzNbWUr2BnqAu5
vPSKArL7jAExaxZBFGycINJLHOxQ4eI4YVxGatSR/s9VWSGG5tBXZ+jdQ9XyA5nS
J2jmv/JCJnyin0I4Eje4dHCnJMTjTln9c3wu+RcvvG2un9Y/mSPCBHx5K58ASXNI
Zt6Fp3sOxxSC5SOobIRjCxbbsadxXmBGTWaim/BrcocFzXfWKpDB6uUf8sOGGMO7
eHHDBQPeyd2Mjd+8tMMTlBNqPLvlDjzj0kjQd/aCsytxAgMBAAE=
-----END PUBLIC KEY-----
# 华为支付公钥 - 文件路径
huawei_public_key: mock/cert/huawei_public_key.pem
# 华为支付公钥 - 文本内容
huawei_public_key_text: |
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA9g1+QcqvC4f1pUiwJ1um1iBUlNn6hRDJrNdv5zB77l5DNo6S6hE4w7VyhkMnkIk89i8kTej1m1ByjRpo7B5OPqafNqI9JBQyQ26A1Zp71zSfe/UicAFiMtF4lWNnAHBYH06sUTvybwYllDVybpi6lL2i8VAGIN8YgoK36lPaYsxWZ911lPCegy7B3kDj1xhBe41cNHgu8wYmjqLU7njleY5Pseherx+Kb58aQvB5xQr8w7KgAyMrsfRH30Btpg/ZWRn8qOXd/DW6eEla3djah4ug8jKdi0qUkA24FLDdOZST4vb5qhgQDVXpqJhYmBIU14YOHsCX9Olu6b7DDjQo/dvOaY3vzWROfV+sV60fUVIps8Vy1EpS/UXeHUxg6r37U8WAxUbSV8d6e4VylLuiIgbX5JpSC1s7jq/cwUwXfSJmKzaCj+C+LJ958IM17FYxIz5xWJtZEzWsPAH7WVCP3b1m4MHU/UwGuMu/Gfdzusnr+Qtan6Wqn9AqUyJP/JfrAgMBAAE=
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA3mVwGyLFfV9H7K0oa3CJdAqMg17vatapR9mlzg1XAwBmyY+5jUkREktQys6fMkVQExYfdFJ9sFBZCFXDt/2tjRcY1N91WC4zMhnoD4QpOKdkDQyf/sbEtqKtIMdP8YG19DFmrz7uJt58yX4awNrIaZYD8bOYTLFm+/9uYQSUd+eJ6IOJMCDkqS9hVS8XdPD9o2nRH35N6QCNPJu/2/PzQTXu7XWI5EhrTfQSYdOI7NLm7IoynVFRp8mkuKYL6ipjyU22TxKNeXSUjqqQka1Hmy5yuYV1GsjcV1FjKkijwekZaAthD6NFuhWC/zNFz24yCkb3/ANKJ/Vn1U/BfzxHEdv3QxQeQVeRn9L021rOWEjz6iME8TDWVOKhAlwxPKNrd4saWDFCiytqEXMtDPkaytSB7UGSTo1tZn4Kku6HRpmUuNEvYi07CEgkZUAAuibqnA3lTKesFmrT5P7GSU57E9Uw/31CLw6OpUYUpSDTJudZfulK+XSt0pRbPekSisKRAgMBAAE=
-----END PUBLIC KEY-----
notifyUrl: https://dev.aigc-quickapp.com/pay/pay/notify.html
returnUrl: https://dev.aigc-quickapp.com/pay/pay/payreturn.html
notifyUrl: https://dev.aigc-quickapp.com/pay/pay/notify.html?pay_type=huaweipay&site_id=1
returnUrl: https://dev.aigc-quickapp.com/pay/pay/payreturn.html?pay_type=huaweipay&site_id=1

View File

@@ -38,7 +38,8 @@
"topthink/think-queue": "^3.0",
"lizhichao/word": "^2.1",
"yunwuxin/think-cron": "^3.0",
"ezyang/htmlpurifier": "^4.16"
"ezyang/htmlpurifier": "^4.16",
"phpseclib/phpseclib": "^3.0"
},
"require-dev": {
"symfony/var-dumper": "4.4.41",

229
src/composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "68f8acafaf19fcf72bc834ac649ff496",
"content-hash": "b209e315899a06512f51b001ad57da9d",
"packages": [
{
"name": "aliyuncs/oss-sdk-php",
@@ -1897,6 +1897,123 @@
"abandoned": "w7corp/easywechat",
"time": "2021-05-18T11:57:12+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v2.8.2",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/e30811f7bc69e4b5b6d5783e712c06c8eabf0226",
"reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226",
"shasum": ""
},
"require": {
"php": "^7|^8"
},
"require-dev": {
"phpunit/phpunit": "^6|^7|^8|^9",
"vimeo/psalm": "^1|^2|^3|^4"
},
"type": "library",
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base16",
"base32",
"base32_decode",
"base32_encode",
"base64",
"base64_decode",
"base64_encode",
"bin2hex",
"encoding",
"hex",
"hex2bin",
"rfc4648"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"time": "2025-09-24T15:12:37+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "php-curl-class/php-curl-class",
"version": "8.9.3",
@@ -2214,6 +2331,116 @@
},
"time": "2022-07-18T19:50:48+00:00"
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.47",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d",
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d",
"shasum": ""
},
"require": {
"paragonie/constant_time_encoding": "^1|^2|^3",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
"php": ">=5.6.1"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"suggest": {
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.47"
},
"funding": [
{
"url": "https://github.com/terrafrost",
"type": "github"
},
{
"url": "https://www.patreon.com/phpseclib",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
"type": "tidelift"
}
],
"time": "2025-10-06T01:07:24+00:00"
},
{
"name": "pimple/pimple",
"version": "v3.5.0",

View File

@@ -23,6 +23,7 @@ return array(
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'f0e7e63bbb278a92db02393536748c5f' => $vendorDir . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
'6747f579ad6817f318cc3a7e7a0abb93' => $vendorDir . '/overtrue/wechat/src/Kernel/Helpers.php',
'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php',
'cc56288302d9df745d97c934d6a6e5f0' => $vendorDir . '/topthink/think-queue/src/common.php',

View File

@@ -13,6 +13,7 @@ return array(
'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'),
'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-queue/src', $vendorDir . '/topthink/think-template/src'),
'thans\\filesystem\\' => array($vendorDir . '/thans/thinkphp-filesystem-cloud/src'),
'phpseclib3\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src'),
'liliuwei\\think\\' => array($vendorDir . '/liliuwei/thinkphp-jump/src'),
'extend\\' => array($baseDir . '/extend'),
@@ -48,6 +49,7 @@ return array(
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/Prophecy'),
'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'),
'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
'PHPStan\\PhpDocParser\\' => array($vendorDir . '/phpstan/phpdoc-parser/src'),
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
'Overtrue\\Socialite\\' => array($vendorDir . '/overtrue/socialite/src'),

View File

@@ -24,6 +24,7 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
'6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'f0e7e63bbb278a92db02393536748c5f' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
'6747f579ad6817f318cc3a7e7a0abb93' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Helpers.php',
'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php',
'cc56288302d9df745d97c934d6a6e5f0' => __DIR__ . '/..' . '/topthink/think-queue/src/common.php',
@@ -45,6 +46,7 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
),
'p' =>
array (
'phpseclib3\\' => 11,
'phpDocumentor\\Reflection\\' => 25,
),
'l' =>
@@ -107,6 +109,7 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
'Psr\\Cache\\' => 10,
'Prophecy\\' => 9,
'PhpOffice\\PhpSpreadsheet\\' => 25,
'ParagonIE\\ConstantTime\\' => 23,
'PHPStan\\PhpDocParser\\' => 21,
'PHPMailer\\PHPMailer\\' => 20,
),
@@ -198,6 +201,10 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
array (
0 => __DIR__ . '/..' . '/thans/thinkphp-filesystem-cloud/src',
),
'phpseclib3\\' =>
array (
0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
),
'phpDocumentor\\Reflection\\' =>
array (
0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
@@ -341,6 +348,10 @@ class ComposerStaticInit516d2ac39a060b91610bddcc729d2cf4
array (
0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet',
),
'ParagonIE\\ConstantTime\\' =>
array (
0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src',
),
'PHPStan\\PhpDocParser\\' =>
array (
0 => __DIR__ . '/..' . '/phpstan/phpdoc-parser/src',

View File

@@ -2159,6 +2159,129 @@
"abandoned": "w7corp/easywechat",
"install-path": "../overtrue/wechat"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v2.8.2",
"version_normalized": "2.8.2.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/e30811f7bc69e4b5b6d5783e712c06c8eabf0226",
"reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226",
"shasum": ""
},
"require": {
"php": "^7|^8"
},
"require-dev": {
"phpunit/phpunit": "^6|^7|^8|^9",
"vimeo/psalm": "^1|^2|^3|^4"
},
"time": "2025-09-24T15:12:37+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base16",
"base32",
"base32_decode",
"base32_encode",
"base64",
"base64_decode",
"base64_encode",
"bin2hex",
"encoding",
"hex",
"hex2bin",
"rfc4648"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"install-path": "../paragonie/constant_time_encoding"
},
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
"version_normalized": "9.99.100.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"time": "2020-10-15T08:29:30+00:00",
"type": "library",
"installation-source": "dist",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"install-path": "../paragonie/random_compat"
},
{
"name": "phar-io/manifest",
"version": "1.0.3",
@@ -2788,6 +2911,119 @@
},
"install-path": "../phpoffice/phpspreadsheet"
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.47",
"version_normalized": "3.0.47.0",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d",
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d",
"shasum": ""
},
"require": {
"paragonie/constant_time_encoding": "^1|^2|^3",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
"php": ">=5.6.1"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"suggest": {
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"time": "2025-10-06T01:07:24+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.47"
},
"funding": [
{
"url": "https://github.com/terrafrost",
"type": "github"
},
{
"url": "https://www.patreon.com/phpseclib",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
"type": "tidelift"
}
],
"install-path": "../phpseclib/phpseclib"
},
{
"name": "phpspec/prophecy",
"version": "v1.22.0",

View File

@@ -286,6 +286,24 @@
'aliases' => array(),
'dev_requirement' => false,
),
'paragonie/constant_time_encoding' => array(
'pretty_version' => 'v2.8.2',
'version' => '2.8.2.0',
'reference' => 'e30811f7bc69e4b5b6d5783e712c06c8eabf0226',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/constant_time_encoding',
'aliases' => array(),
'dev_requirement' => false,
),
'paragonie/random_compat' => array(
'pretty_version' => 'v9.99.100',
'version' => '9.99.100.0',
'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/random_compat',
'aliases' => array(),
'dev_requirement' => false,
),
'phar-io/manifest' => array(
'pretty_version' => '1.0.3',
'version' => '1.0.3.0',
@@ -367,6 +385,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'phpseclib/phpseclib' => array(
'pretty_version' => '3.0.47',
'version' => '3.0.47.0',
'reference' => '9d6ca36a6c2dd434765b1071b2644a1c683b385d',
'type' => 'library',
'install_path' => __DIR__ . '/../phpseclib/phpseclib',
'aliases' => array(),
'dev_requirement' => false,
),
'phpspec/prophecy' => array(
'pretty_version' => 'v1.22.0',
'version' => '1.22.0.0',

View File

@@ -0,0 +1,48 @@
The MIT License (MIT)
Copyright (c) 2016 - 2022 Paragon Initiative Enterprises
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
This library was based on the work of Steve "Sc00bz" Thomas.
------------------------------------------------------------------------------
The MIT License (MIT)
Copyright (c) 2014 Steve Thomas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,84 @@
# Constant-Time Encoding
[![Build Status](https://github.com/paragonie/constant_time_encoding/actions/workflows/ci.yml/badge.svg)](https://github.com/paragonie/constant_time_encoding/actions)
[![Latest Stable Version](https://poser.pugx.org/paragonie/constant_time_encoding/v/stable)](https://packagist.org/packages/paragonie/constant_time_encoding)
[![Latest Unstable Version](https://poser.pugx.org/paragonie/constant_time_encoding/v/unstable)](https://packagist.org/packages/paragonie/constant_time_encoding)
[![License](https://poser.pugx.org/paragonie/constant_time_encoding/license)](https://packagist.org/packages/paragonie/constant_time_encoding)
[![Downloads](https://img.shields.io/packagist/dt/paragonie/constant_time_encoding.svg)](https://packagist.org/packages/paragonie/constant_time_encoding)
Based on the [constant-time base64 implementation made by Steve "Sc00bz" Thomas](https://github.com/Sc00bz/ConstTimeEncoding),
this library aims to offer character encoding functions that do not leak
information about what you are encoding/decoding via processor cache
misses. Further reading on [cache-timing attacks](http://blog.ircmaxell.com/2014/11/its-all-about-time.html).
Our fork offers the following enhancements:
* `mbstring.func_overload` resistance
* Unit tests
* Composer- and Packagist-ready
* Base16 encoding
* Base32 encoding
* Uses `pack()` and `unpack()` instead of `chr()` and `ord()`
## PHP Version Requirements
Version 2 of this library should work on **PHP 7** or newer. For PHP 5
support, see [the v1.x branch](https://github.com/paragonie/constant_time_encoding/tree/v1.x).
If you are adding this as a dependency to a project intended to work on both PHP 5 and PHP 7, please set the required version to `^1|^2` instead of just `^1` or `^2`.
## How to Install
```sh
composer require paragonie/constant_time_encoding
```
## How to Use
```php
use ParagonIE\ConstantTime\Encoding;
// possibly (if applicable):
// require 'vendor/autoload.php';
$data = random_bytes(32);
echo Encoding::base64Encode($data), "\n";
echo Encoding::base32EncodeUpper($data), "\n";
echo Encoding::base32Encode($data), "\n";
echo Encoding::hexEncode($data), "\n";
echo Encoding::hexEncodeUpper($data), "\n";
```
Example output:
```
1VilPkeVqirlPifk5scbzcTTbMT2clp+Zkyv9VFFasE=
2VMKKPSHSWVCVZJ6E7SONRY3ZXCNG3GE6ZZFU7TGJSX7KUKFNLAQ====
2vmkkpshswvcvzj6e7sonry3zxcng3ge6zzfu7tgjsx7kukfnlaq====
d558a53e4795aa2ae53e27e4e6c71bcdc4d36cc4f6725a7e664caff551456ac1
D558A53E4795AA2AE53E27E4E6C71BDCC4D36CC4F6725A7E664CAFF551456AC1
```
If you only need a particular variant, you can just reference the
required class like so:
```php
use ParagonIE\ConstantTime\Base64;
use ParagonIE\ConstantTime\Base32;
$data = random_bytes(32);
echo Base64::encode($data), "\n";
echo Base32::encode($data), "\n";
```
Example output:
```
1VilPkeVqirlPifk5scbzcTTbMT2clp+Zkyv9VFFasE=
2vmkkpshswvcvzj6e7sonry3zxcng3ge6zzfu7tgjsx7kukfnlaq====
```
## Support Contracts
If your company uses this library in their products or services, you may be
interested in [purchasing a support contract from Paragon Initiative Enterprises](https://paragonie.com/enterprise).

View File

@@ -0,0 +1,56 @@
{
"name": "paragonie/constant_time_encoding",
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base64",
"encoding",
"rfc4648",
"base32",
"base16",
"hex",
"bin2hex",
"hex2bin",
"base64_encode",
"base64_decode",
"base32_encode",
"base32_decode"
],
"license": "MIT",
"type": "library",
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"support": {
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"email": "info@paragonie.com",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"require": {
"php": "^7|^8"
},
"require-dev": {
"phpunit/phpunit": "^6|^7|^8|^9",
"vimeo/psalm": "^1|^2|^3|^4"
},
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ParagonIE\\ConstantTime\\Tests\\": "tests/"
}
}
}

View File

@@ -0,0 +1,544 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use InvalidArgumentException;
use RangeException;
use SensitiveParameter;
use TypeError;
use function pack;
use function unpack;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base32
* [A-Z][2-7]
*
* @package ParagonIE\ConstantTime
*/
abstract class Base32 implements EncoderInterface
{
/**
* Decode a Base32-encoded string into raw binary
*
* @param string $encodedString
* @param bool $strictPadding
* @return string
*/
public static function decode(
#[SensitiveParameter]
string $encodedString,
bool $strictPadding = false
): string {
return static::doDecode($encodedString, false, $strictPadding);
}
/**
* Decode an uppercase Base32-encoded string into raw binary
*
* @param string $src
* @param bool $strictPadding
* @return string
*/
public static function decodeUpper(
#[SensitiveParameter]
string $src,
bool $strictPadding = false
): string {
return static::doDecode($src, true, $strictPadding);
}
/**
* Encode into Base32 (RFC 4648)
*
* @param string $binString
* @return string
* @throws TypeError
*/
public static function encode(
#[SensitiveParameter]
string $binString
): string {
return static::doEncode($binString, false, true);
}
/**
* Encode into Base32 (RFC 4648)
*
* @param string $src
* @return string
* @throws TypeError
*/
public static function encodeUnpadded(
#[SensitiveParameter]
string $src
): string {
return static::doEncode($src, false, false);
}
/**
* Encode into uppercase Base32 (RFC 4648)
*
* @param string $src
* @return string
* @throws TypeError
*/
public static function encodeUpper(
#[SensitiveParameter]
string $src
): string {
return static::doEncode($src, true, true);
}
/**
* Encode into uppercase Base32 (RFC 4648)
*
* @param string $src
* @return string
* @throws TypeError
*/
public static function encodeUpperUnpadded(
#[SensitiveParameter]
string $src
): string {
return static::doEncode($src, true, false);
}
/**
* Uses bitwise operators instead of table-lookups to turn 5-bit integers
* into 8-bit integers.
*
* @param int $src
* @return int
*/
protected static function decode5Bits(int $src): int
{
$ret = -1;
// if ($src > 96 && $src < 123) $ret += $src - 97 + 1; // -64
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 96);
// if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
$ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 5-bit integers
* into 8-bit integers.
*
* Uppercase variant.
*
* @param int $src
* @return int
*/
protected static function decode5BitsUpper(int $src): int
{
$ret = -1;
// if ($src > 64 && $src < 91) $ret += $src - 65 + 1; // -64
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
// if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
$ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 5-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode5Bits(int $src): string
{
$diff = 0x61;
// if ($src > 25) $ret -= 72;
$diff -= ((25 - $src) >> 8) & 73;
return pack('C', $src + $diff);
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 5-bit integers.
*
* Uppercase variant.
*
* @param int $src
* @return string
*/
protected static function encode5BitsUpper(int $src): string
{
$diff = 0x41;
// if ($src > 25) $ret -= 40;
$diff -= ((25 - $src) >> 8) & 41;
return pack('C', $src + $diff);
}
/**
* @param string $encodedString
* @param bool $upper
* @return string
*/
public static function decodeNoPadding(
#[SensitiveParameter]
string $encodedString,
bool $upper = false
): string {
$srcLen = Binary::safeStrlen($encodedString);
if ($srcLen === 0) {
return '';
}
if (($srcLen & 7) === 0) {
for ($j = 0; $j < 7 && $j < $srcLen; ++$j) {
if ($encodedString[$srcLen - $j - 1] === '=') {
throw new InvalidArgumentException(
"decodeNoPadding() doesn't tolerate padding"
);
}
}
}
return static::doDecode(
$encodedString,
$upper,
true
);
}
/**
* Base32 decoding
*
* @param string $src
* @param bool $upper
* @param bool $strictPadding
* @return string
*
* @throws TypeError
*/
protected static function doDecode(
#[SensitiveParameter]
string $src,
bool $upper = false,
bool $strictPadding = false
): string {
// We do this to reduce code duplication:
$method = $upper
? 'decode5BitsUpper'
: 'decode5Bits';
// Remove padding
$srcLen = Binary::safeStrlen($src);
if ($srcLen === 0) {
return '';
}
if ($strictPadding) {
if (($srcLen & 7) === 0) {
for ($j = 0; $j < 7; ++$j) {
if ($src[$srcLen - 1] === '=') {
$srcLen--;
} else {
break;
}
}
}
if (($srcLen & 7) === 1) {
throw new RangeException(
'Incorrect padding'
);
}
} else {
$src = \rtrim($src, '=');
$srcLen = Binary::safeStrlen($src);
}
$err = 0;
$dest = '';
// Main loop (no padding):
for ($i = 0; $i + 8 <= $srcLen; $i += 8) {
/** @var array<int, int> $chunk */
$chunk = unpack('C*', Binary::safeSubstr($src, $i, 8));
/** @var int $c0 */
$c0 = static::$method($chunk[1]);
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
/** @var int $c4 */
$c4 = static::$method($chunk[5]);
/** @var int $c5 */
$c5 = static::$method($chunk[6]);
/** @var int $c6 */
$c6 = static::$method($chunk[7]);
/** @var int $c7 */
$c7 = static::$method($chunk[8]);
$dest .= pack(
'CCCCC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
(($c3 << 4) | ($c4 >> 1) ) & 0xff,
(($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff,
(($c6 << 5) | ($c7 ) ) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6 | $c7) >> 8;
}
// The last chunk, which may have padding:
if ($i < $srcLen) {
/** @var array<int, int> $chunk */
$chunk = unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
/** @var int $c0 */
$c0 = static::$method($chunk[1]);
if ($i + 6 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
/** @var int $c4 */
$c4 = static::$method($chunk[5]);
/** @var int $c5 */
$c5 = static::$method($chunk[6]);
/** @var int $c6 */
$c6 = static::$method($chunk[7]);
$dest .= pack(
'CCCC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
(($c3 << 4) | ($c4 >> 1) ) & 0xff,
(($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6) >> 8;
if ($strictPadding) {
$err |= ($c6 << 5) & 0xff;
}
} elseif ($i + 5 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
/** @var int $c4 */
$c4 = static::$method($chunk[5]);
/** @var int $c5 */
$c5 = static::$method($chunk[6]);
$dest .= pack(
'CCCC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
(($c3 << 4) | ($c4 >> 1) ) & 0xff,
(($c4 << 7) | ($c5 << 2) ) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5) >> 8;
} elseif ($i + 4 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
/** @var int $c4 */
$c4 = static::$method($chunk[5]);
$dest .= pack(
'CCC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
(($c3 << 4) | ($c4 >> 1) ) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3 | $c4) >> 8;
if ($strictPadding) {
$err |= ($c4 << 7) & 0xff;
}
} elseif ($i + 3 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
/** @var int $c3 */
$c3 = static::$method($chunk[4]);
$dest .= pack(
'CC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff
);
$err |= ($c0 | $c1 | $c2 | $c3) >> 8;
if ($strictPadding) {
$err |= ($c3 << 4) & 0xff;
}
} elseif ($i + 2 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
/** @var int $c2 */
$c2 = static::$method($chunk[3]);
$dest .= pack(
'CC',
(($c0 << 3) | ($c1 >> 2) ) & 0xff,
(($c1 << 6) | ($c2 << 1) ) & 0xff
);
$err |= ($c0 | $c1 | $c2) >> 8;
if ($strictPadding) {
$err |= ($c2 << 6) & 0xff;
}
} elseif ($i + 1 < $srcLen) {
/** @var int $c1 */
$c1 = static::$method($chunk[2]);
$dest .= pack(
'C',
(($c0 << 3) | ($c1 >> 2) ) & 0xff
);
$err |= ($c0 | $c1) >> 8;
if ($strictPadding) {
$err |= ($c1 << 6) & 0xff;
}
} else {
$dest .= pack(
'C',
(($c0 << 3) ) & 0xff
);
$err |= ($c0) >> 8;
}
}
$check = ($err === 0);
if (!$check) {
throw new RangeException(
'Base32::doDecode() only expects characters in the correct base32 alphabet'
);
}
return $dest;
}
/**
* Base32 Encoding
*
* @param string $src
* @param bool $upper
* @param bool $pad
* @return string
* @throws TypeError
*/
protected static function doEncode(
#[SensitiveParameter]
string $src,
bool $upper = false,
$pad = true
): string {
// We do this to reduce code duplication:
$method = $upper
? 'encode5BitsUpper'
: 'encode5Bits';
$dest = '';
$srcLen = Binary::safeStrlen($src);
// Main loop (no padding):
for ($i = 0; $i + 5 <= $srcLen; $i += 5) {
/** @var array<int, int> $chunk */
$chunk = unpack('C*', Binary::safeSubstr($src, $i, 5));
$b0 = $chunk[1];
$b1 = $chunk[2];
$b2 = $chunk[3];
$b3 = $chunk[4];
$b4 = $chunk[5];
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
static::$method((($b1 >> 1) ) & 31) .
static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
static::$method((($b3 >> 2) ) & 31) .
static::$method((($b3 << 3) | ($b4 >> 5)) & 31) .
static::$method( $b4 & 31);
}
// The last chunk, which may have padding:
if ($i < $srcLen) {
/** @var array<int, int> $chunk */
$chunk = unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
$b0 = $chunk[1];
if ($i + 3 < $srcLen) {
$b1 = $chunk[2];
$b2 = $chunk[3];
$b3 = $chunk[4];
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
static::$method((($b1 >> 1) ) & 31) .
static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
static::$method((($b3 >> 2) ) & 31) .
static::$method((($b3 << 3) ) & 31);
if ($pad) {
$dest .= '=';
}
} elseif ($i + 2 < $srcLen) {
$b1 = $chunk[2];
$b2 = $chunk[3];
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
static::$method((($b1 >> 1) ) & 31) .
static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
static::$method((($b2 << 1) ) & 31);
if ($pad) {
$dest .= '===';
}
} elseif ($i + 1 < $srcLen) {
$b1 = $chunk[2];
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
static::$method((($b1 >> 1) ) & 31) .
static::$method((($b1 << 4) ) & 31);
if ($pad) {
$dest .= '====';
}
} else {
$dest .=
static::$method( ($b0 >> 3) & 31) .
static::$method( ($b0 << 2) & 31);
if ($pad) {
$dest .= '======';
}
}
}
return $dest;
}
}

View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use function pack;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base32Hex
* [0-9][A-V]
*
* @package ParagonIE\ConstantTime
*/
abstract class Base32Hex extends Base32
{
/**
* Uses bitwise operators instead of table-lookups to turn 5-bit integers
* into 8-bit integers.
*
* @param int $src
* @return int
*/
protected static function decode5Bits(int $src): int
{
$ret = -1;
// if ($src > 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src - 47);
// if ($src > 0x60 && $src < 0x77) ret += $src - 0x61 + 10 + 1; // -86
$ret += (((0x60 - $src) & ($src - 0x77)) >> 8) & ($src - 86);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 5-bit integers
* into 8-bit integers.
*
* @param int $src
* @return int
*/
protected static function decode5BitsUpper(int $src): int
{
$ret = -1;
// if ($src > 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src - 47);
// if ($src > 0x40 && $src < 0x57) ret += $src - 0x41 + 10 + 1; // -54
$ret += (((0x40 - $src) & ($src - 0x57)) >> 8) & ($src - 54);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 5-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode5Bits(int $src): string
{
$src += 0x30;
// if ($src > 0x39) $src += 0x61 - 0x3a; // 39
$src += ((0x39 - $src) >> 8) & 39;
return pack('C', $src);
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 5-bit integers.
*
* Uppercase variant.
*
* @param int $src
* @return string
*/
protected static function encode5BitsUpper(int $src): string
{
$src += 0x30;
// if ($src > 0x39) $src += 0x41 - 0x3a; // 7
$src += ((0x39 - $src) >> 8) & 7;
return pack('C', $src);
}
}

View File

@@ -0,0 +1,388 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use InvalidArgumentException;
use RangeException;
use SensitiveParameter;
use SodiumException;
use TypeError;
use function extension_loaded;
use function pack;
use function rtrim;
use function sodium_base642bin;
use function sodium_bin2base64;
use function unpack;
use const SODIUM_BASE64_VARIANT_ORIGINAL;
use const SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING;
use const SODIUM_BASE64_VARIANT_URLSAFE;
use const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base64
* [A-Z][a-z][0-9]+/
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64 implements EncoderInterface
{
/**
* Encode into Base64
*
* Base64 character set "[A-Z][a-z][0-9]+/"
*
* @param string $binString
* @return string
*
* @throws TypeError
*/
public static function encode(
#[SensitiveParameter]
string $binString
): string {
if (extension_loaded('sodium')) {
switch (static::class) {
case Base64::class:
$variant = SODIUM_BASE64_VARIANT_ORIGINAL;
break;
case Base64UrlSafe::class:
$variant = SODIUM_BASE64_VARIANT_URLSAFE;
break;
default:
$variant = 0;
}
if ($variant > 0) {
try {
return sodium_bin2base64($binString, $variant);
} catch (SodiumException $ex) {
throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
}
}
}
return static::doEncode($binString, true);
}
/**
* Encode into Base64, no = padding
*
* Base64 character set "[A-Z][a-z][0-9]+/"
*
* @param string $src
* @return string
*
* @throws TypeError
*/
public static function encodeUnpadded(
#[SensitiveParameter]
string $src
): string {
if (extension_loaded('sodium')) {
switch (static::class) {
case Base64::class:
$variant = SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING;
break;
case Base64UrlSafe::class:
$variant = SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING;
break;
default:
$variant = 0;
}
if ($variant > 0) {
try {
return sodium_bin2base64($src, $variant);
} catch (SodiumException $ex) {
throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
}
}
}
return static::doEncode($src, false);
}
/**
* @param string $src
* @param bool $pad Include = padding?
* @return string
*
* @throws TypeError
*/
protected static function doEncode(
#[SensitiveParameter]
string $src,
bool $pad = true
): string {
$dest = '';
$srcLen = Binary::safeStrlen($src);
// Main loop (no padding):
for ($i = 0; $i + 3 <= $srcLen; $i += 3) {
/** @var array<int, int> $chunk */
$chunk = unpack('C*', Binary::safeSubstr($src, $i, 3));
$b0 = $chunk[1];
$b1 = $chunk[2];
$b2 = $chunk[3];
$dest .=
static::encode6Bits( $b0 >> 2 ) .
static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) .
static::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) .
static::encode6Bits( $b2 & 63);
}
// The last chunk, which may have padding:
if ($i < $srcLen) {
/** @var array<int, int> $chunk */
$chunk = unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
$b0 = $chunk[1];
if ($i + 1 < $srcLen) {
$b1 = $chunk[2];
$dest .=
static::encode6Bits($b0 >> 2) .
static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) .
static::encode6Bits(($b1 << 2) & 63);
if ($pad) {
$dest .= '=';
}
} else {
$dest .=
static::encode6Bits( $b0 >> 2) .
static::encode6Bits(($b0 << 4) & 63);
if ($pad) {
$dest .= '==';
}
}
}
return $dest;
}
/**
* decode from base64 into binary
*
* Base64 character set "./[A-Z][a-z][0-9]"
*
* @param string $encodedString
* @param bool $strictPadding
* @return string
*
* @throws RangeException
* @throws TypeError
*/
public static function decode(
#[SensitiveParameter]
string $encodedString,
bool $strictPadding = false
): string {
// Remove padding
$srcLen = Binary::safeStrlen($encodedString);
if ($srcLen === 0) {
return '';
}
if ($strictPadding) {
if (($srcLen & 3) === 0) {
if ($encodedString[$srcLen - 1] === '=') {
$srcLen--;
if ($encodedString[$srcLen - 1] === '=') {
$srcLen--;
}
}
}
if (($srcLen & 3) === 1) {
throw new RangeException(
'Incorrect padding'
);
}
if ($encodedString[$srcLen - 1] === '=') {
throw new RangeException(
'Incorrect padding'
);
}
if (extension_loaded('sodium')) {
switch (static::class) {
case Base64::class:
$variant = SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING;
break;
case Base64UrlSafe::class:
$variant = SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING;
break;
default:
$variant = 0;
}
if ($variant > 0) {
try {
return sodium_base642bin(Binary::safeSubstr($encodedString, 0, $srcLen), $variant);
} catch (SodiumException $ex) {
throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
}
}
}
} else {
$encodedString = rtrim($encodedString, '=');
$srcLen = Binary::safeStrlen($encodedString);
}
$err = 0;
$dest = '';
// Main loop (no padding):
for ($i = 0; $i + 4 <= $srcLen; $i += 4) {
/** @var array<int, int> $chunk */
$chunk = unpack('C*', Binary::safeSubstr($encodedString, $i, 4));
$c0 = static::decode6Bits($chunk[1]);
$c1 = static::decode6Bits($chunk[2]);
$c2 = static::decode6Bits($chunk[3]);
$c3 = static::decode6Bits($chunk[4]);
$dest .= pack(
'CCC',
((($c0 << 2) | ($c1 >> 4)) & 0xff),
((($c1 << 4) | ($c2 >> 2)) & 0xff),
((($c2 << 6) | $c3 ) & 0xff)
);
$err |= ($c0 | $c1 | $c2 | $c3) >> 8;
}
// The last chunk, which may have padding:
if ($i < $srcLen) {
/** @var array<int, int> $chunk */
$chunk = unpack('C*', Binary::safeSubstr($encodedString, $i, $srcLen - $i));
$c0 = static::decode6Bits($chunk[1]);
if ($i + 2 < $srcLen) {
$c1 = static::decode6Bits($chunk[2]);
$c2 = static::decode6Bits($chunk[3]);
$dest .= pack(
'CC',
((($c0 << 2) | ($c1 >> 4)) & 0xff),
((($c1 << 4) | ($c2 >> 2)) & 0xff)
);
$err |= ($c0 | $c1 | $c2) >> 8;
if ($strictPadding) {
$err |= ($c2 << 6) & 0xff;
}
} elseif ($i + 1 < $srcLen) {
$c1 = static::decode6Bits($chunk[2]);
$dest .= pack(
'C',
((($c0 << 2) | ($c1 >> 4)) & 0xff)
);
$err |= ($c0 | $c1) >> 8;
if ($strictPadding) {
$err |= ($c1 << 4) & 0xff;
}
} elseif ($strictPadding) {
$err |= 1;
}
}
$check = ($err === 0);
if (!$check) {
throw new RangeException(
'Base64::decode() only expects characters in the correct base64 alphabet'
);
}
return $dest;
}
/**
* @param string $encodedString
* @return string
*/
public static function decodeNoPadding(
#[SensitiveParameter]
string $encodedString
): string {
$srcLen = Binary::safeStrlen($encodedString);
if ($srcLen === 0) {
return '';
}
if (($srcLen & 3) === 0) {
// If $strLen is not zero, and it is divisible by 4, then it's at least 4.
if ($encodedString[$srcLen - 1] === '=' || $encodedString[$srcLen - 2] === '=') {
throw new InvalidArgumentException(
"decodeNoPadding() doesn't tolerate padding"
);
}
}
return static::decode(
$encodedString,
true
);
}
/**
* Uses bitwise operators instead of table-lookups to turn 6-bit integers
* into 8-bit integers.
*
* Base64 character set:
* [A-Z] [a-z] [0-9] + /
* 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f
*
* @param int $src
* @return int
*/
protected static function decode6Bits(int $src): int
{
$ret = -1;
// if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
// if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70);
// if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5);
// if ($src == 0x2b) $ret += 62 + 1;
$ret += (((0x2a - $src) & ($src - 0x2c)) >> 8) & 63;
// if ($src == 0x2f) ret += 63 + 1;
$ret += (((0x2e - $src) & ($src - 0x30)) >> 8) & 64;
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 6-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode6Bits(int $src): string
{
$diff = 0x41;
// if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6
$diff += ((25 - $src) >> 8) & 6;
// if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75
$diff -= ((51 - $src) >> 8) & 75;
// if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15
$diff -= ((61 - $src) >> 8) & 15;
// if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3
$diff += ((62 - $src) >> 8) & 3;
return pack('C', $src + $diff);
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use function pack;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base64DotSlash
* ./[A-Z][a-z][0-9]
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64DotSlash extends Base64
{
/**
* Uses bitwise operators instead of table-lookups to turn 6-bit integers
* into 8-bit integers.
*
* Base64 character set:
* ./ [A-Z] [a-z] [0-9]
* 0x2e-0x2f, 0x41-0x5a, 0x61-0x7a, 0x30-0x39
*
* @param int $src
* @return int
*/
protected static function decode6Bits(int $src): int
{
$ret = -1;
// if ($src > 0x2d && $src < 0x30) ret += $src - 0x2e + 1; // -45
$ret += (((0x2d - $src) & ($src - 0x30)) >> 8) & ($src - 45);
// if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 2 + 1; // -62
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 62);
// if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 28 + 1; // -68
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 68);
// if ($src > 0x2f && $src < 0x3a) ret += $src - 0x30 + 54 + 1; // 7
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 7);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 6-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode6Bits(int $src): string
{
$src += 0x2e;
// if ($src > 0x2f) $src += 0x41 - 0x30; // 17
$src += ((0x2f - $src) >> 8) & 17;
// if ($src > 0x5a) $src += 0x61 - 0x5b; // 6
$src += ((0x5a - $src) >> 8) & 6;
// if ($src > 0x7a) $src += 0x30 - 0x7b; // -75
$src -= ((0x7a - $src) >> 8) & 75;
return pack('C', $src);
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use function pack;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base64DotSlashOrdered
* ./[0-9][A-Z][a-z]
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64DotSlashOrdered extends Base64
{
/**
* Uses bitwise operators instead of table-lookups to turn 6-bit integers
* into 8-bit integers.
*
* Base64 character set:
* [.-9] [A-Z] [a-z]
* 0x2e-0x39, 0x41-0x5a, 0x61-0x7a
*
* @param int $src
* @return int
*/
protected static function decode6Bits(int $src): int
{
$ret = -1;
// if ($src > 0x2d && $src < 0x3a) ret += $src - 0x2e + 1; // -45
$ret += (((0x2d - $src) & ($src - 0x3a)) >> 8) & ($src - 45);
// if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 12 + 1; // -52
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 52);
// if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 38 + 1; // -58
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 58);
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 6-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode6Bits(int $src): string
{
$src += 0x2e;
// if ($src > 0x39) $src += 0x41 - 0x3a; // 7
$src += ((0x39 - $src) >> 8) & 7;
// if ($src > 0x5a) $src += 0x61 - 0x5b; // 6
$src += ((0x5a - $src) >> 8) & 6;
return pack('C', $src);
}
}

View File

@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use function pack;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Base64UrlSafe
* [A-Z][a-z][0-9]\-_
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64UrlSafe extends Base64
{
/**
* Uses bitwise operators instead of table-lookups to turn 6-bit integers
* into 8-bit integers.
*
* Base64 character set:
* [A-Z] [a-z] [0-9] - _
* 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2d, 0x5f
*
* @param int $src
* @return int
*/
protected static function decode6Bits(int $src): int
{
$ret = -1;
// if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
// if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70);
// if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5);
// if ($src == 0x2c) $ret += 62 + 1;
$ret += (((0x2c - $src) & ($src - 0x2e)) >> 8) & 63;
// if ($src == 0x5f) ret += 63 + 1;
$ret += (((0x5e - $src) & ($src - 0x60)) >> 8) & 64;
return $ret;
}
/**
* Uses bitwise operators instead of table-lookups to turn 8-bit integers
* into 6-bit integers.
*
* @param int $src
* @return string
*/
protected static function encode6Bits(int $src): string
{
$diff = 0x41;
// if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6
$diff += ((25 - $src) >> 8) & 6;
// if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75
$diff -= ((51 - $src) >> 8) & 75;
// if ($src > 61) $diff += 0x2d - 0x30 - 10; // -13
$diff -= ((61 - $src) >> 8) & 13;
// if ($src > 62) $diff += 0x5f - 0x2b - 1; // 3
$diff += ((62 - $src) >> 8) & 49;
return pack('C', $src + $diff);
}
}

View File

@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use TypeError;
use function function_exists;
use function mb_strlen;
use function mb_substr;
use function strlen;
use function substr;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Binary
*
* Binary string operators that don't choke on
* mbstring.func_overload
*
* @package ParagonIE\ConstantTime
*/
abstract class Binary
{
/**
* Safe string length
*
* @ref mbstring.func_overload
*
* @param string $str
* @return int
*/
public static function safeStrlen(
#[\SensitiveParameter]
string $str
): int {
if (function_exists('mb_strlen')) {
// mb_strlen in PHP 7.x can return false.
/** @psalm-suppress RedundantCast */
return (int) mb_strlen($str, '8bit');
} else {
return strlen($str);
}
}
/**
* Safe substring
*
* @ref mbstring.func_overload
*
* @staticvar boolean $exists
* @param string $str
* @param int $start
* @param ?int $length
* @return string
*
* @throws TypeError
*/
public static function safeSubstr(
#[\SensitiveParameter]
string $str,
int $start = 0,
$length = null
): string {
if ($length === 0) {
return '';
}
if (function_exists('mb_substr')) {
return mb_substr($str, $start, $length, '8bit');
}
// Unlike mb_substr(), substr() doesn't accept NULL for length
if ($length !== null) {
return substr($str, $start, $length);
} else {
return substr($str, $start);
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Interface EncoderInterface
* @package ParagonIE\ConstantTime
*/
interface EncoderInterface
{
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $binString (raw binary)
* @return string
*/
public static function encode(string $binString): string;
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $encodedString
* @param bool $strictPadding Error on invalid padding
* @return string (raw binary)
*/
public static function decode(string $encodedString, bool $strictPadding = false): string;
}

View File

@@ -0,0 +1,299 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use SensitiveParameter;
use TypeError;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Encoding
* @package ParagonIE\ConstantTime
*/
abstract class Encoding
{
/**
* RFC 4648 Base32 encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32Encode(
#[SensitiveParameter]
string $str
): string {
return Base32::encode($str);
}
/**
* RFC 4648 Base32 encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32EncodeUpper(
#[SensitiveParameter]
string $str
): string {
return Base32::encodeUpper($str);
}
/**
* RFC 4648 Base32 decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32Decode(
#[SensitiveParameter]
string $str
): string {
return Base32::decode($str);
}
/**
* RFC 4648 Base32 decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32DecodeUpper(
#[SensitiveParameter]
string $str
): string {
return Base32::decodeUpper($str);
}
/**
* RFC 4648 Base32 encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32HexEncode(
#[SensitiveParameter]
string $str
): string {
return Base32Hex::encode($str);
}
/**
* RFC 4648 Base32Hex encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32HexEncodeUpper(
#[SensitiveParameter]
string $str
): string {
return Base32Hex::encodeUpper($str);
}
/**
* RFC 4648 Base32Hex decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32HexDecode(
#[SensitiveParameter]
string $str
): string {
return Base32Hex::decode($str);
}
/**
* RFC 4648 Base32Hex decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base32HexDecodeUpper(
#[SensitiveParameter]
string $str
): string {
return Base32Hex::decodeUpper($str);
}
/**
* RFC 4648 Base64 encoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base64Encode(
#[SensitiveParameter]
string $str
): string {
return Base64::encode($str);
}
/**
* RFC 4648 Base64 decoding
*
* @param string $str
* @return string
* @throws TypeError
*/
public static function base64Decode(
#[SensitiveParameter]
string $str
): string {
return Base64::decode($str);
}
/**
* Encode into Base64
*
* Base64 character set "./[A-Z][a-z][0-9]"
* @param string $str
* @return string
* @throws TypeError
*/
public static function base64EncodeDotSlash(
#[SensitiveParameter]
string $str
): string {
return Base64DotSlash::encode($str);
}
/**
* Decode from base64 to raw binary
*
* Base64 character set "./[A-Z][a-z][0-9]"
*
* @param string $str
* @return string
* @throws \RangeException
* @throws TypeError
*/
public static function base64DecodeDotSlash(
#[SensitiveParameter]
string $str
): string {
return Base64DotSlash::decode($str);
}
/**
* Encode into Base64
*
* Base64 character set "[.-9][A-Z][a-z]" or "./[0-9][A-Z][a-z]"
* @param string $str
* @return string
* @throws TypeError
*/
public static function base64EncodeDotSlashOrdered(
#[SensitiveParameter]
string $str
): string {
return Base64DotSlashOrdered::encode($str);
}
/**
* Decode from base64 to raw binary
*
* Base64 character set "[.-9][A-Z][a-z]" or "./[0-9][A-Z][a-z]"
*
* @param string $str
* @return string
* @throws \RangeException
* @throws TypeError
*/
public static function base64DecodeDotSlashOrdered(
#[SensitiveParameter]
string $str
): string {
return Base64DotSlashOrdered::decode($str);
}
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $bin_string (raw binary)
* @return string
* @throws TypeError
*/
public static function hexEncode(
#[SensitiveParameter]
string $bin_string
): string {
return Hex::encode($bin_string);
}
/**
* Convert a hexadecimal string into a binary string without cache-timing
* leaks
*
* @param string $hex_string
* @return string (raw binary)
* @throws \RangeException
*/
public static function hexDecode(
#[SensitiveParameter]
string $hex_string
): string {
return Hex::decode($hex_string);
}
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $bin_string (raw binary)
* @return string
* @throws TypeError
*/
public static function hexEncodeUpper(
#[SensitiveParameter]
string $bin_string
): string {
return Hex::encodeUpper($bin_string);
}
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $bin_string (raw binary)
* @return string
*/
public static function hexDecodeUpper(
#[SensitiveParameter]
string $bin_string
): string {
return Hex::decode($bin_string);
}
}

View File

@@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use RangeException;
use SensitiveParameter;
use SodiumException;
use TypeError;
use function extension_loaded;
use function pack;
use function sodium_bin2hex;
use function sodium_hex2bin;
use function unpack;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class Hex
* @package ParagonIE\ConstantTime
*/
abstract class Hex implements EncoderInterface
{
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $binString (raw binary)
* @return string
* @throws TypeError
*/
public static function encode(
#[SensitiveParameter]
string $binString
): string {
if (extension_loaded('sodium')) {
try {
return sodium_bin2hex($binString);
} catch (SodiumException $ex) {
throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
}
}
$hex = '';
$len = Binary::safeStrlen($binString);
for ($i = 0; $i < $len; ++$i) {
/** @var array<int, int> $chunk */
$chunk = unpack('C', $binString[$i]);
$c = $chunk[1] & 0xf;
$b = $chunk[1] >> 4;
$hex .= pack(
'CC',
(87 + $b + ((($b - 10) >> 8) & ~38)),
(87 + $c + ((($c - 10) >> 8) & ~38))
);
}
return $hex;
}
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks, returning uppercase letters (as per RFC 4648)
*
* @param string $binString (raw binary)
* @return string
* @throws TypeError
*/
public static function encodeUpper(
#[SensitiveParameter]
string $binString
): string {
$hex = '';
$len = Binary::safeStrlen($binString);
for ($i = 0; $i < $len; ++$i) {
/** @var array<int, int> $chunk */
$chunk = unpack('C', $binString[$i]);
$c = $chunk[1] & 0xf;
$b = $chunk[1] >> 4;
$hex .= pack(
'CC',
(55 + $b + ((($b - 10) >> 8) & ~6)),
(55 + $c + ((($c - 10) >> 8) & ~6))
);
}
return $hex;
}
/**
* Convert a hexadecimal string into a binary string without cache-timing
* leaks
*
* @param string $encodedString
* @param bool $strictPadding
* @return string (raw binary)
* @throws RangeException
*/
public static function decode(
#[SensitiveParameter]
string $encodedString,
bool $strictPadding = false
): string {
if (extension_loaded('sodium') && $strictPadding) {
try {
return sodium_hex2bin($encodedString);
} catch (SodiumException $ex) {
throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
}
}
$hex_pos = 0;
$bin = '';
$c_acc = 0;
$hex_len = Binary::safeStrlen($encodedString);
$state = 0;
if (($hex_len & 1) !== 0) {
if ($strictPadding) {
throw new RangeException(
'Expected an even number of hexadecimal characters'
);
} else {
$encodedString = '0' . $encodedString;
++$hex_len;
}
}
/** @var array<int, int> $chunk */
$chunk = unpack('C*', $encodedString);
while ($hex_pos < $hex_len) {
++$hex_pos;
$c = $chunk[$hex_pos];
$c_num = $c ^ 48;
$c_num0 = ($c_num - 10) >> 8;
$c_alpha = ($c & ~32) - 55;
$c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
if (($c_num0 | $c_alpha0) === 0) {
throw new RangeException(
'Expected hexadecimal character'
);
}
$c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
if ($state === 0) {
$c_acc = $c_val * 16;
} else {
$bin .= pack('C', $c_acc | $c_val);
}
$state ^= 1;
}
return $bin;
}
}

View File

@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
namespace ParagonIE\ConstantTime;
use TypeError;
/**
* Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
* Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Class RFC4648
*
* This class conforms strictly to the RFC
*
* @package ParagonIE\ConstantTime
*/
abstract class RFC4648
{
/**
* RFC 4648 Base64 encoding
*
* "foo" -> "Zm9v"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base64Encode(
#[\SensitiveParameter]
string $str
): string {
return Base64::encode($str);
}
/**
* RFC 4648 Base64 decoding
*
* "Zm9v" -> "foo"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base64Decode(
#[\SensitiveParameter]
string $str
): string {
return Base64::decode($str, true);
}
/**
* RFC 4648 Base64 (URL Safe) encoding
*
* "foo" -> "Zm9v"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base64UrlSafeEncode(
#[\SensitiveParameter]
string $str
): string {
return Base64UrlSafe::encode($str);
}
/**
* RFC 4648 Base64 (URL Safe) decoding
*
* "Zm9v" -> "foo"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base64UrlSafeDecode(
#[\SensitiveParameter]
string $str
): string {
return Base64UrlSafe::decode($str, true);
}
/**
* RFC 4648 Base32 encoding
*
* "foo" -> "MZXW6==="
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base32Encode(
#[\SensitiveParameter]
string $str
): string {
return Base32::encodeUpper($str);
}
/**
* RFC 4648 Base32 encoding
*
* "MZXW6===" -> "foo"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base32Decode(
#[\SensitiveParameter]
string $str
): string {
return Base32::decodeUpper($str, true);
}
/**
* RFC 4648 Base32-Hex encoding
*
* "foo" -> "CPNMU==="
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base32HexEncode(
#[\SensitiveParameter]
string $str
): string {
return Base32::encodeUpper($str);
}
/**
* RFC 4648 Base32-Hex decoding
*
* "CPNMU===" -> "foo"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base32HexDecode(
#[\SensitiveParameter]
string $str
): string {
return Base32::decodeUpper($str, true);
}
/**
* RFC 4648 Base16 decoding
*
* "foo" -> "666F6F"
*
* @param string $str
* @return string
*
* @throws TypeError
*/
public static function base16Encode(
#[\SensitiveParameter]
string $str
): string {
return Hex::encodeUpper($str);
}
/**
* RFC 4648 Base16 decoding
*
* "666F6F" -> "foo"
*
* @param string $str
* @return string
*/
public static function base16Decode(
#[\SensitiveParameter]
string $str
): string {
return Hex::decode($str, true);
}
}

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Paragon Initiative Enterprises
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )
php -dphar.readonly=0 "$basedir/other/build_phar.php" $*

View File

@@ -0,0 +1,34 @@
{
"name": "paragonie/random_compat",
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"random",
"polyfill",
"pseudorandom"
],
"license": "MIT",
"type": "library",
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"support": {
"issues": "https://github.com/paragonie/random_compat/issues",
"email": "info@paragonie.com",
"source": "https://github.com/paragonie/random_compat"
},
"require": {
"php": ">= 7"
},
"require-dev": {
"vimeo/psalm": "^1",
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
}
}

View File

@@ -0,0 +1,5 @@
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm
pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p
+h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,11 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (MingW32)
iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip
QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg
1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW
NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA
NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV
JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74=
=B6+8
-----END PGP SIGNATURE-----

View File

@@ -0,0 +1,32 @@
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* @version 2.99.99
* @released 2018-06-06
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// NOP

View File

@@ -0,0 +1,57 @@
<?php
$dist = dirname(__DIR__).'/dist';
if (!is_dir($dist)) {
mkdir($dist, 0755);
}
if (file_exists($dist.'/random_compat.phar')) {
unlink($dist.'/random_compat.phar');
}
$phar = new Phar(
$dist.'/random_compat.phar',
FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::KEY_AS_FILENAME,
'random_compat.phar'
);
rename(
dirname(__DIR__).'/lib/random.php',
dirname(__DIR__).'/lib/index.php'
);
$phar->buildFromDirectory(dirname(__DIR__).'/lib');
rename(
dirname(__DIR__).'/lib/index.php',
dirname(__DIR__).'/lib/random.php'
);
/**
* If we pass an (optional) path to a private key as a second argument, we will
* sign the Phar with OpenSSL.
*
* If you leave this out, it will produce an unsigned .phar!
*/
if ($argc > 1) {
if (!@is_readable($argv[1])) {
echo 'Could not read the private key file:', $argv[1], "\n";
exit(255);
}
$pkeyFile = file_get_contents($argv[1]);
$private = openssl_get_privatekey($pkeyFile);
if ($private !== false) {
$pkey = '';
openssl_pkey_export($private, $pkey);
$phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey);
/**
* Save the corresponding public key to the file
*/
if (!@is_readable($dist.'/random_compat.phar.pubkey')) {
$details = openssl_pkey_get_details($private);
file_put_contents(
$dist.'/random_compat.phar.pubkey',
$details['key']
);
}
} else {
echo 'An error occurred reading the private key from OpenSSL.', "\n";
exit(255);
}
}

View File

@@ -0,0 +1,9 @@
<?php
require_once 'lib/byte_safe_strings.php';
require_once 'lib/cast_to_int.php';
require_once 'lib/error_polyfill.php';
require_once 'other/ide_stubs/libsodium.php';
require_once 'lib/random.php';
$int = random_int(0, 65536);

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<psalm
autoloader="psalm-autoload.php"
stopOnFirstError="false"
useDocblockTypes="true"
>
<projectFiles>
<directory name="lib" />
</projectFiles>
<issueHandlers>
<RedundantConditionGivenDocblockType errorLevel="info" />
<UnresolvableInclude errorLevel="info" />
<DuplicateClass errorLevel="info" />
<InvalidOperand errorLevel="info" />
<UndefinedConstant errorLevel="info" />
<MissingReturnType errorLevel="info" />
<InvalidReturnType errorLevel="info" />
</issueHandlers>
</psalm>

View File

@@ -0,0 +1,7 @@
phpseclib Lead Developer: TerraFrost (Jim Wigginton)
phpseclib Developers: monnerat (Patrick Monnerat)
bantu (Andreas Fischer)
petrich (Hans-Jürgen Petrich)
GrahamCampbell (Graham Campbell)
hc-jworman

View File

@@ -0,0 +1,19 @@
# Backers
phpseclib ongoing development is made possible by [Tidelift](https://tidelift.com/subscription/pkg/packagist-phpseclib-phpseclib?utm_source=packagist-phpseclib-phpseclib&utm_medium=referral&utm_campaign=readme) and by contributions by users like you. Thank you.
## Backers
- Allan Simon
- [ChargeOver](https://chargeover.com/)
- Raghu Veer Dendukuri
- Zane Hooper
- [Setasign](https://www.setasign.com/)
- [Charles Severance](https://github.com/csev)
- [Rachel Fish](https://github.com/itsrachelfish)
- Tharyrok
- [cjhaas](https://github.com/cjhaas)
- [istiak-tridip](https://github.com/istiak-tridip)
- [Anna Filina](https://github.com/afilina)
- [blakemckeeby](https://github.com/blakemckeeby)
- [ssddanbrown](https://github.com/ssddanbrown)

20
src/vendor/phpseclib/phpseclib/LICENSE vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright (c) 2011-2019 TerraFrost and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

103
src/vendor/phpseclib/phpseclib/README.md vendored Normal file
View File

@@ -0,0 +1,103 @@
# phpseclib - PHP Secure Communications Library
[![CI Status](https://github.com/phpseclib/phpseclib/actions/workflows/ci.yml/badge.svg?branch=3.0&event=push "CI Status")](https://github.com/phpseclib/phpseclib)
## Supporting phpseclib
- [Become a backer or sponsor on Patreon](https://www.patreon.com/phpseclib)
- [One-time donation via PayPal or crypto-currencies](http://sourceforge.net/donate/index.php?group_id=198487)
- [Subscribe to Tidelift](https://tidelift.com/subscription/pkg/packagist-phpseclib-phpseclib?utm_source=packagist-phpseclib-phpseclib&utm_medium=referral&utm_campaign=readme)
## Introduction
MIT-licensed pure-PHP implementations of the following:
SSH-2, SFTP, X.509, an arbitrary-precision integer arithmetic library, Ed25519 / Ed449 / Curve25519 / Curve449, ECDSA / ECDH (with support for 66 curves), RSA (PKCS#1 v2.2 compliant), DSA / DH, DES / 3DES / RC4 / Rijndael / AES / Blowfish / Twofish / Salsa20 / ChaCha20, GCM / Poly1305
* [Browse Git](https://github.com/phpseclib/phpseclib)
## Documentation
* [Documentation / Manual](https://phpseclib.com/)
* [API Documentation](https://api.phpseclib.com/3.0/) (generated by Doctum)
## Branches
### master
* Development Branch
* Unstable API
* Do not use in production
### 3.0
* Long term support (LTS) release
* Major expansion of cryptographic primitives
* Minimum PHP version: 5.6.1
* PSR-4 autoloading with namespace rooted at `\phpseclib3`
* Install via Composer: `composer require phpseclib/phpseclib:~3.0`
### 2.0
* Long term support (LTS) release
* Modernized version of 1.0
* Minimum PHP version: 5.3.3
* PSR-4 autoloading with namespace rooted at `\phpseclib`
* Install via Composer: `composer require phpseclib/phpseclib:~2.0`
### 1.0
* Long term support (LTS) release
* PHP4 compatible
* Composer compatible (PSR-0 autoloading)
* Install using Composer: `composer require phpseclib/phpseclib:~1.0`
* [Download 1.0.24 as ZIP](http://sourceforge.net/projects/phpseclib/files/phpseclib1.0.24.zip/download)
## Security contact information
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
## Support
Need Support?
* [Checkout Questions and Answers on Stack Overflow](http://stackoverflow.com/questions/tagged/phpseclib)
* [Create a Support Ticket on GitHub](https://github.com/phpseclib/phpseclib/issues/new)
## Special Thanks
<p align="left">
<a target="_blank" href="https://www.sovereign.tech/tech/phpseclib">
<img src="https://phpseclib.com/img/sponsors/sovereign-tech-agency.webp" alt="Sovereign Tech Agency" style="width: 200px">
</a>
</p>
## Additional Thanks
- Allan Simon
- [ChargeOver](https://chargeover.com/)
- <a href="https://jb.gg/OpenSource"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" height="20px"></a>
## Contributing
1. Fork the Project
2. Ensure you have Composer installed (see [Composer Download Instructions](https://getcomposer.org/download/))
3. Install Development Dependencies
```sh
composer install
```
4. Create a Feature Branch
5. Run continuous integration checks:
```sh
composer global require php:^8.1 squizlabs/php_codesniffer friendsofphp/php-cs-fixer vimeo/psalm
phpcs --standard=build/php_codesniffer.xml
php-cs-fixer fix --config=build/php-cs-fixer.php --diff --dry-run --using-cache=no
psalm --config=build/psalm.xml --no-cache --long-progress --report-show-info=false --output-format=text
vendor/bin/phpunit --verbose --configuration tests/phpunit.xml
```
6. Send us a Pull Request

View File

@@ -0,0 +1,84 @@
{
"name": "phpseclib/phpseclib",
"type": "library",
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"keywords": [
"security",
"crypto",
"cryptography",
"encryption",
"signature",
"signing",
"rsa",
"aes",
"blowfish",
"twofish",
"ssh",
"sftp",
"x509",
"x.509",
"asn1",
"asn.1",
"BigInteger"
],
"homepage": "http://phpseclib.sourceforge.net",
"license": "MIT",
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"require": {
"php": ">=5.6.1",
"paragonie/constant_time_encoding": "^1|^2|^3",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"suggest": {
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-dom": "Install the DOM extension to load XML formatted public keys."
},
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib3\\": "phpseclib/"
}
},
"autoload-dev": {
"psr-4": {
"phpseclib3\\Tests\\": "tests/"
}
},
"config": {
"sort-packages": true
}
}

View File

@@ -0,0 +1,507 @@
<?php
/**
* Common String Functions
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Common\Functions;
use ParagonIE\ConstantTime\Base64;
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\ConstantTime\Hex;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\Common\FiniteField;
/**
* Common String Functions
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Strings
{
/**
* String Shift
*
* Inspired by array_shift
*
* @param string $string
* @param int $index
* @return string
*/
public static function shift(&$string, $index = 1)
{
$substr = substr($string, 0, $index);
$string = substr($string, $index);
return $substr;
}
/**
* String Pop
*
* Inspired by array_pop
*
* @param string $string
* @param int $index
* @return string
*/
public static function pop(&$string, $index = 1)
{
$substr = substr($string, -$index);
$string = substr($string, 0, -$index);
return $substr;
}
/**
* Parse SSH2-style string
*
* Returns either an array or a boolean if $data is malformed.
*
* Valid characters for $format are as follows:
*
* C = byte
* b = boolean (true/false)
* N = uint32
* Q = uint64
* s = string
* i = mpint
* L = name-list
*
* uint64 is not supported.
*
* @param string $format
* @param string $data
* @return mixed
*/
public static function unpackSSH2($format, &$data)
{
$format = self::formatPack($format);
$result = [];
for ($i = 0; $i < strlen($format); $i++) {
switch ($format[$i]) {
case 'C':
case 'b':
if (!strlen($data)) {
throw new \LengthException('At least one byte needs to be present for successful C / b decodes');
}
break;
case 'N':
case 'i':
case 's':
case 'L':
if (strlen($data) < 4) {
throw new \LengthException('At least four byte needs to be present for successful N / i / s / L decodes');
}
break;
case 'Q':
if (strlen($data) < 8) {
throw new \LengthException('At least eight byte needs to be present for successful N / i / s / L decodes');
}
break;
default:
throw new \InvalidArgumentException('$format contains an invalid character');
}
switch ($format[$i]) {
case 'C':
$result[] = ord(self::shift($data));
continue 2;
case 'b':
$result[] = ord(self::shift($data)) != 0;
continue 2;
case 'N':
list(, $temp) = unpack('N', self::shift($data, 4));
$result[] = $temp;
continue 2;
case 'Q':
// pack() added support for Q in PHP 5.6.3 and PHP 5.6 is phpseclib 3's minimum version
// so in theory we could support this BUT, "64-bit format codes are not available for
// 32-bit versions" and phpseclib works on 32-bit installs. on 32-bit installs
// 64-bit floats can be used to get larger numbers then 32-bit signed ints would allow
// for. sure, you're not gonna get the full precision of 64-bit numbers but just because
// you need > 32-bit precision doesn't mean you need the full 64-bit precision
$unpacked = unpack('Nupper/Nlower', self::shift($data, 8));
$upper = $unpacked['upper'];
$lower = $unpacked['lower'];
$temp = $upper ? 4294967296 * $upper : 0;
$temp += $lower < 0 ? ($lower & 0x7FFFFFFFF) + 0x80000000 : $lower;
// $temp = hexdec(bin2hex(self::shift($data, 8)));
$result[] = $temp;
continue 2;
}
list(, $length) = unpack('N', self::shift($data, 4));
if (strlen($data) < $length) {
throw new \LengthException("$length bytes needed; " . strlen($data) . ' bytes available');
}
$temp = self::shift($data, $length);
switch ($format[$i]) {
case 'i':
$result[] = new BigInteger($temp, -256);
break;
case 's':
$result[] = $temp;
break;
case 'L':
$result[] = explode(',', $temp);
}
}
return $result;
}
/**
* Create SSH2-style string
*
* @param string $format
* @param string|int|float|array|bool ...$elements
* @return string
*/
public static function packSSH2($format, ...$elements)
{
$format = self::formatPack($format);
if (strlen($format) != count($elements)) {
throw new \InvalidArgumentException('There must be as many arguments as there are characters in the $format string');
}
$result = '';
for ($i = 0; $i < strlen($format); $i++) {
$element = $elements[$i];
switch ($format[$i]) {
case 'C':
if (!is_int($element)) {
throw new \InvalidArgumentException('Bytes must be represented as an integer between 0 and 255, inclusive.');
}
$result .= pack('C', $element);
break;
case 'b':
if (!is_bool($element)) {
throw new \InvalidArgumentException('A boolean parameter was expected.');
}
$result .= $element ? "\1" : "\0";
break;
case 'Q':
if (!is_int($element) && !is_float($element)) {
throw new \InvalidArgumentException('An integer was expected.');
}
// 4294967296 == 1 << 32
$result .= pack('NN', $element / 4294967296, $element);
break;
case 'N':
if (is_float($element)) {
$element = (int) $element;
}
if (!is_int($element)) {
throw new \InvalidArgumentException('An integer was expected.');
}
$result .= pack('N', $element);
break;
case 's':
if (!self::is_stringable($element)) {
throw new \InvalidArgumentException('A string was expected.');
}
$result .= pack('Na*', strlen($element), $element);
break;
case 'i':
if (!$element instanceof BigInteger && !$element instanceof FiniteField\Integer) {
throw new \InvalidArgumentException('A phpseclib3\Math\BigInteger or phpseclib3\Math\Common\FiniteField\Integer object was expected.');
}
$element = $element->toBytes(true);
$result .= pack('Na*', strlen($element), $element);
break;
case 'L':
if (!is_array($element)) {
throw new \InvalidArgumentException('An array was expected.');
}
$element = implode(',', $element);
$result .= pack('Na*', strlen($element), $element);
break;
default:
throw new \InvalidArgumentException('$format contains an invalid character');
}
}
return $result;
}
/**
* Expand a pack string
*
* Converts C5 to CCCCC, for example.
*
* @param string $format
* @return string
*/
private static function formatPack($format)
{
$parts = preg_split('#(\d+)#', $format, -1, PREG_SPLIT_DELIM_CAPTURE);
$format = '';
for ($i = 1; $i < count($parts); $i += 2) {
$format .= substr($parts[$i - 1], 0, -1) . str_repeat(substr($parts[$i - 1], -1), $parts[$i]);
}
$format .= $parts[$i - 1];
return $format;
}
/**
* Convert binary data into bits
*
* bin2hex / hex2bin refer to base-256 encoded data as binary, whilst
* decbin / bindec refer to base-2 encoded data as binary. For the purposes
* of this function, bin refers to base-256 encoded data whilst bits refers
* to base-2 encoded data
*
* @param string $x
* @return string
*/
public static function bits2bin($x)
{
/*
// the pure-PHP approach is faster than the GMP approach
if (function_exists('gmp_export')) {
return strlen($x) ? gmp_export(gmp_init($x, 2)) : gmp_init(0);
}
*/
if (preg_match('#[^01]#', $x)) {
throw new \RuntimeException('The only valid characters are 0 and 1');
}
if (!defined('PHP_INT_MIN')) {
define('PHP_INT_MIN', ~PHP_INT_MAX);
}
$length = strlen($x);
if (!$length) {
return '';
}
$block_size = PHP_INT_SIZE << 3;
$pad = $block_size - ($length % $block_size);
if ($pad != $block_size) {
$x = str_repeat('0', $pad) . $x;
}
$parts = str_split($x, $block_size);
$str = '';
foreach ($parts as $part) {
$xor = $part[0] == '1' ? PHP_INT_MIN : 0;
$part[0] = '0';
$str .= pack(
PHP_INT_SIZE == 4 ? 'N' : 'J',
$xor ^ eval('return 0b' . $part . ';')
);
}
return ltrim($str, "\0");
}
/**
* Convert bits to binary data
*
* @param string $x
* @return string
*/
public static function bin2bits($x, $trim = true)
{
/*
// the pure-PHP approach is slower than the GMP approach BUT
// i want to the pure-PHP version to be easily unit tested as well
if (function_exists('gmp_import')) {
return gmp_strval(gmp_import($x), 2);
}
*/
$len = strlen($x);
$mod = $len % PHP_INT_SIZE;
if ($mod) {
$x = str_pad($x, $len + PHP_INT_SIZE - $mod, "\0", STR_PAD_LEFT);
}
$bits = '';
if (PHP_INT_SIZE == 4) {
$digits = unpack('N*', $x);
foreach ($digits as $digit) {
$bits .= sprintf('%032b', $digit);
}
} else {
$digits = unpack('J*', $x);
foreach ($digits as $digit) {
$bits .= sprintf('%064b', $digit);
}
}
return $trim ? ltrim($bits, '0') : $bits;
}
/**
* Switch Endianness Bit Order
*
* @param string $x
* @return string
*/
public static function switchEndianness($x)
{
$r = '';
for ($i = strlen($x) - 1; $i >= 0; $i--) {
$b = ord($x[$i]);
if (PHP_INT_SIZE === 8) {
// 3 operations
// from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64BitsDiv
$r .= chr((($b * 0x0202020202) & 0x010884422010) % 1023);
} else {
// 7 operations
// from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits
$p1 = ($b * 0x0802) & 0x22110;
$p2 = ($b * 0x8020) & 0x88440;
$r .= chr(
(($p1 | $p2) * 0x10101) >> 16
);
}
}
return $r;
}
/**
* Increment the current string
*
* @param string $var
* @return string
*/
public static function increment_str(&$var)
{
if (function_exists('sodium_increment')) {
$var = strrev($var);
sodium_increment($var);
$var = strrev($var);
return $var;
}
for ($i = 4; $i <= strlen($var); $i += 4) {
$temp = substr($var, -$i, 4);
switch ($temp) {
case "\xFF\xFF\xFF\xFF":
$var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4);
break;
case "\x7F\xFF\xFF\xFF":
$var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4);
return $var;
default:
$temp = unpack('Nnum', $temp);
$var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4);
return $var;
}
}
$remainder = strlen($var) % 4;
if ($remainder == 0) {
return $var;
}
$temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT));
$temp = substr(pack('N', $temp['num'] + 1), -$remainder);
$var = substr_replace($var, $temp, 0, $remainder);
return $var;
}
/**
* Find whether the type of a variable is string (or could be converted to one)
*
* @param mixed $var
* @return bool
* @psalm-assert-if-true string|\Stringable $var
*/
public static function is_stringable($var)
{
return is_string($var) || (is_object($var) && method_exists($var, '__toString'));
}
/**
* Constant Time Base64-decoding
*
* ParagoneIE\ConstantTime doesn't use libsodium if it's available so we'll do so
* ourselves. see https://github.com/paragonie/constant_time_encoding/issues/39
*
* @param string $data
* @return string
*/
public static function base64_decode($data)
{
return function_exists('sodium_base642bin') ?
sodium_base642bin($data, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING, '=') :
Base64::decode($data);
}
/**
* Constant Time Base64-decoding (URL safe)
*
* @param string $data
* @return string
*/
public static function base64url_decode($data)
{
// return self::base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
return function_exists('sodium_base642bin') ?
sodium_base642bin($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, '=') :
Base64UrlSafe::decode($data);
}
/**
* Constant Time Base64-encoding
*
* @param string $data
* @return string
*/
public static function base64_encode($data)
{
return function_exists('sodium_bin2base64') ?
sodium_bin2base64($data, SODIUM_BASE64_VARIANT_ORIGINAL) :
Base64::encode($data);
}
/**
* Constant Time Base64-encoding (URL safe)
*
* @param string $data
* @return string
*/
public static function base64url_encode($data)
{
// return str_replace(['+', '/'], ['-', '_'], self::base64_encode($data));
return function_exists('sodium_bin2base64') ?
sodium_bin2base64($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING) :
Base64UrlSafe::encode($data);
}
/**
* Constant Time Hex Decoder
*
* @param string $data
* @return string
*/
public static function hex2bin($data)
{
return function_exists('sodium_hex2bin') ?
sodium_hex2bin($data) :
Hex::decode($data);
}
/**
* Constant Time Hex Encoder
*
* @param string $data
* @return string
*/
public static function bin2hex($data)
{
return function_exists('sodium_bin2hex') ?
sodium_bin2hex($data) :
Hex::encode($data);
}
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* Pure-PHP implementation of AES.
*
* Uses mcrypt, if available/possible, and an internal implementation, otherwise.
*
* PHP version 5
*
* NOTE: Since AES.php is (for compatibility and phpseclib-historical reasons) virtually
* just a wrapper to Rijndael.php you may consider using Rijndael.php instead of
* to save one include_once().
*
* If {@link self::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
* {@link self::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's 136-bits
* it'll be null-padded to 192-bits and 192 bits will be the key length until {@link self::setKey() setKey()}
* is called, again, at which point, it'll be recalculated.
*
* Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, some functions are available to be called that, in the context of AES, don't
* make a whole lot of sense. {@link self::setBlockLength() setBlockLength()}, for instance. Calling that function,
* however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one).
*
* Here's a short example of how to use this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $aes = new \phpseclib3\Crypt\AES('ctr');
*
* $aes->setKey('abcdefghijklmnop');
*
* $size = 10 * 1024;
* $plaintext = '';
* for ($i = 0; $i < $size; $i++) {
* $plaintext.= 'a';
* }
*
* echo $aes->decrypt($aes->encrypt($plaintext));
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2008 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt;
/**
* Pure-PHP implementation of AES.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class AES extends Rijndael
{
/**
* Dummy function
*
* Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, this function is, technically, available, but it doesn't do anything.
*
* @see \phpseclib3\Crypt\Rijndael::setBlockLength()
* @param int $length
* @throws \BadMethodCallException anytime it's called
*/
public function setBlockLength($length)
{
throw new \BadMethodCallException('The block length cannot be set for AES.');
}
/**
* Sets the key length
*
* Valid key lengths are 128, 192, and 256. Set the link to bool(false) to disable a fixed key length
*
* @see \phpseclib3\Crypt\Rijndael:setKeyLength()
* @param int $length
* @throws \LengthException if the key length isn't supported
*/
public function setKeyLength($length)
{
switch ($length) {
case 128:
case 192:
case 256:
break;
default:
throw new \LengthException('Key of size ' . $length . ' not supported by this algorithm. Only keys of sizes 128, 192 or 256 supported');
}
parent::setKeyLength($length);
}
/**
* Sets the key.
*
* Rijndael supports five different key lengths, AES only supports three.
*
* @see \phpseclib3\Crypt\Rijndael:setKey()
* @see setKeyLength()
* @param string $key
* @throws \LengthException if the key length isn't supported
*/
public function setKey($key)
{
switch (strlen($key)) {
case 16:
case 24:
case 32:
break;
default:
throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported');
}
parent::setKey($key);
}
}

View File

@@ -0,0 +1,835 @@
<?php
/**
* Pure-PHP implementation of Blowfish.
*
* Uses mcrypt, if available, and an internal implementation, otherwise.
*
* PHP version 5
*
* Useful resources are as follows:
*
* - {@link http://en.wikipedia.org/wiki/Blowfish_(cipher) Wikipedia description of Blowfish}
*
* # An overview of bcrypt vs Blowfish
*
* OpenSSH private keys use a customized version of bcrypt. Specifically, instead of
* encrypting OrpheanBeholderScryDoubt 64 times OpenSSH's bcrypt variant encrypts
* OxychromaticBlowfishSwatDynamite 64 times. so we can't use crypt().
*
* bcrypt is basically Blowfish but instead of performing the key expansion once it performs
* the expansion 129 times for each round, with the first key expansion interleaving the salt
* and password. This renders OpenSSL unusable and forces us to use a pure-PHP implementation
* of blowfish.
*
* # phpseclib's four different _encryptBlock() implementations
*
* When using Blowfish as an encryption algorithm, _encryptBlock() is called 9 + 512 +
* (the number of blocks in the plaintext) times.
*
* Each of the first 9 calls to _encryptBlock() modify the P-array. Each of the next 512
* calls modify the S-boxes. The remaining _encryptBlock() calls operate on the plaintext to
* produce the ciphertext. In the pure-PHP implementation of Blowfish these remaining
* _encryptBlock() calls are highly optimized through the use of eval(). Among other things,
* P-array lookups are eliminated by hard-coding the key-dependent P-array values, and thus we
* have explained 2 of the 4 different _encryptBlock() implementations.
*
* With bcrypt things are a bit different. _encryptBlock() is called 1,079,296 times,
* assuming 16 rounds (which is what OpenSSH's bcrypt defaults to). The eval()-optimized
* _encryptBlock() isn't as beneficial because the P-array values are not constant. Well, they
* are constant, but only for, at most, 777 _encryptBlock() calls, which is equivalent to ~6KB
* of data. The average length of back to back _encryptBlock() calls with a fixed P-array is
* 514.12, which is ~4KB of data. Creating an eval()-optimized _encryptBlock() has an upfront
* cost, which is CPU dependent and is probably not going to be worth it for just ~4KB of
* data. Conseqeuently, bcrypt does not benefit from the eval()-optimized _encryptBlock().
*
* The regular _encryptBlock() does unpack() and pack() on every call, as well, and that can
* begin to add up after one million function calls.
*
* In theory, one might think that it might be beneficial to rewrite all block ciphers so
* that, instead of passing strings to _encryptBlock(), you convert the string to an array of
* integers and then pass successive subarrays of that array to _encryptBlock. This, however,
* kills PHP's memory use. Like let's say you have a 1MB long string. After doing
* $in = str_repeat('a', 1024 * 1024); PHP's memory utilization jumps up by ~1MB. After doing
* $blocks = str_split($in, 4); it jumps up by an additional ~16MB. After
* $blocks = array_map(fn($x) => unpack('N*', $x), $blocks); it jumps up by an additional
* ~90MB, yielding a 106x increase in memory usage. Consequently, it bcrypt calls a different
* _encryptBlock() then the regular Blowfish does. That said, the Blowfish _encryptBlock() is
* basically just a thin wrapper around the bcrypt _encryptBlock(), so there's that.
*
* This explains 3 of the 4 _encryptBlock() implementations. the last _encryptBlock()
* implementation can best be understood by doing Ctrl + F and searching for where
* self::$use_reg_intval is defined.
*
* # phpseclib's three different _setupKey() implementations
*
* Every bcrypt round is the equivalent of encrypting 512KB of data. Since OpenSSH uses 16
* rounds by default that's ~8MB of data that's essentially being encrypted whenever
* you use bcrypt. That's a lot of data, however, bcrypt operates within tighter constraints
* than regular Blowfish, so we can use that to our advantage. In particular, whereas Blowfish
* supports variable length keys, in bcrypt, the initial "key" is the sha512 hash of the
* password. sha512 hashes are 512 bits or 64 bytes long and thus the bcrypt keys are of a
* fixed length whereas Blowfish keys are not of a fixed length.
*
* bcrypt actually has two different key expansion steps. The first one (expandstate) is
* constantly XOR'ing every _encryptBlock() parameter against the salt prior _encryptBlock()'s
* being called. The second one (expand0state) is more similar to Blowfish's _setupKey()
* but it can still use the fixed length key optimization discussed above and can do away with
* the pack() / unpack() calls.
*
* I suppose _setupKey() could be made to be a thin wrapper around expandstate() but idk it's
* just a lot of work for very marginal benefits as _setupKey() is only called once for
* regular Blowfish vs the 128 times it's called --per round-- with bcrypt.
*
* # blowfish + bcrypt in the same class
*
* Altho there's a lot of Blowfish code that bcrypt doesn't re-use, bcrypt does re-use the
* initial S-boxes, the initial P-array and the int-only _encryptBlock() implementation.
*
* # Credit
*
* phpseclib's bcrypt implementation is based losely off of OpenSSH's implementation:
*
* https://github.com/openssh/openssh-portable/blob/master/openbsd-compat/bcrypt_pbkdf.c
*
* Here's a short example of how to use this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $blowfish = new \phpseclib3\Crypt\Blowfish('ctr');
*
* $blowfish->setKey('12345678901234567890123456789012');
*
* $plaintext = str_repeat('a', 1024);
*
* echo $blowfish->decrypt($blowfish->encrypt($plaintext));
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\BlockCipher;
/**
* Pure-PHP implementation of Blowfish.
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
*/
class Blowfish extends BlockCipher
{
/**
* Block Length of the cipher
*
* @see Common\SymmetricKey::block_size
* @var int
*/
protected $block_size = 8;
/**
* The mcrypt specific name of the cipher
*
* @see Common\SymmetricKey::cipher_name_mcrypt
* @var string
*/
protected $cipher_name_mcrypt = 'blowfish';
/**
* Optimizing value while CFB-encrypting
*
* @see Common\SymmetricKey::cfb_init_len
* @var int
*/
protected $cfb_init_len = 500;
/**
* The fixed subkeys boxes
*
* S-Box
*
* @var array
*/
private static $sbox = [
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
];
/**
* P-Array consists of 18 32-bit subkeys
*
* @var array
*/
private static $parray = [
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b
];
/**
* The BCTX-working Array
*
* Holds the expanded key [p] and the key-depended s-boxes [sb]
*
* @var array
*/
private $bctx;
/**
* Holds the last used key
*
* @var array
*/
private $kl;
/**
* The Key Length (in bytes)
* {@internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk
* because the encryption / decryption / key schedule creation requires this number and not $key_length. We could
* derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
* of that, we'll just precompute it once.}
*
* @see Common\SymmetricKey::setKeyLength()
* @var int
*/
protected $key_length = 16;
/**
* Default Constructor.
*
* @param string $mode
* @throws \InvalidArgumentException if an invalid / unsupported mode is provided
*/
public function __construct($mode)
{
parent::__construct($mode);
if ($this->mode == self::MODE_STREAM) {
throw new \InvalidArgumentException('Block ciphers cannot be ran in stream mode');
}
}
/**
* Sets the key length.
*
* Key lengths can be between 32 and 448 bits.
*
* @param int $length
*/
public function setKeyLength($length)
{
if ($length < 32 || $length > 448) {
throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes between 32 and 448 bits are supported');
}
$this->key_length = $length >> 3;
parent::setKeyLength($length);
}
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
* @see Common\SymmetricKey::isValidEngine()
* @param int $engine
* @return bool
*/
protected function isValidEngineHelper($engine)
{
if ($engine == self::ENGINE_OPENSSL) {
if ($this->key_length < 16) {
return false;
}
// quoting https://www.openssl.org/news/openssl-3.0-notes.html, OpenSSL 3.0.1
// "Moved all variations of the EVP ciphers CAST5, BF, IDEA, SEED, RC2, RC4, RC5, and DES to the legacy provider"
// in theory openssl_get_cipher_methods() should catch this but, on GitHub Actions, at least, it does not
if (defined('OPENSSL_VERSION_TEXT') && version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
return false;
}
$this->cipher_name_openssl_ecb = 'bf-ecb';
$this->cipher_name_openssl = 'bf-' . $this->openssl_translate_mode();
}
return parent::isValidEngineHelper($engine);
}
/**
* Setup the key (expansion)
*
* @see Common\SymmetricKey::_setupKey()
*/
protected function setupKey()
{
if (isset($this->kl['key']) && $this->key === $this->kl['key']) {
// already expanded
return;
}
$this->kl = ['key' => $this->key];
/* key-expanding p[] and S-Box building sb[] */
$this->bctx = [
'p' => [],
'sb' => self::$sbox
];
// unpack binary string in unsigned chars
$key = array_values(unpack('C*', $this->key));
$keyl = count($key);
// with bcrypt $keyl will always be 16 (because the key is the sha512 of the key you provide)
for ($j = 0, $i = 0; $i < 18; ++$i) {
// xor P1 with the first 32-bits of the key, xor P2 with the second 32-bits ...
for ($data = 0, $k = 0; $k < 4; ++$k) {
$data = ($data << 8) | $key[$j];
if (++$j >= $keyl) {
$j = 0;
}
}
$this->bctx['p'][] = self::$parray[$i] ^ intval($data);
}
// encrypt the zero-string, replace P1 and P2 with the encrypted data,
// encrypt P3 and P4 with the new P1 and P2, do it with all P-array and subkeys
$data = "\0\0\0\0\0\0\0\0";
for ($i = 0; $i < 18; $i += 2) {
list($l, $r) = array_values(unpack('N*', $data = $this->encryptBlock($data)));
$this->bctx['p'][$i ] = $l;
$this->bctx['p'][$i + 1] = $r;
}
for ($i = 0; $i < 0x400; $i += 0x100) {
for ($j = 0; $j < 256; $j += 2) {
list($l, $r) = array_values(unpack('N*', $data = $this->encryptBlock($data)));
$this->bctx['sb'][$i | $j] = $l;
$this->bctx['sb'][$i | ($j + 1)] = $r;
}
}
}
/**
* Initialize Static Variables
*/
protected static function initialize_static_variables()
{
if (is_float(self::$sbox[0x200])) {
self::$sbox = array_map('intval', self::$sbox);
self::$parray = array_map('intval', self::$parray);
}
parent::initialize_static_variables();
}
/**
* bcrypt
*
* @param string $sha2pass
* @param string $sha2salt
* @access private
* @return string
*/
private static function bcrypt_hash($sha2pass, $sha2salt)
{
$p = self::$parray;
$sbox = self::$sbox;
$cdata = array_values(unpack('N*', 'OxychromaticBlowfishSwatDynamite'));
$sha2pass = array_values(unpack('N*', $sha2pass));
$sha2salt = array_values(unpack('N*', $sha2salt));
self::expandstate($sha2salt, $sha2pass, $sbox, $p);
for ($i = 0; $i < 64; $i++) {
self::expand0state($sha2salt, $sbox, $p);
self::expand0state($sha2pass, $sbox, $p);
}
for ($i = 0; $i < 64; $i++) {
for ($j = 0; $j < 8; $j += 2) { // count($cdata) == 8
list($cdata[$j], $cdata[$j + 1]) = self::encryptBlockHelperFast($cdata[$j], $cdata[$j + 1], $sbox, $p);
}
}
return pack('V*', ...$cdata);
}
/**
* Performs OpenSSH-style bcrypt
*
* @param string $pass
* @param string $salt
* @param int $keylen
* @param int $rounds
* @access public
* @return string
*/
public static function bcrypt_pbkdf($pass, $salt, $keylen, $rounds)
{
self::initialize_static_variables();
if (PHP_INT_SIZE == 4) {
throw new \RuntimeException('bcrypt is far too slow to be practical on 32-bit versions of PHP');
}
$sha2pass = hash('sha512', $pass, true);
$results = [];
$count = 1;
while (32 * count($results) < $keylen) {
$countsalt = $salt . pack('N', $count++);
$sha2salt = hash('sha512', $countsalt, true);
$out = $tmpout = self::bcrypt_hash($sha2pass, $sha2salt);
for ($i = 1; $i < $rounds; $i++) {
$sha2salt = hash('sha512', $tmpout, true);
$tmpout = self::bcrypt_hash($sha2pass, $sha2salt);
$out ^= $tmpout;
}
$results[] = $out;
}
$output = '';
for ($i = 0; $i < 32; $i++) {
foreach ($results as $result) {
$output .= $result[$i];
}
}
return substr($output, 0, $keylen);
}
/**
* Key expansion without salt
*
* @access private
* @param int[] $key
* @param int[] $sbox
* @param int[] $p
* @see self::_bcrypt_hash()
*/
private static function expand0state(array $key, array &$sbox, array &$p)
{
// expand0state is basically the same thing as this:
//return self::expandstate(array_fill(0, 16, 0), $key);
// but this separate function eliminates a bunch of XORs and array lookups
$p = [
$p[0] ^ $key[0],
$p[1] ^ $key[1],
$p[2] ^ $key[2],
$p[3] ^ $key[3],
$p[4] ^ $key[4],
$p[5] ^ $key[5],
$p[6] ^ $key[6],
$p[7] ^ $key[7],
$p[8] ^ $key[8],
$p[9] ^ $key[9],
$p[10] ^ $key[10],
$p[11] ^ $key[11],
$p[12] ^ $key[12],
$p[13] ^ $key[13],
$p[14] ^ $key[14],
$p[15] ^ $key[15],
$p[16] ^ $key[0],
$p[17] ^ $key[1]
];
// @codingStandardsIgnoreStart
list( $p[0], $p[1]) = self::encryptBlockHelperFast( 0, 0, $sbox, $p);
list( $p[2], $p[3]) = self::encryptBlockHelperFast($p[ 0], $p[ 1], $sbox, $p);
list( $p[4], $p[5]) = self::encryptBlockHelperFast($p[ 2], $p[ 3], $sbox, $p);
list( $p[6], $p[7]) = self::encryptBlockHelperFast($p[ 4], $p[ 5], $sbox, $p);
list( $p[8], $p[9]) = self::encryptBlockHelperFast($p[ 6], $p[ 7], $sbox, $p);
list($p[10], $p[11]) = self::encryptBlockHelperFast($p[ 8], $p[ 9], $sbox, $p);
list($p[12], $p[13]) = self::encryptBlockHelperFast($p[10], $p[11], $sbox, $p);
list($p[14], $p[15]) = self::encryptBlockHelperFast($p[12], $p[13], $sbox, $p);
list($p[16], $p[17]) = self::encryptBlockHelperFast($p[14], $p[15], $sbox, $p);
// @codingStandardsIgnoreEnd
list($sbox[0], $sbox[1]) = self::encryptBlockHelperFast($p[16], $p[17], $sbox, $p);
for ($i = 2; $i < 1024; $i += 2) {
list($sbox[$i], $sbox[$i + 1]) = self::encryptBlockHelperFast($sbox[$i - 2], $sbox[$i - 1], $sbox, $p);
}
}
/**
* Key expansion with salt
*
* @access private
* @param int[] $data
* @param int[] $key
* @param int[] $sbox
* @param int[] $p
* @see self::_bcrypt_hash()
*/
private static function expandstate(array $data, array $key, array &$sbox, array &$p)
{
$p = [
$p[0] ^ $key[0],
$p[1] ^ $key[1],
$p[2] ^ $key[2],
$p[3] ^ $key[3],
$p[4] ^ $key[4],
$p[5] ^ $key[5],
$p[6] ^ $key[6],
$p[7] ^ $key[7],
$p[8] ^ $key[8],
$p[9] ^ $key[9],
$p[10] ^ $key[10],
$p[11] ^ $key[11],
$p[12] ^ $key[12],
$p[13] ^ $key[13],
$p[14] ^ $key[14],
$p[15] ^ $key[15],
$p[16] ^ $key[0],
$p[17] ^ $key[1]
];
// @codingStandardsIgnoreStart
list( $p[0], $p[1]) = self::encryptBlockHelperFast($data[ 0] , $data[ 1] , $sbox, $p);
list( $p[2], $p[3]) = self::encryptBlockHelperFast($data[ 2] ^ $p[ 0], $data[ 3] ^ $p[ 1], $sbox, $p);
list( $p[4], $p[5]) = self::encryptBlockHelperFast($data[ 4] ^ $p[ 2], $data[ 5] ^ $p[ 3], $sbox, $p);
list( $p[6], $p[7]) = self::encryptBlockHelperFast($data[ 6] ^ $p[ 4], $data[ 7] ^ $p[ 5], $sbox, $p);
list( $p[8], $p[9]) = self::encryptBlockHelperFast($data[ 8] ^ $p[ 6], $data[ 9] ^ $p[ 7], $sbox, $p);
list($p[10], $p[11]) = self::encryptBlockHelperFast($data[10] ^ $p[ 8], $data[11] ^ $p[ 9], $sbox, $p);
list($p[12], $p[13]) = self::encryptBlockHelperFast($data[12] ^ $p[10], $data[13] ^ $p[11], $sbox, $p);
list($p[14], $p[15]) = self::encryptBlockHelperFast($data[14] ^ $p[12], $data[15] ^ $p[13], $sbox, $p);
list($p[16], $p[17]) = self::encryptBlockHelperFast($data[ 0] ^ $p[14], $data[ 1] ^ $p[15], $sbox, $p);
// @codingStandardsIgnoreEnd
list($sbox[0], $sbox[1]) = self::encryptBlockHelperFast($data[2] ^ $p[16], $data[3] ^ $p[17], $sbox, $p);
for ($i = 2, $j = 4; $i < 1024; $i += 2, $j = ($j + 2) % 16) { // instead of 16 maybe count($data) would be better?
list($sbox[$i], $sbox[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox[$i - 2], $data[$j + 1] ^ $sbox[$i - 1], $sbox, $p);
}
}
/**
* Encrypts a block
*
* @param string $in
* @return string
*/
protected function encryptBlock($in)
{
$p = $this->bctx['p'];
// extract($this->bctx['sb'], EXTR_PREFIX_ALL, 'sb'); // slower
$sb = $this->bctx['sb'];
$in = unpack('N*', $in);
$l = $in[1];
$r = $in[2];
list($r, $l) = PHP_INT_SIZE == 4 ?
self::encryptBlockHelperSlow($l, $r, $sb, $p) :
self::encryptBlockHelperFast($l, $r, $sb, $p);
return pack("N*", $r, $l);
}
/**
* Fast helper function for block encryption
*
* @access private
* @param int $x0
* @param int $x1
* @param int[] $sbox
* @param int[] $p
* @return int[]
*/
private static function encryptBlockHelperFast($x0, $x1, array $sbox, array $p)
{
$x0 ^= $p[0];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[1];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[2];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[3];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[4];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[5];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[6];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[7];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[8];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[9];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[10];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[11];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[12];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[13];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[14];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[15];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[16];
return [$x1 & 0xFFFFFFFF ^ $p[17], $x0 & 0xFFFFFFFF];
}
/**
* Slow helper function for block encryption
*
* @access private
* @param int $x0
* @param int $x1
* @param int[] $sbox
* @param int[] $p
* @return int[]
*/
private static function encryptBlockHelperSlow($x0, $x1, array $sbox, array $p)
{
// -16777216 == intval(0xFF000000) on 32-bit PHP installs
$x0 ^= $p[0];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[1];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[2];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[3];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[4];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[5];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[6];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[7];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[8];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[9];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[10];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[11];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[12];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[13];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[14];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[15];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[16];
return [$x1 ^ $p[17], $x0];
}
/**
* Decrypts a block
*
* @param string $in
* @return string
*/
protected function decryptBlock($in)
{
$p = $this->bctx['p'];
$sb = $this->bctx['sb'];
$in = unpack('N*', $in);
$l = $in[1];
$r = $in[2];
for ($i = 17; $i > 2; $i -= 2) {
$l ^= $p[$i];
$r ^= self::safe_intval((self::safe_intval($sb[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]) ^
$sb[0x200 + ($l >> 8 & 0xff)]) +
$sb[0x300 + ($l & 0xff)]);
$r ^= $p[$i - 1];
$l ^= self::safe_intval((self::safe_intval($sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]) ^
$sb[0x200 + ($r >> 8 & 0xff)]) +
$sb[0x300 + ($r & 0xff)]);
}
return pack('N*', $r ^ $p[0], $l ^ $p[1]);
}
/**
* Setup the performance-optimized function for de/encrypt()
*
* @see Common\SymmetricKey::_setupInlineCrypt()
*/
protected function setupInlineCrypt()
{
$p = $this->bctx['p'];
$init_crypt = '
static $sb;
if (!$sb) {
$sb = $this->bctx["sb"];
}
';
$safeint = self::safe_intval_inline();
// Generating encrypt code:
$encrypt_block = '
$in = unpack("N*", $in);
$l = $in[1];
$r = $in[2];
';
for ($i = 0; $i < 16; $i += 2) {
$encrypt_block .= '
$l^= ' . $p[$i] . ';
$r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]') . ' ^
$sb[0x200 + ($l >> 8 & 0xff)]) +
$sb[0x300 + ($l & 0xff)]') . ';
$r^= ' . $p[$i + 1] . ';
$l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]') . ' ^
$sb[0x200 + ($r >> 8 & 0xff)]) +
$sb[0x300 + ($r & 0xff)]') . ';
';
}
$encrypt_block .= '
$in = pack("N*",
$r ^ ' . $p[17] . ',
$l ^ ' . $p[16] . '
);
';
// Generating decrypt code:
$decrypt_block = '
$in = unpack("N*", $in);
$l = $in[1];
$r = $in[2];
';
for ($i = 17; $i > 2; $i -= 2) {
$decrypt_block .= '
$l^= ' . $p[$i] . ';
$r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]') . ' ^
$sb[0x200 + ($l >> 8 & 0xff)]) +
$sb[0x300 + ($l & 0xff)]') . ';
$r^= ' . $p[$i - 1] . ';
$l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]') . ' ^
$sb[0x200 + ($r >> 8 & 0xff)]) +
$sb[0x300 + ($r & 0xff)]') . ';
';
}
$decrypt_block .= '
$in = pack("N*",
$r ^ ' . $p[0] . ',
$l ^ ' . $p[1] . '
);
';
$this->inline_crypt = $this->createInlineCryptFunction(
[
'init_crypt' => $init_crypt,
'init_encrypt' => '',
'init_decrypt' => '',
'encrypt_block' => $encrypt_block,
'decrypt_block' => $decrypt_block
]
);
}
}

View File

@@ -0,0 +1,799 @@
<?php
/**
* Pure-PHP implementation of ChaCha20.
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2019 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt;
use phpseclib3\Exception\BadDecryptionException;
use phpseclib3\Exception\InsufficientSetupException;
/**
* Pure-PHP implementation of ChaCha20.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class ChaCha20 extends Salsa20
{
/**
* The OpenSSL specific name of the cipher
*
* @var string
*/
protected $cipher_name_openssl = 'chacha20';
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
* @param int $engine
* @return bool
*/
protected function isValidEngineHelper($engine)
{
switch ($engine) {
case self::ENGINE_LIBSODIUM:
// PHP 7.2.0 (30 Nov 2017) added support for libsodium
// we could probably make it so that if $this->counter == 0 then the first block would be done with either OpenSSL
// or PHP and then subsequent blocks would then be done with libsodium but idk - it's not a high priority atm
// we could also make it so that if $this->counter == 0 and $this->continuousBuffer then do the first string
// with libsodium and subsequent strings with openssl or pure-PHP but again not a high priority
return function_exists('sodium_crypto_aead_chacha20poly1305_ietf_encrypt') &&
$this->key_length == 32 &&
(($this->usePoly1305 && !isset($this->poly1305Key) && $this->counter == 0) || $this->counter == 1) &&
!$this->continuousBuffer;
case self::ENGINE_OPENSSL:
// OpenSSL 1.1.0 (released 25 Aug 2016) added support for chacha20.
// PHP didn't support OpenSSL 1.1.0 until 7.0.19 (11 May 2017)
// if you attempt to provide openssl with a 128 bit key (as opposed to a 256 bit key) openssl will null
// pad the key to 256 bits and still use the expansion constant for 256-bit keys. the fact that
// openssl treats the IV as both the counter and nonce, however, let's us use openssl in continuous mode
// whereas libsodium does not
if ($this->key_length != 32) {
return false;
}
}
return parent::isValidEngineHelper($engine);
}
/**
* Encrypts a message.
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
* @see self::crypt()
* @param string $plaintext
* @return string $ciphertext
*/
public function encrypt($plaintext)
{
$this->setup();
if ($this->engine == self::ENGINE_LIBSODIUM) {
return $this->encrypt_with_libsodium($plaintext);
}
return parent::encrypt($plaintext);
}
/**
* Decrypts a message.
*
* $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
* At least if the continuous buffer is disabled.
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see self::crypt()
* @param string $ciphertext
* @return string $plaintext
*/
public function decrypt($ciphertext)
{
$this->setup();
if ($this->engine == self::ENGINE_LIBSODIUM) {
return $this->decrypt_with_libsodium($ciphertext);
}
return parent::decrypt($ciphertext);
}
/**
* Encrypts a message with libsodium
*
* @see self::encrypt()
* @param string $plaintext
* @return string $text
*/
private function encrypt_with_libsodium($plaintext)
{
$params = [$plaintext, $this->aad, $this->nonce, $this->key];
$ciphertext = strlen($this->nonce) == 8 ?
sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
if (!$this->usePoly1305) {
return substr($ciphertext, 0, strlen($plaintext));
}
$newciphertext = substr($ciphertext, 0, strlen($plaintext));
$this->newtag = $this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12 ?
substr($ciphertext, strlen($plaintext)) :
$this->poly1305($newciphertext);
return $newciphertext;
}
/**
* Decrypts a message with libsodium
*
* @see self::decrypt()
* @param string $ciphertext
* @return string $text
*/
private function decrypt_with_libsodium($ciphertext)
{
$params = [$ciphertext, $this->aad, $this->nonce, $this->key];
if (isset($this->poly1305Key)) {
if ($this->oldtag === false) {
throw new InsufficientSetupException('Authentication Tag has not been set');
}
if ($this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12) {
$plaintext = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(...$params);
$this->oldtag = false;
if ($plaintext === false) {
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
}
return $plaintext;
}
$newtag = $this->poly1305($ciphertext);
if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
$this->oldtag = false;
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
}
$this->oldtag = false;
}
$plaintext = strlen($this->nonce) == 8 ?
sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
return substr($plaintext, 0, strlen($ciphertext));
}
/**
* Sets the nonce.
*
* @param string $nonce
*/
public function setNonce($nonce)
{
if (!is_string($nonce)) {
throw new \UnexpectedValueException('The nonce should be a string');
}
/*
from https://tools.ietf.org/html/rfc7539#page-7
"Note also that the original ChaCha had a 64-bit nonce and 64-bit
block count. We have modified this here to be more consistent with
recommendations in Section 3.2 of [RFC5116]."
*/
switch (strlen($nonce)) {
case 8: // 64 bits
case 12: // 96 bits
break;
default:
throw new \LengthException('Nonce of size ' . strlen($nonce) . ' not supported by this algorithm. Only 64-bit nonces or 96-bit nonces are supported');
}
$this->nonce = $nonce;
$this->changed = true;
$this->setEngine();
}
/**
* Setup the self::ENGINE_INTERNAL $engine
*
* (re)init, if necessary, the internal cipher $engine
*
* _setup() will be called each time if $changed === true
* typically this happens when using one or more of following public methods:
*
* - setKey()
*
* - setNonce()
*
* - First run of encrypt() / decrypt() with no init-settings
*
* @see self::setKey()
* @see self::setNonce()
* @see self::disableContinuousBuffer()
*/
protected function setup()
{
if (!$this->changed) {
return;
}
$this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
$this->changed = $this->nonIVChanged = false;
if ($this->nonce === false) {
throw new InsufficientSetupException('No nonce has been defined');
}
if ($this->key === false) {
throw new InsufficientSetupException('No key has been defined');
}
if ($this->usePoly1305 && !isset($this->poly1305Key)) {
$this->usingGeneratedPoly1305Key = true;
if ($this->engine == self::ENGINE_LIBSODIUM) {
return;
}
$this->createPoly1305Key();
}
$key = $this->key;
if (strlen($key) == 16) {
$constant = 'expand 16-byte k';
$key .= $key;
} else {
$constant = 'expand 32-byte k';
}
$this->p1 = $constant . $key;
$this->p2 = $this->nonce;
if (strlen($this->nonce) == 8) {
$this->p2 = "\0\0\0\0" . $this->p2;
}
}
/**
* The quarterround function
*
* @param int $a
* @param int $b
* @param int $c
* @param int $d
*/
protected static function quarterRound(&$a, &$b, &$c, &$d)
{
// in https://datatracker.ietf.org/doc/html/rfc7539#section-2.1 the addition,
// xor'ing and rotation are all on the same line so i'm keeping it on the same
// line here as well
// @codingStandardsIgnoreStart
$a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 16);
$c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 12);
$a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 8);
$c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 7);
// @codingStandardsIgnoreEnd
}
/**
* The doubleround function
*
* @param int $x0 (by reference)
* @param int $x1 (by reference)
* @param int $x2 (by reference)
* @param int $x3 (by reference)
* @param int $x4 (by reference)
* @param int $x5 (by reference)
* @param int $x6 (by reference)
* @param int $x7 (by reference)
* @param int $x8 (by reference)
* @param int $x9 (by reference)
* @param int $x10 (by reference)
* @param int $x11 (by reference)
* @param int $x12 (by reference)
* @param int $x13 (by reference)
* @param int $x14 (by reference)
* @param int $x15 (by reference)
*/
protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15)
{
// columnRound
static::quarterRound($x0, $x4, $x8, $x12);
static::quarterRound($x1, $x5, $x9, $x13);
static::quarterRound($x2, $x6, $x10, $x14);
static::quarterRound($x3, $x7, $x11, $x15);
// rowRound
static::quarterRound($x0, $x5, $x10, $x15);
static::quarterRound($x1, $x6, $x11, $x12);
static::quarterRound($x2, $x7, $x8, $x13);
static::quarterRound($x3, $x4, $x9, $x14);
}
/**
* The Salsa20 hash function function
*
* On my laptop this loop unrolled / function dereferenced version of parent::salsa20 encrypts 1mb of text in
* 0.65s vs the 0.85s that it takes with the parent method.
*
* If we were free to assume that the host OS would always be 64-bits then the if condition in leftRotate could
* be eliminated and we could knock this done to 0.60s.
*
* For comparison purposes, RC4 takes 0.16s and AES in CTR mode with the Eval engine takes 0.48s.
* AES in CTR mode with the PHP engine takes 1.19s. Salsa20 / ChaCha20 do not benefit as much from the Eval
* approach due to the fact that there are a lot less variables to de-reference, fewer loops to unroll, etc
*
* @param string $x
*/
protected static function salsa20($x)
{
list(, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15) = unpack('V*', $x);
$z0 = $x0;
$z1 = $x1;
$z2 = $x2;
$z3 = $x3;
$z4 = $x4;
$z5 = $x5;
$z6 = $x6;
$z7 = $x7;
$z8 = $x8;
$z9 = $x9;
$z10 = $x10;
$z11 = $x11;
$z12 = $x12;
$z13 = $x13;
$z14 = $x14;
$z15 = $x15;
// @codingStandardsIgnoreStart
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// @codingStandardsIgnoreEnd
$x0 += $z0;
$x1 += $z1;
$x2 += $z2;
$x3 += $z3;
$x4 += $z4;
$x5 += $z5;
$x6 += $z6;
$x7 += $z7;
$x8 += $z8;
$x9 += $z9;
$x10 += $z10;
$x11 += $z11;
$x12 += $z12;
$x13 += $z13;
$x14 += $z14;
$x15 += $z15;
return pack('V*', $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15);
}
}

View File

@@ -0,0 +1,581 @@
<?php
/**
* Base Class for all asymmetric key ciphers
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\RSA;
use phpseclib3\Exception\NoKeyLoadedException;
use phpseclib3\Exception\UnsupportedFormatException;
use phpseclib3\Math\BigInteger;
/**
* Base Class for all asymmetric cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class AsymmetricKey
{
/**
* Precomputed Zero
*
* @var BigInteger
*/
protected static $zero;
/**
* Precomputed One
*
* @var BigInteger
*/
protected static $one;
/**
* Format of the loaded key
*
* @var string
*/
protected $format;
/**
* Hash function
*
* @var Hash
*/
protected $hash;
/**
* HMAC function
*
* @var Hash
*/
private $hmac;
/**
* Supported plugins (lower case)
*
* @see self::initialize_static_variables()
* @var array
*/
private static $plugins = [];
/**
* Invisible plugins
*
* @see self::initialize_static_variables()
* @var array
*/
private static $invisiblePlugins = [];
/**
* Available Engines
*
* @var boolean[]
*/
protected static $engines = [];
/**
* Key Comment
*
* @var null|string
*/
private $comment;
/**
* @param string $type
* @return array|string
*/
abstract public function toString($type, array $options = []);
/**
* The constructor
*/
protected function __construct()
{
self::initialize_static_variables();
$this->hash = new Hash('sha256');
$this->hmac = new Hash('sha256');
}
/**
* Initialize static variables
*/
protected static function initialize_static_variables()
{
if (!isset(self::$zero)) {
self::$zero = new BigInteger(0);
self::$one = new BigInteger(1);
}
self::loadPlugins('Keys');
if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH') {
self::loadPlugins('Signature');
}
}
/**
* Load the key
*
* @param string $key
* @param string $password optional
* @return PublicKey|PrivateKey
*/
public static function load($key, $password = false)
{
self::initialize_static_variables();
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('load() should not be called from final classes (' . static::class . ')');
}
$components = false;
foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) {
if (isset(self::$invisiblePlugins[static::ALGORITHM]) && in_array($format, self::$invisiblePlugins[static::ALGORITHM])) {
continue;
}
try {
$components = $format::load($key, $password);
} catch (\Exception $e) {
$components = false;
}
if ($components !== false) {
break;
}
}
if ($components === false) {
throw new NoKeyLoadedException('Unable to read key');
}
$components['format'] = $format;
$components['secret'] = isset($components['secret']) ? $components['secret'] : '';
$comment = isset($components['comment']) ? $components['comment'] : null;
$new = static::onLoad($components);
$new->format = $format;
$new->comment = $comment;
return $new instanceof PrivateKey ?
$new->withPassword($password) :
$new;
}
/**
* Loads a private key
*
* @return PrivateKey
* @param string|array $key
* @param string $password optional
*/
public static function loadPrivateKey($key, $password = '')
{
$key = self::load($key, $password);
if (!$key instanceof PrivateKey) {
throw new NoKeyLoadedException('The key that was loaded was not a private key');
}
return $key;
}
/**
* Loads a public key
*
* @return PublicKey
* @param string|array $key
*/
public static function loadPublicKey($key)
{
$key = self::load($key);
if (!$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a public key');
}
return $key;
}
/**
* Loads parameters
*
* @return AsymmetricKey
* @param string|array $key
*/
public static function loadParameters($key)
{
$key = self::load($key);
if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a parameter');
}
return $key;
}
/**
* Load the key, assuming a specific format
*
* @param string $type
* @param string $key
* @param string $password optional
* @return static
*/
public static function loadFormat($type, $key, $password = false)
{
self::initialize_static_variables();
$components = false;
$format = strtolower($type);
if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) {
$format = self::$plugins[static::ALGORITHM]['Keys'][$format];
$components = $format::load($key, $password);
}
if ($components === false) {
throw new NoKeyLoadedException('Unable to read key');
}
$components['format'] = $format;
$components['secret'] = isset($components['secret']) ? $components['secret'] : '';
$new = static::onLoad($components);
$new->format = $format;
return $new instanceof PrivateKey ?
$new->withPassword($password) :
$new;
}
/**
* Loads a private key
*
* @return PrivateKey
* @param string $type
* @param string $key
* @param string $password optional
*/
public static function loadPrivateKeyFormat($type, $key, $password = false)
{
$key = self::loadFormat($type, $key, $password);
if (!$key instanceof PrivateKey) {
throw new NoKeyLoadedException('The key that was loaded was not a private key');
}
return $key;
}
/**
* Loads a public key
*
* @return PublicKey
* @param string $type
* @param string $key
*/
public static function loadPublicKeyFormat($type, $key)
{
$key = self::loadFormat($type, $key);
if (!$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a public key');
}
return $key;
}
/**
* Loads parameters
*
* @return AsymmetricKey
* @param string $type
* @param string|array $key
*/
public static function loadParametersFormat($type, $key)
{
$key = self::loadFormat($type, $key);
if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a parameter');
}
return $key;
}
/**
* Validate Plugin
*
* @param string $format
* @param string $type
* @param string $method optional
* @return mixed
*/
protected static function validatePlugin($format, $type, $method = null)
{
$type = strtolower($type);
if (!isset(self::$plugins[static::ALGORITHM][$format][$type])) {
throw new UnsupportedFormatException("$type is not a supported format");
}
$type = self::$plugins[static::ALGORITHM][$format][$type];
if (isset($method) && !method_exists($type, $method)) {
throw new UnsupportedFormatException("$type does not implement $method");
}
return $type;
}
/**
* Load Plugins
*
* @param string $format
*/
private static function loadPlugins($format)
{
if (!isset(self::$plugins[static::ALGORITHM][$format])) {
self::$plugins[static::ALGORITHM][$format] = [];
foreach (new \DirectoryIterator(__DIR__ . '/../' . static::ALGORITHM . '/Formats/' . $format . '/') as $file) {
if ($file->getExtension() != 'php') {
continue;
}
$name = $file->getBasename('.php');
if ($name[0] == '.') {
continue;
}
$type = 'phpseclib3\Crypt\\' . static::ALGORITHM . '\\Formats\\' . $format . '\\' . $name;
$reflect = new \ReflectionClass($type);
if ($reflect->isTrait()) {
continue;
}
self::$plugins[static::ALGORITHM][$format][strtolower($name)] = $type;
if ($reflect->hasConstant('IS_INVISIBLE')) {
self::$invisiblePlugins[static::ALGORITHM][] = $type;
}
}
}
}
/**
* Returns a list of supported formats.
*
* @return array
*/
public static function getSupportedKeyFormats()
{
self::initialize_static_variables();
return self::$plugins[static::ALGORITHM]['Keys'];
}
/**
* Add a fileformat plugin
*
* The plugin needs to either already be loaded or be auto-loadable.
* Loading a plugin whose shortname overwrite an existing shortname will overwrite the old plugin.
*
* @see self::load()
* @param string $fullname
* @return bool
*/
public static function addFileFormat($fullname)
{
self::initialize_static_variables();
if (class_exists($fullname)) {
$meta = new \ReflectionClass($fullname);
$shortname = $meta->getShortName();
self::$plugins[static::ALGORITHM]['Keys'][strtolower($shortname)] = $fullname;
if ($meta->hasConstant('IS_INVISIBLE')) {
self::$invisiblePlugins[static::ALGORITHM][] = strtolower($shortname);
}
}
}
/**
* Returns the format of the loaded key.
*
* If the key that was loaded wasn't in a valid or if the key was auto-generated
* with RSA::createKey() then this will throw an exception.
*
* @see self::load()
* @return mixed
*/
public function getLoadedFormat()
{
if (empty($this->format)) {
throw new NoKeyLoadedException('This key was created with createKey - it was not loaded with load. Therefore there is no "loaded format"');
}
$meta = new \ReflectionClass($this->format);
return $meta->getShortName();
}
/**
* Returns the key's comment
*
* Not all key formats support comments. If you want to set a comment use toString()
*
* @return null|string
*/
public function getComment()
{
return $this->comment;
}
/**
* Tests engine validity
*
*/
public static function useBestEngine()
{
static::$engines = [
'PHP' => true,
'OpenSSL' => extension_loaded('openssl'),
// this test can be satisfied by either of the following:
// http://php.net/manual/en/book.sodium.php
// https://github.com/paragonie/sodium_compat
'libsodium' => function_exists('sodium_crypto_sign_keypair')
];
return static::$engines;
}
/**
* Flag to use internal engine only (useful for unit testing)
*
*/
public static function useInternalEngine()
{
static::$engines = [
'PHP' => true,
'OpenSSL' => false,
'libsodium' => false
];
}
/**
* __toString() magic method
*
* @return string
*/
public function __toString()
{
return $this->toString('PKCS8');
}
/**
* Determines which hashing function should be used
*
* @param string $hash
*/
public function withHash($hash)
{
$new = clone $this;
$new->hash = new Hash($hash);
$new->hmac = new Hash($hash);
return $new;
}
/**
* Returns the hash algorithm currently being used
*
*/
public function getHash()
{
return clone $this->hash;
}
/**
* Compute the pseudorandom k for signature generation,
* using the process specified for deterministic DSA.
*
* @param string $h1
* @return string
*/
protected function computek($h1)
{
$v = str_repeat("\1", strlen($h1));
$k = str_repeat("\0", strlen($h1));
$x = $this->int2octets($this->x);
$h1 = $this->bits2octets($h1);
$this->hmac->setKey($k);
$k = $this->hmac->hash($v . "\0" . $x . $h1);
$this->hmac->setKey($k);
$v = $this->hmac->hash($v);
$k = $this->hmac->hash($v . "\1" . $x . $h1);
$this->hmac->setKey($k);
$v = $this->hmac->hash($v);
$qlen = $this->q->getLengthInBytes();
while (true) {
$t = '';
while (strlen($t) < $qlen) {
$v = $this->hmac->hash($v);
$t = $t . $v;
}
$k = $this->bits2int($t);
if (!$k->equals(self::$zero) && $k->compare($this->q) < 0) {
break;
}
$k = $this->hmac->hash($v . "\0");
$this->hmac->setKey($k);
$v = $this->hmac->hash($v);
}
return $k;
}
/**
* Integer to Octet String
*
* @param BigInteger $v
* @return string
*/
private function int2octets($v)
{
$out = $v->toBytes();
$rolen = $this->q->getLengthInBytes();
if (strlen($out) < $rolen) {
return str_pad($out, $rolen, "\0", STR_PAD_LEFT);
} elseif (strlen($out) > $rolen) {
return substr($out, -$rolen);
} else {
return $out;
}
}
/**
* Bit String to Integer
*
* @param string $in
* @return BigInteger
*/
protected function bits2int($in)
{
$v = new BigInteger($in, 256);
$vlen = strlen($in) << 3;
$qlen = $this->q->getLength();
if ($vlen > $qlen) {
return $v->bitwise_rightShift($vlen - $qlen);
}
return $v;
}
/**
* Bit String to Octet String
*
* @param string $in
* @return string
*/
private function bits2octets($in)
{
$z1 = $this->bits2int($in);
$z2 = $z1->subtract($this->q);
return $z2->compare(self::$zero) < 0 ?
$this->int2octets($z1) :
$this->int2octets($z2);
}
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* Base Class for all block ciphers
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
/**
* Base Class for all block cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class BlockCipher extends SymmetricKey
{
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* JSON Web Key (RFC7517) Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
/**
* JSON Web Key Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class JWK
{
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password
* @return array
*/
public static function load($key, $password = '')
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
$key = preg_replace('#\s#', '', $key); // remove whitespace
if (PHP_VERSION_ID >= 73000) {
$key = json_decode($key, null, 512, JSON_THROW_ON_ERROR);
} else {
$key = json_decode($key);
if (!$key) {
throw new \RuntimeException('Unable to decode JSON');
}
}
if (isset($key->kty)) {
return $key;
}
if (!is_object($key)) {
throw new \RuntimeException('invalid JWK: not an object');
}
if (!isset($key->keys)) {
throw new \RuntimeException('invalid JWK: object has no property "keys"');
}
if (count($key->keys) != 1) {
throw new \RuntimeException('Although the JWK key format supports multiple keys phpseclib does not');
}
return $key->keys[0];
}
/**
* Wrap a key appropriately
*
* @return string
*/
protected static function wrapKey(array $key, array $options)
{
return json_encode(['keys' => [$key + $options]]);
}
}

View File

@@ -0,0 +1,224 @@
<?php
/**
* OpenSSH Key Handler
*
* PHP version 5
*
* Place in $HOME/.ssh/authorized_keys
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\Random;
use phpseclib3\Exception\BadDecryptionException;
/**
* OpenSSH Formatted RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class OpenSSH
{
/**
* Default comment
*
* @var string
*/
protected static $comment = 'phpseclib-generated-key';
/**
* Binary key flag
*
* @var bool
*/
protected static $binary = false;
/**
* Sets the default comment
*
* @param string $comment
*/
public static function setComment($comment)
{
self::$comment = str_replace(["\r", "\n"], '', $comment);
}
/**
* Break a public or private key down into its constituent components
*
* $type can be either ssh-dss or ssh-rsa
*
* @param string $key
* @param string $password
* @return array
*/
public static function load($key, $password = '')
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
// key format is described here:
// https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
if (strpos($key, 'BEGIN OPENSSH PRIVATE KEY') !== false) {
$key = preg_replace('#(?:^-.*?-[\r\n]*$)|\s#ms', '', $key);
$key = Strings::base64_decode($key);
$magic = Strings::shift($key, 15);
if ($magic != "openssh-key-v1\0") {
throw new \RuntimeException('Expected openssh-key-v1');
}
list($ciphername, $kdfname, $kdfoptions, $numKeys) = Strings::unpackSSH2('sssN', $key);
if ($numKeys != 1) {
// if we wanted to support multiple keys we could update PublicKeyLoader to preview what the # of keys
// would be; it'd then call Common\Keys\OpenSSH.php::load() and get the paddedKey. it'd then pass
// that to the appropriate key loading parser $numKey times or something
throw new \RuntimeException('Although the OpenSSH private key format supports multiple keys phpseclib does not');
}
switch ($ciphername) {
case 'none':
break;
case 'aes256-ctr':
if ($kdfname != 'bcrypt') {
throw new \RuntimeException('Only the bcrypt kdf is supported (' . $kdfname . ' encountered)');
}
list($salt, $rounds) = Strings::unpackSSH2('sN', $kdfoptions);
$crypto = new AES('ctr');
//$crypto->setKeyLength(256);
//$crypto->disablePadding();
$crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
break;
default:
throw new \RuntimeException('The only supported ciphers are: none, aes256-ctr (' . $ciphername . ' is being used)');
}
list($publicKey, $paddedKey) = Strings::unpackSSH2('ss', $key);
list($type) = Strings::unpackSSH2('s', $publicKey);
if (isset($crypto)) {
$paddedKey = $crypto->decrypt($paddedKey);
}
list($checkint1, $checkint2) = Strings::unpackSSH2('NN', $paddedKey);
// any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc.
if ($checkint1 != $checkint2) {
if (isset($crypto)) {
throw new BadDecryptionException('Unable to decrypt key - please verify the password you are using');
}
throw new \RuntimeException("The two checkints do not match ($checkint1 vs. $checkint2)");
}
self::checkType($type);
return compact('type', 'publicKey', 'paddedKey');
}
$parts = explode(' ', $key, 3);
if (!isset($parts[1])) {
$key = base64_decode($parts[0]);
$comment = false;
} else {
$asciiType = $parts[0];
self::checkType($parts[0]);
$key = base64_decode($parts[1]);
$comment = isset($parts[2]) ? $parts[2] : false;
}
if ($key === false) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
list($type) = Strings::unpackSSH2('s', $key);
self::checkType($type);
if (isset($asciiType) && $asciiType != $type) {
throw new \RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $type);
}
if (strlen($key) <= 4) {
throw new \UnexpectedValueException('Key appears to be malformed');
}
$publicKey = $key;
return compact('type', 'publicKey', 'comment');
}
/**
* Toggle between binary and printable keys
*
* Printable keys are what are generated by default. These are the ones that go in
* $HOME/.ssh/authorized_key.
*
* @param bool $enabled
*/
public static function setBinaryOutput($enabled)
{
self::$binary = $enabled;
}
/**
* Checks to see if the type is valid
*
* @param string $candidate
*/
private static function checkType($candidate)
{
if (!in_array($candidate, static::$types)) {
throw new \RuntimeException("The key type ($candidate) is not equal to: " . implode(',', static::$types));
}
}
/**
* Wrap a private key appropriately
*
* @param string $publicKey
* @param string $privateKey
* @param string $password
* @param array $options
* @return string
*/
protected static function wrapPrivateKey($publicKey, $privateKey, $password, $options)
{
list(, $checkint) = unpack('N', Random::string(4));
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
$paddedKey = Strings::packSSH2('NN', $checkint, $checkint) .
$privateKey .
Strings::packSSH2('s', $comment);
$usesEncryption = !empty($password) && is_string($password);
/*
from http://tools.ietf.org/html/rfc4253#section-6 :
Note that the length of the concatenation of 'packet_length',
'padding_length', 'payload', and 'random padding' MUST be a multiple
of the cipher block size or 8, whichever is larger.
*/
$blockSize = $usesEncryption ? 16 : 8;
$paddingLength = (($blockSize - 1) * strlen($paddedKey)) % $blockSize;
for ($i = 1; $i <= $paddingLength; $i++) {
$paddedKey .= chr($i);
}
if (!$usesEncryption) {
$key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey);
} else {
$rounds = isset($options['rounds']) ? $options['rounds'] : 16;
$salt = Random::string(16);
$kdfoptions = Strings::packSSH2('sN', $salt, $rounds);
$crypto = new AES('ctr');
$crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
$paddedKey = $crypto->encrypt($paddedKey);
$key = Strings::packSSH2('sssNss', 'aes256-ctr', 'bcrypt', $kdfoptions, 1, $publicKey, $paddedKey);
}
$key = "openssh-key-v1\0$key";
return "-----BEGIN OPENSSH PRIVATE KEY-----\n" .
chunk_split(Strings::base64_encode($key), 70, "\n") .
"-----END OPENSSH PRIVATE KEY-----\n";
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* PKCS Formatted Key Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
/**
* PKCS1 Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS
{
/**
* Auto-detect the format
*/
const MODE_ANY = 0;
/**
* Require base64-encoded PEM's be supplied
*/
const MODE_PEM = 1;
/**
* Require raw DER's be supplied
*/
const MODE_DER = 2;
/**#@-*/
/**
* Is the key a base-64 encoded PEM, DER or should it be auto-detected?
*
* @var int
*/
protected static $format = self::MODE_ANY;
/**
* Require base64-encoded PEM's be supplied
*
*/
public static function requirePEM()
{
self::$format = self::MODE_PEM;
}
/**
* Require raw DER's be supplied
*
*/
public static function requireDER()
{
self::$format = self::MODE_DER;
}
/**
* Accept any format and auto detect the format
*
* This is the default setting
*
*/
public static function requireAny()
{
self::$format = self::MODE_ANY;
}
}

View File

@@ -0,0 +1,209 @@
<?php
/**
* PKCS1 Formatted Key Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\DES;
use phpseclib3\Crypt\Random;
use phpseclib3\Crypt\TripleDES;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\File\ASN1;
/**
* PKCS1 Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS1 extends PKCS
{
/**
* Default encryption algorithm
*
* @var string
*/
private static $defaultEncryptionAlgorithm = 'AES-128-CBC';
/**
* Sets the default encryption algorithm
*
* @param string $algo
*/
public static function setEncryptionAlgorithm($algo)
{
self::$defaultEncryptionAlgorithm = $algo;
}
/**
* Returns the mode constant corresponding to the mode string
*
* @param string $mode
* @return int
* @throws \UnexpectedValueException if the block cipher mode is unsupported
*/
private static function getEncryptionMode($mode)
{
switch ($mode) {
case 'CBC':
case 'ECB':
case 'CFB':
case 'OFB':
case 'CTR':
return $mode;
}
throw new \UnexpectedValueException('Unsupported block cipher mode of operation');
}
/**
* Returns a cipher object corresponding to a string
*
* @param string $algo
* @return string
* @throws \UnexpectedValueException if the encryption algorithm is unsupported
*/
private static function getEncryptionObject($algo)
{
$modes = '(CBC|ECB|CFB|OFB|CTR)';
switch (true) {
case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches):
$cipher = new AES(self::getEncryptionMode($matches[2]));
$cipher->setKeyLength($matches[1]);
return $cipher;
case preg_match("#^DES-EDE3-$modes$#", $algo, $matches):
return new TripleDES(self::getEncryptionMode($matches[1]));
case preg_match("#^DES-$modes$#", $algo, $matches):
return new DES(self::getEncryptionMode($matches[1]));
default:
throw new UnsupportedAlgorithmException($algo . ' is not a supported algorithm');
}
}
/**
* Generate a symmetric key for PKCS#1 keys
*
* @param string $password
* @param string $iv
* @param int $length
* @return string
*/
private static function generateSymmetricKey($password, $iv, $length)
{
$symkey = '';
$iv = substr($iv, 0, 8);
while (strlen($symkey) < $length) {
$symkey .= md5($symkey . $password . $iv, true);
}
return substr($symkey, 0, $length);
}
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
protected static function load($key, $password)
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
/* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
"outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding
two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here:
http://tools.ietf.org/html/rfc1421#section-4.6.1.1
http://tools.ietf.org/html/rfc1421#section-4.6.1.3
DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell.
DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation
function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's
own implementation. ie. the implementation *is* the standard and any bugs that may exist in that
implementation are part of the standard, as well.
* OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */
if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) {
$iv = Strings::hex2bin(trim($matches[2]));
// remove the Proc-Type / DEK-Info sections as they're no longer needed
$key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
$ciphertext = ASN1::extractBER($key);
if ($ciphertext === false) {
$ciphertext = $key;
}
$crypto = self::getEncryptionObject($matches[1]);
$crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3));
$crypto->setIV($iv);
$key = $crypto->decrypt($ciphertext);
} else {
if (self::$format != self::MODE_DER) {
$decoded = ASN1::extractBER($key);
if ($decoded !== false) {
$key = $decoded;
} elseif (self::$format == self::MODE_PEM) {
throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
}
}
}
return $key;
}
/**
* Wrap a private key appropriately
*
* @param string $key
* @param string $type
* @param string $password
* @param array $options optional
* @return string
*/
protected static function wrapPrivateKey($key, $type, $password, array $options = [])
{
if (empty($password) || !is_string($password)) {
return "-----BEGIN $type PRIVATE KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END $type PRIVATE KEY-----";
}
$encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm;
$cipher = self::getEncryptionObject($encryptionAlgorithm);
$iv = Random::string($cipher->getBlockLength() >> 3);
$cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3));
$cipher->setIV($iv);
$iv = strtoupper(Strings::bin2hex($iv));
return "-----BEGIN $type PRIVATE KEY-----\r\n" .
"Proc-Type: 4,ENCRYPTED\r\n" .
"DEK-Info: " . $encryptionAlgorithm . ",$iv\r\n" .
"\r\n" .
chunk_split(Strings::base64_encode($cipher->encrypt($key)), 64) .
"-----END $type PRIVATE KEY-----";
}
/**
* Wrap a public key appropriately
*
* @param string $key
* @param string $type
* @return string
*/
protected static function wrapPublicKey($key, $type)
{
return "-----BEGIN $type PUBLIC KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END $type PUBLIC KEY-----";
}
}

View File

@@ -0,0 +1,766 @@
<?php
/**
* PKCS#8 Formatted Key Handler
*
* PHP version 5
*
* Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
* is specific to private keys it's basically creating a DER-encoded wrapper
* for keys. This just extends that same concept to public keys (much like ssh-keygen)
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\DES;
use phpseclib3\Crypt\Random;
use phpseclib3\Crypt\RC2;
use phpseclib3\Crypt\RC4;
use phpseclib3\Crypt\TripleDES;
use phpseclib3\Exception\InsufficientSetupException;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
/**
* PKCS#8 Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS8 extends PKCS
{
/**
* Default encryption algorithm
*
* @var string
*/
private static $defaultEncryptionAlgorithm = 'id-PBES2';
/**
* Default encryption scheme
*
* Only used when defaultEncryptionAlgorithm is id-PBES2
*
* @var string
*/
private static $defaultEncryptionScheme = 'aes128-CBC-PAD';
/**
* Default PRF
*
* Only used when defaultEncryptionAlgorithm is id-PBES2
*
* @var string
*/
private static $defaultPRF = 'id-hmacWithSHA256';
/**
* Default Iteration Count
*
* @var int
*/
private static $defaultIterationCount = 2048;
/**
* OIDs loaded
*
* @var bool
*/
private static $oidsLoaded = false;
/**
* Binary key flag
*
* @var bool
*/
private static $binary = false;
/**
* Sets the default encryption algorithm
*
* @param string $algo
*/
public static function setEncryptionAlgorithm($algo)
{
self::$defaultEncryptionAlgorithm = $algo;
}
/**
* Sets the default encryption algorithm for PBES2
*
* @param string $algo
*/
public static function setEncryptionScheme($algo)
{
self::$defaultEncryptionScheme = $algo;
}
/**
* Sets the iteration count
*
* @param int $count
*/
public static function setIterationCount($count)
{
self::$defaultIterationCount = $count;
}
/**
* Sets the PRF for PBES2
*
* @param string $algo
*/
public static function setPRF($algo)
{
self::$defaultPRF = $algo;
}
/**
* Returns a SymmetricKey object based on a PBES1 $algo
*
* @return \phpseclib3\Crypt\Common\SymmetricKey
* @param string $algo
*/
private static function getPBES1EncryptionObject($algo)
{
$algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ?
$matches[1] :
substr($algo, 13); // strlen('pbeWithSHAAnd') == 13
switch ($algo) {
case 'DES':
$cipher = new DES('cbc');
break;
case 'RC2':
$cipher = new RC2('cbc');
$cipher->setKeyLength(64);
break;
case '3-KeyTripleDES':
$cipher = new TripleDES('cbc');
break;
case '2-KeyTripleDES':
$cipher = new TripleDES('cbc');
$cipher->setKeyLength(128);
break;
case '128BitRC2':
$cipher = new RC2('cbc');
$cipher->setKeyLength(128);
break;
case '40BitRC2':
$cipher = new RC2('cbc');
$cipher->setKeyLength(40);
break;
case '128BitRC4':
$cipher = new RC4();
$cipher->setKeyLength(128);
break;
case '40BitRC4':
$cipher = new RC4();
$cipher->setKeyLength(40);
break;
default:
throw new UnsupportedAlgorithmException("$algo is not a supported algorithm");
}
return $cipher;
}
/**
* Returns a hash based on a PBES1 $algo
*
* @return string
* @param string $algo
*/
private static function getPBES1Hash($algo)
{
if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) {
return $matches[1] == 'SHA' ? 'sha1' : $matches[1];
}
return 'sha1';
}
/**
* Returns a KDF baesd on a PBES1 $algo
*
* @return string
* @param string $algo
*/
private static function getPBES1KDF($algo)
{
switch ($algo) {
case 'pbeWithMD2AndDES-CBC':
case 'pbeWithMD2AndRC2-CBC':
case 'pbeWithMD5AndDES-CBC':
case 'pbeWithMD5AndRC2-CBC':
case 'pbeWithSHA1AndDES-CBC':
case 'pbeWithSHA1AndRC2-CBC':
return 'pbkdf1';
}
return 'pkcs12';
}
/**
* Returns a SymmetricKey object baesd on a PBES2 $algo
*
* @return SymmetricKey
* @param string $algo
*/
private static function getPBES2EncryptionObject($algo)
{
switch ($algo) {
case 'desCBC':
$cipher = new DES('cbc');
break;
case 'des-EDE3-CBC':
$cipher = new TripleDES('cbc');
break;
case 'rc2CBC':
$cipher = new RC2('cbc');
// in theory this can be changed
$cipher->setKeyLength(128);
break;
case 'rc5-CBC-PAD':
throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys');
case 'aes128-CBC-PAD':
case 'aes192-CBC-PAD':
case 'aes256-CBC-PAD':
$cipher = new AES('cbc');
$cipher->setKeyLength(substr($algo, 3, 3));
break;
default:
throw new UnsupportedAlgorithmException("$algo is not supported");
}
return $cipher;
}
/**
* Initialize static variables
*
*/
private static function initialize_static_variables()
{
if (!isset(static::$childOIDsLoaded)) {
throw new InsufficientSetupException('This class should not be called directly');
}
if (!static::$childOIDsLoaded) {
ASN1::loadOIDs(is_array(static::OID_NAME) ?
array_combine(static::OID_NAME, static::OID_VALUE) :
[static::OID_NAME => static::OID_VALUE]);
static::$childOIDsLoaded = true;
}
if (!self::$oidsLoaded) {
// from https://tools.ietf.org/html/rfc2898
ASN1::loadOIDs([
// PBES1 encryption schemes
'pbeWithMD2AndDES-CBC' => '1.2.840.113549.1.5.1',
'pbeWithMD2AndRC2-CBC' => '1.2.840.113549.1.5.4',
'pbeWithMD5AndDES-CBC' => '1.2.840.113549.1.5.3',
'pbeWithMD5AndRC2-CBC' => '1.2.840.113549.1.5.6',
'pbeWithSHA1AndDES-CBC' => '1.2.840.113549.1.5.10',
'pbeWithSHA1AndRC2-CBC' => '1.2.840.113549.1.5.11',
// from PKCS#12:
// https://tools.ietf.org/html/rfc7292
'pbeWithSHAAnd128BitRC4' => '1.2.840.113549.1.12.1.1',
'pbeWithSHAAnd40BitRC4' => '1.2.840.113549.1.12.1.2',
'pbeWithSHAAnd3-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.3',
'pbeWithSHAAnd2-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.4',
'pbeWithSHAAnd128BitRC2-CBC' => '1.2.840.113549.1.12.1.5',
'pbeWithSHAAnd40BitRC2-CBC' => '1.2.840.113549.1.12.1.6',
'id-PBKDF2' => '1.2.840.113549.1.5.12',
'id-PBES2' => '1.2.840.113549.1.5.13',
'id-PBMAC1' => '1.2.840.113549.1.5.14',
// from PKCS#5 v2.1:
// http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf
'id-hmacWithSHA1' => '1.2.840.113549.2.7',
'id-hmacWithSHA224' => '1.2.840.113549.2.8',
'id-hmacWithSHA256' => '1.2.840.113549.2.9',
'id-hmacWithSHA384' => '1.2.840.113549.2.10',
'id-hmacWithSHA512' => '1.2.840.113549.2.11',
'id-hmacWithSHA512-224' => '1.2.840.113549.2.12',
'id-hmacWithSHA512-256' => '1.2.840.113549.2.13',
'desCBC' => '1.3.14.3.2.7',
'des-EDE3-CBC' => '1.2.840.113549.3.7',
'rc2CBC' => '1.2.840.113549.3.2',
'rc5-CBC-PAD' => '1.2.840.113549.3.9',
'aes128-CBC-PAD' => '2.16.840.1.101.3.4.1.2',
'aes192-CBC-PAD' => '2.16.840.1.101.3.4.1.22',
'aes256-CBC-PAD' => '2.16.840.1.101.3.4.1.42'
]);
self::$oidsLoaded = true;
}
}
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
protected static function load($key, $password = '')
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
$isPublic = strpos($key, 'PUBLIC') !== false;
$isPrivate = strpos($key, 'PRIVATE') !== false;
$decoded = self::preParse($key);
$meta = [];
$decrypted = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
if (strlen($password) && is_array($decrypted)) {
$algorithm = $decrypted['encryptionAlgorithm']['algorithm'];
switch ($algorithm) {
// PBES1
case 'pbeWithMD2AndDES-CBC':
case 'pbeWithMD2AndRC2-CBC':
case 'pbeWithMD5AndDES-CBC':
case 'pbeWithMD5AndRC2-CBC':
case 'pbeWithSHA1AndDES-CBC':
case 'pbeWithSHA1AndRC2-CBC':
case 'pbeWithSHAAnd3-KeyTripleDES-CBC':
case 'pbeWithSHAAnd2-KeyTripleDES-CBC':
case 'pbeWithSHAAnd128BitRC2-CBC':
case 'pbeWithSHAAnd40BitRC2-CBC':
case 'pbeWithSHAAnd128BitRC4':
case 'pbeWithSHAAnd40BitRC4':
$cipher = self::getPBES1EncryptionObject($algorithm);
$hash = self::getPBES1Hash($algorithm);
$kdf = self::getPBES1KDF($algorithm);
$meta['meta']['algorithm'] = $algorithm;
$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
$map = ASN1::asn1map($temp[0], Maps\PBEParameter::MAP);
$salt = $map['salt'];
$iterationCount = $map['iterationCount'];
$iterationCount = (int) $iterationCount->toString();
$cipher->setPassword($password, $kdf, $hash, $salt, $iterationCount);
$key = $cipher->decrypt($decrypted['encryptedData']);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER 2');
}
break;
case 'id-PBES2':
$meta['meta']['algorithm'] = $algorithm;
$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
$temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
$keyDerivationFunc = $temp['keyDerivationFunc'];
$encryptionScheme = $temp['encryptionScheme'];
$cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']);
$meta['meta']['cipher'] = $encryptionScheme['algorithm'];
$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
$temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
$keyDerivationFunc = $temp['keyDerivationFunc'];
$encryptionScheme = $temp['encryptionScheme'];
if (!$cipher instanceof RC2) {
$cipher->setIV($encryptionScheme['parameters']['octetString']);
} else {
$temp = ASN1::decodeBER($encryptionScheme['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
$map = ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP);
$rc2ParametersVersion = $map['rc2ParametersVersion'];
$iv = $map['iv'];
$effectiveKeyLength = (int) $rc2ParametersVersion->toString();
switch ($effectiveKeyLength) {
case 160:
$effectiveKeyLength = 40;
break;
case 120:
$effectiveKeyLength = 64;
break;
case 58:
$effectiveKeyLength = 128;
break;
//default: // should be >= 256
}
$cipher->setIV($iv);
$cipher->setKeyLength($effectiveKeyLength);
}
$meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm'];
switch ($keyDerivationFunc['algorithm']) {
case 'id-PBKDF2':
$temp = ASN1::decodeBER($keyDerivationFunc['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
$params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP);
if (empty($params['prf'])) {
$params['prf'] = ['algorithm' => 'id-hmacWithSHA1'];
}
$salt = $params['salt'];
$iterationCount = $params['iterationCount'];
$prf = $params['prf'];
$meta['meta']['prf'] = $prf['algorithm'];
$hash = str_replace('-', '/', substr($prf['algorithm'], 11));
$params = [
$password,
'pbkdf2',
$hash,
$salt,
(int) $iterationCount->toString()
];
if (isset($keyLength)) {
$params[] = (int) $keyLength->toString();
}
$cipher->setPassword(...$params);
$key = $cipher->decrypt($decrypted['encryptedData']);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER 3');
}
break;
default:
throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys');
}
break;
case 'id-PBMAC1':
//$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
//$value = ASN1::asn1map($temp[0], Maps\PBMAC1params::MAP);
// since i can't find any implementation that does PBMAC1 it is unsupported
throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.');
// at this point we'll assume that the key conforms to PublicKeyInfo
}
}
$private = ASN1::asn1map($decoded[0], Maps\OneAsymmetricKey::MAP);
if (is_array($private)) {
if ($isPublic) {
throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key');
}
if (isset($private['privateKeyAlgorithm']['parameters']) && !$private['privateKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][1]['content'][1])) {
$temp = $decoded[0]['content'][1]['content'][1];
$private['privateKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
}
if (is_array(static::OID_NAME)) {
if (!in_array($private['privateKeyAlgorithm']['algorithm'], static::OID_NAME)) {
throw new UnsupportedAlgorithmException($private['privateKeyAlgorithm']['algorithm'] . ' is not a supported key type');
}
} else {
if ($private['privateKeyAlgorithm']['algorithm'] != static::OID_NAME) {
throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $private['privateKeyAlgorithm']['algorithm'] . ' key');
}
}
if (isset($private['publicKey'])) {
if ($private['publicKey'][0] != "\0") {
throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($private['publicKey'][0]));
}
$private['publicKey'] = substr($private['publicKey'], 1);
}
return $private + $meta;
}
// EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference
// is that the former has an octet string and the later has a bit string. the first byte of a bit
// string represents the number of bits in the last byte that are to be ignored but, currently,
// bit strings wanting a non-zero amount of bits trimmed are not supported
$public = ASN1::asn1map($decoded[0], Maps\PublicKeyInfo::MAP);
if (is_array($public)) {
if ($isPrivate) {
throw new \UnexpectedValueException('Human readable string claims private key but DER encoded string claims public key');
}
if ($public['publicKey'][0] != "\0") {
throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($public['publicKey'][0]));
}
if (is_array(static::OID_NAME)) {
if (!in_array($public['publicKeyAlgorithm']['algorithm'], static::OID_NAME)) {
throw new UnsupportedAlgorithmException($public['publicKeyAlgorithm']['algorithm'] . ' is not a supported key type');
}
} else {
if ($public['publicKeyAlgorithm']['algorithm'] != static::OID_NAME) {
throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $public['publicKeyAlgorithm']['algorithm'] . ' key');
}
}
if (isset($public['publicKeyAlgorithm']['parameters']) && !$public['publicKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][0]['content'][1])) {
$temp = $decoded[0]['content'][0]['content'][1];
$public['publicKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
}
$public['publicKey'] = substr($public['publicKey'], 1);
return $public;
}
throw new \RuntimeException('Unable to parse using either OneAsymmetricKey or PublicKeyInfo ASN1 maps');
}
/**
* Toggle between binary (DER) and printable (PEM) keys
*
* Printable keys are what are generated by default.
*
* @param bool $enabled
*/
public static function setBinaryOutput($enabled)
{
self::$binary = $enabled;
}
/**
* Wrap a private key appropriately
*
* @param string $key
* @param string $attr
* @param mixed $params
* @param string $password
* @param string $oid optional
* @param string $publicKey optional
* @param array $options optional
* @return string
*/
protected static function wrapPrivateKey($key, $attr, $params, $password, $oid = null, $publicKey = '', array $options = [])
{
self::initialize_static_variables();
$key = [
'version' => 'v1',
'privateKeyAlgorithm' => [
'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid
],
'privateKey' => $key
];
if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
$key['privateKeyAlgorithm']['parameters'] = $params;
}
if (!empty($attr)) {
$key['attributes'] = $attr;
}
if (!empty($publicKey)) {
$key['version'] = 'v2';
$key['publicKey'] = $publicKey;
}
$key = ASN1::encodeDER($key, Maps\OneAsymmetricKey::MAP);
if (!empty($password) && is_string($password)) {
$salt = Random::string(8);
$iterationCount = isset($options['iterationCount']) ? $options['iterationCount'] : self::$defaultIterationCount;
$encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm;
$encryptionScheme = isset($options['encryptionScheme']) ? $options['encryptionScheme'] : self::$defaultEncryptionScheme;
$prf = isset($options['PRF']) ? $options['PRF'] : self::$defaultPRF;
if ($encryptionAlgorithm == 'id-PBES2') {
$crypto = self::getPBES2EncryptionObject($encryptionScheme);
$hash = str_replace('-', '/', substr($prf, 11));
$kdf = 'pbkdf2';
$iv = Random::string($crypto->getBlockLength() >> 3);
$PBKDF2params = [
'salt' => $salt,
'iterationCount' => $iterationCount,
'prf' => ['algorithm' => $prf, 'parameters' => null]
];
$PBKDF2params = ASN1::encodeDER($PBKDF2params, Maps\PBKDF2params::MAP);
if (!$crypto instanceof RC2) {
$params = ['octetString' => $iv];
} else {
$params = [
'rc2ParametersVersion' => 58,
'iv' => $iv
];
$params = ASN1::encodeDER($params, Maps\RC2CBCParameter::MAP);
$params = new ASN1\Element($params);
}
$params = [
'keyDerivationFunc' => [
'algorithm' => 'id-PBKDF2',
'parameters' => new ASN1\Element($PBKDF2params)
],
'encryptionScheme' => [
'algorithm' => $encryptionScheme,
'parameters' => $params
]
];
$params = ASN1::encodeDER($params, Maps\PBES2params::MAP);
$crypto->setIV($iv);
} else {
$crypto = self::getPBES1EncryptionObject($encryptionAlgorithm);
$hash = self::getPBES1Hash($encryptionAlgorithm);
$kdf = self::getPBES1KDF($encryptionAlgorithm);
$params = [
'salt' => $salt,
'iterationCount' => $iterationCount
];
$params = ASN1::encodeDER($params, Maps\PBEParameter::MAP);
}
$crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount);
$key = $crypto->encrypt($key);
$key = [
'encryptionAlgorithm' => [
'algorithm' => $encryptionAlgorithm,
'parameters' => new ASN1\Element($params)
],
'encryptedData' => $key
];
$key = ASN1::encodeDER($key, Maps\EncryptedPrivateKeyInfo::MAP);
if (isset($options['binary']) ? $options['binary'] : self::$binary) {
return $key;
}
return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END ENCRYPTED PRIVATE KEY-----";
}
if (isset($options['binary']) ? $options['binary'] : self::$binary) {
return $key;
}
return "-----BEGIN PRIVATE KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END PRIVATE KEY-----";
}
/**
* Wrap a public key appropriately
*
* @param string $key
* @param mixed $params
* @param string $oid
* @return string
*/
protected static function wrapPublicKey($key, $params, $oid = null, array $options = [])
{
self::initialize_static_variables();
$key = [
'publicKeyAlgorithm' => [
'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid
],
'publicKey' => "\0" . $key
];
if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
$key['publicKeyAlgorithm']['parameters'] = $params;
}
$key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP);
if (isset($options['binary']) ? $options['binary'] : self::$binary) {
return $key;
}
return "-----BEGIN PUBLIC KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END PUBLIC KEY-----";
}
/**
* Perform some preliminary parsing of the key
*
* @param string $key
* @return array
*/
private static function preParse(&$key)
{
self::initialize_static_variables();
if (self::$format != self::MODE_DER) {
$decoded = ASN1::extractBER($key);
if ($decoded !== false) {
$key = $decoded;
} elseif (self::$format == self::MODE_PEM) {
throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
}
}
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER');
}
return $decoded;
}
/**
* Returns the encryption parameters used by the key
*
* @param string $key
* @return array
*/
public static function extractEncryptionAlgorithm($key)
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
$decoded = self::preParse($key);
$r = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
if (!is_array($r)) {
throw new \RuntimeException('Unable to parse using EncryptedPrivateKeyInfo map');
}
if ($r['encryptionAlgorithm']['algorithm'] == 'id-PBES2') {
$decoded = ASN1::decodeBER($r['encryptionAlgorithm']['parameters']->element);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER');
}
$r['encryptionAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], Maps\PBES2params::MAP);
$kdf = &$r['encryptionAlgorithm']['parameters']['keyDerivationFunc'];
switch ($kdf['algorithm']) {
case 'id-PBKDF2':
$decoded = ASN1::decodeBER($kdf['parameters']->element);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER');
}
$kdf['parameters'] = ASN1::asn1map($decoded[0], Maps\PBKDF2params::MAP);
}
}
return $r['encryptionAlgorithm'];
}
}

View File

@@ -0,0 +1,380 @@
<?php
/**
* PuTTY Formatted Key Handler
*
* See PuTTY's SSHPUBK.C and https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixC.html
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\Random;
use phpseclib3\Exception\UnsupportedAlgorithmException;
/**
* PuTTY Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PuTTY
{
/**
* Default comment
*
* @var string
*/
private static $comment = 'phpseclib-generated-key';
/**
* Default version
*
* @var int
*/
private static $version = 2;
/**
* Sets the default comment
*
* @param string $comment
*/
public static function setComment($comment)
{
self::$comment = str_replace(["\r", "\n"], '', $comment);
}
/**
* Sets the default version
*
* @param int $version
*/
public static function setVersion($version)
{
if ($version != 2 && $version != 3) {
throw new \RuntimeException('Only supported versions are 2 and 3');
}
self::$version = $version;
}
/**
* Generate a symmetric key for PuTTY v2 keys
*
* @param string $password
* @param int $length
* @return string
*/
private static function generateV2Key($password, $length)
{
$symkey = '';
$sequence = 0;
while (strlen($symkey) < $length) {
$temp = pack('Na*', $sequence++, $password);
$symkey .= Strings::hex2bin(sha1($temp));
}
return substr($symkey, 0, $length);
}
/**
* Generate a symmetric key for PuTTY v3 keys
*
* @param string $password
* @param string $flavour
* @param int $memory
* @param int $passes
* @param string $salt
* @return array
*/
private static function generateV3Key($password, $flavour, $memory, $passes, $salt)
{
if (!function_exists('sodium_crypto_pwhash')) {
throw new \RuntimeException('sodium_crypto_pwhash needs to exist for Argon2 password hasing');
}
switch ($flavour) {
case 'Argon2i':
$flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13;
break;
case 'Argon2id':
$flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13;
break;
default:
throw new UnsupportedAlgorithmException('Only Argon2i and Argon2id are supported');
}
$length = 80; // keylen + ivlen + mac_keylen
$temp = sodium_crypto_pwhash($length, $password, $salt, $passes, $memory << 10, $flavour);
$symkey = substr($temp, 0, 32);
$symiv = substr($temp, 32, 16);
$hashkey = substr($temp, -32);
return compact('symkey', 'symiv', 'hashkey');
}
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password
* @return array
*/
public static function load($key, $password)
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
if (strpos($key, 'BEGIN SSH2 PUBLIC KEY') !== false) {
$lines = preg_split('#[\r\n]+#', $key);
switch (true) {
case $lines[0] != '---- BEGIN SSH2 PUBLIC KEY ----':
throw new \UnexpectedValueException('Key doesn\'t start with ---- BEGIN SSH2 PUBLIC KEY ----');
case $lines[count($lines) - 1] != '---- END SSH2 PUBLIC KEY ----':
throw new \UnexpectedValueException('Key doesn\'t end with ---- END SSH2 PUBLIC KEY ----');
}
$lines = array_splice($lines, 1, -1);
$lines = array_map(function ($line) {
return rtrim($line, "\r\n");
}, $lines);
$data = $current = '';
$values = [];
$in_value = false;
foreach ($lines as $line) {
switch (true) {
case preg_match('#^(.*?): (.*)#', $line, $match):
$in_value = $line[strlen($line) - 1] == '\\';
$current = strtolower($match[1]);
$values[$current] = $in_value ? substr($match[2], 0, -1) : $match[2];
break;
case $in_value:
$in_value = $line[strlen($line) - 1] == '\\';
$values[$current] .= $in_value ? substr($line, 0, -1) : $line;
break;
default:
$data .= $line;
}
}
$components = call_user_func([static::PUBLIC_HANDLER, 'load'], $data);
if ($components === false) {
throw new \UnexpectedValueException('Unable to decode public key');
}
$components += $values;
$components['comment'] = str_replace(['\\\\', '\"'], ['\\', '"'], $values['comment']);
return $components;
}
$components = [];
$key = preg_split('#\r\n|\r|\n#', trim($key));
if (Strings::shift($key[0], strlen('PuTTY-User-Key-File-')) != 'PuTTY-User-Key-File-') {
return false;
}
$version = (int) Strings::shift($key[0], 3); // should be either "2: " or "3: 0" prior to int casting
if ($version != 2 && $version != 3) {
throw new \RuntimeException('Only v2 and v3 PuTTY private keys are supported');
}
$components['type'] = $type = rtrim($key[0]);
if (!in_array($type, static::$types)) {
$error = count(static::$types) == 1 ?
'Only ' . static::$types[0] . ' keys are supported. ' :
'';
throw new UnsupportedAlgorithmException($error . 'This is an unsupported ' . $type . ' key');
}
$encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
$components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
$publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
$public = Strings::base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
$source = Strings::packSSH2('ssss', $type, $encryption, $components['comment'], $public);
$length = unpack('Nlength', Strings::shift($public, 4))['length'];
$newtype = Strings::shift($public, $length);
if ($newtype != $type) {
throw new \RuntimeException('The binary type does not match the human readable type field');
}
$components['public'] = $public;
switch ($version) {
case 3:
$hashkey = '';
break;
case 2:
$hashkey = 'putty-private-key-file-mac-key';
}
$offset = $publicLength + 4;
switch ($encryption) {
case 'aes256-cbc':
$crypto = new AES('cbc');
switch ($version) {
case 3:
$flavour = trim(preg_replace('#Key-Derivation: (.*)#', '$1', $key[$offset++]));
$memory = trim(preg_replace('#Argon2-Memory: (\d+)#', '$1', $key[$offset++]));
$passes = trim(preg_replace('#Argon2-Passes: (\d+)#', '$1', $key[$offset++]));
$parallelism = trim(preg_replace('#Argon2-Parallelism: (\d+)#', '$1', $key[$offset++]));
$salt = Strings::hex2bin(trim(preg_replace('#Argon2-Salt: ([0-9a-f]+)#', '$1', $key[$offset++])));
$v3key = self::generateV3Key($password, $flavour, $memory, $passes, $salt);
$symkey = $v3key['symkey'];
$symiv = $v3key['symiv'];
$hashkey = $v3key['hashkey'];
break;
case 2:
$symkey = self::generateV2Key($password, 32);
$symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
$hashkey .= $password;
}
}
switch ($version) {
case 3:
$hash = new Hash('sha256');
$hash->setKey($hashkey);
break;
case 2:
$hash = new Hash('sha1');
$hash->setKey(sha1($hashkey, true));
}
$privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$offset++]));
$private = Strings::base64_decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength))));
if ($encryption != 'none') {
$crypto->setKey($symkey);
$crypto->setIV($symiv);
$crypto->disablePadding();
$private = $crypto->decrypt($private);
}
$source .= Strings::packSSH2('s', $private);
$hmac = trim(preg_replace('#Private-MAC: (.+)#', '$1', $key[$offset + $privateLength]));
$hmac = Strings::hex2bin($hmac);
if (!hash_equals($hash->hash($source), $hmac)) {
throw new \UnexpectedValueException('MAC validation error');
}
$components['private'] = $private;
return $components;
}
/**
* Wrap a private key appropriately
*
* @param string $public
* @param string $private
* @param string $type
* @param string $password
* @param array $options optional
* @return string
*/
protected static function wrapPrivateKey($public, $private, $type, $password, array $options = [])
{
$encryption = (!empty($password) || is_string($password)) ? 'aes256-cbc' : 'none';
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
$version = isset($options['version']) ? $options['version'] : self::$version;
$key = "PuTTY-User-Key-File-$version: $type\r\n";
$key .= "Encryption: $encryption\r\n";
$key .= "Comment: $comment\r\n";
$public = Strings::packSSH2('s', $type) . $public;
$source = Strings::packSSH2('ssss', $type, $encryption, $comment, $public);
$public = Strings::base64_encode($public);
$key .= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
$key .= chunk_split($public, 64);
if (empty($password) && !is_string($password)) {
$source .= Strings::packSSH2('s', $private);
switch ($version) {
case 3:
$hash = new Hash('sha256');
$hash->setKey('');
break;
case 2:
$hash = new Hash('sha1');
$hash->setKey(sha1('putty-private-key-file-mac-key', true));
}
} else {
$private .= Random::string(16 - (strlen($private) & 15));
$source .= Strings::packSSH2('s', $private);
$crypto = new AES('cbc');
switch ($version) {
case 3:
$salt = Random::string(16);
$key .= "Key-Derivation: Argon2id\r\n";
$key .= "Argon2-Memory: 8192\r\n";
$key .= "Argon2-Passes: 13\r\n";
$key .= "Argon2-Parallelism: 1\r\n";
$key .= "Argon2-Salt: " . Strings::bin2hex($salt) . "\r\n";
$v3key = self::generateV3Key($password, 'Argon2id', 8192, 13, $salt);
$symkey = $v3key['symkey'];
$symiv = $v3key['symiv'];
$hashkey = $v3key['hashkey'];
$hash = new Hash('sha256');
$hash->setKey($hashkey);
break;
case 2:
$symkey = self::generateV2Key($password, 32);
$symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
$hashkey = 'putty-private-key-file-mac-key' . $password;
$hash = new Hash('sha1');
$hash->setKey(sha1($hashkey, true));
}
$crypto->setKey($symkey);
$crypto->setIV($symiv);
$crypto->disablePadding();
$private = $crypto->encrypt($private);
$mac = $hash->hash($source);
}
$private = Strings::base64_encode($private);
$key .= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
$key .= chunk_split($private, 64);
$key .= 'Private-MAC: ' . Strings::bin2hex($hash->hash($source)) . "\r\n";
return $key;
}
/**
* Wrap a public key appropriately
*
* This is basically the format described in RFC 4716 (https://tools.ietf.org/html/rfc4716)
*
* @param string $key
* @param string $type
* @return string
*/
protected static function wrapPublicKey($key, $type)
{
$key = pack('Na*a*', strlen($type), $type, $key);
$key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" .
'Comment: "' . str_replace(['\\', '"'], ['\\\\', '\"'], self::$comment) . "\"\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
'---- END SSH2 PUBLIC KEY ----';
return $key;
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* Raw Signature Handler
*
* PHP version 5
*
* Handles signatures as arrays
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Signature;
use phpseclib3\Math\BigInteger;
/**
* Raw Signature Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Raw
{
/**
* Loads a signature
*
* @param array $sig
* @return array|bool
*/
public static function load($sig)
{
switch (true) {
case !is_array($sig):
case !isset($sig['r']) || !isset($sig['s']):
case !$sig['r'] instanceof BigInteger:
case !$sig['s'] instanceof BigInteger:
return false;
}
return [
'r' => $sig['r'],
's' => $sig['s']
];
}
/**
* Returns a signature in the appropriate format
*
* @param BigInteger $r
* @param BigInteger $s
* @return string
*/
public static function save(BigInteger $r, BigInteger $s)
{
return compact('r', 's');
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* PrivateKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
/**
* PrivateKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
*/
interface PrivateKey
{
public function sign($message);
//public function decrypt($ciphertext);
public function getPublicKey();
public function toString($type, array $options = []);
/**
* @param string|false $password
* @return mixed
*/
public function withPassword($password = false);
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* PublicKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
/**
* PublicKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
*/
interface PublicKey
{
public function verify($message, $signature);
//public function encrypt($plaintext);
public function toString($type, array $options = []);
public function getFingerprint($algorithm);
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Base Class for all stream ciphers
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
/**
* Base Class for all stream cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class StreamCipher extends SymmetricKey
{
/**
* Block Length of the cipher
*
* Stream ciphers do not have a block size
*
* @see SymmetricKey::block_size
* @var int
*/
protected $block_size = 0;
/**
* Default Constructor.
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
* @return StreamCipher
*/
public function __construct()
{
parent::__construct('stream');
}
/**
* Stream ciphers not use an IV
*
* @return bool
*/
public function usesIV()
{
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
<?php
/**
* Fingerprint Trait for Public Keys
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Traits;
use phpseclib3\Crypt\Hash;
/**
* Fingerprint Trait for Private Keys
*
* @author Jim Wigginton <terrafrost@php.net>
*/
trait Fingerprint
{
/**
* Returns the public key's fingerprint
*
* The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is
* no public key currently loaded, false is returned.
* Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716)
*
* @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned
* for invalid values.
* @return mixed
*/
public function getFingerprint($algorithm = 'md5')
{
$type = self::validatePlugin('Keys', 'OpenSSH', 'savePublicKey');
if ($type === false) {
return false;
}
$key = $this->toString('OpenSSH', ['binary' => true]);
if ($key === false) {
return false;
}
switch ($algorithm) {
case 'sha256':
$hash = new Hash('sha256');
$base = base64_encode($hash->hash($key));
return substr($base, 0, strlen($base) - 1);
case 'md5':
return substr(chunk_split(md5($key), 2, ':'), 0, -1);
default:
return false;
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* Password Protected Trait for Private Keys
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Traits;
/**
* Password Protected Trait for Private Keys
*
* @author Jim Wigginton <terrafrost@php.net>
*/
trait PasswordProtected
{
/**
* Password
*
* @var string|bool
*/
private $password = false;
/**
* Sets the password
*
* Private keys can be encrypted with a password. To unset the password, pass in the empty string or false.
* Or rather, pass in $password such that empty($password) && !is_string($password) is true.
*
* @see self::createKey()
* @see self::load()
* @param string|bool $password
*/
public function withPassword($password = false)
{
$new = clone $this;
$new->password = $password;
return $new;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,405 @@
<?php
/**
* Pure-PHP (EC)DH implementation
*
* PHP version 5
*
* Here's an example of how to compute a shared secret with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $ourPrivate = \phpseclib3\Crypt\DH::createKey();
* $secret = DH::computeSecret($ourPrivate, $theirPublic);
*
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\DH\Parameters;
use phpseclib3\Crypt\DH\PrivateKey;
use phpseclib3\Crypt\DH\PublicKey;
use phpseclib3\Exception\NoKeyLoadedException;
use phpseclib3\Exception\UnsupportedOperationException;
use phpseclib3\Math\BigInteger;
/**
* Pure-PHP (EC)DH implementation
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class DH extends AsymmetricKey
{
/**
* Algorithm Name
*
* @var string
*/
const ALGORITHM = 'DH';
/**
* DH prime
*
* @var BigInteger
*/
protected $prime;
/**
* DH Base
*
* Prime divisor of p-1
*
* @var BigInteger
*/
protected $base;
/**
* Public Key
*
* @var BigInteger
*/
protected $publicKey;
/**
* Create DH parameters
*
* This method is a bit polymorphic. It can take any of the following:
* - two BigInteger's (prime and base)
* - an integer representing the size of the prime in bits (the base is assumed to be 2)
* - a string (eg. diffie-hellman-group14-sha1)
*
* @return Parameters
*/
public static function createParameters(...$args)
{
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')');
}
$params = new Parameters();
if (count($args) == 2 && $args[0] instanceof BigInteger && $args[1] instanceof BigInteger) {
//if (!$args[0]->isPrime()) {
// throw new \InvalidArgumentException('The first parameter should be a prime number');
//}
$params->prime = $args[0];
$params->base = $args[1];
return $params;
} elseif (count($args) == 1 && is_numeric($args[0])) {
$params->prime = BigInteger::randomPrime($args[0]);
$params->base = new BigInteger(2);
return $params;
} elseif (count($args) != 1 || !is_string($args[0])) {
throw new \InvalidArgumentException('Valid parameters are either: two BigInteger\'s (prime and base), a single integer (the length of the prime; base is assumed to be 2) or a string');
}
switch ($args[0]) {
// see http://tools.ietf.org/html/rfc2409#section-6.2 and
// http://tools.ietf.org/html/rfc2412, appendex E
case 'diffie-hellman-group1-sha1':
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
break;
// see http://tools.ietf.org/html/rfc3526#section-3
case 'diffie-hellman-group14-sha1': // 2048-bit MODP Group
case 'diffie-hellman-group14-sha256':
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
break;
// see https://tools.ietf.org/html/rfc3526#section-4
case 'diffie-hellman-group15-sha512': // 3072-bit MODP Group
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
'08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF';
break;
// see https://tools.ietf.org/html/rfc3526#section-5
case 'diffie-hellman-group16-sha512': // 4096-bit MODP Group
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
'08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
'88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
'233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
'93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF';
break;
// see https://tools.ietf.org/html/rfc3526#section-6
case 'diffie-hellman-group17-sha512': // 6144-bit MODP Group
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
'08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
'88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
'233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
'93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
'59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
'043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF';
break;
// see https://tools.ietf.org/html/rfc3526#section-7
case 'diffie-hellman-group18-sha512': // 8192-bit MODP Group
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
'08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
'88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
'233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
'93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
'59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
'043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4' .
'38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED' .
'2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652D' .
'E3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B' .
'4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6' .
'6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851D' .
'F9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92' .
'4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA' .
'9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF';
break;
default:
throw new \InvalidArgumentException('Invalid named prime provided');
}
$params->prime = new BigInteger($prime, 16);
$params->base = new BigInteger(2);
return $params;
}
/**
* Create public / private key pair.
*
* The rationale for the second parameter is described in http://tools.ietf.org/html/rfc4419#section-6.2 :
*
* "To increase the speed of the key exchange, both client and server may
* reduce the size of their private exponents. It should be at least
* twice as long as the key material that is generated from the shared
* secret. For more details, see the paper by van Oorschot and Wiener
* [VAN-OORSCHOT]."
*
* $length is in bits
*
* @param Parameters $params
* @param int $length optional
* @return PrivateKey
*/
public static function createKey(Parameters $params, $length = 0)
{
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
}
$one = new BigInteger(1);
if ($length) {
$max = $one->bitwise_leftShift($length);
$max = $max->subtract($one);
} else {
$max = $params->prime->subtract($one);
}
$key = new PrivateKey();
$key->prime = $params->prime;
$key->base = $params->base;
$key->privateKey = BigInteger::randomRange($one, $max);
$key->publicKey = $key->base->powMod($key->privateKey, $key->prime);
return $key;
}
/**
* Compute Shared Secret
*
* @param PrivateKey|EC $private
* @param PublicKey|BigInteger|string $public
* @return mixed
*/
public static function computeSecret($private, $public)
{
if ($private instanceof PrivateKey) { // DH\PrivateKey
switch (true) {
case $public instanceof PublicKey:
if (!$private->prime->equals($public->prime) || !$private->base->equals($public->base)) {
throw new \InvalidArgumentException('The public and private key do not share the same prime and / or base numbers');
}
return $public->publicKey->powMod($private->privateKey, $private->prime)->toBytes(true);
case is_string($public):
$public = new BigInteger($public, -256);
// fall-through
case $public instanceof BigInteger:
return $public->powMod($private->privateKey, $private->prime)->toBytes(true);
default:
throw new \InvalidArgumentException('$public needs to be an instance of DH\PublicKey, a BigInteger or a string');
}
}
if ($private instanceof EC\PrivateKey) {
switch (true) {
case $public instanceof EC\PublicKey:
$public = $public->getEncodedCoordinates();
// fall-through
case is_string($public):
$point = $private->multiply($public);
switch ($private->getCurve()) {
case 'Curve25519':
case 'Curve448':
$secret = $point;
break;
default:
// according to https://www.secg.org/sec1-v2.pdf#page=33 only X is returned
$secret = substr($point, 1, (strlen($point) - 1) >> 1);
}
/*
if (($secret[0] & "\x80") === "\x80") {
$secret = "\0$secret";
}
*/
return $secret;
default:
throw new \InvalidArgumentException('$public needs to be an instance of EC\PublicKey or a string (an encoded coordinate)');
}
}
}
/**
* Load the key
*
* @param string $key
* @param string $password optional
* @return AsymmetricKey
*/
public static function load($key, $password = false)
{
try {
return EC::load($key, $password);
} catch (NoKeyLoadedException $e) {
}
return parent::load($key, $password);
}
/**
* OnLoad Handler
*
* @return bool
*/
protected static function onLoad(array $components)
{
if (!isset($components['privateKey']) && !isset($components['publicKey'])) {
$new = new Parameters();
} else {
$new = isset($components['privateKey']) ?
new PrivateKey() :
new PublicKey();
}
$new->prime = $components['prime'];
$new->base = $components['base'];
if (isset($components['privateKey'])) {
$new->privateKey = $components['privateKey'];
}
if (isset($components['publicKey'])) {
$new->publicKey = $components['publicKey'];
}
return $new;
}
/**
* Determines which hashing function should be used
*
* @param string $hash
*/
public function withHash($hash)
{
throw new UnsupportedOperationException('DH does not use a hash algorithm');
}
/**
* Returns the hash algorithm currently being used
*
*/
public function getHash()
{
throw new UnsupportedOperationException('DH does not use a hash algorithm');
}
/**
* Returns the parameters
*
* A public / private key is only returned if the currently loaded "key" contains an x or y
* value.
*
* @see self::getPublicKey()
* @return mixed
*/
public function getParameters()
{
$type = DH::validatePlugin('Keys', 'PKCS1', 'saveParameters');
$key = $type::saveParameters($this->prime, $this->base);
return DH::load($key, 'PKCS1');
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* "PKCS1" Formatted EC Key Handler
*
* PHP version 5
*
* Processes keys with the following headers:
*
* -----BEGIN DH PARAMETERS-----
*
* Technically, PKCS1 is for RSA keys, only, but we're using PKCS1 to describe
* DSA, whose format isn't really formally described anywhere, so might as well
* use it to describe this, too.
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DH\Formats\Keys;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* "PKCS1" Formatted DH Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS1 extends Progenitor
{
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
public static function load($key, $password = '')
{
$key = parent::load($key, $password);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER');
}
$components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP);
if (!is_array($components)) {
throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
}
return $components;
}
/**
* Convert EC parameters to the appropriate format
*
* @return string
*/
public static function saveParameters(BigInteger $prime, BigInteger $base, array $options = [])
{
$params = [
'prime' => $prime,
'base' => $base
];
$params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
return "-----BEGIN DH PARAMETERS-----\r\n" .
chunk_split(base64_encode($params), 64) .
"-----END DH PARAMETERS-----\r\n";
}
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* PKCS#8 Formatted DH Key Handler
*
* PHP version 5
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DH\Formats\Keys;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* PKCS#8 Formatted DH Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS8 extends Progenitor
{
/**
* OID Name
*
* @var string
*/
const OID_NAME = 'dhKeyAgreement';
/**
* OID Value
*
* @var string
*/
const OID_VALUE = '1.2.840.113549.1.3.1';
/**
* Child OIDs loaded
*
* @var bool
*/
protected static $childOIDsLoaded = false;
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
public static function load($key, $password = '')
{
$key = parent::load($key, $password);
$type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
$decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
if (empty($decoded)) {
throw new \RuntimeException('Unable to decode BER of parameters');
}
$components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP);
if (!is_array($components)) {
throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
}
$decoded = ASN1::decodeBER($key[$type]);
switch (true) {
case !isset($decoded):
case !isset($decoded[0]['content']):
case !$decoded[0]['content'] instanceof BigInteger:
throw new \RuntimeException('Unable to decode BER of parameters');
}
$components[$type] = $decoded[0]['content'];
return $components;
}
/**
* Convert a private key to the appropriate format.
*
* @param BigInteger $prime
* @param BigInteger $base
* @param BigInteger $privateKey
* @param BigInteger $publicKey
* @param string $password optional
* @param array $options optional
* @return string
*/
public static function savePrivateKey(BigInteger $prime, BigInteger $base, BigInteger $privateKey, BigInteger $publicKey, $password = '', array $options = [])
{
$params = [
'prime' => $prime,
'base' => $base
];
$params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
$params = new ASN1\Element($params);
$key = ASN1::encodeDER($privateKey, ['type' => ASN1::TYPE_INTEGER]);
return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
}
/**
* Convert a public key to the appropriate format
*
* @param BigInteger $prime
* @param BigInteger $base
* @param BigInteger $publicKey
* @param array $options optional
* @return string
*/
public static function savePublicKey(BigInteger $prime, BigInteger $base, BigInteger $publicKey, array $options = [])
{
$params = [
'prime' => $prime,
'base' => $base
];
$params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
$params = new ASN1\Element($params);
$key = ASN1::encodeDER($publicKey, ['type' => ASN1::TYPE_INTEGER]);
return self::wrapPublicKey($key, $params, null, $options);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* DH Parameters
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DH;
use phpseclib3\Crypt\DH;
/**
* DH Parameters
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class Parameters extends DH
{
/**
* Returns the parameters
*
* @param string $type
* @param array $options optional
* @return string
*/
public function toString($type = 'PKCS1', array $options = [])
{
$type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
return $type::saveParameters($this->prime, $this->base, $options);
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* DH Private Key
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DH;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DH;
/**
* DH Private Key
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PrivateKey extends DH
{
use Common\Traits\PasswordProtected;
/**
* Private Key
*
* @var \phpseclib3\Math\BigInteger
*/
protected $privateKey;
/**
* Public Key
*
* @var \phpseclib3\Math\BigInteger
*/
protected $publicKey;
/**
* Returns the public key
*
* @return PublicKey
*/
public function getPublicKey()
{
$type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
if (!isset($this->publicKey)) {
$this->publicKey = $this->base->powMod($this->privateKey, $this->prime);
}
$key = $type::savePublicKey($this->prime, $this->base, $this->publicKey);
return DH::loadFormat('PKCS8', $key);
}
/**
* Returns the private key
*
* @param string $type
* @param array $options optional
* @return string
*/
public function toString($type, array $options = [])
{
$type = self::validatePlugin('Keys', $type, 'savePrivateKey');
if (!isset($this->publicKey)) {
$this->publicKey = $this->base->powMod($this->privateKey, $this->prime);
}
return $type::savePrivateKey($this->prime, $this->base, $this->privateKey, $this->publicKey, $this->password, $options);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* DH Public Key
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DH;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DH;
/**
* DH Public Key
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PublicKey extends DH
{
use Common\Traits\Fingerprint;
/**
* Returns the public key
*
* @param string $type
* @param array $options optional
* @return string
*/
public function toString($type, array $options = [])
{
$type = self::validatePlugin('Keys', $type, 'savePublicKey');
return $type::savePublicKey($this->prime, $this->base, $this->publicKey, $options);
}
/**
* Returns the public key as a BigInteger
*
* @return \phpseclib3\Math\BigInteger
*/
public function toBigInteger()
{
return $this->publicKey;
}
}

View File

@@ -0,0 +1,337 @@
<?php
/**
* Pure-PHP FIPS 186-4 compliant implementation of DSA.
*
* PHP version 5
*
* Here's an example of how to create signatures and verify signatures with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $private = \phpseclib3\Crypt\DSA::createKey();
* $public = $private->getPublicKey();
*
* $plaintext = 'terrafrost';
*
* $signature = $private->sign($plaintext);
*
* echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\DSA\Parameters;
use phpseclib3\Crypt\DSA\PrivateKey;
use phpseclib3\Crypt\DSA\PublicKey;
use phpseclib3\Exception\InsufficientSetupException;
use phpseclib3\Math\BigInteger;
/**
* Pure-PHP FIPS 186-4 compliant implementation of DSA.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class DSA extends AsymmetricKey
{
/**
* Algorithm Name
*
* @var string
*/
const ALGORITHM = 'DSA';
/**
* DSA Prime P
*
* @var BigInteger
*/
protected $p;
/**
* DSA Group Order q
*
* Prime divisor of p-1
*
* @var BigInteger
*/
protected $q;
/**
* DSA Group Generator G
*
* @var BigInteger
*/
protected $g;
/**
* DSA public key value y
*
* @var BigInteger
*/
protected $y;
/**
* Signature Format
*
* @var string
*/
protected $sigFormat;
/**
* Signature Format (Short)
*
* @var string
*/
protected $shortFormat;
/**
* Create DSA parameters
*
* @param int $L
* @param int $N
* @return DSA|bool
*/
public static function createParameters($L = 2048, $N = 224)
{
self::initialize_static_variables();
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')');
}
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
switch (true) {
case $N == 160:
/*
in FIPS 186-1 and 186-2 N was fixed at 160 whereas K had an upper bound of 1024.
RFC 4253 (SSH Transport Layer Protocol) references FIPS 186-2 and as such most
SSH DSA implementations only support keys with an N of 160.
puttygen let's you set the size of L (but not the size of N) and uses 2048 as the
default L value. that's not really compliant with any of the FIPS standards, however,
for the purposes of maintaining compatibility with puttygen, we'll support it
*/
//case ($L >= 512 || $L <= 1024) && (($L & 0x3F) == 0) && $N == 160:
// FIPS 186-3 changed this as follows:
//case $L == 1024 && $N == 160:
case $L == 2048 && $N == 224:
case $L == 2048 && $N == 256:
case $L == 3072 && $N == 256:
break;
default:
throw new \InvalidArgumentException('Invalid values for N and L');
}
$two = new BigInteger(2);
$q = BigInteger::randomPrime($N);
$divisor = $q->multiply($two);
do {
$x = BigInteger::random($L);
list(, $c) = $x->divide($divisor);
$p = $x->subtract($c->subtract(self::$one));
} while ($p->getLength() != $L || !$p->isPrime());
$p_1 = $p->subtract(self::$one);
list($e) = $p_1->divide($q);
// quoting http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#page=50 ,
// "h could be obtained from a random number generator or from a counter that
// changes after each use". PuTTY (sshdssg.c) starts h off at 1 and increments
// it on each loop. wikipedia says "commonly h = 2 is used" so we'll just do that
$h = clone $two;
while (true) {
$g = $h->powMod($e, $p);
if (!$g->equals(self::$one)) {
break;
}
$h = $h->add(self::$one);
}
$dsa = new Parameters();
$dsa->p = $p;
$dsa->q = $q;
$dsa->g = $g;
return $dsa;
}
/**
* Create public / private key pair.
*
* This method is a bit polymorphic. It can take a DSA/Parameters object, L / N as two distinct parameters or
* no parameters (at which point L and N will be generated with this method)
*
* Returns the private key, from which the publickey can be extracted
*
* @param int[] ...$args
* @return PrivateKey
*/
public static function createKey(...$args)
{
self::initialize_static_variables();
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
}
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if (count($args) == 2 && is_int($args[0]) && is_int($args[1])) {
$params = self::createParameters($args[0], $args[1]);
} elseif (count($args) == 1 && $args[0] instanceof Parameters) {
$params = $args[0];
} elseif (!count($args)) {
$params = self::createParameters();
} else {
throw new InsufficientSetupException('Valid parameters are either two integers (L and N), a single DSA object or no parameters at all.');
}
$private = new PrivateKey();
$private->p = $params->p;
$private->q = $params->q;
$private->g = $params->g;
$private->x = BigInteger::randomRange(self::$one, $private->q->subtract(self::$one));
$private->y = $private->g->powMod($private->x, $private->p);
//$public = clone $private;
//unset($public->x);
return $private
->withHash($params->hash->getHash())
->withSignatureFormat($params->shortFormat);
}
/**
* OnLoad Handler
*
* @return bool
*/
protected static function onLoad(array $components)
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if (!isset($components['x']) && !isset($components['y'])) {
$new = new Parameters();
} elseif (isset($components['x'])) {
$new = new PrivateKey();
$new->x = $components['x'];
} else {
$new = new PublicKey();
}
$new->p = $components['p'];
$new->q = $components['q'];
$new->g = $components['g'];
if (isset($components['y'])) {
$new->y = $components['y'];
}
return $new;
}
/**
* Constructor
*
* PublicKey and PrivateKey objects can only be created from abstract RSA class
*/
protected function __construct()
{
$this->sigFormat = self::validatePlugin('Signature', 'ASN1');
$this->shortFormat = 'ASN1';
parent::__construct();
}
/**
* Returns the key size
*
* More specifically, this L (the length of DSA Prime P) and N (the length of DSA Group Order q)
*
* @return array
*/
public function getLength()
{
return ['L' => $this->p->getLength(), 'N' => $this->q->getLength()];
}
/**
* Returns the current engine being used
*
* @see self::useInternalEngine()
* @see self::useBestEngine()
* @return string
*/
public function getEngine()
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
'OpenSSL' : 'PHP';
}
/**
* Returns the parameters
*
* A public / private key is only returned if the currently loaded "key" contains an x or y
* value.
*
* @see self::getPublicKey()
* @return mixed
*/
public function getParameters()
{
$type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
$key = $type::saveParameters($this->p, $this->q, $this->g);
return DSA::load($key, 'PKCS1')
->withHash($this->hash->getHash())
->withSignatureFormat($this->shortFormat);
}
/**
* Determines the signature padding mode
*
* Valid values are: ASN1, SSH2, Raw
*
* @param string $format
*/
public function withSignatureFormat($format)
{
$new = clone $this;
$new->shortFormat = $format;
$new->sigFormat = self::validatePlugin('Signature', $format);
return $new;
}
/**
* Returns the signature format currently being used
*
*/
public function getSignatureFormat()
{
return $this->shortFormat;
}
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* OpenSSH Formatted DSA Key Handler
*
* PHP version 5
*
* Place in $HOME/.ssh/authorized_keys
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
use phpseclib3\Math\BigInteger;
/**
* OpenSSH Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class OpenSSH extends Progenitor
{
/**
* Supported Key Types
*
* @var array
*/
protected static $types = ['ssh-dss'];
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
public static function load($key, $password = '')
{
$parsed = parent::load($key, $password);
if (isset($parsed['paddedKey'])) {
list($type) = Strings::unpackSSH2('s', $parsed['paddedKey']);
if ($type != $parsed['type']) {
throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
}
list($p, $q, $g, $y, $x, $comment) = Strings::unpackSSH2('i5s', $parsed['paddedKey']);
return compact('p', 'q', 'g', 'y', 'x', 'comment');
}
list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $parsed['publicKey']);
$comment = $parsed['comment'];
return compact('p', 'q', 'g', 'y', 'comment');
}
/**
* Convert a public key to the appropriate format
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @param array $options optional
* @return string
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = [])
{
if ($q->getLength() != 160) {
throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
}
// from <http://tools.ietf.org/html/rfc4253#page-15>:
// string "ssh-dss"
// mpint p
// mpint q
// mpint g
// mpint y
$DSAPublicKey = Strings::packSSH2('siiii', 'ssh-dss', $p, $q, $g, $y);
if (isset($options['binary']) ? $options['binary'] : self::$binary) {
return $DSAPublicKey;
}
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
$DSAPublicKey = 'ssh-dss ' . base64_encode($DSAPublicKey) . ' ' . $comment;
return $DSAPublicKey;
}
/**
* Convert a private key to the appropriate format.
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @param BigInteger $x
* @param string $password optional
* @param array $options optional
* @return string
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
{
$publicKey = self::savePublicKey($p, $q, $g, $y, ['binary' => true]);
$privateKey = Strings::packSSH2('si5', 'ssh-dss', $p, $q, $g, $y, $x);
return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
}
}

View File

@@ -0,0 +1,143 @@
<?php
/**
* PKCS#1 Formatted DSA Key Handler
*
* PHP version 5
*
* Used by File/X509.php
*
* Processes keys with the following headers:
*
* -----BEGIN DSA PRIVATE KEY-----
* -----BEGIN DSA PUBLIC KEY-----
* -----BEGIN DSA PARAMETERS-----
*
* Analogous to ssh-keygen's pem format (as specified by -m)
*
* Also, technically, PKCS1 decribes RSA but I am not aware of a formal specification for DSA.
* The DSA private key format seems to have been adapted from the RSA private key format so
* we're just re-using that as the name.
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* PKCS#1 Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS1 extends Progenitor
{
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
public static function load($key, $password = '')
{
$key = parent::load($key, $password);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER');
}
$key = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP);
if (is_array($key)) {
return $key;
}
$key = ASN1::asn1map($decoded[0], Maps\DSAPrivateKey::MAP);
if (is_array($key)) {
return $key;
}
$key = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP);
if (is_array($key)) {
return $key;
}
throw new \RuntimeException('Unable to perform ASN1 mapping');
}
/**
* Convert DSA parameters to the appropriate format
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @return string
*/
public static function saveParameters(BigInteger $p, BigInteger $q, BigInteger $g)
{
$key = [
'p' => $p,
'q' => $q,
'g' => $g
];
$key = ASN1::encodeDER($key, Maps\DSAParams::MAP);
return "-----BEGIN DSA PARAMETERS-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END DSA PARAMETERS-----\r\n";
}
/**
* Convert a private key to the appropriate format.
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @param BigInteger $x
* @param string $password optional
* @param array $options optional
* @return string
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
{
$key = [
'version' => 0,
'p' => $p,
'q' => $q,
'g' => $g,
'y' => $y,
'x' => $x
];
$key = ASN1::encodeDER($key, Maps\DSAPrivateKey::MAP);
return self::wrapPrivateKey($key, 'DSA', $password, $options);
}
/**
* Convert a public key to the appropriate format
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @return string
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
{
$key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP);
return self::wrapPublicKey($key, 'DSA');
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* PKCS#8 Formatted DSA Key Handler
*
* PHP version 5
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
* is specific to private keys it's basically creating a DER-encoded wrapper
* for keys. This just extends that same concept to public keys (much like ssh-keygen)
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* PKCS#8 Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS8 extends Progenitor
{
/**
* OID Name
*
* @var string
*/
const OID_NAME = 'id-dsa';
/**
* OID Value
*
* @var string
*/
const OID_VALUE = '1.2.840.10040.4.1';
/**
* Child OIDs loaded
*
* @var bool
*/
protected static $childOIDsLoaded = false;
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
public static function load($key, $password = '')
{
$key = parent::load($key, $password);
$type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
$decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER of parameters');
}
$components = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP);
if (!is_array($components)) {
throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
}
$decoded = ASN1::decodeBER($key[$type]);
if (empty($decoded)) {
throw new \RuntimeException('Unable to decode BER');
}
$var = $type == 'privateKey' ? 'x' : 'y';
$components[$var] = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP);
if (!$components[$var] instanceof BigInteger) {
throw new \RuntimeException('Unable to perform ASN1 mapping');
}
if (isset($key['meta'])) {
$components['meta'] = $key['meta'];
}
return $components;
}
/**
* Convert a private key to the appropriate format.
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @param BigInteger $x
* @param string $password optional
* @param array $options optional
* @return string
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
{
$params = [
'p' => $p,
'q' => $q,
'g' => $g
];
$params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
$params = new ASN1\Element($params);
$key = ASN1::encodeDER($x, Maps\DSAPublicKey::MAP);
return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
}
/**
* Convert a public key to the appropriate format
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @param array $options optional
* @return string
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = [])
{
$params = [
'p' => $p,
'q' => $q,
'g' => $g
];
$params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
$params = new ASN1\Element($params);
$key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP);
return self::wrapPublicKey($key, $params, null, $options);
}
}

View File

@@ -0,0 +1,112 @@
<?php
/**
* PuTTY Formatted DSA Key Handler
*
* puttygen does not generate DSA keys with an N of anything other than 160, however,
* it can still load them and convert them. PuTTY will load them, too, but SSH servers
* won't accept them. Since PuTTY formatted keys are primarily used with SSH this makes
* keys with N > 160 kinda useless, hence this handlers not supporting such keys.
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor;
use phpseclib3\Math\BigInteger;
/**
* PuTTY Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PuTTY extends Progenitor
{
/**
* Public Handler
*
* @var string
*/
const PUBLIC_HANDLER = 'phpseclib3\Crypt\DSA\Formats\Keys\OpenSSH';
/**
* Algorithm Identifier
*
* @var array
*/
protected static $types = ['ssh-dss'];
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
public static function load($key, $password = '')
{
$components = parent::load($key, $password);
if (!isset($components['private'])) {
return $components;
}
$type = $components['type'];
$comment = $components['comment'];
$public = $components['public'];
$private = $components['private'];
unset($components['public'], $components['private']);
list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $public);
list($x) = Strings::unpackSSH2('i', $private);
return compact('p', 'q', 'g', 'y', 'x', 'comment');
}
/**
* Convert a private key to the appropriate format.
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @param BigInteger $x
* @param string $password optional
* @param array $options optional
* @return string
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = false, array $options = [])
{
if ($q->getLength() != 160) {
throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
}
$public = Strings::packSSH2('iiii', $p, $q, $g, $y);
$private = Strings::packSSH2('i', $x);
return self::wrapPrivateKey($public, $private, 'ssh-dss', $password, $options);
}
/**
* Convert a public key to the appropriate format
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @return string
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
{
if ($q->getLength() != 160) {
throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
}
return self::wrapPublicKey(Strings::packSSH2('iiii', $p, $q, $g, $y), 'ssh-dss');
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* Raw DSA Key Handler
*
* PHP version 5
*
* Reads and creates arrays as DSA keys
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Math\BigInteger;
/**
* Raw DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Raw
{
/**
* Break a public or private key down into its constituent components
*
* @param array $key
* @param string $password optional
* @return array
*/
public static function load($key, $password = '')
{
if (!is_array($key)) {
throw new \UnexpectedValueException('Key should be a array - not a ' . gettype($key));
}
switch (true) {
case !isset($key['p']) || !isset($key['q']) || !isset($key['g']):
case !$key['p'] instanceof BigInteger:
case !$key['q'] instanceof BigInteger:
case !$key['g'] instanceof BigInteger:
case !isset($key['x']) && !isset($key['y']):
case isset($key['x']) && !$key['x'] instanceof BigInteger:
case isset($key['y']) && !$key['y'] instanceof BigInteger:
throw new \UnexpectedValueException('Key appears to be malformed');
}
$options = ['p' => 1, 'q' => 1, 'g' => 1, 'x' => 1, 'y' => 1];
return array_intersect_key($key, $options);
}
/**
* Convert a private key to the appropriate format.
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @param BigInteger $x
* @param string $password optional
* @return string
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '')
{
return compact('p', 'q', 'g', 'y', 'x');
}
/**
* Convert a public key to the appropriate format
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @return string
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
{
return compact('p', 'q', 'g', 'y');
}
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* XML Formatted DSA Key Handler
*
* While XKMS defines a private key format for RSA it does not do so for DSA. Quoting that standard:
*
* "[XKMS] does not specify private key parameters for the DSA signature algorithm since the algorithm only
* supports signature modes and so the application of server generated keys and key recovery is of limited
* value"
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Exception\BadConfigurationException;
use phpseclib3\Math\BigInteger;
/**
* XML Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class XML
{
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
public static function load($key, $password = '')
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
if (!class_exists('DOMDocument')) {
throw new BadConfigurationException('The dom extension is not setup correctly on this system');
}
$use_errors = libxml_use_internal_errors(true);
$dom = new \DOMDocument();
if (substr($key, 0, 5) != '<?xml') {
$key = '<xml>' . $key . '</xml>';
}
if (!$dom->loadXML($key)) {
libxml_use_internal_errors($use_errors);
throw new \UnexpectedValueException('Key does not appear to contain XML');
}
$xpath = new \DOMXPath($dom);
$keys = ['p', 'q', 'g', 'y', 'j', 'seed', 'pgencounter'];
foreach ($keys as $key) {
// $dom->getElementsByTagName($key) is case-sensitive
$temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']");
if (!$temp->length) {
continue;
}
$value = new BigInteger(Strings::base64_decode($temp->item(0)->nodeValue), 256);
switch ($key) {
case 'p': // a prime modulus meeting the [DSS] requirements
// Parameters P, Q, and G can be public and common to a group of users. They might be known
// from application context. As such, they are optional but P and Q must either both appear
// or both be absent
$components['p'] = $value;
break;
case 'q': // an integer in the range 2**159 < Q < 2**160 which is a prime divisor of P-1
$components['q'] = $value;
break;
case 'g': // an integer with certain properties with respect to P and Q
$components['g'] = $value;
break;
case 'y': // G**X mod P (where X is part of the private key and not made public)
$components['y'] = $value;
// the remaining options do not do anything
case 'j': // (P - 1) / Q
// Parameter J is available for inclusion solely for efficiency as it is calculatable from
// P and Q
case 'seed': // a DSA prime generation seed
// Parameters seed and pgenCounter are used in the DSA prime number generation algorithm
// specified in [DSS]. As such, they are optional but must either both be present or both
// be absent
case 'pgencounter': // a DSA prime generation counter
}
}
libxml_use_internal_errors($use_errors);
if (!isset($components['y'])) {
throw new \UnexpectedValueException('Key is missing y component');
}
switch (true) {
case !isset($components['p']):
case !isset($components['q']):
case !isset($components['g']):
return ['y' => $components['y']];
}
return $components;
}
/**
* Convert a public key to the appropriate format
*
* See https://www.w3.org/TR/xmldsig-core/#sec-DSAKeyValue
*
* @param BigInteger $p
* @param BigInteger $q
* @param BigInteger $g
* @param BigInteger $y
* @return string
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
{
return "<DSAKeyValue>\r\n" .
' <P>' . Strings::base64_encode($p->toBytes()) . "</P>\r\n" .
' <Q>' . Strings::base64_encode($q->toBytes()) . "</Q>\r\n" .
' <G>' . Strings::base64_encode($g->toBytes()) . "</G>\r\n" .
' <Y>' . Strings::base64_encode($y->toBytes()) . "</Y>\r\n" .
'</DSAKeyValue>';
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* ASN1 Signature Handler
*
* PHP version 5
*
* Handles signatures in the format described in
* https://tools.ietf.org/html/rfc3279#section-2.2.2
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA\Formats\Signature;
use phpseclib3\File\ASN1 as Encoder;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* ASN1 Signature Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class ASN1
{
/**
* Loads a signature
*
* @param string $sig
* @return array|bool
*/
public static function load($sig)
{
if (!is_string($sig)) {
return false;
}
$decoded = Encoder::decodeBER($sig);
if (empty($decoded)) {
return false;
}
$components = Encoder::asn1map($decoded[0], Maps\DssSigValue::MAP);
return $components;
}
/**
* Returns a signature in the appropriate format
*
* @param BigInteger $r
* @param BigInteger $s
* @return string
*/
public static function save(BigInteger $r, BigInteger $s)
{
return Encoder::encodeDER(compact('r', 's'), Maps\DssSigValue::MAP);
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* Raw DSA Signature Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA\Formats\Signature;
use phpseclib3\Crypt\Common\Formats\Signature\Raw as Progenitor;
/**
* Raw DSA Signature Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Raw extends Progenitor
{
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* SSH2 Signature Handler
*
* PHP version 5
*
* Handles signatures in the format used by SSH2
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA\Formats\Signature;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Math\BigInteger;
/**
* SSH2 Signature Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class SSH2
{
/**
* Loads a signature
*
* @param string $sig
* @return mixed
*/
public static function load($sig)
{
if (!is_string($sig)) {
return false;
}
$result = Strings::unpackSSH2('ss', $sig);
if ($result === false) {
return false;
}
list($type, $blob) = $result;
if ($type != 'ssh-dss' || strlen($blob) != 40) {
return false;
}
return [
'r' => new BigInteger(substr($blob, 0, 20), 256),
's' => new BigInteger(substr($blob, 20), 256)
];
}
/**
* Returns a signature in the appropriate format
*
* @param BigInteger $r
* @param BigInteger $s
* @return string
*/
public static function save(BigInteger $r, BigInteger $s)
{
if ($r->getLength() > 160 || $s->getLength() > 160) {
return false;
}
return Strings::packSSH2(
'ss',
'ssh-dss',
str_pad($r->toBytes(), 20, "\0", STR_PAD_LEFT) .
str_pad($s->toBytes(), 20, "\0", STR_PAD_LEFT)
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* DSA Parameters
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\DSA;
/**
* DSA Parameters
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class Parameters extends DSA
{
/**
* Returns the parameters
*
* @param string $type
* @param array $options optional
* @return string
*/
public function toString($type = 'PKCS1', array $options = [])
{
$type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
return $type::saveParameters($this->p, $this->q, $this->g, $options);
}
}

View File

@@ -0,0 +1,154 @@
<?php
/**
* DSA Private Key
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature;
use phpseclib3\Math\BigInteger;
/**
* DSA Private Key
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PrivateKey extends DSA implements Common\PrivateKey
{
use Common\Traits\PasswordProtected;
/**
* DSA secret exponent x
*
* @var BigInteger
*/
protected $x;
/**
* Returns the public key
*
* If you do "openssl rsa -in private.rsa -pubout -outform PEM" you get a PKCS8 formatted key
* that contains a publicKeyAlgorithm AlgorithmIdentifier and a publicKey BIT STRING.
* An AlgorithmIdentifier contains an OID and a parameters field. With RSA public keys this
* parameters field is NULL. With DSA PKCS8 public keys it is not - it contains the p, q and g
* variables. The publicKey BIT STRING contains, simply, the y variable. This can be verified
* by getting a DSA PKCS8 public key:
*
* "openssl dsa -in private.dsa -pubout -outform PEM"
*
* ie. just swap out rsa with dsa in the rsa command above.
*
* A PKCS1 public key corresponds to the publicKey portion of the PKCS8 key. In the case of RSA
* the publicKey portion /is/ the key. In the case of DSA it is not. You cannot verify a signature
* without the parameters and the PKCS1 DSA public key format does not include the parameters.
*
* @see self::getPrivateKey()
* @return mixed
*/
public function getPublicKey()
{
$type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
if (!isset($this->y)) {
$this->y = $this->g->powMod($this->x, $this->p);
}
$key = $type::savePublicKey($this->p, $this->q, $this->g, $this->y);
return DSA::loadFormat('PKCS8', $key)
->withHash($this->hash->getHash())
->withSignatureFormat($this->shortFormat);
}
/**
* Create a signature
*
* @see self::verify()
* @param string $message
* @return mixed
*/
public function sign($message)
{
$format = $this->sigFormat;
if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
$signature = '';
$result = openssl_sign($message, $signature, $this->toString('PKCS8'), $this->hash->getHash());
if ($result) {
if ($this->shortFormat == 'ASN1') {
return $signature;
}
$loaded = ASN1Signature::load($signature);
$r = $loaded['r'];
$s = $loaded['s'];
return $format::save($r, $s);
}
}
$h = $this->hash->hash($message);
$h = $this->bits2int($h);
while (true) {
$k = BigInteger::randomRange(self::$one, $this->q->subtract(self::$one));
$r = $this->g->powMod($k, $this->p);
list(, $r) = $r->divide($this->q);
if ($r->equals(self::$zero)) {
continue;
}
$kinv = $k->modInverse($this->q);
$temp = $h->add($this->x->multiply($r));
$temp = $kinv->multiply($temp);
list(, $s) = $temp->divide($this->q);
if (!$s->equals(self::$zero)) {
break;
}
}
// the following is an RFC6979 compliant implementation of deterministic DSA
// it's unused because it's mainly intended for use when a good CSPRNG isn't
// available. if phpseclib's CSPRNG isn't good then even key generation is
// suspect
/*
$h1 = $this->hash->hash($message);
$k = $this->computek($h1);
$r = $this->g->powMod($k, $this->p);
list(, $r) = $r->divide($this->q);
$kinv = $k->modInverse($this->q);
$h1 = $this->bits2int($h1);
$temp = $h1->add($this->x->multiply($r));
$temp = $kinv->multiply($temp);
list(, $s) = $temp->divide($this->q);
*/
return $format::save($r, $s);
}
/**
* Returns the private key
*
* @param string $type
* @param array $options optional
* @return string
*/
public function toString($type, array $options = [])
{
$type = self::validatePlugin('Keys', $type, 'savePrivateKey');
if (!isset($this->y)) {
$this->y = $this->g->powMod($this->x, $this->p);
}
return $type::savePrivateKey($this->p, $this->q, $this->g, $this->y, $this->x, $this->password, $options);
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* DSA Public Key
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature;
/**
* DSA Public Key
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PublicKey extends DSA implements Common\PublicKey
{
use Common\Traits\Fingerprint;
/**
* Verify a signature
*
* @see self::verify()
* @param string $message
* @param string $signature
* @return mixed
*/
public function verify($message, $signature)
{
$format = $this->sigFormat;
$params = $format::load($signature);
if ($params === false || count($params) != 2) {
return false;
}
$r = $params['r'];
$s = $params['s'];
if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
$sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature;
$result = openssl_verify($message, $sig, $this->toString('PKCS8'), $this->hash->getHash());
if ($result != -1) {
return (bool) $result;
}
}
$q_1 = $this->q->subtract(self::$one);
if (!$r->between(self::$one, $q_1) || !$s->between(self::$one, $q_1)) {
return false;
}
$w = $s->modInverse($this->q);
$h = $this->hash->hash($message);
$h = $this->bits2int($h);
list(, $u1) = $h->multiply($w)->divide($this->q);
list(, $u2) = $r->multiply($w)->divide($this->q);
$v1 = $this->g->powMod($u1, $this->p);
$v2 = $this->y->powMod($u2, $this->p);
list(, $v) = $v1->multiply($v2)->divide($this->p);
list(, $v) = $v->divide($this->q);
return $v->equals($r);
}
/**
* Returns the public key
*
* @param string $type
* @param array $options optional
* @return string
*/
public function toString($type, array $options = [])
{
$type = self::validatePlugin('Keys', $type, 'savePublicKey');
return $type::savePublicKey($this->p, $this->q, $this->g, $this->y, $options);
}
}

View File

@@ -0,0 +1,480 @@
<?php
/**
* Pure-PHP implementation of EC.
*
* PHP version 5
*
* Here's an example of how to create signatures and verify signatures with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $private = \phpseclib3\Crypt\EC::createKey('secp256k1');
* $public = $private->getPublicKey();
*
* $plaintext = 'terrafrost';
*
* $signature = $private->sign($plaintext);
*
* echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
use phpseclib3\Crypt\EC\Curves\Curve25519;
use phpseclib3\Crypt\EC\Curves\Ed25519;
use phpseclib3\Crypt\EC\Curves\Ed448;
use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
use phpseclib3\Crypt\EC\Parameters;
use phpseclib3\Crypt\EC\PrivateKey;
use phpseclib3\Crypt\EC\PublicKey;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\Exception\UnsupportedCurveException;
use phpseclib3\Exception\UnsupportedOperationException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps\ECParameters;
use phpseclib3\Math\BigInteger;
/**
* Pure-PHP implementation of EC.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class EC extends AsymmetricKey
{
/**
* Algorithm Name
*
* @var string
*/
const ALGORITHM = 'EC';
/**
* Public Key QA
*
* @var object[]
*/
protected $QA;
/**
* Curve
*
* @var EC\BaseCurves\Base
*/
protected $curve;
/**
* Signature Format
*
* @var string
*/
protected $format;
/**
* Signature Format (Short)
*
* @var string
*/
protected $shortFormat;
/**
* Curve Name
*
* @var string
*/
private $curveName;
/**
* Curve Order
*
* Used for deterministic ECDSA
*
* @var BigInteger
*/
protected $q;
/**
* Alias for the private key
*
* Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because
* with x you have x * the base point yielding an (x, y)-coordinate that is the
* public key. But the x is different depending on which side of the equal sign
* you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate.
*
* @var BigInteger
*/
protected $x;
/**
* Context
*
* @var string
*/
protected $context;
/**
* Signature Format
*
* @var string
*/
protected $sigFormat;
/**
* Create public / private key pair.
*
* @param string $curve
* @return PrivateKey
*/
public static function createKey($curve)
{
self::initialize_static_variables();
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
}
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
$curve = strtolower($curve);
if (self::$engines['libsodium'] && $curve == 'ed25519' && function_exists('sodium_crypto_sign_keypair')) {
$kp = sodium_crypto_sign_keypair();
$privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp));
//$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp));
$privatekey->curveName = 'Ed25519';
//$publickey->curveName = $curve;
return $privatekey;
}
$privatekey = new PrivateKey();
$curveName = $curve;
if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) {
$curveName = ucfirst($curveName);
} elseif (substr($curveName, 0, 10) == 'brainpoolp') {
$curveName = 'brainpoolP' . substr($curveName, 10);
}
$curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
if (!class_exists($curve)) {
throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported');
}
$reflect = new \ReflectionClass($curve);
$curveName = $reflect->isFinal() ?
$reflect->getParentClass()->getShortName() :
$reflect->getShortName();
$curve = new $curve();
if ($curve instanceof TwistedEdwardsCurve) {
$arr = $curve->extractSecret(Random::string($curve instanceof Ed448 ? 57 : 32));
$privatekey->dA = $dA = $arr['dA'];
$privatekey->secret = $arr['secret'];
} else {
$privatekey->dA = $dA = $curve->createRandomMultiplier();
}
if ($curve instanceof Curve25519 && self::$engines['libsodium']) {
//$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000');
//$QA = sodium_crypto_scalarmult($dA->toBytes(), $r);
$QA = sodium_crypto_box_publickey_from_secretkey($dA->toBytes());
$privatekey->QA = [$curve->convertInteger(new BigInteger(strrev($QA), 256))];
} else {
$privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA);
}
$privatekey->curve = $curve;
//$publickey = clone $privatekey;
//unset($publickey->dA);
//unset($publickey->x);
$privatekey->curveName = $curveName;
//$publickey->curveName = $curveName;
if ($privatekey->curve instanceof TwistedEdwardsCurve) {
return $privatekey->withHash($curve::HASH);
}
return $privatekey;
}
/**
* OnLoad Handler
*
* @return bool
*/
protected static function onLoad(array $components)
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if (!isset($components['dA']) && !isset($components['QA'])) {
$new = new Parameters();
$new->curve = $components['curve'];
return $new;
}
$new = isset($components['dA']) ?
new PrivateKey() :
new PublicKey();
$new->curve = $components['curve'];
$new->QA = $components['QA'];
if (isset($components['dA'])) {
$new->dA = $components['dA'];
$new->secret = $components['secret'];
}
if ($new->curve instanceof TwistedEdwardsCurve) {
return $new->withHash($components['curve']::HASH);
}
return $new;
}
/**
* Constructor
*
* PublicKey and PrivateKey objects can only be created from abstract RSA class
*/
protected function __construct()
{
$this->sigFormat = self::validatePlugin('Signature', 'ASN1');
$this->shortFormat = 'ASN1';
parent::__construct();
}
/**
* Returns the curve
*
* Returns a string if it's a named curve, an array if not
*
* @return string|array
*/
public function getCurve()
{
if ($this->curveName) {
return $this->curveName;
}
if ($this->curve instanceof MontgomeryCurve) {
$this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448';
return $this->curveName;
}
if ($this->curve instanceof TwistedEdwardsCurve) {
$this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448';
return $this->curveName;
}
$params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]);
$decoded = ASN1::extractBER($params);
$decoded = ASN1::decodeBER($decoded);
$decoded = ASN1::asn1map($decoded[0], ECParameters::MAP);
if (isset($decoded['namedCurve'])) {
$this->curveName = $decoded['namedCurve'];
return $decoded['namedCurve'];
}
if (!$namedCurves) {
PKCS1::useSpecifiedCurve();
}
return $decoded;
}
/**
* Returns the key size
*
* Quoting https://tools.ietf.org/html/rfc5656#section-2,
*
* "The size of a set of elliptic curve domain parameters on a prime
* curve is defined as the number of bits in the binary representation
* of the field order, commonly denoted by p. Size on a
* characteristic-2 curve is defined as the number of bits in the binary
* representation of the field, commonly denoted by m. A set of
* elliptic curve domain parameters defines a group of order n generated
* by a base point P"
*
* @return int
*/
public function getLength()
{
return $this->curve->getLength();
}
/**
* Returns the current engine being used
*
* @see self::useInternalEngine()
* @see self::useBestEngine()
* @return string
*/
public function getEngine()
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if ($this->curve instanceof TwistedEdwardsCurve) {
return $this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context) ?
'libsodium' : 'PHP';
}
return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
'OpenSSL' : 'PHP';
}
/**
* Returns the public key coordinates as a string
*
* Used by ECDH
*
* @return string
*/
public function getEncodedCoordinates()
{
if ($this->curve instanceof MontgomeryCurve) {
return strrev($this->QA[0]->toBytes(true));
}
if ($this->curve instanceof TwistedEdwardsCurve) {
return $this->curve->encodePoint($this->QA);
}
return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true);
}
/**
* Returns the parameters
*
* @see self::getPublicKey()
* @param string $type optional
* @return mixed
*/
public function getParameters($type = 'PKCS1')
{
$type = self::validatePlugin('Keys', $type, 'saveParameters');
$key = $type::saveParameters($this->curve);
return EC::load($key, 'PKCS1')
->withHash($this->hash->getHash())
->withSignatureFormat($this->shortFormat);
}
/**
* Determines the signature padding mode
*
* Valid values are: ASN1, SSH2, Raw
*
* @param string $format
*/
public function withSignatureFormat($format)
{
if ($this->curve instanceof MontgomeryCurve) {
throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
}
$new = clone $this;
$new->shortFormat = $format;
$new->sigFormat = self::validatePlugin('Signature', $format);
return $new;
}
/**
* Returns the signature format currently being used
*
*/
public function getSignatureFormat()
{
return $this->shortFormat;
}
/**
* Sets the context
*
* Used by Ed25519 / Ed448.
*
* @see self::sign()
* @see self::verify()
* @param string $context optional
*/
public function withContext($context = null)
{
if (!$this->curve instanceof TwistedEdwardsCurve) {
throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts');
}
$new = clone $this;
if (!isset($context)) {
$new->context = null;
return $new;
}
if (!is_string($context)) {
throw new \InvalidArgumentException('setContext expects a string');
}
if (strlen($context) > 255) {
throw new \LengthException('The context is supposed to be, at most, 255 bytes long');
}
$new->context = $context;
return $new;
}
/**
* Returns the signature format currently being used
*
*/
public function getContext()
{
return $this->context;
}
/**
* Determines which hashing function should be used
*
* @param string $hash
*/
public function withHash($hash)
{
if ($this->curve instanceof MontgomeryCurve) {
throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
}
if ($this->curve instanceof Ed25519 && $hash != 'sha512') {
throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash');
}
if ($this->curve instanceof Ed448 && $hash != 'shake256-912') {
throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes');
}
return parent::withHash($hash);
}
/**
* __toString() magic method
*
* @return string
*/
public function __toString()
{
if ($this->curve instanceof MontgomeryCurve) {
return '';
}
return parent::__toString();
}
}

View File

@@ -0,0 +1,218 @@
<?php
/**
* Curve methods common to all curves
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Math\BigInteger;
/**
* Base
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Base
{
/**
* The Order
*
* @var BigInteger
*/
protected $order;
/**
* Finite Field Integer factory
*
* @var FiniteField\Integer
*/
protected $factory;
/**
* Returns a random integer
*
* @return object
*/
public function randomInteger()
{
return $this->factory->randomInteger();
}
/**
* Converts a BigInteger to a FiniteField\Integer integer
*
* @return object
*/
public function convertInteger(BigInteger $x)
{
return $this->factory->newInteger($x);
}
/**
* Returns the length, in bytes, of the modulo
*
* @return integer
*/
public function getLengthInBytes()
{
return $this->factory->getLengthInBytes();
}
/**
* Returns the length, in bits, of the modulo
*
* @return integer
*/
public function getLength()
{
return $this->factory->getLength();
}
/**
* Multiply a point on the curve by a scalar
*
* Uses the montgomery ladder technique as described here:
*
* https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
* https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772
*
* @return array
*/
public function multiplyPoint(array $p, BigInteger $d)
{
$alreadyInternal = isset($p[2]);
$r = $alreadyInternal ?
[[], $p] :
[[], $this->convertToInternal($p)];
$d = $d->toBits();
for ($i = 0; $i < strlen($d); $i++) {
$d_i = (int) $d[$i];
$r[1 - $d_i] = $this->addPoint($r[0], $r[1]);
$r[$d_i] = $this->doublePoint($r[$d_i]);
}
return $alreadyInternal ? $r[0] : $this->convertToAffine($r[0]);
}
/**
* Creates a random scalar multiplier
*
* @return BigInteger
*/
public function createRandomMultiplier()
{
static $one;
if (!isset($one)) {
$one = new BigInteger(1);
}
return BigInteger::randomRange($one, $this->order->subtract($one));
}
/**
* Performs range check
*/
public function rangeCheck(BigInteger $x)
{
static $zero;
if (!isset($zero)) {
$zero = new BigInteger();
}
if (!isset($this->order)) {
throw new \RuntimeException('setOrder needs to be called before this method');
}
if ($x->compare($this->order) > 0 || $x->compare($zero) <= 0) {
throw new \RangeException('x must be between 1 and the order of the curve');
}
}
/**
* Sets the Order
*/
public function setOrder(BigInteger $order)
{
$this->order = $order;
}
/**
* Returns the Order
*
* @return BigInteger
*/
public function getOrder()
{
return $this->order;
}
/**
* Use a custom defined modular reduction function
*
* @return object
*/
public function setReduction(callable $func)
{
$this->factory->setReduction($func);
}
/**
* Returns the affine point
*
* @return object[]
*/
public function convertToAffine(array $p)
{
return $p;
}
/**
* Converts an affine point to a jacobian coordinate
*
* @return object[]
*/
public function convertToInternal(array $p)
{
return $p;
}
/**
* Negates a point
*
* @return object[]
*/
public function negatePoint(array $p)
{
$temp = [
$p[0],
$p[1]->negate()
];
if (isset($p[2])) {
$temp[] = $p[2];
}
return $temp;
}
/**
* Multiply and Add Points
*
* @return int[]
*/
public function multiplyAddPoints(array $points, array $scalars)
{
$p1 = $this->convertToInternal($points[0]);
$p2 = $this->convertToInternal($points[1]);
$p1 = $this->multiplyPoint($p1, $scalars[0]);
$p2 = $this->multiplyPoint($p2, $scalars[1]);
$r = $this->addPoint($p1, $p2);
return $this->convertToAffine($r);
}
}

View File

@@ -0,0 +1,373 @@
<?php
/**
* Curves over y^2 + x*y = x^3 + a*x^2 + b
*
* These are curves used in SEC 2 over prime fields: http://www.secg.org/SEC2-Ver-1.0.pdf
* The curve is a weierstrass curve with a[3] and a[2] set to 0.
*
* Uses Jacobian Coordinates for speed if able:
*
* https://en.wikipedia.org/wiki/Jacobian_curve
* https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\BinaryField;
use phpseclib3\Math\BinaryField\Integer as BinaryInteger;
/**
* Curves over y^2 + x*y = x^3 + a*x^2 + b
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class Binary extends Base
{
/**
* Binary Field Integer factory
*
* @var BinaryField
*/
protected $factory;
/**
* Cofficient for x^1
*
* @var object
*/
protected $a;
/**
* Cofficient for x^0
*
* @var object
*/
protected $b;
/**
* Base Point
*
* @var object
*/
protected $p;
/**
* The number one over the specified finite field
*
* @var object
*/
protected $one;
/**
* The modulo
*
* @var BigInteger
*/
protected $modulo;
/**
* The Order
*
* @var BigInteger
*/
protected $order;
/**
* Sets the modulo
*/
public function setModulo(...$modulo)
{
$this->modulo = $modulo;
$this->factory = new BinaryField(...$modulo);
$this->one = $this->factory->newInteger("\1");
}
/**
* Set coefficients a and b
*
* @param string $a
* @param string $b
*/
public function setCoefficients($a, $b)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
$this->a = $this->factory->newInteger(pack('H*', $a));
$this->b = $this->factory->newInteger(pack('H*', $b));
}
/**
* Set x and y coordinates for the base point
*
* @param string|BinaryInteger $x
* @param string|BinaryInteger $y
*/
public function setBasePoint($x, $y)
{
switch (true) {
case !is_string($x) && !$x instanceof BinaryInteger:
throw new \UnexpectedValueException('Argument 1 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer');
case !is_string($y) && !$y instanceof BinaryInteger:
throw new \UnexpectedValueException('Argument 2 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer');
}
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
$this->p = [
is_string($x) ? $this->factory->newInteger(pack('H*', $x)) : $x,
is_string($y) ? $this->factory->newInteger(pack('H*', $y)) : $y
];
}
/**
* Retrieve the base point as an array
*
* @return array
*/
public function getBasePoint()
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
/*
if (!isset($this->p)) {
throw new \RuntimeException('setBasePoint needs to be called before this method');
}
*/
return $this->p;
}
/**
* Adds two points on the curve
*
* @return FiniteField[]
*/
public function addPoint(array $p, array $q)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
if (count($q)) {
return $q;
}
if (count($p)) {
return $p;
}
return [];
}
if (!isset($p[2]) || !isset($q[2])) {
throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
if ($p[0]->equals($q[0])) {
return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
}
// formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html
list($x1, $y1, $z1) = $p;
list($x2, $y2, $z2) = $q;
$o1 = $z1->multiply($z1);
$b = $x2->multiply($o1);
if ($z2->equals($this->one)) {
$d = $y2->multiply($o1)->multiply($z1);
$e = $x1->add($b);
$f = $y1->add($d);
$z3 = $e->multiply($z1);
$h = $f->multiply($x2)->add($z3->multiply($y2));
$i = $f->add($z3);
$g = $z3->multiply($z3);
$p1 = $this->a->multiply($g);
$p2 = $f->multiply($i);
$p3 = $e->multiply($e)->multiply($e);
$x3 = $p1->add($p2)->add($p3);
$y3 = $i->multiply($x3)->add($g->multiply($h));
return [$x3, $y3, $z3];
}
$o2 = $z2->multiply($z2);
$a = $x1->multiply($o2);
$c = $y1->multiply($o2)->multiply($z2);
$d = $y2->multiply($o1)->multiply($z1);
$e = $a->add($b);
$f = $c->add($d);
$g = $e->multiply($z1);
$h = $f->multiply($x2)->add($g->multiply($y2));
$z3 = $g->multiply($z2);
$i = $f->add($z3);
$p1 = $this->a->multiply($z3->multiply($z3));
$p2 = $f->multiply($i);
$p3 = $e->multiply($e)->multiply($e);
$x3 = $p1->add($p2)->add($p3);
$y3 = $i->multiply($x3)->add($g->multiply($g)->multiply($h));
return [$x3, $y3, $z3];
}
/**
* Doubles a point on a curve
*
* @return FiniteField[]
*/
public function doublePoint(array $p)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
if (!count($p)) {
return [];
}
if (!isset($p[2])) {
throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
// formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html
list($x1, $y1, $z1) = $p;
$a = $x1->multiply($x1);
$b = $a->multiply($a);
if ($z1->equals($this->one)) {
$x3 = $b->add($this->b);
$z3 = clone $x1;
$p1 = $a->add($y1)->add($z3)->multiply($this->b);
$p2 = $a->add($y1)->multiply($b);
$y3 = $p1->add($p2);
return [$x3, $y3, $z3];
}
$c = $z1->multiply($z1);
$d = $c->multiply($c);
$x3 = $b->add($this->b->multiply($d->multiply($d)));
$z3 = $x1->multiply($c);
$p1 = $b->multiply($z3);
$p2 = $a->add($y1->multiply($z1))->add($z3)->multiply($x3);
$y3 = $p1->add($p2);
return [$x3, $y3, $z3];
}
/**
* Returns the X coordinate and the derived Y coordinate
*
* Not supported because it is covered by patents.
* Quoting https://www.openssl.org/docs/man1.1.0/apps/ecparam.html ,
*
* "Due to patent issues the compressed option is disabled by default for binary curves
* and can be enabled by defining the preprocessor macro OPENSSL_EC_BIN_PT_COMP at
* compile time."
*
* @return array
*/
public function derivePoint($m)
{
throw new \RuntimeException('Point compression on binary finite field elliptic curves is not supported');
}
/**
* Tests whether or not the x / y values satisfy the equation
*
* @return boolean
*/
public function verifyPoint(array $p)
{
list($x, $y) = $p;
$lhs = $y->multiply($y);
$lhs = $lhs->add($x->multiply($y));
$x2 = $x->multiply($x);
$x3 = $x2->multiply($x);
$rhs = $x3->add($this->a->multiply($x2))->add($this->b);
return $lhs->equals($rhs);
}
/**
* Returns the modulo
*
* @return BigInteger
*/
public function getModulo()
{
return $this->modulo;
}
/**
* Returns the a coefficient
*
* @return \phpseclib3\Math\PrimeField\Integer
*/
public function getA()
{
return $this->a;
}
/**
* Returns the a coefficient
*
* @return \phpseclib3\Math\PrimeField\Integer
*/
public function getB()
{
return $this->b;
}
/**
* Returns the affine point
*
* A Jacobian Coordinate is of the form (x, y, z).
* To convert a Jacobian Coordinate to an Affine Point
* you do (x / z^2, y / z^3)
*
* @return \phpseclib3\Math\PrimeField\Integer[]
*/
public function convertToAffine(array $p)
{
if (!isset($p[2])) {
return $p;
}
list($x, $y, $z) = $p;
$z = $this->one->divide($z);
$z2 = $z->multiply($z);
return [
$x->multiply($z2),
$y->multiply($z2)->multiply($z)
];
}
/**
* Converts an affine point to a jacobian coordinate
*
* @return \phpseclib3\Math\PrimeField\Integer[]
*/
public function convertToInternal(array $p)
{
if (isset($p[2])) {
return $p;
}
$p[2] = clone $this->one;
$p['fresh'] = true;
return $p;
}
}

View File

@@ -0,0 +1,335 @@
<?php
/**
* Generalized Koblitz Curves over y^2 = x^3 + b.
*
* According to http://www.secg.org/SEC2-Ver-1.0.pdf Koblitz curves are over the GF(2**m)
* finite field. Both the $a$ and $b$ coefficients are either 0 or 1. However, SEC2
* generalizes the definition to include curves over GF(P) "which possess an efficiently
* computable endomorphism".
*
* For these generalized Koblitz curves $b$ doesn't have to be 0 or 1. Whether or not $a$
* has any restrictions on it is unclear, however, for all the GF(P) Koblitz curves defined
* in SEC2 v1.0 $a$ is $0$ so all of the methods defined herein will assume that it is.
*
* I suppose we could rename the $b$ coefficient to $a$, however, the documentation refers
* to $b$ so we'll just keep it.
*
* If a later version of SEC2 comes out wherein some $a$ values are non-zero we can create a
* new method for those. eg. KoblitzA1Prime.php or something.
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\PrimeField;
/**
* Curves over y^2 = x^3 + b
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class KoblitzPrime extends Prime
{
/**
* Basis
*
* @var list<array{a: BigInteger, b: BigInteger}>
*/
protected $basis;
/**
* Beta
*
* @var PrimeField\Integer
*/
protected $beta;
// don't overwrite setCoefficients() with one that only accepts one parameter so that
// one might be able to switch between KoblitzPrime and Prime more easily (for benchmarking
// purposes).
/**
* Multiply and Add Points
*
* Uses a efficiently computable endomorphism to achieve a slight speedup
*
* Adapted from:
* https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/short.js#L219
*
* @return int[]
*/
public function multiplyAddPoints(array $points, array $scalars)
{
static $zero, $one, $two;
if (!isset($two)) {
$two = new BigInteger(2);
$one = new BigInteger(1);
}
if (!isset($this->beta)) {
// get roots
$inv = $this->one->divide($this->two)->negate();
$s = $this->three->negate()->squareRoot()->multiply($inv);
$betas = [
$inv->add($s),
$inv->subtract($s)
];
$this->beta = $betas[0]->compare($betas[1]) < 0 ? $betas[0] : $betas[1];
//echo strtoupper($this->beta->toHex(true)) . "\n"; exit;
}
if (!isset($this->basis)) {
$factory = new PrimeField($this->order);
$tempOne = $factory->newInteger($one);
$tempTwo = $factory->newInteger($two);
$tempThree = $factory->newInteger(new BigInteger(3));
$inv = $tempOne->divide($tempTwo)->negate();
$s = $tempThree->negate()->squareRoot()->multiply($inv);
$lambdas = [
$inv->add($s),
$inv->subtract($s)
];
$lhs = $this->multiplyPoint($this->p, $lambdas[0])[0];
$rhs = $this->p[0]->multiply($this->beta);
$lambda = $lhs->equals($rhs) ? $lambdas[0] : $lambdas[1];
$this->basis = static::extendedGCD($lambda->toBigInteger(), $this->order);
///*
foreach ($this->basis as $basis) {
echo strtoupper($basis['a']->toHex(true)) . "\n";
echo strtoupper($basis['b']->toHex(true)) . "\n\n";
}
exit;
//*/
}
$npoints = $nscalars = [];
for ($i = 0; $i < count($points); $i++) {
$p = $points[$i];
$k = $scalars[$i]->toBigInteger();
// begin split
list($v1, $v2) = $this->basis;
$c1 = $v2['b']->multiply($k);
list($c1, $r) = $c1->divide($this->order);
if ($this->order->compare($r->multiply($two)) <= 0) {
$c1 = $c1->add($one);
}
$c2 = $v1['b']->negate()->multiply($k);
list($c2, $r) = $c2->divide($this->order);
if ($this->order->compare($r->multiply($two)) <= 0) {
$c2 = $c2->add($one);
}
$p1 = $c1->multiply($v1['a']);
$p2 = $c2->multiply($v2['a']);
$q1 = $c1->multiply($v1['b']);
$q2 = $c2->multiply($v2['b']);
$k1 = $k->subtract($p1)->subtract($p2);
$k2 = $q1->add($q2)->negate();
// end split
$beta = [
$p[0]->multiply($this->beta),
$p[1],
clone $this->one
];
if (isset($p['naf'])) {
$beta['naf'] = array_map(function ($p) {
return [
$p[0]->multiply($this->beta),
$p[1],
clone $this->one
];
}, $p['naf']);
$beta['nafwidth'] = $p['nafwidth'];
}
if ($k1->isNegative()) {
$k1 = $k1->negate();
$p = $this->negatePoint($p);
}
if ($k2->isNegative()) {
$k2 = $k2->negate();
$beta = $this->negatePoint($beta);
}
$pos = 2 * $i;
$npoints[$pos] = $p;
$nscalars[$pos] = $this->factory->newInteger($k1);
$pos++;
$npoints[$pos] = $beta;
$nscalars[$pos] = $this->factory->newInteger($k2);
}
return parent::multiplyAddPoints($npoints, $nscalars);
}
/**
* Returns the numerator and denominator of the slope
*
* @return FiniteField[]
*/
protected function doublePointHelper(array $p)
{
$numerator = $this->three->multiply($p[0])->multiply($p[0]);
$denominator = $this->two->multiply($p[1]);
return [$numerator, $denominator];
}
/**
* Doubles a jacobian coordinate on the curve
*
* See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
*
* @return FiniteField[]
*/
protected function jacobianDoublePoint(array $p)
{
list($x1, $y1, $z1) = $p;
$a = $x1->multiply($x1);
$b = $y1->multiply($y1);
$c = $b->multiply($b);
$d = $x1->add($b);
$d = $d->multiply($d)->subtract($a)->subtract($c)->multiply($this->two);
$e = $this->three->multiply($a);
$f = $e->multiply($e);
$x3 = $f->subtract($this->two->multiply($d));
$y3 = $e->multiply($d->subtract($x3))->subtract(
$this->eight->multiply($c)
);
$z3 = $this->two->multiply($y1)->multiply($z1);
return [$x3, $y3, $z3];
}
/**
* Doubles a "fresh" jacobian coordinate on the curve
*
* See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-mdbl-2007-bl
*
* @return FiniteField[]
*/
protected function jacobianDoublePointMixed(array $p)
{
list($x1, $y1) = $p;
$xx = $x1->multiply($x1);
$yy = $y1->multiply($y1);
$yyyy = $yy->multiply($yy);
$s = $x1->add($yy);
$s = $s->multiply($s)->subtract($xx)->subtract($yyyy)->multiply($this->two);
$m = $this->three->multiply($xx);
$t = $m->multiply($m)->subtract($this->two->multiply($s));
$x3 = $t;
$y3 = $s->subtract($t);
$y3 = $m->multiply($y3)->subtract($this->eight->multiply($yyyy));
$z3 = $this->two->multiply($y1);
return [$x3, $y3, $z3];
}
/**
* Tests whether or not the x / y values satisfy the equation
*
* @return boolean
*/
public function verifyPoint(array $p)
{
list($x, $y) = $p;
$lhs = $y->multiply($y);
$temp = $x->multiply($x)->multiply($x);
$rhs = $temp->add($this->b);
return $lhs->equals($rhs);
}
/**
* Calculates the parameters needed from the Euclidean algorithm as discussed at
* http://diamond.boisestate.edu/~liljanab/MATH308/GuideToECC.pdf#page=148
*
* @param BigInteger $u
* @param BigInteger $v
* @return BigInteger[]
*/
protected static function extendedGCD(BigInteger $u, BigInteger $v)
{
$one = new BigInteger(1);
$zero = new BigInteger();
$a = clone $one;
$b = clone $zero;
$c = clone $zero;
$d = clone $one;
$stop = $v->bitwise_rightShift($v->getLength() >> 1);
$a1 = clone $zero;
$b1 = clone $zero;
$a2 = clone $zero;
$b2 = clone $zero;
$postGreatestIndex = 0;
while (!$v->equals($zero)) {
list($q) = $u->divide($v);
$temp = $u;
$u = $v;
$v = $temp->subtract($v->multiply($q));
$temp = $a;
$a = $c;
$c = $temp->subtract($a->multiply($q));
$temp = $b;
$b = $d;
$d = $temp->subtract($b->multiply($q));
if ($v->compare($stop) > 0) {
$a0 = $v;
$b0 = $c;
} else {
$postGreatestIndex++;
}
if ($postGreatestIndex == 1) {
$a1 = $v;
$b1 = $c->negate();
}
if ($postGreatestIndex == 2) {
$rhs = $a0->multiply($a0)->add($b0->multiply($b0));
$lhs = $v->multiply($v)->add($b->multiply($b));
if ($lhs->compare($rhs) <= 0) {
$a2 = $a0;
$b2 = $b0->negate();
} else {
$a2 = $v;
$b2 = $c->negate();
}
break;
}
}
return [
['a' => $a1, 'b' => $b1],
['a' => $a2, 'b' => $b2]
];
}
}

View File

@@ -0,0 +1,279 @@
<?php
/**
* Curves over y^2 = x^3 + a*x + x
*
* Technically, a Montgomery curve has a coefficient for y^2 but for Curve25519 and Curve448 that
* coefficient is 1.
*
* Curve25519 and Curve448 do not make use of the y coordinate, which makes it unsuitable for use
* with ECDSA / EdDSA. A few other differences between Curve25519 and Ed25519 are discussed at
* https://crypto.stackexchange.com/a/43058/4520
*
* More info:
*
* https://en.wikipedia.org/wiki/Montgomery_curve
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2019 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Crypt\EC\Curves\Curve25519;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\PrimeField;
use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
/**
* Curves over y^2 = x^3 + a*x + x
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class Montgomery extends Base
{
/**
* Prime Field Integer factory
*
* @var PrimeField
*/
protected $factory;
/**
* Cofficient for x
*
* @var object
*/
protected $a;
/**
* Constant used for point doubling
*
* @var object
*/
protected $a24;
/**
* The Number Zero
*
* @var object
*/
protected $zero;
/**
* The Number One
*
* @var object
*/
protected $one;
/**
* Base Point
*
* @var object
*/
protected $p;
/**
* The modulo
*
* @var BigInteger
*/
protected $modulo;
/**
* The Order
*
* @var BigInteger
*/
protected $order;
/**
* Sets the modulo
*/
public function setModulo(BigInteger $modulo)
{
$this->modulo = $modulo;
$this->factory = new PrimeField($modulo);
$this->zero = $this->factory->newInteger(new BigInteger());
$this->one = $this->factory->newInteger(new BigInteger(1));
}
/**
* Set coefficients a
*/
public function setCoefficients(BigInteger $a)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
$this->a = $this->factory->newInteger($a);
$two = $this->factory->newInteger(new BigInteger(2));
$four = $this->factory->newInteger(new BigInteger(4));
$this->a24 = $this->a->subtract($two)->divide($four);
}
/**
* Set x and y coordinates for the base point
*
* @param BigInteger|PrimeInteger $x
* @param BigInteger|PrimeInteger $y
* @return PrimeInteger[]
*/
public function setBasePoint($x, $y)
{
switch (true) {
case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
}
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
$this->p = [
$x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
$y instanceof BigInteger ? $this->factory->newInteger($y) : $y
];
}
/**
* Retrieve the base point as an array
*
* @return array
*/
public function getBasePoint()
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
/*
if (!isset($this->p)) {
throw new \RuntimeException('setBasePoint needs to be called before this method');
}
*/
return $this->p;
}
/**
* Doubles and adds a point on a curve
*
* See https://tools.ietf.org/html/draft-ietf-tls-curve25519-01#appendix-A.1.3
*
* @return FiniteField[][]
*/
private function doubleAndAddPoint(array $p, array $q, PrimeInteger $x1)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
return [];
}
if (!isset($p[1])) {
throw new \RuntimeException('Affine coordinates need to be manually converted to XZ coordinates');
}
list($x2, $z2) = $p;
list($x3, $z3) = $q;
$a = $x2->add($z2);
$aa = $a->multiply($a);
$b = $x2->subtract($z2);
$bb = $b->multiply($b);
$e = $aa->subtract($bb);
$c = $x3->add($z3);
$d = $x3->subtract($z3);
$da = $d->multiply($a);
$cb = $c->multiply($b);
$temp = $da->add($cb);
$x5 = $temp->multiply($temp);
$temp = $da->subtract($cb);
$z5 = $x1->multiply($temp->multiply($temp));
$x4 = $aa->multiply($bb);
$temp = static::class == Curve25519::class ? $bb : $aa;
$z4 = $e->multiply($temp->add($this->a24->multiply($e)));
return [
[$x4, $z4],
[$x5, $z5]
];
}
/**
* Multiply a point on the curve by a scalar
*
* Uses the montgomery ladder technique as described here:
*
* https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
* https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772
*
* @return array
*/
public function multiplyPoint(array $p, BigInteger $d)
{
$p1 = [$this->one, $this->zero];
$alreadyInternal = isset($p[1]);
$p2 = $this->convertToInternal($p);
$x = $p[0];
$b = $d->toBits();
$b = str_pad($b, 256, '0', STR_PAD_LEFT);
for ($i = 0; $i < strlen($b); $i++) {
$b_i = (int) $b[$i];
if ($b_i) {
list($p2, $p1) = $this->doubleAndAddPoint($p2, $p1, $x);
} else {
list($p1, $p2) = $this->doubleAndAddPoint($p1, $p2, $x);
}
}
return $alreadyInternal ? $p1 : $this->convertToAffine($p1);
}
/**
* Converts an affine point to an XZ coordinate
*
* From https://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html
*
* XZ coordinates represent x y as X Z satsfying the following equations:
*
* x=X/Z
*
* @return PrimeInteger[]
*/
public function convertToInternal(array $p)
{
if (empty($p)) {
return [clone $this->zero, clone $this->one];
}
if (isset($p[1])) {
return $p;
}
$p[1] = clone $this->one;
return $p;
}
/**
* Returns the affine point
*
* @return PrimeInteger[]
*/
public function convertToAffine(array $p)
{
if (!isset($p[1])) {
return $p;
}
list($x, $z) = $p;
return [$x->divide($z)];
}
}

View File

@@ -0,0 +1,785 @@
<?php
/**
* Curves over y^2 = x^3 + a*x + b
*
* These are curves used in SEC 2 over prime fields: http://www.secg.org/SEC2-Ver-1.0.pdf
* The curve is a weierstrass curve with a[1], a[3] and a[2] set to 0.
*
* Uses Jacobian Coordinates for speed if able:
*
* https://en.wikipedia.org/wiki/Jacobian_curve
* https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\Common\FiniteField\Integer;
use phpseclib3\Math\PrimeField;
use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
/**
* Curves over y^2 = x^3 + a*x + b
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class Prime extends Base
{
/**
* Prime Field Integer factory
*
* @var \phpseclib3\Math\PrimeFields
*/
protected $factory;
/**
* Cofficient for x^1
*
* @var object
*/
protected $a;
/**
* Cofficient for x^0
*
* @var object
*/
protected $b;
/**
* Base Point
*
* @var object
*/
protected $p;
/**
* The number one over the specified finite field
*
* @var object
*/
protected $one;
/**
* The number two over the specified finite field
*
* @var object
*/
protected $two;
/**
* The number three over the specified finite field
*
* @var object
*/
protected $three;
/**
* The number four over the specified finite field
*
* @var object
*/
protected $four;
/**
* The number eight over the specified finite field
*
* @var object
*/
protected $eight;
/**
* The modulo
*
* @var BigInteger
*/
protected $modulo;
/**
* The Order
*
* @var BigInteger
*/
protected $order;
/**
* Sets the modulo
*/
public function setModulo(BigInteger $modulo)
{
$this->modulo = $modulo;
$this->factory = new PrimeField($modulo);
$this->two = $this->factory->newInteger(new BigInteger(2));
$this->three = $this->factory->newInteger(new BigInteger(3));
// used by jacobian coordinates
$this->one = $this->factory->newInteger(new BigInteger(1));
$this->four = $this->factory->newInteger(new BigInteger(4));
$this->eight = $this->factory->newInteger(new BigInteger(8));
}
/**
* Set coefficients a and b
*/
public function setCoefficients(BigInteger $a, BigInteger $b)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
$this->a = $this->factory->newInteger($a);
$this->b = $this->factory->newInteger($b);
}
/**
* Set x and y coordinates for the base point
*
* @param BigInteger|PrimeInteger $x
* @param BigInteger|PrimeInteger $y
* @return PrimeInteger[]
*/
public function setBasePoint($x, $y)
{
switch (true) {
case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
}
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
$this->p = [
$x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
$y instanceof BigInteger ? $this->factory->newInteger($y) : $y
];
}
/**
* Retrieve the base point as an array
*
* @return array
*/
public function getBasePoint()
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
/*
if (!isset($this->p)) {
throw new \RuntimeException('setBasePoint needs to be called before this method');
}
*/
return $this->p;
}
/**
* Adds two "fresh" jacobian form on the curve
*
* @return FiniteField[]
*/
protected function jacobianAddPointMixedXY(array $p, array $q)
{
list($u1, $s1) = $p;
list($u2, $s2) = $q;
if ($u1->equals($u2)) {
if (!$s1->equals($s2)) {
return [];
} else {
return $this->doublePoint($p);
}
}
$h = $u2->subtract($u1);
$r = $s2->subtract($s1);
$h2 = $h->multiply($h);
$h3 = $h2->multiply($h);
$v = $u1->multiply($h2);
$x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
$y3 = $r->multiply(
$v->subtract($x3)
)->subtract(
$s1->multiply($h3)
);
return [$x3, $y3, $h];
}
/**
* Adds one "fresh" jacobian form on the curve
*
* The second parameter should be the "fresh" one
*
* @return FiniteField[]
*/
protected function jacobianAddPointMixedX(array $p, array $q)
{
list($u1, $s1, $z1) = $p;
list($x2, $y2) = $q;
$z12 = $z1->multiply($z1);
$u2 = $x2->multiply($z12);
$s2 = $y2->multiply($z12->multiply($z1));
if ($u1->equals($u2)) {
if (!$s1->equals($s2)) {
return [];
} else {
return $this->doublePoint($p);
}
}
$h = $u2->subtract($u1);
$r = $s2->subtract($s1);
$h2 = $h->multiply($h);
$h3 = $h2->multiply($h);
$v = $u1->multiply($h2);
$x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
$y3 = $r->multiply(
$v->subtract($x3)
)->subtract(
$s1->multiply($h3)
);
$z3 = $h->multiply($z1);
return [$x3, $y3, $z3];
}
/**
* Adds two jacobian coordinates on the curve
*
* @return FiniteField[]
*/
protected function jacobianAddPoint(array $p, array $q)
{
list($x1, $y1, $z1) = $p;
list($x2, $y2, $z2) = $q;
$z12 = $z1->multiply($z1);
$z22 = $z2->multiply($z2);
$u1 = $x1->multiply($z22);
$u2 = $x2->multiply($z12);
$s1 = $y1->multiply($z22->multiply($z2));
$s2 = $y2->multiply($z12->multiply($z1));
if ($u1->equals($u2)) {
if (!$s1->equals($s2)) {
return [];
} else {
return $this->doublePoint($p);
}
}
$h = $u2->subtract($u1);
$r = $s2->subtract($s1);
$h2 = $h->multiply($h);
$h3 = $h2->multiply($h);
$v = $u1->multiply($h2);
$x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
$y3 = $r->multiply(
$v->subtract($x3)
)->subtract(
$s1->multiply($h3)
);
$z3 = $h->multiply($z1)->multiply($z2);
return [$x3, $y3, $z3];
}
/**
* Adds two points on the curve
*
* @return FiniteField[]
*/
public function addPoint(array $p, array $q)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
if (count($q)) {
return $q;
}
if (count($p)) {
return $p;
}
return [];
}
// use jacobian coordinates
if (isset($p[2]) && isset($q[2])) {
if (isset($p['fresh']) && isset($q['fresh'])) {
return $this->jacobianAddPointMixedXY($p, $q);
}
if (isset($p['fresh'])) {
return $this->jacobianAddPointMixedX($q, $p);
}
if (isset($q['fresh'])) {
return $this->jacobianAddPointMixedX($p, $q);
}
return $this->jacobianAddPoint($p, $q);
}
if (isset($p[2]) || isset($q[2])) {
throw new \RuntimeException('Affine coordinates need to be manually converted to Jacobi coordinates or vice versa');
}
if ($p[0]->equals($q[0])) {
if (!$p[1]->equals($q[1])) {
return [];
} else { // eg. doublePoint
list($numerator, $denominator) = $this->doublePointHelper($p);
}
} else {
$numerator = $q[1]->subtract($p[1]);
$denominator = $q[0]->subtract($p[0]);
}
$slope = $numerator->divide($denominator);
$x = $slope->multiply($slope)->subtract($p[0])->subtract($q[0]);
$y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]);
return [$x, $y];
}
/**
* Returns the numerator and denominator of the slope
*
* @return FiniteField[]
*/
protected function doublePointHelper(array $p)
{
$numerator = $this->three->multiply($p[0])->multiply($p[0])->add($this->a);
$denominator = $this->two->multiply($p[1]);
return [$numerator, $denominator];
}
/**
* Doubles a jacobian coordinate on the curve
*
* @return FiniteField[]
*/
protected function jacobianDoublePoint(array $p)
{
list($x, $y, $z) = $p;
$x2 = $x->multiply($x);
$y2 = $y->multiply($y);
$z2 = $z->multiply($z);
$s = $this->four->multiply($x)->multiply($y2);
$m1 = $this->three->multiply($x2);
$m2 = $this->a->multiply($z2->multiply($z2));
$m = $m1->add($m2);
$x1 = $m->multiply($m)->subtract($this->two->multiply($s));
$y1 = $m->multiply($s->subtract($x1))->subtract(
$this->eight->multiply($y2->multiply($y2))
);
$z1 = $this->two->multiply($y)->multiply($z);
return [$x1, $y1, $z1];
}
/**
* Doubles a "fresh" jacobian coordinate on the curve
*
* @return FiniteField[]
*/
protected function jacobianDoublePointMixed(array $p)
{
list($x, $y) = $p;
$x2 = $x->multiply($x);
$y2 = $y->multiply($y);
$s = $this->four->multiply($x)->multiply($y2);
$m1 = $this->three->multiply($x2);
$m = $m1->add($this->a);
$x1 = $m->multiply($m)->subtract($this->two->multiply($s));
$y1 = $m->multiply($s->subtract($x1))->subtract(
$this->eight->multiply($y2->multiply($y2))
);
$z1 = $this->two->multiply($y);
return [$x1, $y1, $z1];
}
/**
* Doubles a point on a curve
*
* @return FiniteField[]
*/
public function doublePoint(array $p)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
if (!count($p)) {
return [];
}
// use jacobian coordinates
if (isset($p[2])) {
if (isset($p['fresh'])) {
return $this->jacobianDoublePointMixed($p);
}
return $this->jacobianDoublePoint($p);
}
list($numerator, $denominator) = $this->doublePointHelper($p);
$slope = $numerator->divide($denominator);
$x = $slope->multiply($slope)->subtract($p[0])->subtract($p[0]);
$y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]);
return [$x, $y];
}
/**
* Returns the X coordinate and the derived Y coordinate
*
* @return array
*/
public function derivePoint($m)
{
$y = ord(Strings::shift($m));
$x = new BigInteger($m, 256);
$xp = $this->convertInteger($x);
switch ($y) {
case 2:
$ypn = false;
break;
case 3:
$ypn = true;
break;
default:
throw new \RuntimeException('Coordinate not in recognized format');
}
$temp = $xp->multiply($this->a);
$temp = $xp->multiply($xp)->multiply($xp)->add($temp);
$temp = $temp->add($this->b);
$b = $temp->squareRoot();
if (!$b) {
throw new \RuntimeException('Unable to derive Y coordinate');
}
$bn = $b->isOdd();
$yp = $ypn == $bn ? $b : $b->negate();
return [$xp, $yp];
}
/**
* Tests whether or not the x / y values satisfy the equation
*
* @return boolean
*/
public function verifyPoint(array $p)
{
list($x, $y) = $p;
$lhs = $y->multiply($y);
$temp = $x->multiply($this->a);
$temp = $x->multiply($x)->multiply($x)->add($temp);
$rhs = $temp->add($this->b);
return $lhs->equals($rhs);
}
/**
* Returns the modulo
*
* @return BigInteger
*/
public function getModulo()
{
return $this->modulo;
}
/**
* Returns the a coefficient
*
* @return PrimeInteger
*/
public function getA()
{
return $this->a;
}
/**
* Returns the a coefficient
*
* @return PrimeInteger
*/
public function getB()
{
return $this->b;
}
/**
* Multiply and Add Points
*
* Adapted from:
* https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L125
*
* @return int[]
*/
public function multiplyAddPoints(array $points, array $scalars)
{
$length = count($points);
foreach ($points as &$point) {
$point = $this->convertToInternal($point);
}
$wnd = [$this->getNAFPoints($points[0], 7)];
$wndWidth = [isset($points[0]['nafwidth']) ? $points[0]['nafwidth'] : 7];
for ($i = 1; $i < $length; $i++) {
$wnd[] = $this->getNAFPoints($points[$i], 1);
$wndWidth[] = isset($points[$i]['nafwidth']) ? $points[$i]['nafwidth'] : 1;
}
$naf = [];
// comb all window NAFs
$max = 0;
for ($i = $length - 1; $i >= 1; $i -= 2) {
$a = $i - 1;
$b = $i;
if ($wndWidth[$a] != 1 || $wndWidth[$b] != 1) {
$naf[$a] = $scalars[$a]->getNAF($wndWidth[$a]);
$naf[$b] = $scalars[$b]->getNAF($wndWidth[$b]);
$max = max(count($naf[$a]), count($naf[$b]), $max);
continue;
}
$comb = [
$points[$a], // 1
null, // 3
null, // 5
$points[$b] // 7
];
$comb[1] = $this->addPoint($points[$a], $points[$b]);
$comb[2] = $this->addPoint($points[$a], $this->negatePoint($points[$b]));
$index = [
-3, /* -1 -1 */
-1, /* -1 0 */
-5, /* -1 1 */
-7, /* 0 -1 */
0, /* 0 -1 */
7, /* 0 1 */
5, /* 1 -1 */
1, /* 1 0 */
3 /* 1 1 */
];
$jsf = self::getJSFPoints($scalars[$a], $scalars[$b]);
$max = max(count($jsf[0]), $max);
if ($max > 0) {
$naf[$a] = array_fill(0, $max, 0);
$naf[$b] = array_fill(0, $max, 0);
} else {
$naf[$a] = [];
$naf[$b] = [];
}
for ($j = 0; $j < $max; $j++) {
$ja = isset($jsf[0][$j]) ? $jsf[0][$j] : 0;
$jb = isset($jsf[1][$j]) ? $jsf[1][$j] : 0;
$naf[$a][$j] = $index[3 * ($ja + 1) + $jb + 1];
$naf[$b][$j] = 0;
$wnd[$a] = $comb;
}
}
$acc = [];
$temp = [0, 0, 0, 0];
for ($i = $max; $i >= 0; $i--) {
$k = 0;
while ($i >= 0) {
$zero = true;
for ($j = 0; $j < $length; $j++) {
$temp[$j] = isset($naf[$j][$i]) ? $naf[$j][$i] : 0;
if ($temp[$j] != 0) {
$zero = false;
}
}
if (!$zero) {
break;
}
$k++;
$i--;
}
if ($i >= 0) {
$k++;
}
while ($k--) {
$acc = $this->doublePoint($acc);
}
if ($i < 0) {
break;
}
for ($j = 0; $j < $length; $j++) {
$z = $temp[$j];
$p = null;
if ($z == 0) {
continue;
}
$p = $z > 0 ?
$wnd[$j][($z - 1) >> 1] :
$this->negatePoint($wnd[$j][(-$z - 1) >> 1]);
$acc = $this->addPoint($acc, $p);
}
}
return $this->convertToAffine($acc);
}
/**
* Precomputes NAF points
*
* Adapted from:
* https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L351
*
* @return int[]
*/
private function getNAFPoints(array $point, $wnd)
{
if (isset($point['naf'])) {
return $point['naf'];
}
$res = [$point];
$max = (1 << $wnd) - 1;
$dbl = $max == 1 ? null : $this->doublePoint($point);
for ($i = 1; $i < $max; $i++) {
$res[] = $this->addPoint($res[$i - 1], $dbl);
}
$point['naf'] = $res;
/*
$str = '';
foreach ($res as $re) {
$re[0] = bin2hex($re[0]->toBytes());
$re[1] = bin2hex($re[1]->toBytes());
$str.= " ['$re[0]', '$re[1]'],\r\n";
}
file_put_contents('temp.txt', $str);
exit;
*/
return $res;
}
/**
* Precomputes points in Joint Sparse Form
*
* Adapted from:
* https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/utils.js#L96
*
* @return int[]
*/
private static function getJSFPoints(Integer $k1, Integer $k2)
{
static $three;
if (!isset($three)) {
$three = new BigInteger(3);
}
$jsf = [[], []];
$k1 = $k1->toBigInteger();
$k2 = $k2->toBigInteger();
$d1 = 0;
$d2 = 0;
while ($k1->compare(new BigInteger(-$d1)) > 0 || $k2->compare(new BigInteger(-$d2)) > 0) {
// first phase
$m14 = $k1->testBit(0) + 2 * $k1->testBit(1);
$m14 += $d1;
$m14 &= 3;
$m24 = $k2->testBit(0) + 2 * $k2->testBit(1);
$m24 += $d2;
$m24 &= 3;
if ($m14 == 3) {
$m14 = -1;
}
if ($m24 == 3) {
$m24 = -1;
}
$u1 = 0;
if ($m14 & 1) { // if $m14 is odd
$m8 = $k1->testBit(0) + 2 * $k1->testBit(1) + 4 * $k1->testBit(2);
$m8 += $d1;
$m8 &= 7;
$u1 = ($m8 == 3 || $m8 == 5) && $m24 == 2 ? -$m14 : $m14;
}
$jsf[0][] = $u1;
$u2 = 0;
if ($m24 & 1) { // if $m24 is odd
$m8 = $k2->testBit(0) + 2 * $k2->testBit(1) + 4 * $k2->testBit(2);
$m8 += $d2;
$m8 &= 7;
$u2 = ($m8 == 3 || $m8 == 5) && $m14 == 2 ? -$m24 : $m24;
}
$jsf[1][] = $u2;
// second phase
if (2 * $d1 == $u1 + 1) {
$d1 = 1 - $d1;
}
if (2 * $d2 == $u2 + 1) {
$d2 = 1 - $d2;
}
$k1 = $k1->bitwise_rightShift(1);
$k2 = $k2->bitwise_rightShift(1);
}
return $jsf;
}
/**
* Returns the affine point
*
* A Jacobian Coordinate is of the form (x, y, z).
* To convert a Jacobian Coordinate to an Affine Point
* you do (x / z^2, y / z^3)
*
* @return PrimeInteger[]
*/
public function convertToAffine(array $p)
{
if (!isset($p[2])) {
return $p;
}
list($x, $y, $z) = $p;
$z = $this->one->divide($z);
$z2 = $z->multiply($z);
return [
$x->multiply($z2),
$y->multiply($z2)->multiply($z)
];
}
/**
* Converts an affine point to a jacobian coordinate
*
* @return PrimeInteger[]
*/
public function convertToInternal(array $p)
{
if (isset($p[2])) {
return $p;
}
$p[2] = clone $this->one;
$p['fresh'] = true;
return $p;
}
}

View File

@@ -0,0 +1,215 @@
<?php
/**
* Curves over a*x^2 + y^2 = 1 + d*x^2*y^2
*
* http://www.secg.org/SEC2-Ver-1.0.pdf provides for curves with custom parameters.
* ie. the coefficients can be arbitrary set through specially formatted keys, etc.
* As such, Prime.php is built very generically and it's not able to take full
* advantage of curves with 0 coefficients to produce simplified point doubling,
* point addition. Twisted Edwards curves, in contrast, do not have a way, currently,
* to customize them. As such, we can omit the super generic stuff from this class
* and let the named curves (Ed25519 and Ed448) define their own custom tailored
* point addition and point doubling methods.
*
* More info:
*
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\PrimeField;
use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
/**
* Curves over a*x^2 + y^2 = 1 + d*x^2*y^2
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class TwistedEdwards extends Base
{
/**
* The modulo
*
* @var BigInteger
*/
protected $modulo;
/**
* Cofficient for x^2
*
* @var object
*/
protected $a;
/**
* Cofficient for x^2*y^2
*
* @var object
*/
protected $d;
/**
* Base Point
*
* @var object[]
*/
protected $p;
/**
* The number zero over the specified finite field
*
* @var object
*/
protected $zero;
/**
* The number one over the specified finite field
*
* @var object
*/
protected $one;
/**
* The number two over the specified finite field
*
* @var object
*/
protected $two;
/**
* Sets the modulo
*/
public function setModulo(BigInteger $modulo)
{
$this->modulo = $modulo;
$this->factory = new PrimeField($modulo);
$this->zero = $this->factory->newInteger(new BigInteger(0));
$this->one = $this->factory->newInteger(new BigInteger(1));
$this->two = $this->factory->newInteger(new BigInteger(2));
}
/**
* Set coefficients a and b
*/
public function setCoefficients(BigInteger $a, BigInteger $d)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
$this->a = $this->factory->newInteger($a);
$this->d = $this->factory->newInteger($d);
}
/**
* Set x and y coordinates for the base point
*/
public function setBasePoint($x, $y)
{
switch (true) {
case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
}
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
$this->p = [
$x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
$y instanceof BigInteger ? $this->factory->newInteger($y) : $y
];
}
/**
* Returns the a coefficient
*
* @return PrimeInteger
*/
public function getA()
{
return $this->a;
}
/**
* Returns the a coefficient
*
* @return PrimeInteger
*/
public function getD()
{
return $this->d;
}
/**
* Retrieve the base point as an array
*
* @return array
*/
public function getBasePoint()
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
/*
if (!isset($this->p)) {
throw new \RuntimeException('setBasePoint needs to be called before this method');
}
*/
return $this->p;
}
/**
* Returns the affine point
*
* @return PrimeInteger[]
*/
public function convertToAffine(array $p)
{
if (!isset($p[2])) {
return $p;
}
list($x, $y, $z) = $p;
$z = $this->one->divide($z);
return [
$x->multiply($z),
$y->multiply($z)
];
}
/**
* Returns the modulo
*
* @return BigInteger
*/
public function getModulo()
{
return $this->modulo;
}
/**
* Tests whether or not the x / y values satisfy the equation
*
* @return boolean
*/
public function verifyPoint(array $p)
{
list($x, $y) = $p;
$x2 = $x->multiply($x);
$y2 = $y->multiply($y);
$lhs = $this->a->multiply($x2)->add($y2);
$rhs = $this->d->multiply($x2)->multiply($y2)->add($this->one);
return $lhs->equals($rhs);
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* Curve25519
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2019 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Montgomery;
use phpseclib3\Math\BigInteger;
class Curve25519 extends Montgomery
{
public function __construct()
{
// 2^255 - 19
$this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16));
$this->a24 = $this->factory->newInteger(new BigInteger('121666'));
$this->p = [$this->factory->newInteger(new BigInteger(9))];
// 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed
$this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16));
/*
$this->setCoefficients(
new BigInteger('486662'), // a
);
$this->setBasePoint(
new BigInteger(9),
new BigInteger('14781619447589544791020593568409986887264606134616475288964881837755586237401')
);
*/
}
/**
* Multiply a point on the curve by a scalar
*
* Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8
*
* @return array
*/
public function multiplyPoint(array $p, BigInteger $d)
{
//$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes())));
//return [$this->factory->newInteger(new BigInteger($r, 256))];
$d = $d->toBytes();
$d &= "\xF8" . str_repeat("\xFF", 30) . "\x7F";
$d = strrev($d);
$d |= "\x40";
$d = new BigInteger($d, -256);
return parent::multiplyPoint($p, $d);
}
/**
* Creates a random scalar multiplier
*
* @return BigInteger
*/
public function createRandomMultiplier()
{
return BigInteger::random(256);
}
/**
* Performs range check
*/
public function rangeCheck(BigInteger $x)
{
if ($x->getLength() > 256 || $x->isNegative()) {
throw new \RangeException('x must be a positive integer less than 256 bytes in length');
}
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* Curve448
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2019 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Montgomery;
use phpseclib3\Math\BigInteger;
class Curve448 extends Montgomery
{
public function __construct()
{
// 2^448 - 2^224 - 1
$this->setModulo(new BigInteger(
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
16
));
$this->a24 = $this->factory->newInteger(new BigInteger('39081'));
$this->p = [$this->factory->newInteger(new BigInteger(5))];
// 2^446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d
$this->setOrder(new BigInteger(
'3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
'7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3',
16
));
/*
$this->setCoefficients(
new BigInteger('156326'), // a
);
$this->setBasePoint(
new BigInteger(5),
new BigInteger(
'355293926785568175264127502063783334808976399387714271831880898' .
'435169088786967410002932673765864550910142774147268105838985595290' .
'606362')
);
*/
}
/**
* Multiply a point on the curve by a scalar
*
* Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8
*
* @return array
*/
public function multiplyPoint(array $p, BigInteger $d)
{
//$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes())));
//return [$this->factory->newInteger(new BigInteger($r, 256))];
$d = $d->toBytes();
$d[0] = $d[0] & "\xFC";
$d = strrev($d);
$d |= "\x80";
$d = new BigInteger($d, 256);
return parent::multiplyPoint($p, $d);
}
/**
* Creates a random scalar multiplier
*
* @return BigInteger
*/
public function createRandomMultiplier()
{
return BigInteger::random(446);
}
/**
* Performs range check
*/
public function rangeCheck(BigInteger $x)
{
if ($x->getLength() > 448 || $x->isNegative()) {
throw new \RangeException('x must be a positive integer less than 446 bytes in length');
}
}
}

View File

@@ -0,0 +1,333 @@
<?php
/**
* Ed25519
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\Random;
use phpseclib3\Math\BigInteger;
class Ed25519 extends TwistedEdwards
{
const HASH = 'sha512';
/*
Per https://tools.ietf.org/html/rfc8032#page-6 EdDSA has several parameters, one of which is b:
2. An integer b with 2^(b-1) > p. EdDSA public keys have exactly b
bits, and EdDSA signatures have exactly 2*b bits. b is
recommended to be a multiple of 8, so public key and signature
lengths are an integral number of octets.
SIZE corresponds to b
*/
const SIZE = 32;
public function __construct()
{
// 2^255 - 19
$this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16));
$this->setCoefficients(
// -1
new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC', 16), // a
// -121665/121666
new BigInteger('52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3', 16) // d
);
$this->setBasePoint(
new BigInteger('216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A', 16),
new BigInteger('6666666666666666666666666666666666666666666666666666666666666658', 16)
);
$this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16));
// algorithm 14.47 from http://cacr.uwaterloo.ca/hac/about/chap14.pdf#page=16
/*
$this->setReduction(function($x) {
$parts = $x->bitwise_split(255);
$className = $this->className;
if (count($parts) > 2) {
list(, $r) = $x->divide($className::$modulo);
return $r;
}
$zero = new BigInteger();
$c = new BigInteger(19);
switch (count($parts)) {
case 2:
list($qi, $ri) = $parts;
break;
case 1:
$qi = $zero;
list($ri) = $parts;
break;
case 0:
return $zero;
}
$r = $ri;
while ($qi->compare($zero) > 0) {
$temp = $qi->multiply($c)->bitwise_split(255);
if (count($temp) == 2) {
list($qi, $ri) = $temp;
} else {
$qi = $zero;
list($ri) = $temp;
}
$r = $r->add($ri);
}
while ($r->compare($className::$modulo) > 0) {
$r = $r->subtract($className::$modulo);
}
return $r;
});
*/
}
/**
* Recover X from Y
*
* Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.1.3
*
* Used by EC\Keys\Common.php
*
* @param BigInteger $y
* @param boolean $sign
* @return object[]
*/
public function recoverX(BigInteger $y, $sign)
{
$y = $this->factory->newInteger($y);
$y2 = $y->multiply($y);
$u = $y2->subtract($this->one);
$v = $this->d->multiply($y2)->add($this->one);
$x2 = $u->divide($v);
if ($x2->equals($this->zero)) {
if ($sign) {
throw new \RuntimeException('Unable to recover X coordinate (x2 = 0)');
}
return clone $this->zero;
}
// find the square root
/* we don't do $x2->squareRoot() because, quoting from
https://tools.ietf.org/html/rfc8032#section-5.1.1:
"For point decoding or "decompression", square roots modulo p are
needed. They can be computed using the Tonelli-Shanks algorithm or
the special case for p = 5 (mod 8). To find a square root of a,
first compute the candidate root x = a^((p+3)/8) (mod p)."
*/
$exp = $this->getModulo()->add(new BigInteger(3));
$exp = $exp->bitwise_rightShift(3);
$x = $x2->pow($exp);
// If v x^2 = -u (mod p), set x <-- x * 2^((p-1)/4), which is a square root.
if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
$temp = $this->getModulo()->subtract(new BigInteger(1));
$temp = $temp->bitwise_rightShift(2);
$temp = $this->two->pow($temp);
$x = $x->multiply($temp);
if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
throw new \RuntimeException('Unable to recover X coordinate');
}
}
if ($x->isOdd() != $sign) {
$x = $x->negate();
}
return [$x, $y];
}
/**
* Extract Secret Scalar
*
* Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.1.5
*
* Used by the various key handlers
*
* @param string $str
* @return array
*/
public function extractSecret($str)
{
if (strlen($str) != 32) {
throw new \LengthException('Private Key should be 32-bytes long');
}
// 1. Hash the 32-byte private key using SHA-512, storing the digest in
// a 64-octet large buffer, denoted h. Only the lower 32 bytes are
// used for generating the public key.
$hash = new Hash('sha512');
$h = $hash->hash($str);
$h = substr($h, 0, 32);
// 2. Prune the buffer: The lowest three bits of the first octet are
// cleared, the highest bit of the last octet is cleared, and the
// second highest bit of the last octet is set.
$h[0] = $h[0] & chr(0xF8);
$h = strrev($h);
$h[0] = ($h[0] & chr(0x3F)) | chr(0x40);
// 3. Interpret the buffer as the little-endian integer, forming a
// secret scalar s.
$dA = new BigInteger($h, 256);
return [
'dA' => $dA,
'secret' => $str
];
}
/**
* Encode a point as a string
*
* @param array $point
* @return string
*/
public function encodePoint($point)
{
list($x, $y) = $point;
$y = $y->toBytes();
$y[0] = $y[0] & chr(0x7F);
if ($x->isOdd()) {
$y[0] = $y[0] | chr(0x80);
}
$y = strrev($y);
return $y;
}
/**
* Creates a random scalar multiplier
*
* @return \phpseclib3\Math\PrimeField\Integer
*/
public function createRandomMultiplier()
{
return $this->extractSecret(Random::string(32))['dA'];
}
/**
* Converts an affine point to an extended homogeneous coordinate
*
* From https://tools.ietf.org/html/rfc8032#section-5.1.4 :
*
* A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T),
* with x = X/Z, y = Y/Z, x * y = T/Z.
*
* @return \phpseclib3\Math\PrimeField\Integer[]
*/
public function convertToInternal(array $p)
{
if (empty($p)) {
return [clone $this->zero, clone $this->one, clone $this->one, clone $this->zero];
}
if (isset($p[2])) {
return $p;
}
$p[2] = clone $this->one;
$p[3] = $p[0]->multiply($p[1]);
return $p;
}
/**
* Doubles a point on a curve
*
* @return FiniteField[]
*/
public function doublePoint(array $p)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
if (!count($p)) {
return [];
}
if (!isset($p[2])) {
throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
// from https://tools.ietf.org/html/rfc8032#page-12
list($x1, $y1, $z1, $t1) = $p;
$a = $x1->multiply($x1);
$b = $y1->multiply($y1);
$c = $this->two->multiply($z1)->multiply($z1);
$h = $a->add($b);
$temp = $x1->add($y1);
$e = $h->subtract($temp->multiply($temp));
$g = $a->subtract($b);
$f = $c->add($g);
$x3 = $e->multiply($f);
$y3 = $g->multiply($h);
$t3 = $e->multiply($h);
$z3 = $f->multiply($g);
return [$x3, $y3, $z3, $t3];
}
/**
* Adds two points on the curve
*
* @return FiniteField[]
*/
public function addPoint(array $p, array $q)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
if (count($q)) {
return $q;
}
if (count($p)) {
return $p;
}
return [];
}
if (!isset($p[2]) || !isset($q[2])) {
throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
if ($p[0]->equals($q[0])) {
return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
}
// from https://tools.ietf.org/html/rfc8032#page-12
list($x1, $y1, $z1, $t1) = $p;
list($x2, $y2, $z2, $t2) = $q;
$a = $y1->subtract($x1)->multiply($y2->subtract($x2));
$b = $y1->add($x1)->multiply($y2->add($x2));
$c = $t1->multiply($this->two)->multiply($this->d)->multiply($t2);
$d = $z1->multiply($this->two)->multiply($z2);
$e = $b->subtract($a);
$f = $d->subtract($c);
$g = $d->add($c);
$h = $b->add($a);
$x3 = $e->multiply($f);
$y3 = $g->multiply($h);
$t3 = $e->multiply($h);
$z3 = $f->multiply($g);
return [$x3, $y3, $z3, $t3];
}
}

View File

@@ -0,0 +1,273 @@
<?php
/**
* Ed448
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\Random;
use phpseclib3\Math\BigInteger;
class Ed448 extends TwistedEdwards
{
const HASH = 'shake256-912';
const SIZE = 57;
public function __construct()
{
// 2^448 - 2^224 - 1
$this->setModulo(new BigInteger(
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
16
));
$this->setCoefficients(
new BigInteger(1),
// -39081
new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6756', 16)
);
$this->setBasePoint(
new BigInteger('4F1970C66BED0DED221D15A622BF36DA9E146570470F1767EA6DE324' .
'A3D3A46412AE1AF72AB66511433B80E18B00938E2626A82BC70CC05E', 16),
new BigInteger('693F46716EB6BC248876203756C9C7624BEA73736CA3984087789C1E' .
'05A0C2D73AD3FF1CE67C39C4FDBD132C4ED7C8AD9808795BF230FA14', 16)
);
$this->setOrder(new BigInteger(
'3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
'7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3',
16
));
}
/**
* Recover X from Y
*
* Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.2.3
*
* Used by EC\Keys\Common.php
*
* @param BigInteger $y
* @param boolean $sign
* @return object[]
*/
public function recoverX(BigInteger $y, $sign)
{
$y = $this->factory->newInteger($y);
$y2 = $y->multiply($y);
$u = $y2->subtract($this->one);
$v = $this->d->multiply($y2)->subtract($this->one);
$x2 = $u->divide($v);
if ($x2->equals($this->zero)) {
if ($sign) {
throw new \RuntimeException('Unable to recover X coordinate (x2 = 0)');
}
return clone $this->zero;
}
// find the square root
$exp = $this->getModulo()->add(new BigInteger(1));
$exp = $exp->bitwise_rightShift(2);
$x = $x2->pow($exp);
if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
throw new \RuntimeException('Unable to recover X coordinate');
}
if ($x->isOdd() != $sign) {
$x = $x->negate();
}
return [$x, $y];
}
/**
* Extract Secret Scalar
*
* Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.2.5
*
* Used by the various key handlers
*
* @param string $str
* @return array
*/
public function extractSecret($str)
{
if (strlen($str) != 57) {
throw new \LengthException('Private Key should be 57-bytes long');
}
// 1. Hash the 57-byte private key using SHAKE256(x, 114), storing the
// digest in a 114-octet large buffer, denoted h. Only the lower 57
// bytes are used for generating the public key.
$hash = new Hash('shake256-912');
$h = $hash->hash($str);
$h = substr($h, 0, 57);
// 2. Prune the buffer: The two least significant bits of the first
// octet are cleared, all eight bits the last octet are cleared, and
// the highest bit of the second to last octet is set.
$h[0] = $h[0] & chr(0xFC);
$h = strrev($h);
$h[0] = "\0";
$h[1] = $h[1] | chr(0x80);
// 3. Interpret the buffer as the little-endian integer, forming a
// secret scalar s.
$dA = new BigInteger($h, 256);
return [
'dA' => $dA,
'secret' => $str
];
$dA->secret = $str;
return $dA;
}
/**
* Encode a point as a string
*
* @param array $point
* @return string
*/
public function encodePoint($point)
{
list($x, $y) = $point;
$y = "\0" . $y->toBytes();
if ($x->isOdd()) {
$y[0] = $y[0] | chr(0x80);
}
$y = strrev($y);
return $y;
}
/**
* Creates a random scalar multiplier
*
* @return \phpseclib3\Math\PrimeField\Integer
*/
public function createRandomMultiplier()
{
return $this->extractSecret(Random::string(57))['dA'];
}
/**
* Converts an affine point to an extended homogeneous coordinate
*
* From https://tools.ietf.org/html/rfc8032#section-5.2.4 :
*
* A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T),
* with x = X/Z, y = Y/Z, x * y = T/Z.
*
* @return \phpseclib3\Math\PrimeField\Integer[]
*/
public function convertToInternal(array $p)
{
if (empty($p)) {
return [clone $this->zero, clone $this->one, clone $this->one];
}
if (isset($p[2])) {
return $p;
}
$p[2] = clone $this->one;
return $p;
}
/**
* Doubles a point on a curve
*
* @return FiniteField[]
*/
public function doublePoint(array $p)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
if (!count($p)) {
return [];
}
if (!isset($p[2])) {
throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
// from https://tools.ietf.org/html/rfc8032#page-18
list($x1, $y1, $z1) = $p;
$b = $x1->add($y1);
$b = $b->multiply($b);
$c = $x1->multiply($x1);
$d = $y1->multiply($y1);
$e = $c->add($d);
$h = $z1->multiply($z1);
$j = $e->subtract($this->two->multiply($h));
$x3 = $b->subtract($e)->multiply($j);
$y3 = $c->subtract($d)->multiply($e);
$z3 = $e->multiply($j);
return [$x3, $y3, $z3];
}
/**
* Adds two points on the curve
*
* @return FiniteField[]
*/
public function addPoint(array $p, array $q)
{
if (!isset($this->factory)) {
throw new \RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
if (count($q)) {
return $q;
}
if (count($p)) {
return $p;
}
return [];
}
if (!isset($p[2]) || !isset($q[2])) {
throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
if ($p[0]->equals($q[0])) {
return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
}
// from https://tools.ietf.org/html/rfc8032#page-17
list($x1, $y1, $z1) = $p;
list($x2, $y2, $z2) = $q;
$a = $z1->multiply($z2);
$b = $a->multiply($a);
$c = $x1->multiply($x2);
$d = $y1->multiply($y2);
$e = $this->d->multiply($c)->multiply($d);
$f = $b->subtract($e);
$g = $b->add($e);
$h = $x1->add($y1)->multiply($x2->add($y2));
$x3 = $a->multiply($f)->multiply($h->subtract($c)->subtract($d));
$y3 = $a->multiply($g)->multiply($d->subtract($c));
$z3 = $f->multiply($g);
return [$x3, $y3, $z3];
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* brainpoolP160r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP160r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16));
$this->setCoefficients(
new BigInteger('340E7BE2A280EB74E2BE61BADA745D97E8F7C300', 16),
new BigInteger('1E589A8595423412134FAA2DBDEC95C8D8675E58', 16)
);
$this->setBasePoint(
new BigInteger('BED5AF16EA3F6A4F62938C4631EB5AF7BDBCDBC3', 16),
new BigInteger('1667CB477A1A8EC338F94741669C976316DA6321', 16)
);
$this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16));
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* brainpoolP160t1
*
* This curve is a twisted version of brainpoolP160r1 with A = -3. With brainpool,
* the curves ending in r1 are the "regular" curves and the curves ending in "t1"
* are the twisted version of the r1 curves. Per https://tools.ietf.org/html/rfc5639#page-7
* you can convert a point on an r1 curve to a point on a t1 curve thusly:
*
* F(x,y) := (x*Z^2, y*Z^3)
*
* The advantage of A = -3 is that some of the point doubling and point addition can be
* slightly optimized. See http://hyperelliptic.org/EFD/g1p/auto-shortw-projective-3.html
* vs http://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html for example.
*
* phpseclib does not currently take advantage of this optimization opportunity
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP160t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16));
$this->setCoefficients(
new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620C', 16), // eg. -3
new BigInteger('7A556B6DAE535B7B51ED2C4D7DAA7A0B5C55F380', 16)
);
$this->setBasePoint(
new BigInteger('B199B13B9B34EFC1397E64BAEB05ACC265FF2378', 16),
new BigInteger('ADD6718B7C7C1961F0991B842443772152C9E0AD', 16)
);
$this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16));
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* brainpoolP192r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP192r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16));
$this->setCoefficients(
new BigInteger('6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF', 16),
new BigInteger('469A28EF7C28CCA3DC721D044F4496BCCA7EF4146FBF25C9', 16)
);
$this->setBasePoint(
new BigInteger('C0A0647EAAB6A48753B033C56CB0F0900A2F5C4853375FD6', 16),
new BigInteger('14B690866ABD5BB88B5F4828C1490002E6773FA2FA299B8F', 16)
);
$this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16));
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* brainpoolP192t1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP192t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16));
$this->setCoefficients(
new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86294', 16), // eg. -3
new BigInteger('13D56FFAEC78681E68F9DEB43B35BEC2FB68542E27897B79', 16)
);
$this->setBasePoint(
new BigInteger('3AE9E58C82F63C30282E1FE7BBF43FA72C446AF6F4618129', 16),
new BigInteger('097E2C5667C2223A902AB5CA449D0084B7E5B3DE7CCC01C9', 16)
);
$this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16));
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* brainpoolP224r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP224r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16));
$this->setCoefficients(
new BigInteger('68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43', 16),
new BigInteger('2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B', 16)
);
$this->setBasePoint(
new BigInteger('0D9029AD2C7E5CF4340823B2A87DC68C9E4CE3174C1E6EFDEE12C07D', 16),
new BigInteger('58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD', 16)
);
$this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16));
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* brainpoolP224t1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP224t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16));
$this->setCoefficients(
new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FC', 16), // eg. -3
new BigInteger('4B337D934104CD7BEF271BF60CED1ED20DA14C08B3BB64F18A60888D', 16)
);
$this->setBasePoint(
new BigInteger('6AB1E344CE25FF3896424E7FFE14762ECB49F8928AC0C76029B4D580', 16),
new BigInteger('0374E9F5143E568CD23F3F4D7C0D4B1E41C8CC0D1C6ABD5F1A46DB4C', 16)
);
$this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16));
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* brainpoolP256r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP256r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16));
$this->setCoefficients(
new BigInteger('7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9', 16),
new BigInteger('26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6', 16)
);
$this->setBasePoint(
new BigInteger('8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262', 16),
new BigInteger('547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997', 16)
);
$this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16));
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* brainpoolP256t1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP256t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16));
$this->setCoefficients(
new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5374', 16), // eg. -3
new BigInteger('662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04', 16)
);
$this->setBasePoint(
new BigInteger('A3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F4', 16),
new BigInteger('2D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE', 16)
);
$this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16));
}
}

Some files were not shown because too many files have changed in this diff Show More