Google Authenticator


Google Authenticator est un logiciel open-source basé sur l’authentification en deux étapes, développé par Google. Le logiciel fournit un nombre de 6 chiffres que l'utilisateur doit donner lors de son authentification, en plus de son pseudo et de son mot de passe. Développé à l'origine pour les services Google (comme Gmail), le logiciel permet de s'authentifier sur des services tiers tels que LastPass ou Dropbox.
source: WikiPedia

Ce système astucieux permet à partir d'un code secret communiqué une seule fois à l'utilisateur, d'obtenir un mot de passe qui change toutes les 30 secondes.
Il existe une version Android sur Google Play qui permet d'avoir ce mot de passe sur votre smartphone, mais il peut être intéressant d'avoir cette information sur votre bureau Windows, ou même de le calculer pour les besoins d'un programme.

Version Delphi


Voici un code Delphi compatible de la version 6 à XE5 pour calculer le mot de passe du moment à partir d'un code secret.

unit GoogleAuthenticator;

{
  Google Authenticator for Delphi (c)2014 by Execute SARL 
  http://www.execute.re

  Licensed under the GPL license.

  see http://en.wikipedia.org/wiki/Google_Authenticator

  v1.0 2014-01-15
  v1.1 2017-06-28 remove Windows dependency by Stephen Ball

}

interface

// ie: GoogleAuthenticatorCode('JBSWY3DPEHPK3PXP')

function GoogleAuthenticatorCode(const Secret: string): string;

implementation

uses
  // Windows, 
  System.DateUtils,
  SysUtils;

{ for Windows only
function UnixTime: Int64;
var
  SystemTime: TSystemTime;
begin
  GetSystemTime(SystemTime);
  with SystemTime do
    Result := Round((EncodeDate(wYear, wMonth, wDay) - UnixDateDelta + EncodeTime(wHour, wMinute, wSecond, wMilliseconds)) * SecsPerDay);
end;
}
function UnixTime: Int64;
var
  SystemTime : TDateTime;
  aYear, aMonth, aDay, aHour, aMinute, aSecond, aMilliseconds : Word;
begin
  SystemTime := TTimeZone.Local.ToUniversalTime(Now);
  DecodeDate(SystemTime, aYear, aMonth, aDay);
  DecodeTime(SystemTime, aHour, aMinute, aSecond, aMilliseconds);

  Result := Round((EncodeDate(aYear, aMonth, aDay) - UnixDateDelta + EncodeTime(aHour, aMinute, aSecond, aMilliseconds)) * SecsPerDay);
end;

type
  TBytes = array of Byte;

function Base32ToBin(const Str: string): TBytes;
const
  Base32Chars: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';  // RFC 4648/3548
var
  Index: Integer;
  Count: Integer;
  Bits : Integer;
  Val  : Integer;
  Ofs  : Integer;
begin
  Count := (5 * Length(Str)) div 8;
  SetLength(Result, Count);
  bits := 0;
  val  := 0;
  ofs  := 1;
  for Index := 0 to Count - 1 do
  begin
    while Bits < 8 do
    begin
      Val := (Val shl 5) or (Pos(UpCase(Str[ofs]), Base32Chars) - 1);
      Inc(Ofs);
      Inc(Bits, 5);
    end;
    Dec(Bits, 8);
    Result[Index] := Byte(Val shr Bits);
    Val := Val and ((1 shl Bits) - 1);
  end;
end;

function IntToBytes(Value, Len: Integer): TBytes;
var
  Index: Integer;
begin
  SetLength(Result, Len);
  for Index := Len - 1 downto 0 do
  begin
    Result[Index] := Byte(Value);
    Value := Value shr 8;
  end;
end;

type
  TSHA1Context = record
    Size : Integer;
    Hash : array[0..4] of Cardinal; // 20 bytes
    Index: Integer;
    Block: array[0..63] of Byte;
  end;

procedure SHA1Reset(var Context: TSHA1Context);
begin
  Context.Size := 0;
  Context.Hash[0] := $67452301;
  Context.Hash[1] := $EFCDAB89;
  Context.Hash[2] := $98BADCFE;
  Context.Hash[3] := $10325476;
  Context.Hash[4] := $C3D2E1F0;
  Context.Index := 0;
end;

function SHA1CircularShift(bits, data: Cardinal): Cardinal;
begin
  Result := (data shl bits) or (data shr (32 - bits));
end;

procedure SHA1ProcessBlock(var Context: TSHA1Context);
const
  K: array[0..3] of Cardinal = ($5A827999, $6ED9EBA1, $8F1BBCDC, $CA62C1D6);
var
  W: array[0..79] of Cardinal;
  t: Integer;
  index: Integer;
  A, B, C, D, E: Cardinal;
  temp: Cardinal;
begin
 // Initialize the first 16 words in the array W
  for t := 0 to 15 do begin
    index := 4 * t;
    W[t] := Context.Block[index] shl 24
      or Context.Block[index + 1] shl 16
      or Context.Block[index + 2] shl 8
      or Context.Block[index + 3];
  end;
  for t := 16 to 79 do begin
    W[t] := SHA1CircularShift(1, W[t - 3] xor W[t - 8] xor W[t - 14] xor W[t - 16]);
  end;
  A := Context.Hash[0];
  B := Context.Hash[1];
  C := Context.Hash[2];
  D := Context.Hash[3];
  E := Context.Hash[4];
  for t := 0 to 19 do begin
    temp := SHA1CircularShift(5, A) + ((B and C) or ((not B) and D)) + E + W[t] + K[0];
    E := D;
    D := C;
    C := SHA1CircularShift(30, B);
    B := A;
    A := temp;
  end;
  for t := 20 to 39 do begin
    temp := SHA1CircularShift(5, A) + (B xor C xor D) + E + W[t] + K[1];
    E := D;
    D := C;
    C := SHA1CircularShift(30, B);
    B := A;
    A := temp;
  end;
  for t := 40 to 59 do begin
    temp := SHA1CircularShift(5, A) + ((B and C) or (B and D) or (C and D)) + E + W[t] + K[2];
    E := D;
    D := C;
    C := SHA1CircularShift(30, B);
    B := A;
    A := temp;
  end;
  for t := 60 to 79 do begin
    temp := SHA1CircularShift(5, A) + (B xor C xor D) + E + W[t] + K[3];
    E := D;
    D := C;
    C := SHA1CircularShift(30, B);
    B := A;
    A := temp;
  end;
  Inc(Context.Hash[0], A);
  Inc(Context.Hash[1], B);
  Inc(Context.Hash[2], C);
  Inc(Context.Hash[3], D);
  Inc(Context.Hash[4], E);
  Context.Index := 0;
end;

procedure SHA1Input(var Context: TSHA1Context; const Data: TBytes);
var
  i: Integer;
begin
  for i := 0 to Length(Data) - 1 do
  begin
    Context.Block[Context.Index] := Data[i];
    Inc(Context.Size);
    Inc(Context.Index);
    if Context.Index = 64 then
      SHA1ProcessBlock(Context);
  end;
end;

procedure SHA1PadMessage(var Context: TSHA1Context);
var
  i: Integer;
begin
  i := Context.Index;
  Context.Block[i] := $80;
  Inc(i);
  if i > 56 then
  begin
    FillChar(Context.Block[i], 64 - i, 0);
    Context.Index := 64;
    SHA1ProcessBlock(Context);
    FillChar(Context.Block[0], 56, 0);
  end else begin
    FillChar(Context.Block[i], 56 - i, 0);
  end;
  Context.Index := 56;
 // Store the message length as the last 8 bytes
  Context.Block[56] := 0;
  Context.Block[57] := 0;
  Context.Block[58] := 0;
  Context.Block[59] := Context.Size shr 29;
  Context.Block[60] := Context.Size shr 21;
  Context.Block[61] := Context.Size shr 13;
  Context.Block[62] := Context.Size shr 5;
  Context.Block[63] := Context.Size shl 3;
  SHA1ProcessBlock(Context);
end;

function SHA1Result(var Context: TSHA1Context): TBytes;
var
  i: Integer;
begin
  SHA1PadMessage(Context);
  SetLength(Result, 20);
  for i := 0 to 19 do
  begin
    Result[i] := Byte(Context.Hash[i shr 2] shr (8 * (3 - (i and 3))));
  end;
end;

function XorBytes(const Src: TBytes; Value: Byte): TBytes;
var
  Len  : Integer;
  Index: Integer;
begin
  SetLength(Result, 64);
  Len := Length(Src);
  if Len > 64 then
    Len := 64
  else
    FillChar(Result[Len], 64 - Len, Value);
  for Index := 0 to Len - 1 do
  begin
    Result[Index] := Src[Index] xor Value;
  end;
end;

function HMAC_SHA1(const Key, Value: TBytes): TBytes;
var
  opad: TBytes;
  ipad: TBytes;
  sha1: TSHA1Context;
begin
  opad := XorBytes(key, $5C);
  ipad := XorBytes(key, $36);
// Result := SHA1(opad + SHA1(ipad + Value))
  SHA1Reset(sha1);
  SHA1Input(sha1, ipad);
  SHA1Input(sha1, Value);
  Result := SHA1Result(sha1);
  SHA1Reset(sha1);
  SHA1Input(sha1, opad);
  SHA1Input(sha1, Result);
  Result := SHA1Result(sha1);
end;

function BytesToHex(const Value: TBytes): string;
const
  hx: array[0..$F] of Char = '0123456789abcdef';
var
  Len: Integer;
  Idx: Integer;
  b  : Byte;
begin
  Len := Length(Value);
  SetLength(Result, 2 * Len);
  for Idx := 0 to Len - 1 do
  begin
    b := Value[Idx];
    Result[2 * Idx + 1] := Hx[b shr 4];
    Result[2 * Idx + 2] := Hx[b and $F];
  end;
end;

function GoogleAuthenticatorCode(const Secret: string): string;
var
  key   : TBytes;
  epoch : TBytes;
  hmac  : TBytes;
  offset: Integer;
  index : Integer;
  otp   : Cardinal;
begin
  key := Base32ToBin(Secret);
//WriteLn('Key = ', BytesToHex(key));
  epoch := IntToBytes(UnixTime div 30, 8);
//WriteLn('Epoch = ', BytesToHex(epoch));
  hmac := HMAC_SHA1(key, epoch);
//WriteLn('HMac = ', BytesToHex(hmac));
  offset := hmac[19] and $F;
  otp := 0;
  for Index := 0 to 3 do
  begin
    otp := otp shl 8 + hmac[Offset + Index];
  end;
  otp := otp and $7fffffff;
  Result := IntToStr(otp mod 1000000);
  if Length(Result) < 6 then
    Result := StringOfChar('0', 6 - Length(Result)) + Result;
end;

end.


Pour utiliser ce code, il suffit d'ajouter l'unité GoogleAuthenticator à la clause uses du projet et d'invoquer la fonction GoogleAuthenticatorCode().

uses GoogleAuthenticator;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit2.Text := GoogleAuthenticatorCode(Edit1.Text);
end;

Le code est sous licence GPL.
Date de dernière modification : 28/06/2017