导读:本期聚焦于星河创作的《Android一次性密码OTP是什么?如何实现短信验证码与动态口令功能》,敬请观看详情。手机收到的那条六位数字验证码,背后其实是一套完整的一次性密码机制。本文从OTP的基本概念讲起,先厘清HOTP与TOTP两种主流算法的区别,再结合Android平台的实际开发场景,介绍如何接收短信验证码并自动填充、如何用Google Authenticator风格的TOTP生成动态口令,以及本地验证的完整流程。文中还会分析OTP的安全隐患,比如重放攻击、验证码轰炸和SIM卡劫持等问题,并给出对应的防护建议。无论你是要做登录验证还是支付二次确认,这篇文章都能帮你把OTP的原理和落地代码一次搞清楚。

一次性密码(One-time Password,简称OTP)指的是只能使用一次、有效期很短的动态验证码,广泛应用于登录验证、支付确认、敏感操作二次认证等场景。相比固定密码,OTP最大的优势在于即使被窃取,攻击者也无法重复利用。在Android开发中,实现OTP功能主要涉及两条路线:一是基于短信下发验证码并由服务端校验,二是基于时间同步算法(TOTP)在客户端本地生成动态口令。这篇文章会从算法原理讲到具体代码,帮助你完整理解并落地OTP方案。

Android一次性密码OTP是什么?如何实现短信验证码与动态口令功能

OTP的两种核心算法:HOTP与TOTP

谈到一次性密码,绕不开RFC 4226定义的HOTP(基于计数器的OTP)和RFC 6238定义的TOTP(基于时间的OTP)。两者都使用相同的核心运算——HMAC,区别只在于动态因子的来源。HOTP使用一个单调递增的计数器作为因子,每次生成密码后计数器加一,客户端与服务端必须保持计数器同步;TOTP则用当前时间戳除以时间步长(通常是30秒)得到的时间片作为因子,天然解决了同步问题,这也是Google Authenticator等验证器采用TOTP的原因。

生成过程可以概括为几个步骤:先用共享密钥和动态因子做HMAC运算得到一串字节,再从这串字节中按固定偏移取出4个字节,转换成一个整数,最后对这个整数取模得到指定位数的数字(一般是6位)。下面是一个Java实现的TOTP生成器,可以直接用在Android项目中。

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class TotpGenerator {

    private static final String ALGORITHM = "HmacSHA1";
    private static final long TIME_STEP = 30L; // 时间步长30秒
    private static final int CODE_DIGITS = 6;

    public static String generateTotp(byte[] secretKey) {
        try {
            // 用当前时间计算时间片,等价于HOTP的计数器
            long timeSlice = System.currentTimeMillis() / 1000L / TIME_STEP;
            byte[] counter = new byte[8];
            for (int i = 7; i >= 0; i--) {
                counter[i] = (byte) (timeSlice & 0xFF);
                timeSlice >>= 8;
            }
            Mac mac = Mac.getInstance(ALGORITHM);
            mac.init(new SecretKeySpec(secretKey, ALGORITHM));
            byte[] hash = mac.doFinal(counter);

            // 动态截取:取最后一个字节的低4位作为偏移量
            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);

            int otp = binary % (int) Math.pow(10, CODE_DIGITS);
            return String.format("%0" + CODE_DIGITS + "d", otp);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException("TOTP生成失败", e);
        }
    }
}

需要特别注意的是取模时的高位字节要和0x7F做与运算,否则可能产生负数导致验证码异常。另外时间因素依赖设备时钟,如果用户手动调慢了系统时间,验证就会失败,实际项目中应该提示用户开启自动时间同步,或在验证失败时引导检查时间设置。

Android接收短信验证码并自动填充

短信验证码是另一种常见形态:服务端生成随机码,通过短信网关下发到用户手机,用户输入后由服务端比对。这种方式对用户来说门槛最低,但体验上多了一步手动输入。Android提供了SMS Retriever API,可以在不申请短信读取权限的情况下,监听应用发出的验证码短信并自动提取,大幅提升填充体验。

使用SMS Retriever的流程是:客户端调用SmsRetrieverClient启动监听,服务端下发的短信末尾必须附加一段11位的哈希签名(由应用包名和证书计算得出),系统验证签名匹配后,通过广播把短信正文回传给应用。应用拿到正文后用正则提取数字即可自动填入输入框。

// 启动SMS Retriever监听(需依赖 com.google.android.gms:play-services-auth-api-phone)
SmsRetrieverClient client = SmsRetriever.getClient(context);
Task<Void> task = client.startSmsRetriever();
task.addOnSuccessListener(aVoid -> {
    // 监听启动成功,等待短信广播
});
task.addOnFailureListener(e -> {
    // 启动失败,降级为手动输入
});

// 注册广播接收器提取验证码
BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String message = intent.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE);
        if (message != null) {
            // 用正则提取4到8位数字验证码
            java.util.regex.Matcher matcher =
                java.util.regex.Pattern.compile("\\b(\\d{4,8})\\b").matcher(message);
            if (matcher.find()) {
                String code = matcher.group(1);
                otpInput.setText(code);
                // 可以在这里自动触发提交
            }
        }
    }
};
context.registerReceiver(receiver,
        new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION));

这种方案最大的好处是完全绕开了RECEIVE_SMS权限。自从Google Play对危险权限收紧政策后,普通应用申请短信权限几乎无法过审,SMS Retriever成了唯一合规的自动化路径。它的限制是短信有效期一般只有5分钟,监听窗口最长也是5分钟,超时后需要重新启动监听。

服务端这边也要做好配套:验证码生成后存入Redis等缓存并设置过期时间,一般建议60到120秒有效,验证成功或失败达到上限后立即作废。同一个手机号的发送频率要做限制,比如60秒内只能发一次、一天最多发10次,否则很容易被刷接口造成短信费用损失。

OTP的安全隐患与防护实践

OTP并不是万无一失的。首先是重放攻击:如果服务端不把已使用的验证码标记失效,攻击者截获后在有效期内重放依然能通过验证。所以验证码必须一次性消费,用完即删,可以用Redis的getdel命令保证原子性。

其次是验证码轰炸问题。攻击者恶意调用发送接口,导致用户手机在短时间内收到大量短信,既骚扰用户又消耗短信费。除了频率限制,还可以引入图形验证码或行为验证作为发送前置条件,并在同一手机号存在未过期验证码时复用旧码而非重发。

针对短信通道,还有SIM卡劫持和短信嗅探的风险。攻击者通过社工手段补办受害者SIM卡,或利用伪基站截获短信。这也是为什么银行类应用倾向于把短信验证和TOTP动态口令、设备指纹结合起来做多重因子,而不是单靠短信一道防线。

最后给出服务端校验TOTP时的一个实用细节:由于网络延迟和时钟偏差,建议允许前后各一个时间窗口的验证码通过,也就是同时校验当前、前一个和后一个时间片生成的三个码,只要任意一个匹配即认为有效。这在几乎不损失安全性的前提下,显著降低了因时钟偏差导致的验证失败率。

总结

在Android上落地一次性密码,短信验证码适合面向大众用户的低门槛场景,配合SMS Retriever可以实现免权限自动填充;TOTP动态口令则更适合安全性要求较高的场景,无需依赖短信通道,离线可用。两者并非互斥,成熟的做法是根据业务敏感程度组合使用,同时严格落实验证码一次性消费、频率限制、时间窗口容忍这些细节。把算法原理和防护措施都理解到位,OTP才能真正发挥它相比静态密码的安全价值。

Android OTP一次性密码短信验证码修改时间:2026-09-08 09:55:06

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260908/52693.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。