加密与安全_HOTP一次性密码生成算法

文章目录

  • HOTP 的基础原理
  • HOTP 的工作流程
  • HOTP 的应用场景
  • HOTP 的安全性
  • 安全性增强措施
  • Code
    • 生成HOTP
    • 可配置项
    • 校验HOTP
    • 可拓展功能
      • 计数器(counter)
        • 计数器在客户端和服务端的作用
        • 计数器的同步机制
        • 客户端和服务端中的计数器表现
        • 服务端如何处理计数器不同步
        • 计数器在客户端和服务端的举例
        • 如何在 Java 实现中体现计数器
        • 小结
    • 一个服务端程序应对多个客户端
      • 关键问题
      • 解决方案
        • 1. 计数器的存储和管理
        • 2. 服务端管理多个客户端计数器的架构
        • 3. 具体实现步骤
        • 4. Java 示例代码
      • 关键点解释
      • 进一步优化
      • 小结
  • 计数器容错机制
  • 验证失败常见原因
    • 1. 计数器不同步
    • 2. 密钥不匹配
    • 3. 编码问题
    • 4. 生成 OTP 时计数器没有递增
    • 5. 输入 OTP 错误
    • 解决方案

在这里插入图片描述

HOTP 的基础原理

HOTP 是基于 HMAC(Hash-based Message Authentication Code)算法的一种一次性密码生成机制。其核心思想是通过计数器的变化和共享密钥生成一次性密码。每次使用时,计数器递增,因此每个密码只能使用一次。 它遵循 RFC 4226 标准。

核心组件

  • 共享密钥(K):服务器和客户端事先约定并保存的密钥。
  • 计数器(C):每生成一个一次性密码,计数器值增加,确保密码的唯一性。
  • HMAC 算法:使用 HMAC-SHA-1 或其他 HMAC 哈希算法,结合共享密钥和计数器生成动态密码。

生成公式

HOTP(K, C) = HMAC-SHA-1(K, C) mod 10^6

其中:

  • K 是共享密钥。
  • C 是计数器。
  • 输出值取前 6 位数字(或更多,取决于配置),通常为 6 位数字密码。

HOTP 的工作流程

HOTP 的密码生成和验证基于计数器的增量。具体步骤如下:

  • 密码生成

    1. 客户端和服务器预先共享一个密钥 K
    2. 每次生成密码时,客户端使用当前计数器值 C 和密钥 K 计算 HMAC 值。
    3. 从 HMAC 结果中截取 6 位或 8 位数字,生成一次性密码。
  • 密码验证

    1. 服务器接收到客户端的密码后,使用相同的共享密钥 K 和当前计数器值 C 生成 HMAC 值。
    2. 如果生成的 HMAC 值与客户端提供的密码匹配,认证成功,服务器递增计数器 C
    3. 服务器通常允许一定的容错窗口(如 ±2 个计数器值),以防止由于计数器不同步导致的验证失败。

HOTP 的应用场景

HOTP 广泛应用于需要基于事件或计数器的系统中,典型场景包括:

  • 硬件令牌:银行、企业安全系统等早期使用的物理设备,用户通过令牌生成动态密码。
  • 基于事件的身份验证系统:每当发生某些特定事件(如用户发起登录请求或支付请求)时,系统使用 HOTP 生成密码。
  • 离线环境:由于 HOTP 不依赖时间,因此在网络连接不稳定或设备无法持续联网的场景下尤为适用。

HOTP 的安全性

优势

  • 基于事件驱动:HOTP 的密码生成依赖于计数器,用户可以在不依赖时间同步的情况下生成一次性密码,适用于网络不稳定或离线操作场景。
  • 兼容性强:HOTP 算法简单,易于实现,且支持广泛的设备和系统。
  • 无时间同步问题:由于它基于计数器而非时间,客户端和服务器之间无需保持时间同步。

潜在问题

  • 密码有效期较长:HOTP 密码在未使用前一直有效,因此可能被攻击者截取后重放。这一点使得它在安全性方面较 TOTP 弱。
  • 计数器同步问题:客户端和服务器的计数器需要同步。如果用户多次生成密码但没有使用,则可能导致计数器不同步。为此,服务器通常允许一个容错窗口,但这也可能被攻击者利用来猜测计数器的值。
  • 有限容错窗口可能被滥用:服务器在验证时允许的容错窗口可能导致暴力破解攻击,即攻击者尝试多个计数器值,直到找到有效的密码。

安全性增强措施

  • 设置较短的密码有效期:在服务端设置较短的密码有效期,确保未使用的 HOTP 密码快速失效。
  • 配合其他身份验证手段:与二次身份验证或生物识别等方法结合使用,防止密码被重放攻击或暴力破解。
  • 动态调整容错窗口:服务器可以根据用户行为动态调整容错窗口的大小,以减少密码被暴力破解的风险。

Code

生成HOTP

基于 HMAC-SHA-256 算法生成一次性密码(OTP)。

我们使用了 javax.crypto 包中的 HMAC 相关类来实现 HMAC-SHA-356 算法,并生成 6 位的 OTP。

package com.artisan.otp.hotp;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;

/**
 * @author 小工匠
 * @version 1.0
 * @date 2024/10/1 21:22
 * @mark: show me the code , change the world
 */

public class HOTP {

    // 生成HOTP码
    public static String generateHOTP(String key, long counter, int digits) throws Exception {
        // 将key转换为字节数组
        byte[] keyBytes = hexStr2Bytes(key);

        // 计算HMAC-SHA-1
        byte[] counterBytes = longToBytes(counter);
        byte[] hmacResult = hmacSHA1(keyBytes, counterBytes);

        // 获取动态截取码(Dynamic Truncation)
        int otp = dynamicTruncate(hmacResult) % (int) Math.pow(10, digits);

        // 格式化OTP为固定长度,不足位数用0填充
        return String.format("%0" + digits + "d", otp);
    }

    // 使用HmacSHA256生成hash
    private static byte[] hmacSHA1(byte[] key, byte[] counter) throws Exception {
        // HmacSHA1   HmacSHA256
        SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(signKey);
        return mac.doFinal(counter);
    }

    // 动态截取(Dynamic Truncation)
    private static int dynamicTruncate(byte[] hmacResult) {
        // 取HMAC结果的最后字节的低4位作为偏移量
        int offset = hmacResult[hmacResult.length - 1] & 0xf;

        // 从偏移位置起,取4个字节组成一个31位的整数
        return ((hmacResult[offset] & 0x7f) << 24) |
                ((hmacResult[offset + 1] & 0xff) << 16) |
                ((hmacResult[offset + 2] & 0xff) << 8) |
                (hmacResult[offset + 3] & 0xff);
    }

    // 将long类型的计数器转换为字节数组(8字节)
    private static byte[] longToBytes(long value) {
        return BigInteger.valueOf(value).toByteArray();
    }

    // 将十六进制字符串转换为字节数组
    private static byte[] hexStr2Bytes(String hex) {
        byte[] bytes = new byte[hex.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
        }
        return bytes;
    }

    public static void main(String[] args) {
        try {
            // 测试HOTP生成
            String secret = "3132333435363738393031323334353637383930"; // 十六进制密钥
            long counter = 1; // 计数器
            int digits = 6; // OTP长度
            String hotp = generateHOTP(secret, counter, digits);
            System.out.println("Generated HOTP: " + hotp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码解释

  • 密钥 (key):密钥为一个十六进制字符串表示的共享密钥,转换为字节数组用于生成 HMAC。
  • 计数器 (counter):每次生成新的 OTP 时,计数器递增。这是 HOTP 的核心机制。
  • HMAC-SHA256:使用 javax.crypto.Mac 类生成 HMAC-SHA256哈希值。
  • 动态截取 (dynamicTruncate):HOTP 的最后步骤,根据哈希结果的偏移量提取 31 位的数值,然后对其取模来生成 6 位的一次性密码。

运行结果

main 方法中,使用一个示例密钥和计数器来生成 6 位的 HOTP。例如,如果你使用的计数器为 1,生成的 HOTP 可能是 638487

在这里插入图片描述

可配置项

  • 密钥长度:可以使用更长的密钥,建议密钥使用至少 160 位或 256 位的密钥(如 SHA-256 或更高)。
  • OTP 位数digits 参数允许生成 6 位、7 位或 8 位等不同长度的 OTP。

校验HOTP

为了验证 HOTP,我们需要客户端生成的 OTP 与服务器端生成的 OTP 一致。由于 HOTP 依赖于共享密钥和计数器,所以我们要确保在客户端和服务器端使用相同的密钥和计数器值。

验证 HOTP 的 Java 实现

在下面的实现中,服务器会接受用户输入的 OTP,并与使用相同计数器生成的 OTP 进行比较。如果 OTP 匹配,则验证成功。

package com.artisan.otp.hotp;
import java.util.Scanner;

public class HOTPVerifier2 {

    // 验证HOTP
    public static boolean verifyHOTP(String key, long counter, String inputOTP, int digits) throws Exception {
        // 生成服务器端的HOTP
        String serverHOTP = HOTP.generateHOTP(key, counter, digits);

        // 比较用户输入的OTP和服务器生成的HOTP
        return serverHOTP.equals(inputOTP);
    }

    public static void main(String[] args) {
        try {
            // 设置密钥和计数器
            String secret = "3132333435363738393031323334353637383930"; // 与客户端一致的十六进制密钥
            long counter = 1; // 当前计数器值
            int digits = 6; // OTP长度

            Scanner scanner = new Scanner(System.in);
            String inputOTP;
            boolean isValid;

            // 允许用户多次输入OTP进行验证
            while (true) {
                // 生成服务器端的HOTP
                String expectedHOTP = HOTP.generateHOTP(secret, counter, digits);
                System.out.println("服务器生成的 HOTP: " + expectedHOTP);

                // 提示用户输入OTP
                System.out.print("请输入 OTP(或输入 'exit' 退出):");
                inputOTP = scanner.nextLine();

                // 如果用户输入 'exit' 则退出程序
                if (inputOTP.equalsIgnoreCase("exit")) {
                    System.out.println("退出验证程序。");
                    break;
                }

                // 验证用户输入的OTP是否正确
                isValid = verifyHOTP(secret, counter, inputOTP, digits);
                if (isValid) {
                    System.out.println("验证成功,OTP正确!");
                    // 验证成功后增加计数器
                    counter++;
                } else {
                    System.out.println("验证失败,OTP不正确!");
                }

                System.out.println();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


  • verifyHOTP:该方法用于验证客户端生成的 OTP 是否与服务器生成的 OTP 相同。它会调用 HOTP.generateHOTP 方法生成服务器端的 OTP,然后将其与用户输入的 OTP 进行比较。
  • 密钥 (key):服务器和客户端必须使用相同的密钥。此密钥在初始化时由双方共享。
  • 计数器 (counter):计数器确保每次生成的 OTP 都是唯一的。每验证一次 OTP,计数器需要递增。
  • OTP 位数 (digits):定义 OTP 的位数,如 6 位、7 位或 8 位。

验证流程

  1. 生成服务器端的 OTP:服务器根据密钥和计数器生成一个 OTP。
  2. 用户输入 OTP:用户在客户端生成 OTP 后,在服务器端输入以进行验证。
  3. 服务器验证 OTP:服务器通过比较自己生成的 OTP 和用户输入的 OTP,决定是否验证成功。

在这里插入图片描述

服务器和客户端使用相同的计数器值和密钥,所以 OTP 匹配。如果输入的 OTP 错误,服务器会显示验证失败。


可拓展功能

  1. 计数器同步:在实际应用中,计数器同步可能出现问题。你可以实现一个容错机制,允许服务器在一定范围内(例如,±2个计数器)接受 OTP 。
  2. 安全提示:为了提升安全性,可以结合其他验证方式,比如 IP 白名单、二次验证或时间限制等。

计数器(counter)

在 HOTP(HMAC-based One Time Password)机制中,计数器(counter) 是保证 OTP(一次性密码)唯一性和安全性的重要组成部分。它在客户端和服务端都存在,并且双方必须保持同步。

计数器在客户端和服务端的作用
  • 客户端:每次用户请求生成 OTP 时,客户端都会使用当前的计数器值与共享密钥,通过 HMAC-SHA-256 算法生成一次性密码(OTP)。客户端自己维护计数器,每生成一次 OTP,计数器可以递增。

  • 服务端:当服务端验证用户输入的 OTP 时,它也使用同样的密钥和计数器生成相同的 OTP。为了验证成功,服务端的计数器必须和客户端的计数器同步或相差在一个容忍窗口内(如±2)。每次验证成功后,服务端也需要递增计数器以准备下一次验证。

计数器的同步机制
  • 初始同步:客户端和服务端通常从一个初始值开始,比如 0 或 1。初始值在客户端和服务端协商时确定。由于计数器是由双方共享的,客户端和服务端在初次使用时保持一致。

  • 递增:每次生成 OTP 后,客户端和服务端的计数器都会递增。每当客户端生成一个 OTP 并输入,服务器验证后也递增计数器。这样,保证双方的计数器同步,并确保下一个 OTP 的生成不会重复。

客户端和服务端中的计数器表现
  1. 客户端计数器

    • 每当用户请求生成 OTP 时,客户端会读取当前计数器值,生成 OTP。
    • 在用户提交 OTP 后,客户端可以选择递增计数器以生成下一个 OTP。
    • 如果客户端的计数器与服务端的计数器同步,它们生成的 OTP 是相同的。
  2. 服务端计数器

    • 服务端在接收到用户提交的 OTP 时,会使用自己维护的计数器值生成 OTP,并将其与用户的 OTP 进行比较。
    • 如果 OTP 匹配,服务端验证成功,并递增计数器。
    • 如果 OTP 不匹配,服务端可以尝试在一定的计数器范围内(如 ±1 或 ±2)进行匹配,以防止客户端和服务端的计数器不同步。
服务端如何处理计数器不同步

由于某些原因(如客户端多次生成 OTP 而没有使用,网络延迟等),客户端和服务端的计数器可能会不同步。服务端可以通过以下策略处理这种情况:

  1. 容错窗口(window):服务端在验证 OTP 时,可以尝试在当前计数器值的基础上,向前或向后偏移几个计数器值。例如,服务端可以尝试使用 counter ± 1counter ± 2 的值进行 OTP 验证。这种方式允许计数器有一个容错范围,避免客户端和服务端不同步导致验证失败。

  2. 重同步机制:某些系统中,会通过一个重新同步流程来重新对齐客户端和服务端的计数器。例如,如果检测到计数器不同步,可以让客户端和服务端通过一个安全通道重新协商新的计数器值。

  3. 计数器更新:服务端在验证成功后,会更新自己的计数器值,使其与客户端保持同步。

计数器在客户端和服务端的举例
  • 客户端生成 OTP

    • 假设当前客户端的计数器为 5,密钥为 "secret", 生成 OTP 为 123456
    • 客户端显示 OTP 并递增计数器,计数器更新为 6。
  • 服务端验证 OTP

    • 服务端接收到 OTP 123456,当前计数器也是 5(与客户端同步)。
    • 服务端生成 OTP 123456 并验证成功,然后将计数器更新为 6。
  • 客户端下一次生成 OTP

    • 客户端使用计数器值 6 生成新的 OTP(例如,654321),提交给服务端。
    • 服务端的计数器也为 6,生成相同的 OTP 并验证成功,继续递增计数器。
如何在 Java 实现中体现计数器

在 Java 实现中,计数器可以作为一个持久化的变量,客户端和服务端都需要维护各自的计数器值。以下是计数器的处理流程:

// 假设客户端生成OTP时计数器值为5
long clientCounter = 5;
String clientHOTP = HOTP.generateHOTP(secret, clientCounter, digits); 
System.out.println("客户端生成的 OTP: " + clientHOTP);

// 假设服务端当前计数器值为5
long serverCounter = 5;
boolean isValid = HOTPVerifier.verifyHOTP(secret, serverCounter, clientHOTP, digits);
if (isValid) {
    System.out.println("验证成功,计数器同步!");
    // 递增服务端的计数器
    serverCounter++;
} else {
    System.out.println("验证失败,可能计数器不同步。");
}
小结
  • 客户端和服务端的计数器同步 是 HOTP 正常工作的核心。
  • 容错机制 允许一定范围内的计数器不同步,以提升用户体验。
  • 计数器持久化 是关键:客户端和服务端在每次生成或验证后都要更新并保存计数器的值。

一个服务端程序应对多个客户端

当一个服务端程序需要处理多个客户端的 HOTP 验证时,计数器(counter) 的管理变得更加复杂,因为每个客户端都会有自己的计数器,并且需要和服务端保持同步。为了确保 OTP 的唯一性和正确性,服务端必须为每个客户端维护独立的计数器,并正确更新计数器的状态。

关键问题

  1. 每个客户端都有独立的计数器:每个客户端的计数器必须单独管理,因为每个客户端的 OTP 会根据各自的计数器生成。
  2. 持久化计数器:服务端需要确保计数器在每次 OTP 验证后持久化,以避免计数器不同步的风险。如果服务重启或在不同会话间,需要从持久化存储中加载计数器。
  3. 并发控制:当多个客户端同时发起 OTP 请求时,服务端需要确保对同一个客户端的计数器读写操作是线程安全的,以防计数器状态被并发修改。

解决方案

1. 计数器的存储和管理

服务端可以为每个客户端使用独立的存储(如数据库、内存缓存等)来持久化和管理计数器。通常,可以通过客户端的唯一标识符(如用户 ID、设备 ID 等)来关联计数器。

  • 存储方式
    • 数据库:可以使用关系型数据库(如 MySQL、PostgreSQL)或 NoSQL 数据库(如 Redis、MongoDB)来存储每个客户端的计数器。
    • 内存缓存:在高性能环境中,使用内存缓存(如 Redis)存储计数器,以便快速访问和更新。
2. 服务端管理多个客户端计数器的架构

服务端需要为每个客户端分配唯一的计数器。以下是服务端如何处理多个客户端的示例架构:

  1. 客户端身份识别:服务端需要使用某种方式来识别每个客户端(例如,用户 ID 或设备 ID)。这样可以保证每个客户端有唯一的计数器。

  2. 计数器存储:每个客户端的计数器可以存储在数据库或缓存中,按客户端唯一 ID 来索引。

  3. 计数器读写的同步处理:在多线程或并发请求的场景下,确保对计数器的读写是线程安全的。可以使用锁机制来确保同一时刻只有一个请求在修改某个客户端的计数器。

3. 具体实现步骤
  1. 生成 OTP

    • 当客户端请求生成 OTP 时,服务端从数据库或缓存中读取该客户端的计数器,生成 OTP,并将计数器递增。
  2. 验证 OTP

    • 当客户端发送 OTP 给服务端验证时,服务端读取该客户端的计数器,并用相同的密钥生成 OTP。
    • 服务端还可以允许一定范围的容错窗口(如 counter ± 2)来应对客户端和服务端的计数器不同步问题。
    • 验证成功后,更新计数器并持久化。
4. Java 示例代码

以下是处理多个客户端的伪代码示例,展示了如何为每个客户端维护独立的计数器。

package com.artisan.otp.hotp;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class HOTPServer {

    // 使用ConcurrentHashMap存储每个客户端的计数器,key为客户端ID,value为计数器值
    private ConcurrentHashMap<String, Long> clientCounters = new ConcurrentHashMap<>();
    // 使用一个锁来保证对每个客户端计数器的线程安全访问
    private Lock lock = new ReentrantLock();

    // 模拟数据库存储的客户端密钥
    private ConcurrentHashMap<String, String> clientSecrets = new ConcurrentHashMap<>();

    public HOTPServer() {
        // 初始化假设有两个客户端,每个客户端都有一个共享密钥
        clientSecrets.put("client1", "3132333435363738393031323334353637383930");
        clientSecrets.put("client2", "3132333435363738393031323334353637383931");
        
        // 初始化计数器
        clientCounters.put("client1", 1L);
        clientCounters.put("client2", 1L);
    }

    // 生成OTP的函数,传入客户端ID
    public String generateOTP(String clientId) throws Exception {
        lock.lock(); // 锁定以确保计数器的安全访问
        try {
            // 获取客户端的计数器和密钥
            Long counter = clientCounters.get(clientId);
            String secret = clientSecrets.get(clientId);

            if (counter == null || secret == null) {
                throw new Exception("客户端未注册");
            }

            // 生成OTP
            String otp = HOTP.generateHOTP(secret, counter, 6);

            // 递增计数器并更新
            clientCounters.put(clientId, counter + 1);

            return otp;
        } finally {
            lock.unlock(); // 解锁
        }
    }

    // 验证OTP的函数
    public boolean verifyOTP(String clientId, String inputOTP) throws Exception {
        lock.lock();
        try {
            // 获取客户端的计数器和密钥
            Long counter = clientCounters.get(clientId);
            String secret = clientSecrets.get(clientId);

            if (counter == null || secret == null) {
                throw new Exception("客户端未注册");
            }

            // 生成服务器端的OTP
            String serverOTP = HOTP.generateHOTP(secret, counter, 6);

            // 验证客户端输入的OTP
            if (serverOTP.equals(inputOTP)) {
                // 验证成功后更新计数器
                clientCounters.put(clientId, counter + 1);
                return true;
            } else {
                return false;
            }
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        try {
            HOTPServer server = new HOTPServer();
            String clientId = "client1";

            // 服务端生成OTP
            String otp = server.generateOTP(clientId);
            System.out.println("生成的 OTP: " + otp);

            // 客户端提交OTP
            boolean isValid = server.verifyOTP(clientId, otp);
            System.out.println("验证结果: " + (isValid ? "成功" : "失败"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

关键点解释

  1. ConcurrentHashMap:使用 ConcurrentHashMap 存储每个客户端的计数器,这样可以在并发情况下安全地处理多个客户端的请求。

  2. 线程安全:使用 ReentrantLock 确保对计数器的读写是线程安全的。每当一个客户端请求生成或验证 OTP 时,服务端会锁定该客户端的计数器,防止并发修改。

  3. 独立管理多个客户端:通过客户端唯一的 ID(如 clientId),服务端可以独立管理每个客户端的计数器和密钥。

  4. 计数器递增:每次成功生成或验证 OTP 后,服务端都会递增相应客户端的计数器并持久化,以确保下次生成的 OTP 是唯一的。

进一步优化

  1. 计数器持久化:在生产环境中,计数器需要持久化到数据库或缓存中(如 Redis)以保证即使服务重启后,计数器也能正确恢复。

  2. 容错机制:可以允许服务端在计数器一定范围内(如 ±2)进行 OTP 验证,以应对客户端和服务端计数器不同步的问题。

  3. 并发优化:如果某个客户端有大量并发请求,可以进一步优化锁机制,使用更细粒度的锁或分布式锁来提升性能。

小结

  • 服务端需要为每个客户端单独维护计数器,计数器与客户端的身份信息关联。
  • 使用线程安全的数据结构和锁机制来确保并发情况下计数器的正确性。
  • 持久化计数器以避免重启服务后丢失计数器状态,并使用容错窗口来应对客户端与服务端的计数器不同步问题。

计数器容错机制

可以修改 verifyOTP 方法,允许服务端在一定范围内(例如 ±2)验证 OTP,以应对计数器不完全同步的问题:

public boolean verifyOTP(String clientId, String inputOTP) throws Exception {
    lock.lock();
    try {
        // 获取客户端的计数器和密钥
        Long counter = clientCounters.get(clientId);
        String secret = clientSecrets.get(clientId);

        if (counter == null || secret == null) {
            throw new Exception("客户端未注册");
        }

        // 尝试在一定范围内验证 OTP(容错窗口为 ±2)
        int window = 2; // 容错范围
        for (int i = -window; i <= window; i++) {
            // 使用当前计数器加上偏移量生成 OTP
            String serverOTP = HOTP.generateHOTP(secret, counter + i, 6);

            // 验证客户端输入的 OTP
            if (serverOTP.equals(inputOTP)) {
                // 验证成功后更新计数器
                clientCounters.put(clientId, counter + i + 1); // 更新到同步的计数器值
                return true;
            }
        }

        // 如果在窗口范围内都验证失败,则返回false
        return false;
    } finally {
        lock.unlock();
    }
}

验证失败常见原因

1. 计数器不同步

  • 在 HOTP 中,计数器在每次生成 OTP 后都会递增。为了保证 OTP 的有效性,服务端和客户端的计数器必须同步。
  • 如果在 OTP 生成和验证的过程中,服务端的计数器和客户端不一致,OTP 验证会失败。

2. 密钥不匹配

  • HOTP 基于 HMAC 算法,需要服务端和客户端共享同一个密钥。如果在代码中,客户端和服务端使用了不同的密钥,会导致生成的 OTP 不同,从而验证失败。

3. 编码问题

  • HOTP 的密钥需要经过 Base32 或 Base64 编码,有时密钥的编码格式可能不一致,导致 OTP 计算出现偏差。

4. 生成 OTP 时计数器没有递增

  • 如果客户端生成 OTP 后没有递增计数器,服务端在验证时会发现生成的 OTP 和当前计数器不匹配,从而验证失败。

5. 输入 OTP 错误

  • 如果用户输入的 OTP 有误(例如少了前导零,或者格式不对),即使客户端和服务端的密钥和计数器同步,也会导致验证失败。

解决方案

  1. 检查计数器同步:确保生成 OTP 时,客户端和服务端使用相同的计数器值。客户端的计数器每次生成 OTP 后应递增,而服务端在验证时也应同步递增。

  2. 检查密钥一致性:确保客户端和服务端使用相同的共享密钥,并且该密钥的编码一致(通常是 Base32)。

  3. 增加容错窗口:可以允许服务端在一定范围内(如 counter ± 1counter ± 2)接受 OTP,以应对计数器稍微不同步的情况。可以修改验证部分的逻辑,允许在一定范围内检查 OTP。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/887440.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据链路层 ——MAC

目录 MAC帧协议 mac地址 以太网帧格式 ARP协议 ARP报文格式​编辑 RARP 其他的网络服务或者协议 DNS ICMP协议 ping traceroute NAT技术 代理服务器 网络层负责规划转发路线&#xff0c;而链路层负责在网络节点之间的转发&#xff0c;也就是"一跳"的具体传输…

R语言的下载、安装及环境配置(RstudioVSCode)

0x01 R语言篇 一、软件介绍 R for Windows是一个免费的用于统计计算和统计制图的优秀工具&#xff0c;是R语言开发工具。它拥有数据存储和处理系统、数组运算工具&#xff08;其向量、矩阵运算方面功能尤其强大&#xff09;、完整连贯的统计分析工具、优秀的统计制图等功能。…

大厂笔试现已经禁用本地IDE怎么看

如果我说本来面试做题这种事情就是反人类你相信吗&#xff1f; 这个罪恶的源头就是 Google&#xff0c;说是为了选择高素质的计算机编程水平的人才&#xff0c;然后把面试就变成了考试&#xff0c;最大的受益者当然是印度人了。 当把一个考察过程变成标准化的考试过程&#x…

CTFshow 命令执行 web29~web36(正则匹配绕过)

目录 web29 方法一&#xff1a;include伪协议包含文件读取 方法二&#xff1a;写入文件 方法三&#xff1a;通识符 web30 方法一&#xff1a;filter伪协议文件包含读取 方法二&#xff1a;命令执行函数绕过 方法三&#xff1a;写入文件 web31 方法一&#xff1a;filter伪…

openEuler 24.03 (LTS) 部署 K8s(v1.31.1) 高可用集群(Kubespray Ansible 方式)

写在前面 实验需要一个 CNI 为 flannel 的 K8s 集群之前有一个 calico 的版本有些旧了,所以国庆部署了一个v1.31.1 版本 3 * master 5 * work时间关系直接用的工具 kubespray博文内容为部署过程以及一些躺坑分享需要科学上网理解不足小伙伴帮忙指正 &#x1f603;,生活加油 99…

用示波器测动态滞回线

大学物理&#xff08;下&#xff09;实验-中南民族大学通信工程2022级 手动逐个处理数据较为麻烦且还要绘图&#xff0c;故想到用pythonmatplotlib来计算结果并数据可视化。 代码实现 import matplotlib.pyplot as plt# 样品一磁化曲线 X [0, 0.2, 0.4, 0.6, 0.8, 1, 1.5, 2.…

macos安装mongodb

文章目录 说明安装和配置安装mongodb配置PATH变量 验证日志及数据存放目录 mac启动和关闭mongodb后台启动失败问题mongodb-compass(GUI) 说明 Homebrew core 列表目前已经将 MongoDB 移除,不再为其提供支持。但是使用国内镜像的brew还是可以安装的&#xff01;这里直接从官网下…

小阿轩yx-案例:jenkins部署Maven和NodeJS项目

小阿轩yx-案例&#xff1a;jenkins部署Maven和NodeJS项目 前言 在 Java 项目开发中&#xff0c;项目的编译、测试、打包等是比较繁琐的&#xff0c;属于重复劳动的工作&#xff0c;浪费人力和时间成本。以往开发项目时&#xff0c;程序员往往需要花较多的精力在引用 jar 包搭…

MOELoRA —— 多任务医学应用中的参数高效微调方法

人工智能咨询培训老师叶梓 转载标明出处 在医疗场景中&#xff0c;LLMs可以应用于多种不同的任务&#xff0c;如医生推荐、诊断预测、药物推荐、医学实体识别、临床报告生成等。这些任务的输入和输出差异很大&#xff0c;给统一模型的微调带来了挑战。而且LLMs的参数众多&…

CSS3渐变

一、线性渐变 通过background-image: linear-gradient(...)设置线性渐变 语法&#xff1a; linear-gradient(direction,color1,color2, . . ) direction&#xff1a;渐变方向&#xff0c;默认从上到下&#xff0c;可选值&#xff1a; 简单选取&#xff1a; ① to right&…

SpringBoot框架下的教育系统开发全解析

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理微服务在线教育系统的相关信息成为必然。开…

文章资讯职场话题网站源码整站资源自带2000+数据

介绍&#xff1a; 数据有点多&#xff0c;数据资源包比较大&#xff0c;压缩后还有250m左右。值钱的是数据&#xff0c;网站上传后直接可用&#xff0c;爽飞了 环境&#xff1a;NGINX1.18 mysql5.6 php7.2 代码下载

Jetson 开发系列:如何用GPU跑本地大模型?

最近刚入手一台 Jetson Ori Nano 开发板&#xff0c;前两篇把开发前的准备工作做了&#xff1a; 搭建本地环境&#xff1a;Jetson 开发系列&#xff1a;Orin Nano 开箱&#xff01;一款强大的嵌入式&物联网开发板管理音频设备&#xff1a;Jetson 开发系列&#xff1a;Linu…

使用Buildpacks构建Docker镜像

## 使用Buildpacks构建Docker镜像 ![](../assets/运维手册-Buildpacks-Buildpacks.io.png) ### Buildpacks简介 与Dockerfile相比&#xff0c;Buildpacks为构建应用程序提供了更高层次的抽象。具体来说&#xff0c;Buildpacks&#xff1a; * 提供一个平衡的控制&#xff0c;…

大佬,简单解释下“嵌入式软件开发”和“嵌入式硬件开发”的区别

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;首先&#xff0c;嵌入式硬…

【AIGC半月报】AIGC大模型启元:2024.10(上)

【AIGC半月报】AIGC大模型启元&#xff1a;2024.10&#xff08;上&#xff09; (1) YOLO11&#xff08;Ultralytics新作&#xff09; (1) YOLO11&#xff08;Ultralytics新作&#xff09; 2024.10.01 Ultralytics在 YOLO Vision 2024 活动上宣布发布其新的计算机视觉模型 YOLO…

无人机电力巡检:点亮电力巡检新视野!

一、无人机电力巡查的优势 提高巡检效率&#xff1a;无人机可以搭载高清摄像头、红外热像仪等先进设备&#xff0c;实时拍摄和传输图像&#xff0c;帮助巡检人员快速发现潜在问题&#xff0c;如电线破损、绝缘子污损、设备过热等&#xff0c;从而大大缩短了巡检周期。 降低人…

Vue+NestJS项目实操(图书管理后台)

一、项目搭建 前端基于vben进行二次开发 在Github下载vben框架&#xff0c;搜索vben即可 下载地址&#xff1a;https://github.com/vbenjs/vue-vben-admin 下载完成后&#xff0c;进行安装依赖&#xff0c;使用命令&#xff1a; // 下载依赖 pnpm install// 运行项目 pnpm …

开源的云平台有哪些?

开源云平台为用户提供了构建、管理和运行云基础设施及应用的能力&#xff0c;同时允许社区参与开发和改进。以下是一些知名的开源云平台&#xff1a; 1. OpenStack 简介&#xff1a;OpenStack&#xff1a;一个广泛使用的开源云平台&#xff0c;它由多个组件组成&#xff0c;提…

HTML+CSS - 表单交互(一)

1. 前言 ​​​​​​​ Web 表单是用于和用户交互的强大工具——其常用于收集用户数据和控制用户界面。 web 表单是用户和 web 站点或应用程序之间交互的主要内容之一。它们允许用户输入数据&#xff0c;大多数情况下会将数据发送到 web 服务器进行处理和存储 2. form标签 …