美文网首页
TOTP 及 Google 身份验证器原理

TOTP 及 Google 身份验证器原理

作者: MarcusMa | 来源:发表于2018-07-14 11:54 被阅读0次

    TOTP

    参考标准

    源码

    
    // This CODE from RFC4226
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.lang.reflect.UndeclaredThrowableException;
    import java.math.BigInteger;
    import java.security.GeneralSecurityException;
    
    public class TOTP {
    
        private TOTP() {}
    
        /**
         * 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 crypto: the crypto algorithm (HmacSHA1, HmacSHA256,
         *                             HmacSHA512)
         * @param keyBytes: the bytes to use for the HMAC key
         * @param text: the message or text to be authenticated
         */
    
        private static byte[] hmac_sha(String crypto, byte[] keyBytes,
                                       byte[] text){
            try {
                Mac hmac;
                hmac = Mac.getInstance(crypto);
                SecretKeySpec macKey =
                        new SecretKeySpec(keyBytes, "RAW");
                hmac.init(macKey);
                return hmac.doFinal(text);
            } catch (GeneralSecurityException gse) {
                throw new UndeclaredThrowableException(gse);
            }
        }
    
    
        /**
         * This method converts a HEX string to Byte[]
         *
         * @param hex: the HEX string
         *
         * @return: a byte array
         */
    
        private static byte[] hexStr2Bytes(String hex){
            // Adding one byte to get the right conversion
            // Values starting with "0" can be converted
            byte[] bArray = new BigInteger("10" + hex,16).toByteArray();
    
            // Copy all the REAL bytes, not the "first"
            byte[] ret = new byte[bArray.length - 1];
            for (int i = 0; i < ret.length; i++)
                ret[i] = bArray[i+1];
            return ret;
        }
    
        private static final int[] DIGITS_POWER
                // 0 1  2   3    4     5      6       7        8
                = {1,10,100,1000,10000,100000,1000000,10000000,100000000 };
    
    
        /**
         * This method generates a TOTP value for the given
         * set of parameters.
         *
         * @param key: the shared secret, HEX encoded
         * @param time: a value that reflects a time
         * @param returnDigits: number of digits to return
         *
         * @return: a numeric String in base 10 that includes
         *              {@link truncationDigits} digits
         */
    
        public static String generateTOTP(String key,
                                          String time,
                                          String returnDigits){
            return generateTOTP(key, time, returnDigits, "HmacSHA1");
        }
    
    
    
        /**
         * This method generates a TOTP value for the given
         * set of parameters.
         *
         * @param key: the shared secret, HEX encoded
         * @param time: a value that reflects a time
         * @param returnDigits: number of digits to return
         *
         * @return: a numeric String in base 10 that includes
         *              {@link truncationDigits} digits
         */
    
        public static String generateTOTP256(String key,
                                             String time,
                                             String returnDigits){
            return generateTOTP(key, time, returnDigits, "HmacSHA256");
        }
    
        /**
         * This method generates a TOTP value for the given
         * set of parameters.
         *
         * @param key: the shared secret, HEX encoded
         * @param time: a value that reflects a time
         * @param returnDigits: number of digits to return
         *
         * @return: a numeric String in base 10 that includes
         *              {@link truncationDigits} digits
         */
    
        public static String generateTOTP512(String key,
                                             String time,
                                             String returnDigits){
            return generateTOTP(key, time, returnDigits, "HmacSHA512");
        }
    
    
        /**
         * This method generates a TOTP value for the given
         * set of parameters.
         *
         * @param key: the shared secret, HEX encoded
         * @param time: a value that reflects a time
         * @param returnDigits: number of digits to return
         * @param crypto: the crypto function to use
         *
         * @return: a numeric String in base 10 that includes
         *              {@link truncationDigits} digits
         */
    
        public static String generateTOTP(String key,
                                          String time,
                                          String returnDigits,
                                          String crypto){
            int codeDigits = Integer.decode(returnDigits).intValue();
            String result = null;
    
            // Using the counter
            // First 8 bytes are for the movingFactor
            // Compliant with base RFC 4226 (HOTP)
            while (time.length() < 16 )
                time = "0" + time;
    
            // Get the HEX in a Byte[]
            byte[] msg = hexStr2Bytes(time);
            byte[] k = hexStr2Bytes(key);
    
            byte[] hash = hmac_sha(crypto, k, msg);
    
            // put selected bytes into result int
            int offset = hash[hash.length - 1] & 0xf;
    
            int binary =
                    ((hash[offset] & 0x7f) << 24) |
                            ((hash[offset + 1] & 0xff) << 16) |
                            ((hash[offset + 2] & 0xff) << 8) |
                            (hash[offset + 3] & 0xff);
    
            int otp = binary % DIGITS_POWER[codeDigits];
    
            result = Integer.toString(otp);
            while (result.length() < codeDigits) {
                result = "0" + result;
            }
            return result;
        }
    
    }
    
    // This CODE from RFC6238
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    
    public class OneTimePasswordAlgorithm {
        private OneTimePasswordAlgorithm() {
        }
    
        // These are used to calculate the check-sum digits.
        //                                0  1  2  3  4  5  6  7  8  9
        private static final int[] doubleDigits =
                {0, 2, 4, 6, 8, 1, 3, 5, 7, 9};
    
        /**
         * Calculates the checksum using the credit card algorithm.
         * This algorithm has the advantage that it detects any single
         * mistyped digit and any single transposition of
         * adjacent digits.
         *
         * @param num    the number to calculate the checksum for
         * @param digits number of significant places in the number
         * @return the checksum of num
         */
        public static int calcChecksum(long num, int digits) {
            boolean doubleDigit = true;
            int total = 0;
            while (0 < digits--) {
                int digit = (int) (num % 10);
                num /= 10;
                if (doubleDigit) {
                    digit = doubleDigits[digit];
                }
                total += digit;
                doubleDigit = !doubleDigit;
            }
            int result = total % 10;
            if (result > 0) {
                result = 10 - result;
            }
            return result;
        }
    
        /**
         * This method uses the JCE to provide the HMAC-SHA-1
         * * algorithm.
         * HMAC computes a Hashed Message Authentication Code and
         * in this case SHA1 is the hash algorithm used.
         *
         * @param keyBytes the bytes to use for the HMAC-SHA-1 key
         * @param text     the message or text to be authenticated.
         * @throws NoSuchAlgorithmException if no provider makes
         *                                  either HmacSHA1 or HMAC-SHA-1
         *                                  digest algorithms available.
         * @throws InvalidKeyException      The secret provided was not a valid HMAC-SHA-1 key.
         */
    
        public static byte[] hmac_sha1(byte[] keyBytes, byte[] text)
                throws NoSuchAlgorithmException, InvalidKeyException {
            //        try {
            Mac hmacSha1;
            try {
                hmacSha1 = Mac.getInstance("HmacSHA1");
            } catch (NoSuchAlgorithmException nsae) {
                hmacSha1 = Mac.getInstance("HMAC-SHA-1");
            }
            SecretKeySpec macKey =
                    new SecretKeySpec(keyBytes, "RAW");
            hmacSha1.init(macKey);
            return hmacSha1.doFinal(text);
            //        } catch (GeneralSecurityException gse) {
            //            throw new UndeclaredThrowableException(gse);
            //        }
        }
    
        private static final int[] DIGITS_POWER
                // 0 1  2   3    4     5      6       7        8
                = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
    
        /**
         * This method generates an OTP value for the given
         * set of parameters.
         *
         * @param secret           the shared secret
         * @param movingFactor     the counter, time, or other value that
         *                         changes on a per use basis.
         * @param codeDigits       the number of digits in the OTP, not
         *                         including the checksum, if any.
         * @param addChecksum      a flag that indicates if a checksum digit
         *                         *                     should be appended to the OTP.
         * @param truncationOffset the offset into the MAC result to
         *                         begin truncation.  If this value is out of
         *                         the range of 0 ... 15, then dynamic
         *                         truncation  will be used.
         *                         Dynamic truncation is when the last 4
         *                         bits of the last byte of the MAC are
         *                         used to determine the start offset.
         * @return A numeric String in base 10 that includes
         * {@link codeDigits} digits plus the optional checksum
         * digit if requested.
         * @throws NoSuchAlgorithmException if no provider makes
         *                                  either HmacSHA1 or HMAC-SHA-1
         *                                  digest algorithms available.
         * @throws InvalidKeyException      The secret provided was not
         *                                  a valid HMAC-SHA-1 key.
         */
        static public String generateOTP(byte[] secret,
                                         long movingFactor,
                                         int codeDigits,
                                         boolean addChecksum,
                                         int truncationOffset)
                throws NoSuchAlgorithmException, InvalidKeyException {
            // put movingFactor value into text byte array
            String result = null;
            int digits = addChecksum ? (codeDigits + 1) : codeDigits;
            byte[] text = new byte[8];
            for (int i = text.length - 1; i >= 0; i--) {
                text[i] = (byte) (movingFactor & 0xff);
                movingFactor >>= 8;
            }
    
            // compute hmac hash
            byte[] hash = hmac_sha1(secret, text);
    
            // put selected bytes into result int
            int offset = hash[hash.length - 1] & 0xf;
            if ((0 <= truncationOffset) &&
                    (truncationOffset < (hash.length - 4))) {
                offset = truncationOffset;
            }
            int binary =
                    ((hash[offset] & 0x7f) << 24)
                            | ((hash[offset + 1] & 0xff) << 16)
                            | ((hash[offset + 2] & 0xff) << 8)
                            | (hash[offset + 3] & 0xff);
    
            int otp = binary % DIGITS_POWER[codeDigits];
            if (addChecksum) {
                otp = (otp * 10) + calcChecksum(otp, codeDigits);
            }
            result = Integer.toString(otp);
            while (result.length() < digits) {
                result = "0" + result;
            }
            return result;
        }
    }
    
    

    Google 身份验证器App 及其原理

    开源地址

    https://github.com/google/google-authenticator-android

    支持模式

    • TOTP 基于时间
    • HOTP 基于计数器

    TOTP 模式要点

    1. 基本参数

    • HMAC算法使用固定HmacSHA1
    • Token更新时长固定30
    • 生成token的seed(byte[]类型)长度必须大于20(低版本的app要求大于10)
    • 外界输入Token数据维度只有两个:账户名称seed的Base32格式

    2. 激活模式

    2.1 扫描二维码模式:

    二维码识别后的字符串满足如下Uri格式:

    • Uri的 Scheme: otpauth (必须)
    • Uri的 Authority:totphotp(必须)
    • 账户名称:在Authority后/ 到?直接的部分 (必须)
    • 查询字段:键secret, 值Base32后的seed的内容,seed长度大于20bytes(必须)
    • 例如

    otpauth://totp/test@test.com?secret=DJK6HG7PI45HCEBC

    2.2 手动输入方式:

    输入的密钥必须是Base23后的seed的内容

    相关文章

      网友评论

          本文标题:TOTP 及 Google 身份验证器原理

          本文链接:https://www.haomeiwen.com/subject/monepftx.html