Java & C# implementation of TOTP: Time-Based One-Time Password Algorithm
/* * Copyright (c) 2018-2019 yingtingxu(徐应庭). All rights reserved. */ package com.arch.totp; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; /** * The implementation of TOTP: Time-Based One-Time Password Algorithm * <p> * see: * TOTP: https://tools.ietf.org/html/rfc6238 */ public class Totp { /** * TOTP supported hash algorithms */ public enum HashAlgorithm { HmacSHA1("HmacSHA1"), HmacSHA256("HmacSHA256"), HmacSHA512("HmacSHA512"); private String name; HashAlgorithm(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return getName(); } } //region public default setting // default hash algorithm public static final HashAlgorithm DEFAULT_HASH_ALGORITHM = HashAlgorithm.HmacSHA1; // default time step in seconds public static final int DEFAULT_TIME_STEP = 30; // default number of digits public static final int DEFAULT_DIGITS = 8; // T0 is the Unix time to start counting time steps // (default value is 0, i.e., the Unix epoch) public static final int DEFAULT_T0 = 0; //endregion //region private members private static final int[] DIGITS_POWER // 0 1 2 3 4 5 6 7 8 = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000}; private int timeStep; private int t0; private int digits; private HashAlgorithm hashAlgorithm; private Totp() { this.timeStep = DEFAULT_TIME_STEP; this.t0 = DEFAULT_T0; this.digits = DEFAULT_DIGITS; this.hashAlgorithm = DEFAULT_HASH_ALGORITHM; } //endregion //region builder methods public static Totp withDefault() { return new Totp(); } public Totp timeStep(int timeStep) { this.timeStep = timeStep; return this; } public Totp epoch(int t0) { this.t0 = t0; return this; } public Totp algorithm(HashAlgorithm algorithm) { this.hashAlgorithm = algorithm; return this; } public Totp digits(int digits) { this.digits = digits; return this; } //endregion /** * This method generates a TOTP value for the given secret. * * @param secret: the shared secret, HEX encoded * @return: a numeric String in base 10 that includes truncated digits */ public String generateTotp(String secret) { return generateTotp(secret, getCurrentTimeStepNumber()); } /** * This method generates a TOTP value for the given secret. * * @param secret: the shared secret, HEX encoded * @param timeStepNumber: the number of time step between the initial time T0 and the current unix time. * @return: a numeric String in base 10 that includes truncated digits */ private String generateTotp(String secret, long timeStepNumber) { Assert.hasText(secret, "secret cannot be null or empty"); // Using the counter // First 8 bytes are for the movingFactor // Compliant with base RFC 4226 (HOTP) byte[] timeStepBytes = ByteBuffer.allocate(8) .putLong(timeStepNumber) .array(); byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8); // the output would be a 20 byte long byte[] hmacHash = getHmacSHA(secretBytes, timeStepBytes); // put selected bytes into result int int offset = hmacHash[hmacHash.length - 1] & 0xf; int truncatedHash = ((hmacHash[offset] & 0x7f) << 24) | ((hmacHash[offset + 1] & 0xff) << 16) | ((hmacHash[offset + 2] & 0xff) << 8) | (hmacHash[offset + 3] & 0xff); int otp = truncatedHash % DIGITS_POWER[digits]; String result = Integer.toString(otp); while (result.length() < digits) { result = "0" + result; } return result; } /** * Validates the TOTP for the specified secret * * @param secret: the shared secret, HEX encoded * @param totp: the TOTP generated by the secret * @return {@code true} if the TOTP is valid */ public boolean validateTotp(String secret, String totp) { if (!StringUtils.hasText(secret) || !StringUtils.hasText(totp)) { return false; } // Allow a variance of no greater than 90 seconds in either direction long timeStepNumber = getCurrentTimeStepNumber(); for (int i = -2; i <= 2; i++) { String totp2 = generateTotp(secret, timeStepNumber + i); if (totp.equals(totp2)) { return true; } } // No match return false; } private long getCurrentTimeStepNumber() { return (System.currentTimeMillis() / 1000L - t0) / timeStep; } /** * This method uses the JCE to provide the crypto algorithm. * HMAC computes a Hashed Message Authentication Code with the * crypto hash algorithm as a parameter. * * @param secretBytes: the bytes to use for the HMAC key * @param textBytes: the message or text to be authenticated */ private byte[] getHmacSHA(byte[] secretBytes, byte[] textBytes) { try { Mac hmac = Mac.getInstance(hashAlgorithm.getName()); SecretKeySpec spec = new SecretKeySpec(secretBytes, "RAW"); hmac.init(spec); return hmac.doFinal(textBytes); } catch (GeneralSecurityException gse) { throw new IllegalStateException(gse); } } }
// Copyright (c) 2018-2019 yingtingxu(徐应庭). All rights reserved. using System; using System.Diagnostics; using System.Net; using System.Security.Cryptography; using System.Text; namespace Arch.Core { /// <summary> /// https://tools.ietf.org/html/rfc6238 /// </summary> public static class Totp { private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static TimeSpan _timestep = TimeSpan.FromSeconds(30); private static readonly Encoding _encoding = new UTF8Encoding(false, true); private static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumber, string modifier) { // # of 0's = length of pin const int Mod = 1000000; // See https://tools.ietf.org/html/rfc4226 // We can add an optional modifier var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber)); var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier)); // Generate DT string var offset = hash[hash.Length - 1] & 0xf; Debug.Assert(offset + 4 < hash.Length); var binaryCode = (hash[offset] & 0x7f) << 24 | (hash[offset + 1] & 0xff) << 16 | (hash[offset + 2] & 0xff) << 8 | (hash[offset + 3] & 0xff); return binaryCode % Mod; } private static byte[] ApplyModifier(byte[] input, string modifier) { if (string.IsNullOrEmpty(modifier)) { return input; } var modifierBytes = _encoding.GetBytes(modifier); var combined = new byte[checked(input.Length + modifierBytes.Length)]; Buffer.BlockCopy(input, 0, combined, 0, input.Length); Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length); return combined; } // More info: https://tools.ietf.org/html/rfc6238#section-4 private static ulong GetCurrentTimeStepNumber() { var delta = DateTime.UtcNow - _unixEpoch; return (ulong)(delta.Ticks / _timestep.Ticks); } /// <summary> /// Generates TOTP for the specified <paramref name="securityToken"/>. /// </summary> /// <param name="securityToken">The security token to generate TOTP.</param> /// <param name="modifier">The modifier.</param> /// <returns>The generated code.</returns> public static int GenerateTotp(byte[] securityToken, string modifier = null) { if (securityToken == null) { throw new ArgumentNullException(nameof(securityToken)); } // Allow a variance of no greater than 90 seconds in either direction var currentTimeStep = GetCurrentTimeStepNumber(); using (var hashAlgorithm = new HMACSHA1(securityToken)) { return ComputeTotp(hashAlgorithm, currentTimeStep, modifier); } } /// <summary> /// Validates the TOTP for the specified <paramref name="securityToken"/>. /// </summary> /// <param name="securityToken">The security token for verifying.</param> /// <param name="code">The TOTP to validate.</param> /// <param name="modifier">The modifier</param> /// <returns><c>True</c> if validate succeed, otherwise, <c>false</c>.</returns> public static bool ValidateTotp(byte[] securityToken, int code, string modifier = null) { if (securityToken == null) { throw new ArgumentNullException(nameof(securityToken)); } // Allow a variance of no greater than 90 seconds in either direction var currentTimeStep = GetCurrentTimeStepNumber(); using (var hashAlgorithm = new HMACSHA1(securityToken)) { for (var i = -2; i <= 2; i++) { var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier); if (computedTotp == code) { return true; } } } // No match return false; } /// <summary> /// Generates TOTP for the specified <paramref name="securityToken"/>. /// </summary> /// <param name="securityToken">The security token to generate code.</param> /// <param name="modifier">The modifier.</param> /// <returns>The generated code.</returns> public static int GenerateTotp(string securityToken, string modifier = null) => GenerateCode(Encoding.Unicode.GetBytes(securityToken), modifier); /// <summary> /// Validates the TOTP for the specified <paramref name="securityToken"/>. /// </summary> /// <param name="securityToken">The security token for verifying.</param> /// <param name="code">The code to validate.</param> /// <param name="modifier">The modifier</param> /// <returns><c>True</c> if validate succeed, otherwise, <c>false</c>.</returns> public static bool ValidateTotp(string securityToken, int code, string modifier = null) => ValidateCode(Encoding.Unicode.GetBytes(securityToken), code, modifier); } }
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4