如果不能正常显示,请查看原文 , 或返回

OTP原理及实现

公司项目需求:为局域网以外的网站后台用户开发动态密保的功能。
在当前的现有设备下,最方便实现的就两种:1、通过短信获取动态密码登录;2、通过手机令牌来实现。

OTP 原理:
相关的博文:http://blog.csdn.net/goldboar/article/details/7065948
百度百科: http://baike.baidu.com/view/1505735.htm
otp从技术来分有三种形式, 时间同步、事件同步、挑战/应答。
(1) 时间同步
原理是基于 动态令牌动态口令验证服务器的时间比对,基于 时间同步令牌,一般每60秒产生一个新口令,要求服务器能够十分精确的保持正确的时钟,同时对其令牌的晶振频率有严格的要求,这种技术对应的终端是硬件令牌。
(2)事件同步
基于事件同步的令牌,其原理是通过某一特定的事件次序及相同的种子值作为输入,通过HASH算法中运算出一致的密码。
(3)挑战/应答
常用于的网上业务,在网站/应答上输入 服务端下发的 挑战码动态令牌输入该挑战码,通过内置的算法上生成一个6/8位的随机数字,口令一次有效,这种技术目前应用最为普遍,包括刮刮卡、短信密码、动态令牌也有挑战/应答形式。

主流的动态令牌技术是时间同步和挑战/应答两种形式。

项目采用:
https://code.google.com/p/androidtoken/ 实现TOTP动态口令登录
android token 该项目支持HOTP (事件令牌)和TOTP (时间令牌)规范
配置令牌支持:KeyUriFormat和QR码,以及手动创建;

项目实现:
    我这里采用添加方便的qr码,也就是常见的二维码来实现用户通过手机来绑定一个token;
首先,需要有的就是服务器端和客户端都共有的一个seed。

private final static String NUM_CHAR = "0123456789";  
   private static int charLen = NUM_CHAR.length();  


   /**  
    * 根据系统时间获得指定位数的随机数  
    * @param randomNumberDigit 随机数的位数  
    *  @return  获得的随机数  
    */  
   public static String getRandomNumber(int randomNumberDigit) {  
      long seed = System.currentTimeMillis();// 获得系统时间,作为生成随机数的种子  
      StringBuffer sb = new StringBuffer();// 装载生成的随机数  
      Random random = new Random(seed);// 调用种子生成随机数  
      for (int i = 0; i < randomNumberDigit; i++) {  
         sb.append(NUM_CHAR.charAt(random.nextInt(charLen)));  
      }  


      return sb.toString();  
   }

通过http://www.oschina.net/p/jquery-qrcode-js可以很方便的生成二维码。
二维码的内容
otpauth://totp/oa?secret=63985989418859891633&period=60&digits=8(android 支持这个协议,能直接扫描读取,添加一个token)
secret:密钥,也就是上面生成的seed;period:每60秒生成一次;digits:生成的随机码长度。

通过动态口令验证以后,服务器端也保存上seed;需要登陆的时候,服务器端生成动态口令和手机的来次对比就行了。

服务器端生成动态密码的方法:

/**
	 * 每60秒生成1个8位动态密码
	 * 
	 * @param seed
	 * @return
	 */
	public static String getTOTP(String seed) {
		long T0 = 0;
		long X = 60;
		Calendar cal = Calendar.getInstance();
		long time = cal.getTimeInMillis() / 1000;
		String steps = "0";
		try {
			long T = (time - T0) / X;
			steps = Long.toHexString(T).toUpperCase();
			while (steps.length() < 16)
				steps = "0" + steps;
			return generateTOTP(seed, steps, "8", "HmacSHA1");
		} catch (final Exception e) {
			System.out.println("Error : " + e);
			return "生成动态口令失败";
		}
	}
/**
	 * This method generates a TOTP value for the given set of parameters.
	 * 
	 * @param key
	 *            : the shared secret, HEX encoded
	 * @param time
	 *            : a value that reflects a time
	 * @param returnDigits
	 *            : number of digits to return
	 * @param crypto
	 *            : the crypto function to use
	 * 
	 * @return: a numeric String in base 10 that includes
	 *          {@link truncationDigits} digits
	 */
	public static String generateTOTP(String key, String time,
			String returnDigits, String crypto) {
		int codeDigits = Integer.decode(returnDigits).intValue();
		String result = null;
		// Using the counter
		// First 8 bytes are for the movingFactor
		// Compliant with base RFC 4226 (HOTP)
		while (time.length() < 16)
			time = "0" + time;
		// Get the HEX in a Byte[]
		byte[] msg = hexStr2Bytes(time);
		byte[] k = hexStr2Bytes(key);

		byte[] hash = hmac_sha(crypto, k, msg);
		// put selected bytes into result int
		int offset = hash[hash.length - 1] & 0xf;
		int binary = ((hash[offset] & 0x7f) << 24)
				| ((hash[offset + 1] & 0xff) << 16)
				| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
		int otp = binary % DIGITS_POWER[codeDigits];
		result = Integer.toString(otp);
		while (result.length() < codeDigits) {
			result = "0" + result;
		}
		return result;
	}
ps:直接下载的那个android token有广告,升级一下就没了


返回