使用spring+html5实现安全传输随机数字密码键盘
随着互联网的飞跃式发展,移动支付已越来越受欢迎并且已成为常态,很多三方支付公司推出了很多支付方式如快捷支付、认证支付、
扫码支付等等。快捷支付和认证支付可分为移动app控件和移动HTML5网页。用户第一次使用快捷支付或认证支付进行支付的时候,需先绑定
银行卡。在绑定银行卡的过程中,需要验证银行卡信息。不同银行、不同银行卡验证的要素不一样,有些需要验证四要素,有的需要验证八要
素。对于需要验证银行卡的交易密码的情况,怎样保证交易密码的安全不被别人所窃取呢?为了保证交易密码不在传输过程中被窃取,出现了
安全传输随机数字密码键盘。
立即学习“前端免费学习笔记(深入)”;
安全传输随机数字密码键盘怎么实现呢?今天给大家详细的介绍安全传输随机数字密码键盘的原理和代码实现。下图是实现的数字键
盘效果:
一、实现原理
用户点击“交易密码”输入框,页面异步向后台发送“获取密码键盘”的请求,后台接收到请求之后随机生成“1234567890与随机密文的对应
关系”和“随机密文”和“1234567890图片“的对应关系,然后把它们关系放入dto实例中并放入Redis中,最后把随机密文以集合的方式返回到页面,
页面js获取到密文集合后以循环的方式向后台请求对应的数字图片流,并展示在页面。
当用户点击数字键盘中的数字图片,就会把图片对应的密文放入到pkey隐藏输入框中,多个数字以逗号隔开,当点击支付的时候,就会
把peykey隐藏输入框的值传入到后台,后台从redis中取出“密文”与“1234567890数字“的对应关系,就取出了对应交易密码。
二、具体实现
1).Html5页面
页面主要展示密码输入框和支付按钮,需要导入jQuery、bootstrap及pwdkey.js等。下面是具体代码:
<%@ page language="java" import="java.util.*"contentType="text/html; charset=UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><%@ page pageEncoding="UTF-8"%><%String path = request.getContextPath();%><!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd"><html><head><meta name="viewport"content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" /><meta http-equiv="Cache-Control" CONTENT="private,must-revalidate"><link rel="stylesheet"href='<c:url value="/js/bootstrap/css/bootstrap.min.css"/>'><!-- 引入js脚本文件 begin --><!--[if lt IE 9]> <script src="http://cdn.bootcss.com/html5shiv/3.7.2/html5shiv.min.js"></script> <script src="http://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script><![endif]--><script src="<c:url value="/js/JQuery/jquery-1.10.0.min.js"/>"></script><script src="<c:url value="/js/bootstrap/js/bootstrap.min.js"/>"></script><script type="text/javascript" src="<c:url value="/js/pwdkey.js"/>"></script><title>xxx付款</title><style type="text/css">.input-out {padding-top: 20px;}.btn-out {margin:30px 10px;}.btn-out button {width: 100%;background: #5CACEE;border: #5CACEE;color: #fff;height: 50px;border-radius: 3px;font-size: 18px;font-family: "Microsoft Yahei", "??????", SimHei, Tahoma, Arial, Helvetica, STHeiti;}.keyboard {background: #fff;}.keyboard table {width:100%;text-align:center;}.keyboard table td {padding: 15px;}.keyboard table a,.keyboard table a:hover,.keyboard table a:focus {color: #333;text-decoration: none;}.input-out label {color:#D2D1D1;font-weight: normal;font-size: 16px;}.bottom {color:#888888;margin-bottom: 15px;text-align:center;margin-top:100px;}.bottom a {color:#888888;}.bottom img {vertical-align: middle;width: 18px;}</style></head><body><form action="<%=path%>/pay" method="post" id="from"> <div class="content"> <div class="input-out pass-label" style="border-bottom: 1px solid #ddd;padding-left: 12px;" random="2321321321" path="<%=path%>" ><label id="pin" >交易密码</label> </div> </div> <div class="btn-out"> <button type="button" class="btn btn-default" ontouchstart="check();" id="pay">支付</button> </div></form><div class="bottom" id="bottom-out"> @@##@@" /> <span>客服电话:4000-xxx-xxx</span></div><!-- jianpan--><div class="keyboard" style="display:none;" id="keyboard"> <table class="table-bordered" id="key_table"> </table></div></body></html>
2).密码键盘js代码
用户点击“交易密码”输入框,页面异步向后台发送“获取密码键盘”的请求,后台接收到请求之后把随机密文以集合的方式返回到页面,
页面js获取到密文集合后以循环的方式向后台请求对应的数字图片流并展示在页面。具体代码如下:
$(document).ready( function() { $("#pay").removeAttr("disabled"); $("input").click(function() { hideKey(); }); $("button").click(function() { hideKey(); }); $(".pass-label").click(function() {var rangdom = $(this).attr("random"); var path = $(this).attr("path"); pwdkey(this, rangdom, path); }); window.addEventListener("onorientationchange" in window ? "orientationchange": "resize", hengshuping, false); });function hengshuping() {if (window.orientation == 180 || window.orientation == 0) { $("div#keyboard td").each(function() { $(this).css("padding", "15px"); });}if (window.orientation == 90 || window.orientation == -90) { $("div#keyboard td").each(function() { $(this).css("padding", "8px"); });}window.scrollTo(0, $(".pass-label").offset().top);}function pwdkey(obj, rangdom, path) {$('.keyboard').addClass("navbar-fixed-bottom");$('.keyboard').css({ "z-index" : "9999"});if (rangdom == null || rangdom == "") { alert("无法加载密码键盘,请刷新后重试!"); return false;}if ($("#pkey").val() == null || $("#pkey").val() == "undefined") { $(obj) .html( $(obj).html() + '<input type="hidden" name="pkey" id="pkey" />');}$("#pin").html("交易密码");setCssNomal();$("#pkey").val("");$ .ajax({ type : 'post', url : path + "/common/pkey.do", cache : false, async : false, data : {rangdom : rangdom }, success : function(data) {if (data == null || data == "" || data == "undefined"|| data.length != 10) { alert("无法加载密码键盘,请刷新后重试!"); return false;} else { var key_table = $("#key_table"); key_table.html(""); var content = '<tr>'; for (var i = 0; i < 12; i++) {if ((i + 1) % 3 == 0 && i != 0 && i <= 5) { content = content + '<td style="width:33%;" key="' + data[i] + '" ontouchstart="return ontouch(this);">@@##@@</td></tr><tr>';} else if (i <= 7) { content = content + '<td style="width:33%;" key="' + data[i] + '" ontouchstart="return ontouch(this);">@@##@@</td>';} else if (i == 8) { content = content + '<td style="width:33%;" ontouchstart="return deleteOne();">@@##@@</td>' + '</tr><tr>';} else if (i < 11) { content = content + '<td style="width:33%;" key="' + data[i - 1] + '" ontouchstart="return ontouch(this);">@@##@@</td>';} else { content = content + '<td style="width:33%;" onclick="return _ok();">@@##@@</td>' + '</tr>';} } key_table.html(content); setTimeout(function() {$("#keyboard").show(); }, 600); hengshuping();} }, error : function() {alert("无法加载键盘,请刷新后重试!"); } });}function ontouch(obj) {var pkey = $("#pkey").val();var key = $(obj).attr("key");if (pkey == "") { $("#pin").html("");}var content = $("#pin").html();if (content != "" && content.length >= 6) { return false;}if (pkey != "") { key = "," + key;}pkey = pkey + key;$("#pkey").val(pkey);$("#pin").append("*");setCssKey();}function deleteOne() {var pkey = $("#pkey").val() + "";if (pkey == "") { return false;}var local = pkey.lastIndexOf(",");if (local == -1) { $("#pkey").val(""); $("#pin").html("交易密码"); setCssNomal();} else { pkey = pkey.substring(0, local - 1); var content = $("#pin").html(); content = content.substring(0, content.length - 1); $("#pkey").val(pkey); $("#pin").html(content);}}function _ok() {$("#key_table").html("");$("#keyboard").hide();}function showkey() {$("#keyboard").show();}function hideKey() {$("#key_table").html("");$("#keyboard").hide();}function setCssKey() {$("#pin").css({ "font-size" : "18px", "color" : "#030303", "font-weight" : "normal", "letter-spacing" : "1px"});}function setCssNomal() {$("#pin").css({ "font-size" : "16px", "color" : "#D2D1D1", "font-weight" : "normal"});}
3).获取密码键盘后台方法
该方法将随机生成“1234567890与随机密文的对应关系”和“随机密文”和“1234567890图片“的对应关系,然后把它们关系放入dto实例中
并放入redis中,最后把随机密文以集合的方式返回到页面。具体代码如下:
获取密码键盘:
/** * * @Description: 获取密码键盘 * @param request * @param rangdom 随机字符串 * @return * */@SuppressWarnings("unchecked")@ResponseBody@RequestMapping(value = "common/pkey.do", method = RequestMethod.POST)public Object digitkeyboard(HttpServletRequest request, String rangdom) { LOG.info("[获取密码键盘digitkeyboard][parames:outOrderId=" + rangdom + "]"); try { if (StringUtils.isBlank(rangdom)) { return ""; } PwdKeyDto pwdkey = PwdKeyUtils.digitkeyboard(); redisUtil.set("pwdkey_" + rangdom, pwdkey,redisUtil.getDigitkeyimeOut()); return pwdkey.getRundomKeys(); } catch (Exception e) { LOG.error("[获取密码键盘digitkeyboard][error:{}",e); } return "";}密码PwdKeyDto:/** * * @ClassName: PwdKeyDto * @Description: 密码映射Dto * @author xxxx <a target="_blank" href="mailto:xxxx@xxx.com">xxxx@xxx.com</a> * @date 2015年6月25日 上午11:01:20 * */public class PwdKeyDto implements Serializable{/** 描述*/private static final long serialVersionUID = 1L;private List<String> rundomKeys;// 随机Keysprivate Map<String, String> valueKeyMaps;// 密文和明文映射private Map<String, String> imgKeyMaps;// 密文和明文映射public PwdKeyDto() { super(); // TODO Auto-generated constructor stub}public List<String> getRundomKeys() { return rundomKeys;}public void setRundomKeys(List<String> rundomKeys) { this.rundomKeys = rundomKeys;}public PwdKeyDto(List<String> rundomKeys, Map<String, String> valueKeyMaps, Map<String, String> imgKeyMaps) { super(); this.rundomKeys = rundomKeys; this.valueKeyMaps = valueKeyMaps; this.imgKeyMaps = imgKeyMaps;}public Map<String, String> getValueKeyMaps() { return valueKeyMaps;}public void setValueKeyMaps(Map<String, String> valueKeyMaps) { this.valueKeyMaps = valueKeyMaps;}public Map<String, String> getImgKeyMaps() { return imgKeyMaps;}public void setImgKeyMaps(Map<String, String> imgKeyMaps) { this.imgKeyMaps = imgKeyMaps;}}
生成键盘的PwdKeyUtils工具类:
/** * * @ClassName: PwdKeyUtils * @Description: 密码处理工具类 * @author xxxx <a target="_blank" href="mailto:xxxx@xxxx.com">xxxx@xxxx.com</a> * @date 2015年6月25日 上午11:03:24 * */ public class PwdKeyUtils { private final static Map<String, String> imagesValueMap; /** * * @Description: 获取密码键盘映射关系 * @param imagesValueMap * @return * */ static { imagesValueMap = new HashMap<String, String>(); imagesValueMap.put("0", "images/keys/0.png"); imagesValueMap.put("1", "images/keys/1.png"); imagesValueMap.put("2", "images/keys/2.png"); imagesValueMap.put("3", "images/keys/3.png"); imagesValueMap.put("4", "images/keys/4.png"); imagesValueMap.put("5", "images/keys/5.png"); imagesValueMap.put("6", "images/keys/6.png"); imagesValueMap.put("7", "images/keys/7.png"); imagesValueMap.put("8", "images/keys/8.png"); imagesValueMap.put("9", "images/keys/9.png"); } public static PwdKeyDto digitkeyboard() { List<String> rundomKeys = new ArrayList<String>();// 随机key映射 Map<String, String> valueKeys = new HashMap<String, String>();// 密文和明文映射 Map<String, String> imgKeyMaps = new HashMap<String, String>();// 密文和图片映射 List<String> keys = new ArrayList<String>(); for (int i = 0; i < 10; i++) { keys.add(i + ""); } for (int i = 0; i < 10; i++) { Random r = new Random(); int index = r.nextInt(keys.size()); String key = keys.get(index); keys.remove(index); String randomkey = randomKey(24); rundomKeys.add(randomkey); valueKeys.put(randomkey, key); imgKeyMaps.put(randomkey, imagesValueMap.get(key)); } PwdKeyDto dto = new PwdKeyDto(rundomKeys, valueKeys, imgKeyMaps); return dto; } /** * * @Description:获取动态key * @param num * key位数 * @return * */ public static String randomKey(int num) { StringBuffer sb = new StringBuffer(""); char[] chars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; for (int i = 0; i < num; i++) { int id = (int) Math.ceil(Math.random() * 60); sb.append(chars[id]); } return sb.toString(); } /** * * @Description:解密pin * @param request * @param pin * @return * */ public static String decryptPinData(HttpServletRequest request, String ciphertextpin) throws Exception { if (StringUtils.isNotBlank(ciphertextpin)) { Map<String, String> valuekeys = (Map<String, String>) request .getSession().getAttribute("valuekeys"); if (valuekeys == null || valuekeys.size() != 10) { throw new Exception(); } String[] ciphertextpins = ciphertextpin.split(","); StringBuffer sb = new StringBuffer(""); for (String ctpin : ciphertextpins) { sb.append(valuekeys.get(ctpin)); } } return null; } }
4).获取图片流后台方法
用户页面获取到随机密文集合后以循环的方式向后台请求该方法获取对应的数字图片流。具体代码如下:
/** * 获取key图片 * * @throws Exception */@RequestMapping(value = "/common/getKey.do", method = RequestMethod.GET)public void getKey(String key, String rangdom, HttpServletRequest request, HttpServletResponse response) throws Exception { LOG.info("[获取密码键盘key(getKey)][parms:key=" + key + "]"); PwdKeyDto pwdkey = (PwdKeyDto) redisUtil.get("pwdkey_" + rangdom); if (pwdkey == null || pwdkey.getImgKeyMaps() == null) { LOG.error("获取图片[getKey]:未获取到对应的键盘的映射关系"); throw new Exception(); } Map<String, String> imagekeys = pwdkey.getImgKeyMaps(); String path = imagekeys.get(key); String rootPath = request.getSession().getServletContext() .getRealPath("/"); path = rootPath + path; LOG.info("[获取密码键盘key(getKey)][path=" + path + "]"); if (StringUtils.isNotEmpty(path)) { try { InputStream fis = new FileInputStream(new File(path)); BufferedInputStream bis = new BufferedInputStream(fis); OutputStream fos = response.getOutputStream(); BufferedOutputStream bos = new BufferedOutputStream(fos); String fileName = "image."; String[] strs = path.split("\\."); fileName = fileName + strs[strs.length - 1]; setFileDownloadHeader(request, response, fileName); int byteRead = 0; byte[] buffer = new byte[8192]; while ((byteRead = bis.read(buffer, 0, 8192)) != -1) {bos.write(buffer, 0, byteRead); } bos.flush(); fis.close(); bis.close(); fos.close(); bos.close(); } catch (Exception e) { LOG.error("获取图片[path:" + path + "])失败:" + e.toString(), e); } }}
5).用户支付
当用户点击数字键盘中的数字图片,就会把图片对应的密文放入到pkey隐藏输入框中,多个数字以逗号隔开,当点击支付的时候,就
会把peykey隐藏输入框的值传入到后台,后台从redis中取出“密文”与“1234567890数字“的对应关系,就取出了对应交易密码。具体代码如下:
页面提交支付js:
function check(){hideKey();var pin=""; pin=$("#pkey").val(); if(pin==""||pin==undefined) { bool=false; alert("请输入交易密码");return false; }else { var keys=pin.split(","); if(keys.length!=6) { alert("请输入6位交易密码");return false; } } $.ajax({ type : 'post', url : "test/pay.do", data : { random:"2321321321", pin:pin }, cache : false, success : function(data) { if(data.success){ alert(data.message);}else{ } }, error : function(){alert("系统异常,请重试!"); } });}
后台解析密文方法:
/** * * @Description: 支付 * @param pin 交易密码密文 * @param random 随机码 * @return * */@ResponseBody@RequestMapping(value = "/test/pay.do", method = RequestMethod.POST)public Object pay(String pin,String random, HttpServletRequest request) { try { LOG.info("[支付(pay)][params:pin=" + pin + ",random="+random+"]"); if (StringUtils.isNotBlank(pin)) { StringBuffer sb = new StringBuffer(""); PwdKeyDto pwdkey = (PwdKeyDto) redisUtil.get("pwdkey_" + random); if (pwdkey == null || pwdkey.getValueKeyMaps() == null) {return new Result(false,"密码键盘已失效,请重新输入密码"); } Map<String, String> valuekeys = pwdkey.getValueKeyMaps(); String[] pins = pin.split(","); if (pins.length != 6) {return new Result(false,"交易密码位数不对"); } for (String pinKey : pins) {String val = valuekeys.get(pinKey);if (StringUtils.isBlank(val)) { return new Result(false,"密码键盘已失效,请重新输入密码");}sb.append(val); } /** * sb.toString()就是明文交易密码,下面就是具体的支付操作 */ } return new Result(true, "成功"); } catch (Exception e) { LOG.error("[支付(pay)][error:{}]",e); return new Result(false, "支付异常,请重试!"); }}