PHPCMS V9 登录加密改造

要改造 phpcms 的后台登录,使其前端使用加密方式提交,后端解密,你可以采用 RSA 非对称加密AES 对称加密 方式来增强安全性。

方案设计

  1. 前端加密

    • 生成公私钥对(推荐使用 RSA)。
    • 前端使用公钥加密密码,然后提交给后端。
    • 避免明文密码在网络中传输。
  2. 后端解密

    • 后端使用私钥解密前端传输的密码。
    • 然后继续使用原有的用户验证逻辑

目录

改造前

改造好后

改造方法


改造前

改造好后

改造方法

复制代码
# 生成私钥
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

# 生成公钥
openssl rsa -pubout -in private_key.pem -out public_key.pem

前端phpcms\modules\admin\templates\login.tpl.php

复制代码
<?php defined('IN_ADMIN') or exit('No permission resources.'); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=<?php echo CHARSET;?>" />
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<title><?php echo L('phpcms_logon')?></title>
<style type="text/css">
    div{overflow:hidden; *display:inline-block;}div{*display:block;}
    .login_box{background:url(<?php echo IMG_PATH?>admin_img/login_bg.jpg) no-repeat; width:602px; height:416px; overflow:hidden; position:absolute; left:50%; top:50%; margin-left:-301px; margin-top:-208px;}
    .login_iptbox{bottom:90px;_bottom:72px;color:#FFFFFF;font-size:12px;height:30px;left:50%;
margin-left:-280px;position:absolute;width:560px; overflow:visible;}
    .login_iptbox .ipt{height:24px; width:110px; margin-right:22px; color:#fff; background:url(<?php echo IMG_PATH?>admin_img/ipt_bg.jpg) repeat-x; *line-height:24px; border:none; color:#000; overflow:hidden;}
    <?php if(SYS_STYLE=='en'){ ?>
    .login_iptbox .ipt{width:100px; margin-right:12px;}
    <?php }?>
    .login_iptbox label{ *position:relative; *top:-6px;}
    .login_iptbox .ipt_reg{margin-left:12px;width:46px; margin-right:16px; background:url(<?php echo IMG_PATH?>admin_img/ipt_bg.jpg) repeat-x; *overflow:hidden;text-align:left;padding:2px 0 2px 5px;font-size:16px;font-weight:bold;}
    .login_tj_btn{ background:url(<?php echo IMG_PATH?>admin_img/login_dl_btn.jpg) no-repeat 0px 0px; width:52px; height:24px; margin-left:16px; border:none; cursor:pointer; padding:0px; float:right;}
    .yzm{position:absolute; background:url(<?php echo IMG_PATH?>admin_img/login_ts140x89.gif) no-repeat; width:140px; height:89px;right:56px;top:-96px; text-align:center; font-size:12px; display:none;}
    .yzm a:link,.yzm a:visited{color:#036;text-decoration:none;}
    .yzm a:hover{color:#C30;}
    .yzm img{cursor:pointer; margin:4px auto 7px; width:130px; height:50px; border:1px solid #fff;}
    .cr{font-size:12px;font-style:inherit;text-align:center;color:#ccc;width:100%; position:absolute; bottom:58px;}
    .cr a{color:#ccc;text-decoration:none;}
</style>

<script src="http://zxjw.gov:800/statics/js/jsencrypt.min.js"></script>

<script>
document.addEventListener("DOMContentLoaded", function () {
    document.getElementById("loginForm").addEventListener("submit", function (event) {
        event.preventDefault(); // 阻止默认提交行为
        
        var username = document.getElementById("username").value;
        var password = document.getElementById("password").value;

        // 这里替换成你的 RSA 公钥
        var publicKey = `-----BEGIN PUBLIC KEY-----

-----END PUBLIC KEY-----
`;

        var encrypt = new JSEncrypt();
        encrypt.setPublicKey(publicKey);
        var encryptedPassword = encrypt.encrypt(password);

        // 通过 AJAX 提交加密后的密码
        var formData = new FormData();
        formData.append("username", username);
        formData.append("password", encryptedPassword);
        formData.append("code", document.getElementById("code").value);
        formData.append("dosubmit", "1");

        fetch("index.php?m=admin&c=index&a=login&dosubmit=1", {
            method: "POST",
            body: formData
        }).then(response => response.text())
          .then(data => console.log(data));
    });
});
</script>
</head>

<body>
<div id="login_bg" class="login_box">
    <div class="login_iptbox">
        <form id="loginForm">
            <label><?php echo L('username')?>:</label>
            <input id="username" name="username" type="text" class="ipt" value="" />
            <label><?php echo L('password')?>:</label>
            <input id="password" name="password" type="text" class="ipt" value="" />
            <label><?php echo L('security_code')?>:</label>
            <input id="code" name="code" type="text" class="ipt ipt_reg" onfocus="document.getElementById('yzm').style.display='block'" />
            <div id="yzm" class="yzm">
                <?php echo form::checkcode('code_img')?><br />
                <a href="javascript:document.getElementById('code_img').src='<?php echo SITE_PROTOCOL.SITE_URL.WEB_PATH;?>api.php?op=checkcode&m=admin&c=index&a=checkcode&time='+Math.random();void(0);">
                    <?php echo L('click_change_validate')?>
                </a>
            </div>
            <input type="submit" class="login_tj_btn" value=""/>
        </form>
    </div>
    <div class="cr"><?php echo L("copyright")?></div>
</div>
</body>
</html>

后端phpcms\modules\admin\index.php

复制代码
public function login() {
			if(isset($_GET['dosubmit'])) {
				// 不是口令卡验证
				if (!isset($_GET['card'])) {
					$username = isset($_POST['username']) ? trim($_POST['username']) : showmessage(L('nameerror'), HTTP_REFERER);
					$code = isset($_POST['code']) && trim($_POST['code']) ? trim($_POST['code']) : showmessage(L('input_code'), HTTP_REFERER);
					if ($_SESSION['code'] != strtolower($code)) {
						$_SESSION['code'] = '';
						showmessage(L('code_error'), HTTP_REFERER);
					}
					$_SESSION['code'] = '';
				} else { // 口令卡验证
					if (!isset($_SESSION['card_verif']) || $_SESSION['card_verif'] != 1) {
						showmessage(L('your_password_card_is_not_validate'), '?m=admin&c=index&a=public_card');
					}
					$username = $_SESSION['card_username'] ? $_SESSION['card_username'] : showmessage(L('nameerror'), HTTP_REFERER);
				}

				if (!is_username($username)) {
					showmessage(L('username_illegal'), HTTP_REFERER);
				}

				// 检查密码错误次数
				$this->times_db = pc_base::load_model('times_model');
				$rtime = $this->times_db->get_one(array('username' => $username, 'isadmin' => 1));
				$maxloginfailedtimes = getcache('common', 'commons');
				$maxloginfailedtimes = (int)$maxloginfailedtimes['maxloginfailedtimes'];

				if ($rtime['times'] >= $maxloginfailedtimes) {
					$minute = 60 - floor((SYS_TIME - $rtime['logintime']) / 60);
					if ($minute > 0) showmessage(L('wait_1_hour', array('minute' => $minute)));
				}

				// 查询帐号
				$r = $this->db->get_one(array('username' => $username));
				if (!$r) showmessage(L('user_not_exist'), '?m=admin&c=index&a=login');

				// **RSA 解密密码**
				$privateKey = file_get_contents("D:/phpstudy_pro/WWW/private_key.pem");
				$encryptedPassword = $_POST['password'];
				openssl_private_decrypt(base64_decode($encryptedPassword), $decryptedPassword, $privateKey);

				if (!$decryptedPassword) {
					showmessage(L('password_decrypt_error'), '?m=admin&c=index&a=login');
				}

				// 进行密码匹配
				$password = md5(md5(trim($decryptedPassword)) . $r['encrypt']);

				if ($r['password'] != $password) {
					$ip = ip();
					if ($rtime && $rtime['times'] < $maxloginfailedtimes) {
						$times = $maxloginfailedtimes - intval($rtime['times']);
						$this->times_db->update(array('ip' => $ip, 'isadmin' => 1, 'times' => '+=1'), array('username' => $username));
					} else {
						$this->times_db->delete(array('username' => $username, 'isadmin' => 1));
						$this->times_db->insert(array('username' => $username, 'ip' => $ip, 'isadmin' => 1, 'logintime' => SYS_TIME, 'times' => 1));
						$times = $maxloginfailedtimes;
					}
					showmessage(L('password_error', array('times' => $times)), '?m=admin&c=index&a=login', 1000);
				}

				// 登录成功,清空错误次数记录
				$this->times_db->delete(array('username' => $username));

				// 口令卡验证
				if (!isset($_GET['card']) && $r['card'] && pc_base::load_config('system', 'safe_card') == 1) {
					$_SESSION['card_username'] = $username;
					$_SESSION['card_password'] = $_POST['password'];
					header("location:?m=admin&c=index&a=public_card");
					exit;
				} elseif (isset($_GET['card']) && pc_base::load_config('system', 'safe_card') == 1 && $r['card']) {
					$_SESSION['card_username'] = '';
					$_SESSION['card_password'] = '';
					$_SESSION['card_verif'] = '';
				}

				// 更新最后登录信息
				$this->db->update(array('lastloginip' => ip(), 'lastlogintime' => SYS_TIME), array('userid' => $r['userid']));
				$_SESSION['userid'] = $r['userid'];
				$_SESSION['roleid'] = $r['roleid'];
				$_SESSION['pc_hash'] = random(6, 'abcdefghigklmnopqrstuvwxwyABCDEFGHIGKLMNOPQRSTUVWXWY0123456789');
				$_SESSION['lock_screen'] = 0;

				// 设置 Cookie
				$default_siteid = self::return_siteid();
				$cookie_time = SYS_TIME + 86400 * 30;
				if (!$r['lang']) $r['lang'] = 'zh-cn';
				param::set_cookie('admin_username', $username, $cookie_time);
				param::set_cookie('siteid', $default_siteid, $cookie_time);
				param::set_cookie('userid', $r['userid'], $cookie_time);
				param::set_cookie('admin_email', $r['email'], $cookie_time);
				param::set_cookie('sys_lang', $r['lang'], $cookie_time);
				echo 
				showmessage(L('login_success'), '?m=admin&c=index');

				// 检查是否启用 vms
				$video_setting = getcache('video', 'video');
				if ($video_setting['sn'] && $video_setting['skey']) {
					$vmsapi = pc_base::load_app_class('ku6api', 'video');
					$vmsapi->member_login_vms();
				}
			} else {
				pc_base::load_sys_class('form', '', 0);
				include $this->admin_tpl('login');
			}
		}