竹磬网-邵珠庆の日记 生命只有一次,你可以用它来做些更多伟大的事情–Make the world a little better and easier


1412月/180

CentOS 使用 Google Authenticator 登录验证

发布在 邵珠庆

Google Authentication 项目 包含了多个手机平台的一次性验证码生成器的实现,以及一个可插拔的验证认证模块(PAM)。这些实现支持基于 HMAC 的一次性验证码(HOTP)算法(RFC 4226)和基于时间的一次性验证码(TOTP)算法(RFC 6238)。

下面将在 CentOS 上安装并使用 Google Authenticator 做登录的身份验证,当前系统的版本为

CentOS Linux release 7.2.1511 (Core)

安装 Google Authenticator PAM module

  • 确保 ntpd 已安装并正常运行运行

    yum install -y ntpdate
    systemctl start ntpd
    systemctl enable ntpd
    

    ntpdate 是用来自动同步时间的程序,这里启动它并设置它开机自动启动。

  • 安装一些接下去会用到的组件

    yum install -y git make gcc libtool pam-devel
    
  • 编译安装 Google Authenticator PAM module

    git clone https://github.com/google/google-authenticator
    cd google-authenticator/libpam
    ./bootstrap.sh
    ./configure
    make
    make install
    ln -s /usr/local/lib/security/pam_google_authenticator.so /usr/lib64/security/
    

配置 SSH 服务

打开 /etc/ssh/sshd_config 文件

vim /etc/ssh/sshd_config

修改下面字段的配置

ChallengeResponseAuthentication yes
PasswordAuthentication no
PubkeyAuthentication yes
UsePAM yes

然后重启一下 sshd 服务,使配置生效

systemctl restart sshd

这里将 PubkeyAuthentication 配置成了 yes 表示支持公钥验证登录,即使某个账号启用了 Google Authenticator 验证,只要登录者机器的公钥在这个账号的授权下,就可以不输入密码和 Google Authenticator 的认证码直接登录。

配置 PAM

打开 /etc/pam.d/sshd 文件

vim /etc/pam.d/sshd

这里分四种情况来配置

  • 验证密码和认证码,没有启用 Google Authenticator 服务的账号只验证密码(推荐)

    auth substack password-auth
    #...
    auth required pam_google_authenticator.so nullok
    

    password-auth 与 pam_google_authenticator 的先后顺序决定了先输入密码还是先输入认证码。

  • 验证密码和认证码,没有启用 Google Authenticator 服务的账号无法使用密码登录

    auth substack password-auth
    #...
    auth required pam_google_authenticator.so
    
  • 只验证认证码,不验证密码,没有启用 Google Authenticator 服务的账号不用输入密码直接可以成功登录

    #auth substack password-auth
    #...
    auth required pam_google_authenticator.so nullok
    

    注释掉 auth substack password-auth 配置就不会再验证账号密码了。

  • 只验证认证码,不验证密码,没有启用 Google Authenticator 服务的账号无法使用密码登录

    #auth substack password-auth
    #...
    auth required pam_google_authenticator.so
    

启用 Google Authenticator

切换至想要使用 Google Authenticator 来做登录验证的账号,执行下面操作

google-authenticator

然后会出现下面一系列交互式的对话做对应的设置

Do you want authentication tokens to be time-based (y/n) y
https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/shenyu@shenyu.me%3Fsecret%3DKHMH46EWI2RIRZ53KQTNGHXNP4%26issuer%3Dshenyu.me
# 这里是个二维码
Your new secret key is: KHMH46EWI2RIRZ53KQTNGHXNP4
Your verification code is 753579
Your emergency scratch codes are:
  99181037
  68865807
  88385439
  59103432
  81045035

这里会显示一个二维码,如果你的终端终端不支持显示二维码,可以手动打开这个网页链接(墙)来查看二维码或者手动输入后面的密钥(secret key)来代替扫描二维码,之后的操作会用到这个二维码/密钥(secret key)。这里还有一个认证码(verifiction code),暂时不知道有什么用,以及 5 个紧急救助码(emergency scratch code),紧急救助码就是当你无法获取认证码时(比如手机丢了),可以当做认证码来用,每用一个少一个,但其实可以手动添加的,建议如果 root 账户使用 Google Authenticator 的话一定要把紧急救助码另外保存一份。

Do you want me to update your "/home/test/.google_authenticator" file? (y/n) y

是否更新用户的 Google Authenticator 配置文件,选择 y 才能使上面操作对当前用户生效,其实就是在对应用户的 Home 目录下生成了一个 .google_authenticator 文件,如果你想停用这个用户的 Google Authenticator 验证,只需要删除这个用户 Home 目录下的 .google_authenticator 文件就可以了。

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y

每次生成的认证码是否同时只允许一个人使用?这里选择 y

By default, tokens are good for 30 seconds. In order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with
poor time synchronization, you can increase the window from its default
size of  -1min (window size of 3) to about  -4min (window size of
17 acceptable tokens).
Do you want to do so? (y/n) n

是否增加时间误差?这里选择 n

If the computer that you are logging into isn\'t hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y

是否启用次数限制?这里选择 y,默认每 30 秒最多尝试登录 3 次。

上面交互式的设置也可用通过参数一次性设置(推荐)

google-authenticator -t -f -d -l shenyu@shenyu.me -i SHENYU.ME -r 3 -R 30 -W

可以看到,通过参数还可以自定义 发行商 和 标签,执行 google-authenticator -h 来查看所有的参数设置

google-authenticator []
 -h, --help               Print this message
 -c, --counter-based      Set up counter-based (HOTP) verification
 -t, --time-based         Set up time-based (TOTP) verification
 -d, --disallow-reuse     Disallow reuse of previously used TOTP tokens
 -D, --allow-reuse        Allow reuse of previously used TOTP tokens
 -f, --force              Write file without first confirming with user
 -l, --label=

设置 Google Authenticator 手机 App

在手机上下载并安装 Google Authenticator

手机类型 App 程序名称
IOS Google Authenticator
Android 谷歌动态口令(请在手机对应的应用商店里搜索下载)

安装完后,打开 Google Authenticator/谷歌动态口令 App,点击 开始设置,选择 扫描条形码 扫描上面 google-authenticator 命令生成的二维码,然后手机上就能看到对应的认证码了。

自动草稿

这里的认证码每 30 秒变化一次,认证码上面的 SHENYU.ME 对应的是 google-authenticator 参数 -i 设置的发行商,认证码下面的 shenyu@shenyu.me 对应的是 google-authenticator 参数 -l设置的标签,如果你没有通过 google-authenticator 的参数设置发行商和标签,默认会使用系统的 hostname 来作为发行商,标签则则使用用户名和 hostname 的组合,格式为 username@hostname,标签其实是后期可以通过手机App来修改的,而发行商则修改不了。

现在重新使用 SSH 登录服务器,就会要求输入密码和 Verification code 来验证身份。如果登陆时遇到问题,请查看日志文件 /var/log/secure

参考资料

38月/160

PHP下SSL加密解密、验证、签名方法(很简单)

发布在 邵珠庆

RSA超级简单,依赖于OpenSSL扩展,这里就不多废话了,直接奉上代码

 

签名:
function sign($data) {
//读取私钥文件(签名一定是商户自己本地生成的私钥)
$priKey = file_get_contents('key/rsa_private_key.pem');

//转换为openssl密钥,必须是没有经过pkcs8转换的私钥(不要使用这个私钥rsa_private_key_pkcs8.pem)

$res = openssl_get_privatekey($priKey);

//调用openssl内置签名方法,生成签名$sign
openssl_sign($data, $sign, $res);

//释放资源
openssl_free_key($res);

return $sign;
}
验证:
function verify($data, $sign) {
//读取支付宝公钥文件(一定是阿里后台生成提供的公钥)
$pubKey = file_get_contents('key/alipay_public_key.pem');

//转换为openssl格式密钥
$res = openssl_get_publickey($pubKey);

//调用openssl内置方法验签,返回bool值
$result = (bool)openssl_verify($data, $sign, $res);

//释放资源
openssl_free_key($res);

return $result;

解密
function decrypt($content) {

//读取商户私钥(商户自己生成的私钥)
$priKey = file_get_contents('key/rsa_private_key.pem');

//转换为openssl密钥,必须是没有经过pkcs8转换的私钥
$res = openssl_get_privatekey($priKey);

//声明明文字符串变量
$result = '';

//循环按照128位解密
for($i = 0; $i < strlen($content)/128; $i++ ) {
$data = substr($content, $i * 128, 128);

//拆分开长度为128的字符串片段通过私钥进行解密,返回$decrypt解析后的明文
openssl_private_decrypt($data, $decrypt, $res);

//明文片段拼接
$result .= $decrypt;
}

//释放资源
openssl_free_key($res);

//返回明文
return $result;
}

 

AES密钥是什么

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),是目前对称密钥加密中比较通用的一种加密方式。

AES密钥有什么用

支付宝开放平台所有OpenAPI均支持对接口的请求内容和响应内容进行AES加密,部分OpenAPI(芝麻信用等)强制要求AES加密。加密后,在网络上传输的接口报文内容将会由明文内容变为密文内容,可以大大提升接口内容传输的安全性。

AES密钥与RSA密钥的关系

  • AES密钥是对接口请求和响应内容进行加密,密文无法被第三方识别,从而防止接口传输数据泄露。
  • RSA密钥是对接口请求和响应内容进行签名,开发者和支付宝开放平台分别加签验签,以确认接口传输的内容没有被篡改。不论接口内容是明文还是密文,RSA均可正常签名。
  • 开发者可对请求参数先做AES加密,然后对密文进行RSA签名。
257月/160

Bootstrap表单验证插件bootstrapValidator使用方法整理

发布在 邵珠庆

文档首页 :
http://bv.doc.javake.cn/api/





 
 
$(document).ready(function() {
   
// 原价、现价同时双验证
$("#original_price").blur(function () {
    $("#form_yanzheng").data('bootstrapValidator').resetForm().validateField('price');
});
$("#price").blur(function () {
    $("#form_yanzheng").data('bootstrapValidator').resetForm().validateField('original_price');
});
 
//表单验证
$('#form_yanzheng').bootstrapValidator({
    message: '值没有被验证',
    feedbackIcons: {
        valid: 'glyphicon glyphicon-ok',
        invalid: 'glyphicon glyphicon-remove',
        validating: 'glyphicon glyphicon-refresh'
    },
    fields: {
        subject: {
            message: 'The username is not valid',
            trigger: 'blur', //失去焦点就会触发
            validators: {
                notEmpty: {
                    message: '商品名称不能为空'
                },
                stringLength: {
                    min: 2,
                    max: 15,
                    message: '输入字符数量错误'
                },
                callback: {
                    message: '商品名称有非法内容',
                    callback: function () {
                        var filter = true;
                        var subject = $("#subject").val();
 
                        if (subject.indexOf("储值卡") >= 0) {
                            filter = false;
                        }
                        if (subject.indexOf("充值卡") >= 0) {
                            filter = false;
                        }
                        if (subject.indexOf("会员卡") >= 0) {
                            filter = false;
                        }
                        if (subject.indexOf("vip卡") >= 0) {
                            filter = false;
                        }
                        if (subject.indexOf("打折卡") >= 0) {
                            filter = false;
                        }
                        if (subject.indexOf("年卡") >= 0) {
                            filter = false;
                        }
                        if (subject.indexOf("美容卡") >= 0) {
                            filter = false;
                        }
                        if (subject.indexOf("健身卡") >= 0) {
                            filter = false;
                        }
 
                        return filter;
                    }
                }
            }
        },
        original_price: {
            trigger: 'blur', //失去焦点就会触发
            message: 'The username is not valid',
            validators: {
                notEmpty: {
                    message: '输入整数或者2位小数'
                },
                regexp: {
                    regexp: /^(\d+\.\d{1,2}|\d+)$/,
                    message: '输入整数或者2位小数'
                },
                callback: {
                    message: '原价要大于等于现价',
                    callback: function () {
                        var op = $('#original_price').val();
                        var pp = $('#price').val();
                        return parseFloat(op) >= parseFloat(pp);
                    }
                }
            }
        },
        price: {
            trigger: 'blur', //失去焦点就会触发
            message: 'The username is not valid',
            validators: {
                notEmpty: {
                    message: '输入整数或者2位小数'
                },
                regexp: {
                    regexp: /^(\d+\.\d{1,2}|\d+)$/,
                    message: '输入整数或者2位小数'
                },
                callback: {
                    message: '原价要大于等于现价',
                    callback: function () {
                        var op = $('#original_price').val();
                        var pp = $('#price').val();
                        return parseFloat(op) >= parseFloat(pp);
                    }
                }
            }
        },
        inventory: {
            trigger: 'blur', //失去焦点就会触发
            message: 'The username is not valid',
            validators: {
                notEmpty: {
                    message: '输入整数或者2位小数'
                },
                between: {
                    min: 0,
                    max: 999999,
                    message: '输入有效库存数量'
                }
            }
        },
        validity_period: {
            trigger: 'blur', //失去焦点就会触发
            message: 'The username is not valid',
            validators: {
                notEmpty: {
                    message: '请正确填写有效天数7-360天'
                },
                between: {
                    min: 7,
                    max: 360,
                    message: '请合理输入使用有效期'
                }
            }
        }
    }
}).on('success.form.bv', function (e) {
 
    var refertype = $('#refertype').val();
    var urlpath = "{:U('Index/" + refertype + "')}";
 
    $.ajax({
        type: "POST",
        url: urlpath,
        data: $('#form_yanzheng').serialize(),
        dataType: 'json',
        success: function (data, statusText, xhr, $form) {
            if (data.type == 'true') {
                swal({
                    title: data.info,
                    text: "",
                    type: "success",
                }, function () {
                    self.location = "{:U('Index/index')}";
                });
 
            } else {
                swal(data.info, "", "error");
                return false;
            }
        }
    });
});
});

74月/110

关于API和OAuth授权验证

发布在 邵珠庆

前两天有事去市里了,发现火狐便携版仍然那么杯具。。

订阅什么的就没管,积了好多。。

博客更新是回来后再补的,订阅的童鞋应该发现的。。

这几天有折腾腾讯微博的API,(刚蓝屏一次,话说用win7快一年了,蓝屏不到10次,MS还不错),SDK什么的都没有ASP的,而吾辈偏偏就只会一点ASP。。。

只好自己试着搞定,经过多方Google,终于明白点oauth是什么了,可是运行时总是失败。。。。。

两篇和API相关的文章:

OAuth是什么?:http://www.99ria.com/blog/?p=340

OAuth认证步骤:http://isouth.org/archives/286.html

还有就是新浪的API文档吧,腾讯的只是给出了需要的接口地址而已。。

比较麻烦的是oauth_signature(签名),我的理解就是好比你写了封信,你在信中表明了自己的身份(App Key),然后写了其他需要的内容,但是这封信是不是你写的呢?

你需要将已经写好的内容(base string)以及一个Key(App Secret)作为参数,按照指定的签名方法(oauth_signature_method)进行运算,得出一个oauth_signature值(HMAC-SHA1 [Key,base_string])写在信的未尾作为签名;

收信人在拿到信后,需要找出信中所述App Key对应的App Secret,然后自己进行下签名运算,看得出的oauth_signature值是不是和你写的一样。

App Key和App Secret就相当于帐号密码,但是oauth授权验证大部分都是跨站进行或者使用GET方式传递数据,为了安全而采用oauth_signature作为一次性密码。

base_string里包含了一个表明当前时间的时间戳(oauth_timestamp,自1970年1月1日00:00:00 GMT以来的秒数)和一个32位的的随机字符串(oauth_nonce),想要逆向算出App Secret…………

上边说了OAuth授权的安全机制,现在再来看授权过程:

API通过以下四个步骤来完成认证授权并访问或修改受限资源的流程

1. 获取未授权的Request Token(temporary credentials)

2. 请求用户授权Request Token

3. 使用授权后的Request Token换取Access Token(token credentials)

4. 使用 Access Token 访问或修改受保护资源

其中1~3步使用https方式, 第4步使用http方式。

我们注册并使用腾讯微博,帐号密码及所发布的信息都保存在腾讯微博的服务器上,但是可以通过API接口将数据拿到站外使用,

所以第一步,应用方帮用户向腾讯服务器进行“预约”,同时也是对应用方资格的一个验证,

二、三步对用户身份进行验证,“办理”将数据拿到站外使用的其他“手续”,最后就可以在应用方 “访问或修改受保护资源”。

和OpenID挺像的,,只是OpenID应用中,对用户身份进行验证的一方只负责验证,不提供资源。

关于API和OAuth授权验证《关于API和OAuth授权验证》 http://www.wdssmq.com/post/ZuoWanDeMengHaiZhenShiXuanYiJiaKeHuanTuCaoChuMei.html

33月/100

js 验证表单提交验证类

发布在 邵珠庆

附加:js验证radio是否选择
<script language="JavaScript">
function checkform(obj)
{
for(i=0;i<obj.oo.length;i++)
if(obj.oo[i].checked==true) return true;
alert("请选择")
return false;
}
</script>
<form id="form1" name="form1" method="post" action="" onsubmit="return checkform(this)">
<input type="radio" name="oo" value="radiobutton" />
<input type="radio" name="oo" value="radiobutton" />
<input type="submit" name="Submit" value="提交 " />
</form>
1. 长度限制
<script>
function test()
{
if(document.a.b.value.length>50)
{
alert("不能超过50个字符!");
document.a.b.focus();
return false;
}
}
</script>
<form name=a onsubmit="return test()">
<textarea name="b" cols="40" wrap="VIRTUAL" rows="6"></textarea>
<input type="submit" name="Submit" value="check">
</form>

2. 只能是汉字
<input onkeyup="value="/oblog/value.replace(/[^/u4E00-/u9FA5]/g,'')">

3." 只能是英文
<script language=javascript>
function onlyEng()
{
if(!(event.keyCode>=65&&event.keyCode<=90))
event.returnvalue=false;
}
</script>

<input onkeydown="onlyEng();">

4. 只能是数字
<script language=javascript>
function onlyNum()
{
if(!((event.keyCode>=48&&event.keyCode<=57)||(event.keyCode>=96&&event.keyCode<=105)))
//考虑小键盘上的数字键
event.returnvalue=false;
}
</script>

<input onkeydown="onlyNum();">

5. 只能是英文字符和数字
<input onkeyup="value="/oblog/value.replace(/[/W]/g,"'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^/d]/g,''))">

6. 验证 油箱格式
<SCRIPT LANGUAGE=javascript RUNAT=Server>
function isEmail(strEmail) {
if (strEmail.search(/^/w+((-/w+)|(/./w+))*/@[A-Za-z0-9]+((/.|-)[A-Za-z0-9]+)*/.[A-Za-z0-9]+$/) != -1)
return true;
else
alert("oh");
}
</SCRIPT>
<input type=text onblur=isEmail(this.value)>

7. 屏蔽关键字(这里屏蔽***和****)
<script language="javascript1.2">
function test() {
if((a.b.value.indexOf ("***") == 0)||(a.b.value.indexOf ("****") == 0)){
alert(" ");
a.b.focus();
return false;}
}
</script>
<form name=a onsubmit="return test()">
<input type=text name=b>
<input type="submit" name="Submit" value="check">
</form>

8. 两次输入密码是否相同
<FORM METHOD=POST ACTION="">
<input type="password" id="input1">
<input type="password" id="input2">
<input type="button" value="test" onclick="check()">
</FORM>
<script>
function check()
{
with(document.all){
if(input1.value!=input2.value)
{
alert("false")
input1.value = "";
input2.value = "";
}
else document.forms[0].submit();
}
}
</script>
够了吧
屏蔽右键 很酷
oncontextmenu="return false" ondragstart="return false" onselectstart="return false"
加在body中

2.1 表单项不能为空

<script language="javascript">
<!--
function CheckForm()
{
if (document.form.name.value.length == 0) {
alert("请输入您姓名!");
document.form.name.focus();
return false;
}
return true;
}
-->
</script>

2.2 比较两个表单项的值是否相同

<script language="javascript">
<!--
function CheckForm()
if (document.form.PWD.value != document.form.PWD_Again.value) {
alert("您两次输入的密码不一样!请重新输入.");
document.ADDUser.PWD.focus();
return false;
}
return true;
}
-->
</script>

2.3 表单项只能为数字和"_",用于电话/银行帐号验证上,可扩展到域名注册等

<script language="javascript">
<!--
function isNumber(String)
{
var Letters = "1234567890-"; //可以自己增加可输入值
var i;
var c;
if(String.charAt( 0 )=='-')
return false;
if( String.charAt( String.length - 1 ) == '-' )
return false;
for( i = 0; i < String.length; i ++ )
{
c = String.charAt( i );
if (Letters.indexOf( c ) < 0)
return false;
}
return true;
}
function CheckForm()
{
if(! isNumber(document.form.TEL.value)) {
alert("您的电话号码不合法!");
document.form.TEL.focus();
return false;
}
return true;
}
-->
</script>
2.4 表单项输入数值/长度限定

<script language="javascript">
<!--
function CheckForm()
{
if (document.form.count.value > 100 || document.form.count.value < 1)
{
alert("输入数值不能小于零大于100!");
document.form.count.focus();
return false;
}
if (document.form.MESSAGE.value.length<10)
{
alert("输入文字小于10!");
document.form.MESSAGE.focus();
return false;
}
return true;
}
//-->
</script>

2.5 中文/英文/数字/邮件地址合法性判断

<SCRIPT LANGUAGE="javascript">
<!--

function isEnglish(name) //英文值检测
{
if(name.length == 0)
return false;
for(i = 0; i < name.length; i++) {
if(name.charCodeAt(i) > 128)
return false;
}
return true;
}

function isChinese(name) //中文值检测
{
if(name.length == 0)
return false;
for(i = 0; i < name.length; i++) {
if(name.charCodeAt(i) > 128)
return true;
}
return false;
}

function isMail(name) // E-mail值检测
{
if(! isEnglish(name))
return false;
i = name.indexOf(" at ");
j = name dot lastIndexOf(" at ");
if(i == -1)
return false;
if(i != j)
return false;
if(i == name dot length)
return false;
return true;
}

function isNumber(name) //数值检测
{
if(name.length == 0)
return false;
for(i = 0; i < name.length; i++) {
if(name.charAt(i) < "0" || name.charAt(i) > "9")
return false;
}
return true;
}

function CheckForm()
{
if(! isMail(form.Email.value)) {
alert("您的电子邮件不合法!");
form.Email.focus();
return false;
}
if(! isEnglish(form.name.value)) {
alert("英文名不合法!");
form.name.focus();
return false;
}
if(! isChinese(form.cnname.value)) {
alert("中文名不合法!");
form.cnname.focus();
return false;
}
if(! isNumber(form.PublicZipCode.value)) {
alert("邮政编码不合法!");
form.PublicZipCode.focus();
return false;
}
return true;
}
//-->
</SCRIPT>

2.6 限定表单项不能输入的字符

<script language="javascript">
<!--

function contain(str,charset)// 字符串包含测试函数
{
var i;
for(i=0;i<charset.length;i++)
if(str.indexOf(charset.charAt(i))>=0)
return true;
return false;
}

function CheckForm()
{
if ((contain(document.form.NAME.value, "%/(/)><")) || (contain(document.form.MESSAGE.value, "%/(/)><")))
{
alert("输入了非法字符");
document.form.NAME.focus();
return false;
}
return true;
}
//-->
</script>