Apakah ada API publik untuk menggunakan Google Authenticator ( otentikasi dua faktor) pada aplikasi web yang berjalan sendiri (mis. LAMP stack)?
Apakah ada API publik untuk menggunakan Google Authenticator ( otentikasi dua faktor) pada aplikasi web yang berjalan sendiri (mis. LAMP stack)?
Jawaban:
The proyek open source. Saya belum menggunakannya. Tapi itu menggunakan algoritma yang didokumentasikan (dicatat dalam RFC yang tercantum pada halaman proyek sumber terbuka), dan implementasi authenticator mendukung banyak akun.
Proses yang sebenarnya mudah. Kode satu waktu, pada dasarnya, adalah generator angka acak semu. Penghasil bilangan acak adalah rumus yang pernah diberi seed, atau nomor awal, terus membuat aliran bilangan acak. Diberikan seed, sementara angka mungkin acak satu sama lain, urutannya sendiri bersifat deterministik. Jadi, setelah Anda memiliki perangkat dan server "dalam sinkronisasi" maka angka acak yang dibuat perangkat, setiap kali Anda menekan "tombol angka berikutnya", akan sama, acak, angka yang diharapkan server.
Sistem kata sandi satu kali yang aman lebih canggih daripada generator angka acak, tetapi konsepnya serupa. Ada juga detail lain untuk membantu menjaga perangkat dan server tetap sinkron.
Jadi, tidak perlu orang lain meng-host otentikasi, seperti, katakanlah OAuth. Alih-alih, Anda perlu menerapkan algoritma yang kompatibel dengan aplikasi yang disediakan Google untuk perangkat seluler. Perangkat lunak itu (harus) tersedia di proyek sumber terbuka.
Tergantung pada kecanggihan Anda, Anda harus memiliki semua yang Anda butuhkan untuk mengimplementasikan sisi server dari proses ini memberikan proyek OSS dan RFC. Saya tidak tahu apakah ada implementasi khusus untuk perangkat lunak server Anda (PHP, Java, .NET, dll.)
Tetapi, khususnya, Anda tidak perlu layanan luar kantor untuk menangani ini.
Algoritma ini didokumentasikan dalam RFC6238 . Agak seperti ini:
Saya sudah bermain menerapkan algoritma dalam javascript di sini: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/
Ada berbagai perpustakaan untuk PHP (The LAMP Stack)
PHP
https://code.google.com/p/ga4php/
http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/
Anda harus berhati-hati ketika menerapkan dua faktor auth, Anda perlu memastikan jam Anda di server dan klien disinkronkan, bahwa ada perlindungan terhadap serangan brute force pada token dan bahwa benih awal yang digunakan cukup besar.
Anda dapat menggunakan solusi saya , diposting sebagai jawaban untuk pertanyaan saya (ada kode Python lengkap dan penjelasan ):
Agak mudah untuk mengimplementasikannya dalam PHP atau Perl, saya pikir. Jika Anda memiliki masalah dengan ini, beri tahu saya.
Saya juga memposting kode saya di GitHub sebagai modul Python.
Saya menemukan ini: https://github.com/PHPGangsta/GoogleAuthenticator . Saya mengujinya dan bekerja dengan baik untuk saya.
Theres: https://www.gauthify.com yang menawarkannya sebagai layanan
Bukan LAMP tetapi jika Anda menggunakan C # ini adalah kode yang saya gunakan:
Kode berasal dari:
https://github.com/kspearrin/Otp.NET
Kelas Base32Encoding dari jawaban ini:
https://stackoverflow.com/a/7135008/3850405
Contoh program:
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");
}
}
Bagi mereka yang menggunakan Laravel, https://github.com/sitepoint-editors/google-laravel-2FA ini adalah cara yang bagus untuk menyelesaikan masalah ini.
Untuk pengguna C #, jalankan Aplikasi Konsol sederhana ini untuk memahami cara memverifikasi kode token satu kali. Perhatikan bahwa kita perlu menginstal pustaka Otp.Net dari paket Nuget terlebih dahulu.
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!");
}
}
}