手机动态口令

一.什么是OTP和TOTP

OTP全称叫One-time Password,也称动态口令,是根据专门的算法每隔60秒生成一个与时间相关的、不可预测的随机数字组合,每个口令只能使用一次,每天可以产生1440个密码。
TOTP算法(Time-based One-time Password algorithm)是一种从共享密钥和当前时间计算一次性密码的算法。

二.怎么使用动态口令登录认证

  • 1.通过qrcode在web管理台生成一个带有key的二维码,key需要加密保存数据库。
  • 2.基于js版的totp开发的跨平台的手机app,通过app扫描二维码进行绑定key,实现一个60秒切换一次口令的手机动态令牌。
  • 3.用户使用手机动态口令登录系统,后端使用基于java的totp实现结合之前存储到数据库的key进行动态口令校验。

三.js和java版totp使用hmac-sha1实现

1.totp的js实现,需要自行下载hmac-sha1的js库

  • totp.js代码
var totp = {
};

//mtimeStep 60,
totp.generateOtp = function(mSeed,mtimeStep,mOtpLength) {
                      //0 1  2   3    4     5      6       7        8          9           10
    var DIGITS_POWER = [1,10,100,1000,10000,100000,1000000,10000000,100000000, 1000000000, 10000000000];
    
    var counter = new Array(8);
    //GMT时间毫秒数
    var time = parseInt(new Date().getTime()/1000);
    //mtimeStep
    var movingFactor = parseInt(time/mtimeStep);
    
    for(var i = counter.length - 1;i >= 0;i --){
        counter[i] = movingFactor & 0xff;
        movingFactor >>= 8;
    }
    
    var tmp2 = totp.stringToHex(counter);
    var result = CryptoJS.HmacSHA1(tmp2,mSeed).toString();
    
    var hash = totp.hexToArr(result);
    var offset = hash[hash.length - 1] & 0xf;
    
    var otpBinary = ((hash[offset] & 0x7f) << 24)
                    |((hash[offset + 1] & 0xff) << 16)
                    |((hash[offset + 2] & 0xff) << 8)
                    |(hash[offset + 3] & 0xff);
    
    var otp = otpBinary % DIGITS_POWER[mOtpLength];
    var result = otp + "";
    
    while(result.length < mOtpLength){
        result = "0" + result;
    }
    
    return result;      
};

totp.hexToArr = function(hex){
    var arr = new Array(parseInt(hex.length/2));
    for(var i = 0;i < arr.length;i ++){
        arr[i] = parseInt(hex.substr(2*i,2),16) & 0xff;
    }
    
    return arr;
};

totp.stringToHex = function(arr){
    var result = "";
    
    var tmp = "";
    for(var i = 0;i < arr.length;i ++){
        tmp = arr[i].toString(16);
        if(tmp.length == 1){
            tmp = "0" + tmp;
        }
        
        result += tmp;
    }
    
    return result;
};
  • totp.js测试代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <script type="text/javascript" src="js/hmac-sha1.js"></script>
    <script type="text/javascript" src="js/totp.js"></script>
    <script type="text/javascript">
        //第一个参数密钥,第二个参数时间间隔60秒,第三个参数otp长度6位
        var otp = totp.generateOtp("3132333435363738393031323334353637383930",60,6);
        console.log(otp);
    </script>
</body>
</html>

2.totp的java实现

import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/**
 * java版totp
 * @author admin
 */
public class Totp {
    
    private static final int[] DIGITS_POWER
    // 0 1  2   3    4     5      6       7        8
    = {1,10,100,1000,10000,100000,1000000,10000000,100000000};
    
    /**
     * 采用默认值3分钟计算比对(GMT时间),60秒产生一个6位otp值
     * @param seed 种子信息
     * @param checkVal 校验值
     * @return
     */
    public static boolean checkOtp(String seed,String checkVal){
        return checkOtp(seed, checkVal, 60, 6);
    }
    
    /**
     * 根据长度创建手机令牌码,长度不要修改
     * @param seed
     * @param date
     * @param length
     * @return
     */
    public static String createOtp(String seed){
        return createOtp(seed, 60, 6);
    }
    
    /**
     * 根据配置的时间计算比对(Asia/Shanhai上海时区)
     * @param seed 种子信息
     * @param checkVal 校验值
     * @param min 客户端和服务端分钟误差
     * @return
     */
    static boolean checkOtp(String seed,String checkVal,int timeStep,int min){
        String tmp = createOtp(seed, timeStep, min);
        if(tmp.equals(checkVal)) return true;
        
        return false;
    }
    
    /**
     * 根据长度创建手机令牌码,长度不要修改
     * @param seed
     * @param date
     * @param length
     * @return
     */
    static String createOtp(String seed,int timeStep, int otpLength){
        Date date = new Date();
        Calendar time = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        time.setTime(date);
        
        long value = time.getTimeInMillis()/1000;
        int movingFactor = (int) (value/timeStep);
        return generateOtp(seed, movingFactor, otpLength);
    }
    
    static byte[] stringToHex(String hexInputString){
        
        byte[] bts = new byte[hexInputString.length() / 2];
        
        for (int i = 0; i < bts.length; i++) {
            bts[i] = (byte) Integer.parseInt(hexInputString.substring(2*i, 2*i+2), 16);
        }
        
        return bts;
    }

    static String generateOtp(String mSeed,long time,int mOtpLength) {
        
        byte[] counter = new byte[8];
        long movingFactor = time;
        
        for(int i = counter.length - 1; i >= 0; i--){
            counter[i] = (byte)(movingFactor & 0xff);
            movingFactor >>= 8;
        }
        
        byte[] hash = hmacSha(mSeed.getBytes(), byteArrayToHexString(counter).getBytes());
        
        int offset = hash[hash.length - 1] & 0xf;
        
        int otpBinary = ((hash[offset] & 0x7f) << 24)
                        |((hash[offset + 1] & 0xff) << 16)
                        |((hash[offset + 2] & 0xff) << 8)
                        |(hash[offset + 3] & 0xff);
        
        int otp = otpBinary % DIGITS_POWER[mOtpLength];
        String result = Integer.toString(otp);
        
        
        while(result.length() < mOtpLength){
            result = "0" + result;
        }
        
        return result;      
    }

    static byte[] hmacSha(byte[] seed, byte[] counter) {
        
        try{
            Mac hmacSha1;
            
            try{
                hmacSha1 = Mac.getInstance("HmacSHA1");
            }catch(NoSuchAlgorithmException ex){
                hmacSha1 = Mac.getInstance("HMAC-SHA-1");
            }
            
            SecretKeySpec macKey = new SecretKeySpec(seed, "RAW");
            hmacSha1.init(macKey);
            
            return hmacSha1.doFinal(counter);
            
        }catch(GeneralSecurityException ex){
            throw new RuntimeException(ex);
        }
    }
    
    static String byteArrayToHexString(byte[] digest) {
        
        StringBuffer buffer = new StringBuffer();
        
        for(int i =0; i < digest.length; i++){
            String hex = Integer.toHexString(0xff & digest[i]);
            
            if(hex.length() == 1)
                buffer.append("0");
            
            buffer.append(hex);

        }
        
        return buffer.toString();
    }

    public static void main(String[] args) throws Exception {
        String seed = "3132333435363738393031323334353637383930";
        System.out.println(createOtp(seed));
        System.out.println(checkOtp(seed,"527078"));
    }
    
}

四.HOTP原理类似

后面会给出hotp

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,021评论 8 265
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 如果你来华容作客,一定不要错过华容团子这道最朴素的美食。团子是华容人早餐桌上最盛行的小吃,是华容老百姓的最爱,毫不...
    温馨小语阅读 323评论 3 2
  • 八爪鱼海外版Octoparse经过三个月的短暂运营,每个月均保持近200%的增长趋势!无任何广告投放,全靠产品口碑...
    PaulBlack阅读 11,439评论 0 3