首页 > 编程语言 >java和c#里的TOTP统一算法

java和c#里的TOTP统一算法

时间:2023-10-16 10:11:40浏览次数:42  
标签:SHA1 java c# SHA TOTP 密钥 哈希 HMAC

基础说明

本文根据 RFC4226 和 RFC6238 文档,详细的介绍 HOTP 和 TOTP 算法的原理和实现。
两步验证已经被广泛应用于各种互联网应用当中,用来提供安全性。对于如何使用两步验证,大家并不陌生,无非是开启两步验证,然后出现一个二维码,使用支持两步验证的移动应用比如 Google Authenticator 或者 LassPass Authenticator 扫一下二维码。这时候应用会出现一个6位数的一次性密码,首次需要输入验证从而完成开启过程。以后在登陆的时候,除了输入用户名和密码外,还需要把当前的移动应用上显示的6位数编码输入才能完成登陆。
这个过程的背后主要由两个算法来支撑:HOTP 和 TOTP。也分别对应着两份 RFC 协议 RFC4266 和 RFC6238。前者是 HOTP 的标准,后者是 TOTP 的标准。本文将使用图文并茂的方式详细介绍 HOTP 和 TOTP 的算法原理,并在最后分析其安全性。当然所有内容都是基于协议的,通过自己的理解更加直观的表达出来。

为了确保在不同语言下生成相同的 TOTP 结果,你需要确保使用相同的密钥和相同的时间戳步长。同时,你还需要确保使用相同的哈希算法(通常是 HMAC-SHA1 或 HMAC-SHA256)。

参数解释

  • base32Key: 这是生成totp数字时的共享密钥
  • timeStep: 这是时间步长,作用是每隔多长时间(秒),你的totp数字变化一次,即一个totp数字的失效时间
  • digits: 这是生成多少位的totp数字
  • HMAC-SHA1: 是一种基于哈希函数的消息认证码算法,用于保护数据完整性和身份验证

HMAC-SHA1

HMAC-SHA1(Hash-based Message Authentication Code with SHA-1)是一种基于哈希函数的消息认证码算法,用于保护数据完整性和身份验证。它结合了两个主要的技术:哈希函数(SHA-1)和密钥(Key)。

下面是 HMAC-SHA1 算法的工作原理和说明:

  1. 输入数据:HMAC-SHA1 接受两个输入:消息数据(Message)和密钥(Key)。消息数据可以是任意长度的二进制数据。

  2. 密钥填充:如果密钥的长度小于哈希函数的块大小,HMAC-SHA1 会将密钥填充到相应的块大小,通常使用 0x00 字节。

  3. 内部填充:将密钥与常数 0x36 做异或操作,然后将结果与消息数据连接起来。

  4. 哈希计算:对连接后的数据进行 SHA-1 哈希计算。SHA-1 生成一个固定长度(160位或20字节)的哈希值。

  5. 外部填充:将密钥与常数 0x5C 做异或操作,然后将结果与内部哈希值连接起来。

  6. 二次哈希计算:对连接后的数据进行 SHA-1 哈希计算。这一次的哈希计算包括了内部哈希值和密钥。

  7. 结果:HMAC-SHA1 的最终结果是SHA-1 哈希的输出。

HMAC-SHA1 的主要目的是确保数据的完整性和身份验证。由于它需要密钥,因此只有知道密钥的实体才能生成正确的 HMAC 值。这使得 HMAC-SHA1 在加密通信和身份验证中非常有用,例如在数字签名、认证协议(如OAuth)、以及一次性密码算法(如TOTP和HOTP)中广泛使用。

需要注意的是,SHA-1 已经不再被视为安全的哈希算法,因为它存在碰撞漏洞。因此,安全敏感的应用程序应该使用更强大的哈希算法,如SHA-256或SHA-3,来代替 SHA-1。如果可能,也应该使用更安全的 HMAC 变种,如HMAC-SHA-256。

核心代码

以下是一个 Java 和 C# 中可以生成相同 TOTP 结果的示例代码,使用的是 HMAC-SHA1 哈希算法和 Joda-Time 库来处理时间:

Java 示例:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base32;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

public class TOTPGenerator {
    public static String generateTOTP(String base32Key, int timeStep, int digits) throws Exception {
        long counter = (System.currentTimeMillis() / 1000) / timeStep;
        byte[] key = new Base32().decode(base32Key);
        
        SecretKeySpec secretKey = new SecretKeySpec(key, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(secretKey);
        
        byte[] counterBytes = new byte[8];
        for (int i = 0; i < 8; i++) {
            counterBytes[7 - i] = (byte) (counter >> (8 * i));
        }
        
        byte[] hash = mac.doFinal(counterBytes);
        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, digits);
        return String.format("%0" + digits + "d", otp);
    }
}

C# 示例:

using System;
using System.Security.Cryptography;
using System.Text;

public class TOTPGenerator
{
    public static string GenerateTOTP(string base32Key, int timeStep, int digits)
    {
        long counter = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds / timeStep;
        byte[] key = Base32Decode(base32Key);

        using (HMACSHA1 hmac = new HMACSHA1(key))
        {
            byte[] counterBytes = BitConverter.GetBytes(counter);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(counterBytes);
            }

            byte[] hash = hmac.ComputeHash(counterBytes);
            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, digits);
            return otp.ToString($"D{digits}");
        }
    }

    private static byte[] Base32Decode(string base32)
    {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
        var bits = base32.ToUpper().ToCharArray().Select(c => Convert.ToString(chars.IndexOf(c), 2).PadLeft(5, '0')).Aggregate((a, b) => a + b);
        return Enumerable.Range(0, bits.Length / 8).Select(i => Convert.ToByte(bits.Substring(i * 8, 8), 2)).ToArray();
    }
}

这两个示例使用了相同的密钥(base32Key)、时间戳步长(timeStep)和位数(digits),并使用相同的 HMAC-SHA1 哈希算法来生成 TOTP。确保在实际应用中提供相同的参数值,你将能够生成相同的 TOTP 结果。

测试代码

// C#
string totp = GenerateTOTP("pkulaw", 30, 8);
Console.WriteLine("Current TOTP:" + totp);
// 结果:30396996

// java
String totp=generateTOTP("pkulaw", 30, 8);
System.out.println(totp);
// 结果:30396996

上面的两种语言的测试代码,在30秒之内(一般使用UTC时间计算), 产生的totp码是相同的。

标签:SHA1,java,c#,SHA,TOTP,密钥,哈希,HMAC
From: https://www.cnblogs.com/lori/p/17766768.html

相关文章

  • 【转】第一篇-linux中socket通信
     转,原文:https://zhuanlan.zhihu.com/p/628583834---------------- 客户端和服务端是如何通信的在linux中客户端和服务端通信的流程如图所示:流程分析:服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户......
  • 什么???CSS也能原子化!
    1.什么是原子化CSS?AtomicCSSistheapproachtoCSSarchitecturethatfavorssmall,single-purposeclasseswithnamesbasedonvisualfunction.Let’sDefineExactlyWhatAtomicCSSis上文的意思翻译过来就是原子化CSS是一种CSS的架构方法,倾向于使用用途单一且......
  • Programming abstractions in C阅读笔记:p179-p180
    《ProgrammingAbstractionsInC》学习第60天,p179-p180总结。一、技术总结1.palindrome(回文)(1)包含单个字符的字符串(如"a"),或者空字符串(如"")也是回文。(2)示例:“level”、"noon"。2.predicatefunction(1)predicate的意思pre-("forth")+*deik-("show"),“t......
  • 安装centos可以联网
    一问题VMvare中安装了centOS,连不了外网,解决办法如下:二解决1.打开文件cat/etc/sysconfig/network-scripts/ifcfg-ens33vi/etc/sysconfig/network-scripts/ifcfg-ens33 2.修改最后一行,使网卡随操作系统启动 3.重新加载网卡systemctlrestartnetwork ......
  • C#固定时间间隔触发的计数器
    在运行需要很长时间完成的任务时,一般需要定期展示当前处理进度,比如批量文件复制时,显示复制的文件数和总传输字节数,这时候就需要用计数器对处理任务的完成度进行统计,并以一定时间间隔(如500ms)显示当前统计结果。实现上述功能需要使用一些内部变量来保存当前进度:longlast......
  • web前端html+css页面内容的六种隐藏方式
    一、使用透明度语法:opacity:0注意:元素消失,但是还会占据空间,只是视觉看不出来<style>.box{width:100px;height:100px;background-color:aquamarine;opacity:0;}</style><divclass="box"></div> 二、使用display语法:display:none注意:元素消失,不会占据空间<style......
  • 题解 AcWing 1272. 与众不同
    题目描述定义完美序列:若一个序列内没有重复的数,称这个数列为完美数列。每次给定一个区间\([l,r]\),求这个区间内最长的完美序列长度。具体思路设\(len_i\)表示从\(i\)出发往右的最长完美序列长度。我们定义一个指针\(st\),表示当前枚举的区间左端点,同时定义多一个指针\(......
  • 【RocketMQ】RocketMQ 5.0新特性(三)- Controller模式
    在RocketMQ5.0以前,有两种集群部署模式,分别为主从模式(Master-Slave模式)和Dledger模式。主从模式主从模式中分为Master和Slave两个角色,集群中可以有多个Master节点,一个Master节点可以有多个Slave节点。Master节点负责接收生产者发送的写入请求,将消息写入CommitLog文件,Slave节点会......
  • PAT_A1070 Mooncake
    MooncakeisaChinesebakeryproducttraditionallyeatenduringtheMid-AutumnFestival.Manytypesoffillingsandcrustscanbefoundintraditionalmooncakesaccordingtotheregion'sculture.Nowgiventheinventoryamountsandthepricesofall......
  • -source 1.5 中不支持方法引用,请使用 -source 8 或更高版本以启用方法引用”的解决方
    https://www.codenong.com/js0e0b40bd9ba3/问题描述:最近在本地打包,时不时遇到如下的报错信息,修改eclipse的Javacompiler和javabuildpath中版本号为1.8,依然未解决  jdk版本过低的报错.png解决方案:1、pom.xml文件增加配置: 12345 <properties>  <pro......