Google Authenticator เป็นบริการสาธารณะหรือไม่


คำตอบ:


121

โครงการเปิดแหล่งที่มา ฉันไม่ได้ใช้มัน แต่มันใช้อัลกอริทึมที่ทำเป็นเอกสาร (ระบุไว้ใน RFC ที่แสดงรายการอยู่ในหน้าโครงการโอเพนซอร์ส) และการใช้งานตัวตรวจสอบความถูกต้องรองรับหลายบัญชี

กระบวนการจริงนั้นตรงไปตรงมา รหัสครั้งเดียวคือตัวสร้างตัวเลขสุ่มหลอก เครื่องกำเนิดตัวเลขสุ่มเป็นสูตรที่ครั้งหนึ่งได้รับเมล็ดหรือหมายเลขเริ่มต้นยังคงสร้างกระแสของตัวเลขสุ่ม ให้เมล็ดในขณะที่ตัวเลขอาจสุ่มกันลำดับนั้นเป็นแบบกำหนดขึ้น ดังนั้นเมื่อคุณมีอุปกรณ์และเซิร์ฟเวอร์ของคุณ "ซิงค์" แล้วตัวเลขสุ่มที่อุปกรณ์สร้างขึ้นทุกครั้งที่คุณกดปุ่ม "หมายเลขถัดไป" จะเป็นตัวเลขสุ่มที่เซิร์ฟเวอร์คาดหวัง

ระบบรหัสผ่านครั้งเดียวที่ปลอดภัยนั้นซับซ้อนกว่าตัวสร้างตัวเลขแบบสุ่ม แต่แนวคิดก็คล้ายกัน นอกจากนี้ยังมีรายละเอียดอื่น ๆ เพื่อช่วยให้อุปกรณ์และเซิร์ฟเวอร์ซิงค์กัน

ดังนั้นไม่จำเป็นต้องให้บุคคลอื่นเป็นเจ้าภาพในการตรวจสอบสิทธิ์เช่นพูด OAuth แต่คุณต้องใช้อัลกอริทึมนั้นที่เข้ากันได้กับแอพที่ Google จัดให้สำหรับอุปกรณ์มือถือ ซอฟต์แวร์ดังกล่าวมีอยู่ในโครงการโอเพ่นซอร์ส

ขึ้นอยู่กับความซับซ้อนของคุณคุณควรมีทุกสิ่งที่คุณต้องใช้ฝั่งเซิร์ฟเวอร์ของกระบวนการนี้ให้โครงการ OSS และ RFC ฉันไม่ทราบว่ามีการใช้งานเฉพาะสำหรับซอฟต์แวร์เซิร์ฟเวอร์ของคุณ (PHP, Java, .NET และอื่น ๆ )

แต่โดยเฉพาะคุณไม่ต้องการบริการนอกสถานที่เพื่อจัดการกับเรื่องนี้


3
ในทางกลับกันการใช้โซลูชันที่มีอยู่แล้วที่รู้จักกันดีและง่ายต่อการใช้งานบนอุปกรณ์พกพาที่แตกต่างกันนั้นมีประโยชน์มาก ... (คำใบ้คำใบ้)
simpleuser

26
คุณหมายถึง SMS มันช้าไม่น่าเชื่อถือและมีราคาแพง
Achraf Almouloudi

ฉัน blogged เกี่ยวกับวิธีการใช้ Google Authenticator / RFC6238 เข้ากันได้ 2fa สำหรับเว็บไซต์ใน java บริสุทธิ์: asaph.org/2016/04/google-authenticator-2fa-java.html (ปลั๊กไร้ยางอาย)
Asaph

2
FYI NIST ไม่แนะนำให้ใช้การรับรองความถูกต้องแบบสองปัจจัยอีกต่อไปโดยใช้ SMS ในเดือนสิงหาคม 2559 อย่ากังวลเรื่องค่าใช้จ่ายที่ไม่ปลอดภัย
TheDPQ

57

อัลกอริทึมการบันทึกไว้ในRFC6238 ไปแบบนี้:

  • เซิร์ฟเวอร์ของคุณให้ความลับแก่ผู้ใช้ในการติดตั้งลงใน Google Authenticator Google ทำเช่นนี้เป็นรหัส QR เอกสารที่นี่
  • Google Authenticator สร้างรหัส 6 หลักโดยจาก SHA1-HMAC ของเวลา Unix และความลับ (รายละเอียดเพิ่มเติมมากมายในเรื่องนี้ใน RFC)
  • เซิร์ฟเวอร์ยังรู้เวลาลับ / unix เพื่อตรวจสอบรหัส 6 หลัก

ฉันเคยเล่นการใช้อัลกอริทึมใน javascript ที่นี่: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/


20

มีไลบรารี่หลากหลายสำหรับ PHP (The LAMP Stack)

PHP

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

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

คุณควรระวังเมื่อใช้การตรวจสอบสองปัจจัยคุณต้องตรวจสอบให้แน่ใจว่านาฬิกาของคุณบนเซิร์ฟเวอร์และไคลเอนต์นั้นได้รับการซิงโครไนซ์ว่ามีการป้องกันการโจมตีด้วยโทเค็นแรง ๆ บนโทเค็น


เนื้อหานั้นยอดเยี่ยม แต่ใครก็ตามที่ใช้ลิงค์แรกควรใช้วิธีการป้องกันการฉีด SQL เนื่องจากมีข้อบกพร่องที่อาจเกิดขึ้น ดูประเด็นที่หยิบยกขึ้นมาสำหรับประเด็นแรก ลิงค์ที่สองนั้นสมบูรณ์แบบ
ก.ย.

9

คุณสามารถใช้วิธีการแก้ปัญหาของฉันโพสต์เป็นคำตอบสำหรับคำถามของฉัน (มีรหัสหลามเต็มและคำอธิบาย ):

การใช้งาน Google Authenticator ใน Python

ฉันคิดว่ามันค่อนข้างง่ายที่จะนำไปใช้ใน PHP หรือ Perl หากคุณมีปัญหาใด ๆ กับสิ่งนี้โปรดแจ้งให้เราทราบ

ฉันได้โพสต์รหัสของฉันบน GitHubเป็นโมดูล Python


1
หลังจากข้อเท็จจริงเล็กน้อย ... ฉันแค่ต้องการติดตามโดยกล่าวถึงว่ามีโมดูล Perl ใน CPAN: รับรองความถูกต้อง :: GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator )
DavidO

6

ฉันพบนี้: https://github.com/PHPGangsta/GoogleAuthenticator ฉันทดสอบและใช้งานได้ดีสำหรับฉัน


1
ฉันคิดว่าคุณจะชอบgithub.com/RobThree/TwoFactorAuth มันขึ้นอยู่กับห้องสมุดข้างต้น แต่เป็นการปรับปรุงอย่างมากด้วยคุณสมบัติเพิ่มเติมและเอกสารที่ชัดเจนยิ่งขึ้น
Ema4rl


3

ใช่ไม่จำเป็นต้องใช้บริการเครือข่ายเนื่องจากแอป Google Authenticator จะไม่สื่อสารกับเซิร์ฟเวอร์ 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');
    }

}

Base32Encoding:

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 # ให้เรียกใช้แอปคอนโซลแบบง่ายนี้เพื่อทำความเข้าใจวิธีตรวจสอบรหัสโทเค็นครั้งเดียว โปรดทราบว่าเราต้องติดตั้งไลบรารีOtp.Netจากแพ็คเกจ Nuget ก่อน

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.