공공 서비스로 Google OTP를 사용할 수 있습니까?


답변:


121

프로젝트는 오픈 소스이다. 나는 그것을 사용하지 않았습니다. 그러나 문서화 된 알고리즘 (오픈 소스 프로젝트 페이지에 나열된 RFC에 명시되어 있음)을 사용하고 있으며 인증 자 구현은 여러 계정을 지원합니다.

실제 과정은 간단합니다. 일회성 코드는 본질적으로 의사 난수 생성기입니다. 난수 생성기는 일단 시드 또는 시작 번호가 주어지면 난수 스트림을 계속 생성하는 공식입니다. 시드가 주어지면 숫자는 서로 무작위 일 수 있지만 시퀀스 자체는 결정적입니다. 따라서 장치와 서버가 "동기화"되면 장치가 생성하는 난수는 "다음 숫자 버튼"을 누를 때마다 서버가 예상하는 것과 동일한 임의의 숫자입니다.

안전한 일회용 암호 시스템은 난수 생성기보다 정교하지만 개념은 유사합니다. 장치와 서버의 동기화를 유지하는 데 도움이되는 다른 세부 정보도 있습니다.

따라서 OAuth와 같이 다른 사람이 인증을 호스팅 할 필요가 없습니다. 대신 Google이 휴대 기기에 제공하는 앱과 호환되는 알고리즘을 구현해야합니다. 이 소프트웨어는 오픈 소스 프로젝트에서 사용 가능해야합니다.

정교함에 따라이 프로세스의 서버 측을 구현하는 데 필요한 모든 것이 OSS 프로젝트와 RFC를 제공해야합니다. 서버 소프트웨어 (PHP, Java, .NET 등)에 대한 특정 구현이 있는지 모르겠습니다.

그러나 특히이를 처리하기 위해 오프 사이트 서비스가 필요하지 않습니다.


3
반면에, 많은 다른 모바일 장치에서 사용 가능하고 이미 잘 알려져 있고 쉽게 구할 수있는 솔루션을 사용하면 큰 이점이 있습니다 ... (힌트 힌트)
simpleuser

26
당신은 SMS를 의미합니까? 느리고 신뢰할 수 없으며 비용이 많이 듭니다.
Achraf Almouloudi 2019

순수 자바 웹 사이트에 Google OTP / RFC6238 호환 2fa를 구현하는 방법에 대해 블로그를 작성했습니다. asaph.org/2016/04/google-authenticator-2fa-java.html(shameless plug)
Asaph

2
FYI NIST는 더 이상 2016 년 8 월부터 SMS를 사용한 2 단계 인증을 권장하지 않습니다. 비용이 안전하지 않은 것으로 간주되는 비용은 신경 쓰지 마십시오.
TheDPQ

57

알고리즘은 RFC6238에 문서화되어 있습니다 . 다음과 같이 조금갑니다 :

  • 서버는 사용자에게 Google OTP에 설치할 수있는 비밀을 제공합니다. Google은 여기에 설명 된 QR 코드로이 작업을 수행합니다 .
  • Google Authenticator는 Unix 시간의 SHA1-HMAC 및 비밀 번호 (6 개의 숫자 코드)를 생성합니다 (RFC에서 이에 대한 자세한 정보 표시).
  • 서버는 또한 6 자리 코드를 확인하기위한 비밀 / 유닉스 시간을 알고 있습니다.

나는 자바 스크립트에서 알고리즘을 구현 한 플레이를 보았습니다 : http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/


20

PHP를위한 다양한 라이브러리 (LAMP 스택)가 있습니다

PHP

https://code.google.com/p/ga4php/

http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/

2 단계 인증을 구현할 때는 서버와 클라이언트의 시계가 동기화되고 토큰에 대한 무차별 대입 공격으로부터 보호되고 사용 된 초기 시드의 크기가 적절해야합니다.


내용은 훌륭했지만 첫 번째 링크를 사용하는 사람은 잠재적 인 결함이 있으므로 SQL 삽입 방지 방법을 구현해야합니다. 첫 번째 문제에 대해 제기 된 문제를 살펴보십시오. 두 번째 링크는 완벽합니다.
Septronic

9

내 질문에 대한 답변으로 게시 된 내 솔루션을 사용할 수 있습니다 ( 전체 Python 코드설명이 있습니다 )

Python에서 Google OTP 구현

PHP 또는 Perl로 구현하는 것이 다소 쉽다고 생각합니다. 이것에 문제가 있으면 알려주십시오.

또한 GitHub 에 Python 모듈로 코드를 게시했습니다 .


1
사실 조금 후에 ... CPAN에 Perl 모듈이 있다고 언급하여 후속 조치를 원했습니다. Auth :: GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator ).
DavidO



3

예, 네트워크 서비스가 필요하지 않습니다. Google OTP 앱은 Google 서버와 통신하지 않으므로 시간이 지남에 따라 서버가 생성하는 QR 코드에서 휴대 전화에 입력하는 초기 비밀과 동기화됩니다.


2

LAMP는 아니지만 C #을 사용하면 이것이 사용하는 코드입니다.

원래 코드 :

https://github.com/kspearrin/Otp.NET

Base32Encoding 클래스는 다음과 같습니다.

https://stackoverflow.com/a/7135008/3850405

프로그램 예 :

class Program
{
    static void Main(string[] args)
    {
        var bytes = Base32Encoding.ToBytes("JBSWY3DPEHPK3PXP");

        var totp = new Totp(bytes);

        var result = totp.ComputeTotp();
        var remainingTime = totp.RemainingSeconds();
    }
}

Totp :

public class Totp
{
    const long unixEpochTicks = 621355968000000000L;

    const long ticksToSeconds = 10000000L;

    private const int step = 30;

    private const int totpSize = 6;

    private byte[] key;

    public Totp(byte[] secretKey)
    {
        key = secretKey;
    }

    public string ComputeTotp()
    {
        var window = CalculateTimeStepFromTimestamp(DateTime.UtcNow);

        var data = GetBigEndianBytes(window);

        var hmac = new HMACSHA1();
        hmac.Key = key;
        var hmacComputedHash = hmac.ComputeHash(data);

        int offset = hmacComputedHash[hmacComputedHash.Length - 1] & 0x0F;
        var otp = (hmacComputedHash[offset] & 0x7f) << 24
               | (hmacComputedHash[offset + 1] & 0xff) << 16
               | (hmacComputedHash[offset + 2] & 0xff) << 8
               | (hmacComputedHash[offset + 3] & 0xff) % 1000000;

        var result = Digits(otp, totpSize);

        return result;
    }

    public int RemainingSeconds()
    {
        return step - (int)(((DateTime.UtcNow.Ticks - unixEpochTicks) / ticksToSeconds) % step);
    }

    private byte[] GetBigEndianBytes(long input)
    {
        // Since .net uses little endian numbers, we need to reverse the byte order to get big endian.
        var data = BitConverter.GetBytes(input);
        Array.Reverse(data);
        return data;
    }

    private long CalculateTimeStepFromTimestamp(DateTime timestamp)
    {
        var unixTimestamp = (timestamp.Ticks - unixEpochTicks) / ticksToSeconds;
        var window = unixTimestamp / (long)step;
        return window;
    }

    private string Digits(long input, int digitCount)
    {
        var truncatedValue = ((int)input % (int)Math.Pow(10, digitCount));
        return truncatedValue.ToString().PadLeft(digitCount, '0');
    }

}

인코딩 :

public static class Base32Encoding
{
    public static byte[] ToBytes(string input)
    {
        if (string.IsNullOrEmpty(input))
        {
            throw new ArgumentNullException("input");
        }

        input = input.TrimEnd('='); //remove padding characters
        int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
        byte[] returnArray = new byte[byteCount];

        byte curByte = 0, bitsRemaining = 8;
        int mask = 0, arrayIndex = 0;

        foreach (char c in input)
        {
            int cValue = CharToValue(c);

            if (bitsRemaining > 5)
            {
                mask = cValue << (bitsRemaining - 5);
                curByte = (byte)(curByte | mask);
                bitsRemaining -= 5;
            }
            else
            {
                mask = cValue >> (5 - bitsRemaining);
                curByte = (byte)(curByte | mask);
                returnArray[arrayIndex++] = curByte;
                curByte = (byte)(cValue << (3 + bitsRemaining));
                bitsRemaining += 3;
            }
        }

        //if we didn't end with a full byte
        if (arrayIndex != byteCount)
        {
            returnArray[arrayIndex] = curByte;
        }

        return returnArray;
    }

    public static string ToString(byte[] input)
    {
        if (input == null || input.Length == 0)
        {
            throw new ArgumentNullException("input");
        }

        int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
        char[] returnArray = new char[charCount];

        byte nextChar = 0, bitsRemaining = 5;
        int arrayIndex = 0;

        foreach (byte b in input)
        {
            nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
            returnArray[arrayIndex++] = ValueToChar(nextChar);

            if (bitsRemaining < 4)
            {
                nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
                returnArray[arrayIndex++] = ValueToChar(nextChar);
                bitsRemaining += 5;
            }

            bitsRemaining -= 3;
            nextChar = (byte)((b << bitsRemaining) & 31);
        }

        //if we didn't end with a full char
        if (arrayIndex != charCount)
        {
            returnArray[arrayIndex++] = ValueToChar(nextChar);
            while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
        }

        return new string(returnArray);
    }

    private static int CharToValue(char c)
    {
        int value = (int)c;

        //65-90 == uppercase letters
        if (value < 91 && value > 64)
        {
            return value - 65;
        }
        //50-55 == numbers 2-7
        if (value < 56 && value > 49)
        {
            return value - 24;
        }
        //97-122 == lowercase letters
        if (value < 123 && value > 96)
        {
            return value - 97;
        }

        throw new ArgumentException("Character is not a Base32 character.", "c");
    }

    private static char ValueToChar(byte b)
    {
        if (b < 26)
        {
            return (char)(b + 65);
        }

        if (b < 32)
        {
            return (char)(b + 24);
        }

        throw new ArgumentException("Byte is not a value Base32 value.", "b");
    }

}


-1

C # 사용자의 경우이 간단한 콘솔 앱을 실행하여 일회용 토큰 코드를 확인하는 방법을 이해하십시오. Nuget 패키지에서 라이브러리 Otp.Net 을 먼저 설치해야합니다 .

static string secretKey = "JBSWY3DPEHPK3PXP"; //add this key to your Google Authenticator app  

private static void Main(string[] args)
{
        var bytes = Base32Encoding.ToBytes(secretKey);

        var totp = new Totp(bytes);

        while (true)
        {
            Console.Write("Enter your code from Google Authenticator app: ");
            string userCode = Console.ReadLine();

            //Generate one time token code
            string tokenInApp = totp.ComputeTotp();
            int remainingSeconds = totp.RemainingSeconds();

            if (userCode.Equals(tokenInApp)
                && remainingSeconds > 0)
            {
                Console.WriteLine("Success!");
            }
            else
            {
                Console.WriteLine("Failed. Try again!");
            }
        }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.