<?php

namespace loyalsoft;

/**
 * 使用openssl实现非对称加密,用于安全的通讯中,
 * 可进行签名/验签, 加密/解密
 * @author gwang email:wanggangzero@qq.com
 * @copyright © 2015-12-3, SJZ LoyalSoft Corporation & gwang. All rights reserved.
 */
class MyRsa
{

    /**
     * 测试
     */
    public static function Test()
    {
        $path = __DIR__ . DIRECTORY_SEPARATOR . "key";
        $msRsa = new MyRsa($path);
//        $msRsa->createKey();
        $data = "中华人民共和国";
        echo "data: $data";
        $crypted = $msRsa->privEncrypt($data);
        echo "encrypted: $crypted";
        $decrypted = $msRsa->pubDecrypt($crypted);
        echo "decrypted: $decrypted";
        unset($msRsa);
    }

// 测试签名/验签
    public static function testSign()
    {
        $path = __DIR__ . DIRECTORY_SEPARATOR . "key";
        $msRsa = new MyRsa($path);
//        $msRsa->createKey();
        $data = "中华人民共和国";
        echoLine("data: $data");
        $sign = $msRsa->sign_str($data);
        echoLine("sign: $sign");
        $verify = $msRsa->verify($data, $sign);
        echoLine("verify : $verify");
        unset($msRsa);
    }

    private $_privKey;
    private $_pubKey;
    private $_keyPath; // 密钥保存路径

    /**
     * 构造函数
     *  @param string $path 密钥保存路径
     */

    public function __construct($path)
    {
        if (empty($path) || !is_dir($path)) {
            throw new \Exception('Must set the keys save path');
        } $this->_keyPath = $path;
    }

    /**
     * 创建密钥对,保存到 $this->_keyPath
     * @deprecated since version now() 还是用OpenSSL工具生成秘钥吧, 这个函数生成的C#不认, 不知道为什么.-gwang 2018年1月19日14:17:48
     * */
    public function createKey()
    {
        //参数设置
        $config = [
            "digest_alg" => "sha512",
            //文件路径根据自己的要求进行填充
//            "config" => "./conf/openssl.cnf",
            //字节数    512 1024  2048   4096 等
            "private_key_bits" => 1024,
            //加密类型
            "private_key_type" => OPENSSL_KEYTYPE_RSA,
        ];

        $r = openssl_pkey_new($config);
        openssl_pkey_export($r, $privKey);
        $tsday = TimeUtil::dtToday();
        file_put_contents($this->_keyPath . DIRECTORY_SEPARATOR . "key.$tsday.pem", $privKey);
        $this->_privKey = openssl_pkey_get_public($privKey);
        $rp = openssl_pkey_get_details($r);
        $pubKey = $rp['key'];
        file_put_contents($this->_keyPath . DIRECTORY_SEPARATOR . "pub.$tsday.pem", $pubKey);
        $this->_pubKey = openssl_pkey_get_public($pubKey);
    }

    /*
     * setup the private key
     */

    public function setupPrivKey()
    {
        if (is_resource($this->_privKey)) {
            return true;
        }
        $file = $this->_keyPath . DIRECTORY_SEPARATOR . 'key.pem';
        $prk = file_get_contents($file);
        $this->_privKey = openssl_pkey_get_private($prk);
        return true;
    }

    /**
     *  setup the public key
     */
    public function setupPubKey()
    {
        if (is_resource($this->_pubKey)) {
            return true;
        }
        $file = $this->_keyPath . DIRECTORY_SEPARATOR . 'pub.pem';
        $puk = file_get_contents($file);
        $this->_pubKey = openssl_pkey_get_public($puk);
        return true;
    }

    /**
     * 生成签名
     * @param array $params
     * @return string sign with base64 encoded
     */
    public function sign_arr($params)
    {
        $content = "";
        if ($params) {
            ksort($params);
            $i = 0;
            foreach ($params as $key => $value) {
                if ($key != "sign") {
                    $content .= ($i == 0 ? '' : '&') . $key . '=' . $value;
                    $i++;
                }
            }
        }
        return $this->sign_str($content);
    }

    /**
     *  签名
     * @param string $data
     * @return string with base64 encoded
     */
    public function sign_str($data)
    {
        if (!is_string($data)) {
            return null;
        }
        $this->setupPrivKey();
        $sign = null;
        $r = openssl_sign($data, $sign, $this->_privKey);
        if ($r) {
            return base64_encode($sign);
        }
        return null;
    }

    /**
     *  验签
     * @param type $data
     * @param type $sign
     * @return boolean true 通过, false 不通过
     */
    public function verify($data, $sign)
    {
        if (!is_string($data) || !is_string($sign)) {
            return false;
        }
        $this->setupPubKey();
        $r = openssl_verify($data, base64_decode($sign), $this->_pubKey);
        if ($r == 1) {
            return true;
        }
        return false;
    }

    /**
     * 验签(数组)
     * @param array $arr
     * @param string $sign
     * @return boolean true 通过, false 不通过
     */
    public function verify_arr($arr, $sign)
    {
        if (!is_array($arr) || !is_string($sign)) {
            CLog::err("验签防御代码:参数有误", 'RSA');
            return false;
        }
        return $sign == $this->sign_arr($arr);
    }

    /**
     *   encrypt with the private key
     */
    public function privEncrypt($data)
    {
        if (!is_string($data)) {
            return null;
        }
        $this->setupPrivKey();
        $encrypted = null;
        $r = openssl_private_encrypt($data, $encrypted, $this->_privKey);
        if ($r) {
            return base64_encode($encrypted);
        }
        return null;
    }

    /**
     *      * decrypt with the private key
     */
    public function privDecrypt($encrypted)
    {
        if (!is_string($encrypted)) {
            return null;
        }
        $this->setupPrivKey();
        $encrypted = base64_decode($encrypted);
        $r = openssl_private_decrypt($encrypted, $decrypted, $this->_privKey);
        if ($r) {
            return $decrypted;
        }
        return null;
    }

    /**
     *  encrypt with public key
     */
    public function pubEncrypt($data)
    {
        if (!is_string($data)) {
            return null;
        }
        $this->setupPubKey();
        $encrypted = null;
        $r = openssl_public_encrypt($data, $encrypted, $this->_pubKey);
        if ($r) {
            return base64_encode($encrypted);
        }
        return null;
    }

    /**
     *  decrypt with the public key
     */
    public function pubDecrypt($crypted)
    {
        if (!is_string($crypted)) {
            return null;
        }
        $this->setupPubKey();
        $crypted = base64_decode($crypted);
        $r = openssl_public_decrypt($crypted, $decrypted, $this->_pubKey);
        if ($r) {
            return $decrypted;
        }
        return null;
    }

    /**
     * 析构函数
     */
    public function __destruct()
    {
        if (is_resource($this->_pubKey)) {
            openssl_free_key($this->_pubKey);
        }
        if (is_resource($this->_privKey)) {
            openssl_free_key($this->_privKey);
        }
    }

}
