概叙
科普文:软件架构设计之应用安全【身份验证(Authentication):短信验证、图片验证、TOTP一次性密码、2FA双重身份验证等小结】-CSDN博客
科普文:软件架构设计之应用安全【身份验证(Authentication):OTP一次性密码 TOTP和HOTP小结】-CSDN博客
科普文:软件架构设计之应用安全【身份验证(Authentication):OTP一次性密码 HOTP详解】-CSDN博客
一次性密码(OTP,One-Time Password)是一种用于身份验证的安全机制,通常用于提高用户账户的安全性。
前面我们一步一步地分析一下OTP的工作原理。
- 生成:OTP通常在用户尝试登录时生成。它可以通过多种方式生成,比如时间同步(TOTP,基于时间的一次性密码)或事件驱动(HOTP,基于计数的一次性密码)。
- 发送:生成的OTP会通过安全的方式发送给用户,例如通过短信、电子邮件或专用的认证应用程序(如Google Authenticator)。
- 验证:用户输入收到的OTP,系统会对比输入的密码与生成的密码,进行验证。
- 有效性:OTP一般有时间限制,比如有效期为30秒,过期后需要重新生成。
这种机制能有效防止重放攻击和钓鱼攻击,因为每个密码只使用一次且是短暂有效的。
- OTP的主要优点:OTP提供了更高的安全性,因为每个密码只使用一次,防止重放攻击。
- 时间同步和事件驱动的OTP:时间同步(TOTP)依赖于时间戳生成密码,而事件驱动(HOTP)则基于一个计数器,每次使用后递增。
- 生成TOTP和HOTP:TOTP通过当前时间和一个共享密钥生成,而HOTP则通过计数器和共享密钥生成。
- OTP的应用场景:主要用于在线银行、电子邮件、社交媒体等需要安全验证的场合。
- 双因素认证(2FA):2FA是结合两种不同的认证因素(如密码和OTP),增强安全性的身份验证方法。
- 使用OTP时可能面临的安全风险:包括中间人攻击、短信拦截和应用程序被攻击。
- 如何增强OTP的安全性:使用加密传输、限制输入错误次数和设置有效期等方法。
- OTP的标准协议:有一些如RFC 6238(TOTP)和RFC 4226(HOTP)的标准协议。
- 常见的OTP生成器:包括Google Authenticator、Authy、Duo Mobile等。
- OTP和传统密码的区别:OTP是一次性且短暂有效,而传统密码可以重复使用且通常有效期较长。
- 如何处理OTP的丢失或未收到问题:通常可以通过备用验证方式,如安全问题或备用邮箱,来恢复访问。
- OTP是否能完全替代传统密码:OTP能增强安全性,但不应完全替代,结合使用更为理想。
- 在移动设备上使用OTP的优势和劣势:优势是便捷性,劣势包括手机丢失或被盗的风险。
- 什么情况下应该考虑使用OTP:当涉及敏感信息或交易时,特别是在需要远程访问的情况下。
在这里我们再继续看看OTP的另一种方式RFC 6238(TOTP)。
基于时间的动态密码TOTP详解
详细协议参考:RFC 6238: TOTP: Time-Based One-Time Password Algorithm
TOTP算法
TOTP Algorithm This variant of the HOTP algorithm specifies the calculation of a one-time password value, based on a representation of the counter as a time factor.
TOTP算法其实就是沿用HOTP算法,只是将counter计数器换成时间戳。
TOTP算法描述
时间窗口 X :表示多长时间算作计数器的一步,通常会设为30秒
初始计数时间戳 $T_0$: 使用 Unix 时间戳来表示 OTP 生成的时候的初始计数时间。
TOTP算法流程
TOTP 算法的关键在于如何更具当前时间和时间窗口计算出计数,也就是如何根据当前时间和 X 来计算 HOTP 算法中的 C。
HOTP 算法中的 C 是使用当前 Unix 时间戳减去初始计数时间戳,然后除以时间窗口而获得的。
Java实现TOTP示例
package com.zxx.study.rjava.otp;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
/**
* @author zhouxx
* @create 2025-05-06 11:55
*/
public class TOTPService implements OTPService {
final String algorithm;
final Mac mac;
final byte[] buffer;
final int macLength;
final int modDivisor;
final int HOTP_Length;
public TOTPService() throws NoSuchAlgorithmException {
algorithm = "HmacSHA1";
mac = Mac.getInstance(algorithm);
this.macLength = mac.getMacLength();
buffer = new byte[macLength];
HOTP_Length = 6;
modDivisor = (int) Math.pow(10, HOTP_Length); // 取模生成6位数字
}
protected Key generateKey(String password) throws UnsupportedEncodingException, InvalidKeyException {
Key key = new SecretKeySpec(password.getBytes("UTF-8"), algorithm);
// 创建MAC对象并初始化
mac.init(key);
return key;
}
public int generateOneTimePassword(String password) throws Exception {
long currentTimeStep = System.currentTimeMillis() / 1000 / 30; // 每30秒为一个时间窗口
int hotpNumber = generateOneTimePassword(generateKey(password), currentTimeStep);
return hotpNumber;
}
@Override
public int generateOneTimePassword(Key key, long timeStep) throws Exception {
// 构造输入数据(通常是时间步长,例如每30秒变化一次)
//byte[] data = new byte[8];
for (int i = 7; i >= 0; i--) {
buffer[i] = (byte) (timeStep & 0xFF);
timeStep >>= 8;
}
byte[] hash = mac.doFinal(buffer);
// 动态截取
int offset = hash[hash.length - 1] & 0x0F;
int binary =
((hash[offset] & 0x7F) << 24)
| ((hash[offset + 1] & 0xFF) << 16)
| ((hash[offset + 2] & 0xFF) << 8)
| (hash[offset + 3] & 0xFF);
// 取模生成6位数字
return binary % this.modDivisor;
}
@Override
public boolean verifyHOTP(Key key, long timeStep, int inputOTP) throws Exception {
int hotpNumber = generateOneTimePassword(key, timeStep);
//String hotpString = String.format("%0" + HOTP_Length + "d", hotpNumber);
return hotpNumber==inputOTP;
}
}
打印10次密码,每次sleep10秒,可以看到30秒内动态密码相同。
HOTP和TOTP算法安全性分析
前面我们分析了OTP算法中有两个参数没有用,T 和 s。这两个参数对安全有重要的作用。
官方协议在这里给出了5点安全要求,其中第一点是协议本身的要求,理论上进行约束,我们主要关心另外4点,分别是 HOTP
的验证,限制访问参数,重新同步参数以及共享密钥的管理。
对于二步验证的安全问题实际上就是如何保证第二步验证尽可能不被攻击的前提下向用户提供更方便的服务。
通过下图我们可以详细的了解 HOTP 的验证过程,同时还可以了解参数 s 和 T 的用途。
如果用户严格按照生成一次 OTP,然后验证一次的话,服务器直接可以验证成功。因为算法将会输入相同的参数。
如果用户无意间多生成了若干次 OTP 但是没有用来验证,服务器和客户端就产生差异,这时候服务器端会自动累积计数器来尝试匹配用户输入的OTP,但是这种尝试是有限制的,这也就是前面说到的参数 s 的作用。一旦服务器尝试 s 次仍未匹配成功,那么就会通知客户端需要重新生成验证来验证,只要验证成功。
协议中对于参数 s 给出的建议是:服务器建议设置该参数,但是在保证可用性的前提下,尽可能小。
另外还可以要求用户输入一个 HOTP序列(比如连续生成多个 OTP 发送到服务器进行验证)来进行重新同步计数器。既然涉及到重试,服务器同样对重试次数有所限制,从而防止暴力破解。这里当用户重试次数超过了阈值 T,服务器就应该将该账号标记为有安全风险,需要提醒用户。
协议中对个参数 T 给出的建议是:同样 T也不建议设的很大。
另外还提供了另外一个基于延时的策略还防止暴力破解攻击,服务器设置一个惩罚时间,一旦用户验证失败,需要在惩罚时间乘以失败次数的时间内禁止用户重新尝试验证。这个延时需要跨不同的登陆会话,以防止并发的攻击。
Google Authenticator
在 Google Authenticator 的开源项目的 README 里有明确提到:https://github.com/google/google-authenticator
These implementations support the HMAC-Based One-time Password (HOTP) algorithm specified in RFC 4226 and the Time-based One-time Password (TOTP) algorithm specified in RFC 6238.
也就是说,至此,我们也明白了,其实 Google Authenticator 算法核心也是 HOTP 以及 TOTP ,在明白了整个动态密码算法的核心之后,有没有一种豁然开朗的感觉呢?既知其然,又知其所以然了,对吧?每次看着应用定时生成密码,