Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:15028
HistoryNov 14, 2006 - 12:00 a.m.

Digipass Go3 Token Dumper (at least for 2006)

2006-11-1400:00:00
vulners.com
15

The initial reverse engineering of Vasco’s Digipass Go3 algorithm follows in C++.
I think this implementation is a "rough" approximation, if we take some limitations about 2006 and the calculations made into account. Or I'm just joking… :)

This generator was able to predict an "otp" collision, within ~10 days range.
I publish this here, for further study/analysis by the community. The dumper part is something off a mess, used in a needed/just in time basis. Hack it around.
(the names are based in the meta-info used inside Vasco's dpx files; [TARGET] is an otp used to synchronize with a token device)

The 3 secrets' derivation is 3DES 112 based, and real ".dpx" files were used with success.
The core is also 3DES 112 based, as a hash/generator.

I have strong evidences (opcodes) to believe that Vasco's used openssl library, without proper acknowledgment. Who knows?
As DES is free, I guess the patents holded by the company protect only the synchronization side of digipass. Just a theory (I'm lazy, tired, and didn't research).

A brute-force approach was used instead, because I believe in law.
(I hope law also believes me!)

Decimalization from DES_cblock truncation was simplified, and some edge cases omitted.
Tested with gcc/Linux, and cl.exe/Windows.

May the Force be with you!

fc (a.k.a. “faypou”)

/* (c) 2006-2006 faypou (a.k.a fc) */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include <string.h>

#ifdef _WIN32

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma comment(lib, "libeay32.lib")

typedef unsigned __int64 uint64_t;

#else // _WIN32

#include <unistd.h>
#include <sys/time.h>

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef const char * LPCSTR;
typedef unsigned long long uint64_t;

#endif // _WIN32

#include <openssl/des.h>

// ----------------------------------------------------------------------------

#define TRACE printf

#if 1
#define HIT_KEY_TO_CONTINUE() do { TRACE("\t\thit a key to continue\n");\
getc(stdin);\
}while (0)
#endif

//#define HIT_KEY_TO_CONTINUE()

// ----------------------------------------------------------------------------

#define SERIAL_LEN (5)
#define ARGC_COUNT (7)

#define MK __argv[1]
#define DEL __argv[2]
#define DKEY __argv[3]
#define TDKEY __argv[4]
#define OFFSET __argv[5]
#define SERIAL __argv[6]

#define TARGET __argv[7]

// ----------------------------------------------------------------------------

typedef struct Digipass_GO3_ctx_
{
BYTE vMasterKey [sizeof(DES_cblock) * 2];
BYTE vDEL [sizeof(DES_cblock) ];
BYTE vDES64KEY [sizeof(DES_cblock) ];
BYTE vA_TDES64KEY[sizeof(DES_cblock) ];
BYTE vA_OFFSET [sizeof(DES_cblock) ];
BYTE vSERIAL [SERIAL_LEN ];

    // master keys
    DES_key_schedule  ks_master[2];

    // hold 3DES 112 &quot;master-derived&quot; keys
    DES_key_schedule  ks_digipass[2];
    DES_cblock        digipass_k[2];

    DES_cblock secret1; // 8 bytes
    DES_cblock secret2; // 8 bytes

    // only 3 first bytes used to derive OTPs
    DES_cblock        secret3;

    // finally, token keys
    DES_key_schedule ks_token[2];

} Digipass_GO3_ctx_t;

// ----------------------------------------------------------------------------

class CDigipassGO3
{
public:
CDigipassGO3() {
ResetState();
}

            ~CDigipassGO3&#40;&#41; {
                    ResetState&#40;&#41;;
                    }

            static BYTE inline TO_I&#40;char c&#41; {
                    BYTE cc = &#40;BYTE&#41; toupper&#40;&#40;BYTE&#41;c&#41;;
                    return &#40; c &gt; &#39;9&#39; /* hex chars */ &#41; ? &#40;cc - &#39;7&#39;&#41; : &#40;cc - &#39;0&#39;&#41;;
                    }

            bool InitCtx&#40;   LPCSTR szMK,
                                            LPCSTR szDEL,
                                            LPCSTR szDKEY,
                                            LPCSTR szTDKEY,
                                            LPCSTR szOFFSET,
                                            LPCSTR szSERIAL
                                            &#41;;

            void GetOTP&#40;time_t start, char *GeneratedOTP = NULL&#41;;

            bool Synchronize&#40;LPCSTR szTarget&#41;;

            time_t GetTimeDrift&#40;void&#41; {
                            return m_sync_delta;
                    }

            LPCSTR GetOTP_Str&#40;void&#41; {
                            return m_szTokenCode;
                    }

            enum {
                            // time step itself is fixed during Digipass init
                            TIME_WINDOW_SIZE = 100,

                            GO3_CODE_LEN = 6,

                            SEC_DELTA = 36,

                            GO3_PERIOD = &#40;1 * SEC_DELTA&#41;
                    };

    private:
            void ResetState&#40;void&#41; {
                    memset&#40;this, 0, sizeof&#40;*this&#41;&#41;;
                    }

            void MakePreSecretFromSerial&#40;DES_cblock &amp;pre, BYTE ord&#41;;

            bool DeriveKeys&#40;void&#41;;

            static bool ConvertHexStrToByteVector&#40;LPCSTR szSTR, BYTE *pBase&#41;;

            Digipass_GO3_ctx_t   m_ctx;
            BYTE                *m_pDerivePinPtr;
            size_t               m_sync_delta;
            char                 m_szTokenCode[GO3_CODE_LEN + 1];

};

// ----------------------------------------------------------------------------
// converts the hex string representation to byte vector representation;
// ----------------------------------------------------------------------------
bool CDigipassGO3::ConvertHexStrToByteVector(LPCSTR szSTR, BYTE *pBase)
{
size_t len = strlen(szSTR);

    if &#40; len &amp; 1 &#41;
    {
            TRACE&#40;&quot;&#92;tmalformed hex_str not multiple of 2: &#39;&#37;s&#39;...&#92;n&quot;, szSTR&#41;;
            return false;
    }

    TRACE&#40;&quot;&#92;tconverting &#39;&#37;s&#39; to bin...&#92;n&quot;, szSTR&#41;;

    for &#40; size_t i = 0, j = 0; i &lt; len; j++, i += 2 &#41;
    {
            pBase[j] = &#40;TO_I&#40;szSTR[i]&#41; &lt;&lt; 4&#41; + TO_I&#40;szSTR[i + 1]&#41;;
    }

    return true;

}

// ----------------------------------------------------------------------------
//
// we received string material; make DigipassGO3 derivations;
//
// ----------------------------------------------------------------------------
bool CDigipassGO3::InitCtx( LPCSTR szMK,
LPCSTR szDEL,
LPCSTR szDKEY,
LPCSTR szTDKEY,
LPCSTR szOFFSET,
LPCSTR szSERIAL
)
{
if ( !ConvertHexStrToByteVector( szMK, m_ctx.vMasterKey ) ||
!ConvertHexStrToByteVector( szDEL, m_ctx.vDEL ) ||
!ConvertHexStrToByteVector( szDKEY, m_ctx.vDES64KEY ) ||
!ConvertHexStrToByteVector( szTDKEY, m_ctx.vA_TDES64KEY ) ||
!ConvertHexStrToByteVector( szOFFSET, m_ctx.vA_OFFSET ) ||
!ConvertHexStrToByteVector( szSERIAL, m_ctx.vSERIAL )
)
{
TRACE("\tcannot get str material…\n");
return false;
}

    return DeriveKeys&#40;&#41;;

}

// ----------------------------------------------------------------------------
// prepare the secrets from token serial number; each one has hadcoded value;
// ----------------------------------------------------------------------------
void CDigipassGO3::MakePreSecretFromSerial(DES_cblock &pre, BYTE ord)
{
memset(&pre, 0, sizeof(DES_cblock));

    BYTE *p = &#40;BYTE *&#41; &amp;pre;

    memcpy&#40;p + &#40;sizeof&#40;DES_cblock&#41; -SERIAL_LEN&#41;, m_ctx.vSERIAL, SERIAL_LEN&#41;;

    if &#40; ord == 0x01 &#41; {
            p[0] = 0x01;
    }
    else if &#40; ord == 0x02 &#41; {
            p[1] = 0x10;
    }
    else if &#40; ord == 0x03 &#41; {
            p[1] = 0x01;
    }

}

// ----------------------------------------------------------------------------
// Here, the digipass derivation actually happens.
// ----------------------------------------------------------------------------
bool CDigipassGO3::DeriveKeys()
{
DES_cblock des1;
memcpy(&des1, m_ctx.vMasterKey, sizeof(DES_cblock));

    DES_cblock des2;
            memcpy&#40;&amp;des2, m_ctx.vMasterKey + sizeof&#40;DES_cblock&#41;, sizeof&#40;DES_cblock&#41;&#41;;

    DES_set_key_unchecked&#40;&amp;des1, &amp;m_ctx.ks_master[0]&#41;;
    DES_set_key_unchecked&#40;&amp;des2, &amp;m_ctx.ks_master[1]&#41;;

    DES_ecb3_encrypt&#40;&#40;DES_cblock *&#41;m_ctx.vDEL, &amp;m_ctx.digipass_k[0], 
                                    &amp;m_ctx.ks_master[0], &amp;m_ctx.ks_master[1], &amp;m_ctx.ks_master[0], 
                                    DES_ENCRYPT
                                    &#41;;

    DES_ecb3_encrypt&#40;&amp;m_ctx.digipass_k[0], &amp;m_ctx.digipass_k[1], 
                                    &amp;m_ctx.ks_master[0], &amp;m_ctx.ks_master[1], &amp;m_ctx.ks_master[0], 
                                    DES_ENCRYPT
                                    &#41;;

    DES_set_odd_parity&#40;&amp;m_ctx.digipass_k[0]&#41;;
    DES_set_odd_parity&#40;&amp;m_ctx.digipass_k[1]&#41;;

    DES_set_key_unchecked&#40;&amp;m_ctx.digipass_k[0], &amp;m_ctx.ks_digipass[0]&#41;;
    DES_set_key_unchecked&#40;&amp;m_ctx.digipass_k[1], &amp;m_ctx.ks_digipass[1]&#41;;

    //
    // ks_digipass[0] &amp;&amp; ks_digipass[1] 
    //
    DES_cblock pre1;
            MakePreSecretFromSerial&#40;pre1, 0x01&#41;;

    DES_cblock a;
    DES_cblock b;

    DES_ecb3_encrypt&#40;&amp;pre1, &amp;a, 
                            &amp;m_ctx.ks_digipass[0], &amp;m_ctx.ks_digipass[1], &amp;m_ctx.ks_digipass[0], 
                            DES_ENCRYPT
                            &#41;;

    for &#40; int i = 0; i &lt; sizeof&#40;DES_cblock&#41;; i++ &#41;
    {
            DES_cblock tmp;
                    memcpy&#40;&amp;tmp, &#40;BYTE *&#41;&amp;a + i, sizeof&#40;DES_cblock&#41; -i&#41;;
                    memcpy&#40;&#40;BYTE *&#41;&amp;tmp + sizeof&#40;DES_cblock&#41; -i, m_ctx.vDES64KEY, i&#41;;

            DES_ecb3_encrypt&#40;&amp;tmp, &amp;b, 
                            &amp;m_ctx.ks_digipass[0], &amp;m_ctx.ks_digipass[1], &amp;m_ctx.ks_digipass[0],
                            DES_ENCRYPT
                            &#41;;

            BYTE al = *&#40;BYTE *&#41; &amp;b;
            BYTE cl = m_ctx.vDES64KEY[i] ^ al;
            &#40;&#40;BYTE *&#41;&amp;m_ctx.secret1&#41;[i] = cl;
    }

    DES_cblock pre2;
            MakePreSecretFromSerial&#40;pre2, 0x02&#41;;

    DES_ecb3_encrypt&#40;&amp;pre2, &amp;a, 
                            &amp;m_ctx.ks_digipass[0], &amp;m_ctx.ks_digipass[1], &amp;m_ctx.ks_digipass[0], 
                            DES_ENCRYPT
                            &#41;;

    //
    // FIXME: each loop/round should be unified in a separate function;
    //
    for &#40; int i = 0; i &lt; sizeof&#40;DES_cblock&#41;; i++ &#41;
    {
            DES_cblock tmp;
                    memcpy&#40;&amp;tmp, &#40;BYTE *&#41;&amp;a + i, sizeof&#40;DES_cblock&#41; -i&#41;;
                    memcpy&#40;&#40;BYTE *&#41;&amp;tmp + sizeof&#40;DES_cblock&#41; -i, m_ctx.vA_TDES64KEY, i&#41;;

            DES_ecb3_encrypt&#40;&amp;tmp, &amp;b, 
                                            &amp;m_ctx.ks_digipass[0], &amp;m_ctx.ks_digipass[1], &amp;m_ctx.ks_digipass[0],
                                             DES_ENCRYPT
                                            &#41;;

            BYTE al = *&#40;BYTE *&#41; &amp;b;
            BYTE cl = m_ctx.vA_TDES64KEY[i] ^ al;
            &#40;&#40;BYTE *&#41;&amp;m_ctx.secret2&#41;[i] = cl;
    }

    DES_cblock pre3;
            MakePreSecretFromSerial&#40;pre3, 0x03&#41;;

    DES_ecb3_encrypt&#40;&amp;pre3, &amp;a, 
                                    &amp;m_ctx.ks_digipass[0], &amp;m_ctx.ks_digipass[1], &amp;m_ctx.ks_digipass[0], 
                                    DES_ENCRYPT
                                    &#41;;

    for &#40; int i = 0; i &lt; sizeof&#40;DES_cblock&#41;; i++ &#41;
    {
            DES_cblock tmp;
                    memcpy&#40;&amp;tmp, &#40;BYTE *&#41;&amp;a + i, sizeof&#40;DES_cblock&#41; -i&#41;;
                    memcpy&#40;&#40;BYTE *&#41;&amp;tmp + sizeof&#40;DES_cblock&#41; -i, m_ctx.vA_OFFSET, i&#41;;

            DES_ecb3_encrypt&#40;&amp;tmp, &amp;b, 
                                            &amp;m_ctx.ks_digipass[0], &amp;m_ctx.ks_digipass[1], &amp;m_ctx.ks_digipass[0],
                                             DES_ENCRYPT
                                            &#41;;

            BYTE al = *&#40;BYTE *&#41; &amp;b;
            BYTE cl = m_ctx.vA_OFFSET[i] ^ al;
            &#40;&#40;BYTE *&#41;&amp;m_ctx.secret3&#41;[i] = cl;
    }

    DES_set_key_unchecked&#40;&amp;m_ctx.secret1, &amp;m_ctx.ks_token[0]&#41;;
    DES_set_key_unchecked&#40;&amp;m_ctx.secret2, &amp;m_ctx.ks_token[1]&#41;;

    m_pDerivePinPtr = &#40;BYTE *&#41;&amp;m_ctx.secret3;

    TRACE&#40;&quot;&#92;tCDigipassGO3::DeriveKeys&#40;&#41; done...&#92;n&quot;&#41;;

    return true;

}

// ----------------------------------------------------------------------------
// THE generator; start must have been sync'ed before generation;
// FIXME: thread unsafe outside MS CRT
// ----------------------------------------------------------------------------
void CDigipassGO3::GetOTP(time_t start, char *szTokenCode)
{
DES_cblock token_code = { 0 };
struct tm time_tm = *(gmtime(&start)); // here

    DWORD dwTmpCalc31 = &#40;DWORD&#41; &#40;time_tm.tm_min * 60&#41;;

    dwTmpCalc31 += &#40;DWORD&#41; time_tm.tm_sec;

    uint64_t tmp = &#40;uint64_t&#41; dwTmpCalc31 * &#40;uint64_t&#41;0x38E38E39;

    tmp &gt;&gt;= 32;
    tmp &gt;&gt;= 3;


    BYTE calc1 = &#40;&#40;BYTE&#41;&#40;time_tm.tm_year / &#40;TIME_WINDOW_SIZE / 10&#41;&#41; &lt;&lt; 4&#41; +
                                    &#40;BYTE&#41;&#40;time_tm.tm_year &#37; 0x0A&#41;;

    BYTE calc2 = &#40;&#40;BYTE&#41;&#40;time_tm.tm_hour / &#40;TIME_WINDOW_SIZE / 10&#41;&#41; &lt;&lt; 4&#41; +
                                    &#40;BYTE&#41;&#40;time_tm.tm_hour &#37; 0x0A&#41;;

    BYTE calc3 = &#40;&#40;&#40;BYTE&#41; tmp / 0x0A&#41; * GO3_CODE_LEN&#41; + &#40;BYTE&#41; tmp;

    BYTE calcA = &#40;&#40;BYTE&#41;&#40;time_tm.tm_mday / &#40;TIME_WINDOW_SIZE / 10&#41;&#41; &lt;&lt; 4&#41; +
                                    &#40;BYTE&#41;&#40;time_tm.tm_mday &#37; 0x0A&#41;;

    time_tm.tm_mon++; // time_tm.tm_mon + 1; // ok
    BYTE calcB = &#40;&#40;BYTE&#41;&#40;time_tm.tm_mon / &#40;TIME_WINDOW_SIZE / 10&#41;&#41; &lt;&lt; 4&#41; +
                                    &#40;BYTE&#41;&#40;time_tm.tm_mon &#37; 0x0A&#41;;

    // [0], [1], [2] - ok &#40;secret3[0], secret3[1], secret3[2]&#41;
    m_pDerivePinPtr[3] = calc1;
    m_pDerivePinPtr[4] = calcB;
    m_pDerivePinPtr[5] = calcA;
    m_pDerivePinPtr[6] = calc2;
    m_pDerivePinPtr[7] = calc3;

    DES_ecb3_encrypt&#40;&amp;m_ctx.secret3, &amp;token_code, 
                                            &amp;m_ctx.ks_token[0], &amp;m_ctx.ks_token[1], &amp;m_ctx.ks_token[0], 
                                            DES_ENCRYPT
                                    &#41;;

    //
    // extrated from fixed binary position
    //
    const static BYTE c_table[0x100] = {
                                                    0x92, 0x82, 0x55, 0x23, 0x90, 0x71, 0x22, 0x63, 
                                                    0x37, 0x25, 0xFE, 0xFF, 0xFA, 0xFB, 0xFC, 0xFD,
                                                    0x59, 0x53, 0x06, 0x44, 0x79, 0x75, 0x88, 0x13, 
                                                    0x64, 0x36, 0xEF, 0xEA, 0xEB, 0xEC, 0xED, 0xEE,
                                                    0x34, 0x46, 0x35, 0x21, 0x57, 0x27, 0x20, 0x65, 
                                                    0x77, 0x03, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
                                                    0x10, 0x78, 0x81, 0x49, 0x84, 0x01, 0x32, 0x96, 
                                                    0x11, 0x02, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xCA,
                                                    0x04, 0x24, 0x00, 0x54, 0x45, 0x72, 0x87, 0x09, 
                                                    0x73, 0x83, 0xBC, 0xBD, 0xBE, 0xBF, 0xBA, 0xBB,
                                                    0x76, 0x98, 0x12, 0x42, 0x38, 0x33, 0x94, 0x05, 
                                                    0x91, 0x86, 0xAD, 0xAE, 0xAF, 0xAA, 0xAB, 0xAC,
                                                    0x28, 0x39, 0x68, 0x47, 0x15, 0x56, 0x60, 0x17,
                                                    0x99, 0x07, 0x9E, 0x9F, 0x9A, 0x9B, 0x9C, 0x9D,
                                                    0x26, 0x18, 0x50, 0x74, 0x93, 0x89, 0x70, 0x61,
                                                    0x31, 0x58, 0x8F, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
                                                    0x16, 0x69, 0x30, 0x08, 0x43, 0x85, 0x67, 0x62,
                                                    0x95, 0x48, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
                                                    0x52, 0x66, 0x14, 0x29, 0x19, 0x97, 0x51, 0x40,
                                                    0x80, 0x41, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x6A,
                                                    0xE5, 0xF4, 0xA3, 0xB2, 0xC1, 0xD0, 0xE9, 0xF8,
                                                    0xA7, 0xB6, 0x5C, 0x5D, 0x5E, 0x5F, 0x5A, 0x5B,
                                                    0xF5, 0xA4, 0xB3, 0xC2, 0xD1, 0xE0, 0xF9, 0xA8,
                                                    0xB7, 0xC6, 0x4D, 0x4E, 0x4F, 0x4A, 0x4B, 0x4C,
                                                    0xA5, 0xB4, 0xC3, 0xD2, 0xE1, 0xF0, 0xA9, 0xB8,
                                                    0xC7, 0xD6, 0x3E, 0x3F, 0x3A, 0x3B, 0x3C, 0x3D,
                                                    0xB5, 0xC4, 0xD3, 0xE2, 0xF1, 0xA0, 0xB9, 0xC8,
                                                    0xD7, 0xE6, 0x2F, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
                                                    0xC5, 0xD4, 0xE3, 0xF2, 0xA1, 0xB0, 0xC9, 0xD8,
                                                    0xE7, 0xF6, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
                                                    0xD5, 0xE4, 0xF3, 0xA2, 0xB1, 0xC0, 0xD9, 0xE8,
                                                    0xF7, 0xA6, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x0A
                                            };

    BYTE *pTokenCode  = &#40;BYTE *&#41; &amp;token_code;
    BYTE *pTokenCode2 = pTokenCode;

    BYTE cl = pTokenCode[0];
    BYTE dl = pTokenCode[2];
    BYTE al = pTokenCode[3];
    BYTE bl = 0;

    dl ^= cl;

    cl            = pTokenCode[4];
    pTokenCode[2] = dl;
    dl            = pTokenCode[1];

    pTokenCode2 += 4;
    al ^= dl;

    dl            = pTokenCode[5];
    pTokenCode[3] = al;

    al = pTokenCode[6];
    cl ^= al;

    pTokenCode2[0] = cl;

    cl = pTokenCode[7];
    dl ^= cl;

    pTokenCode[5] = dl;

    for &#40; int i = 0; i &lt; sizeof&#40;DES_cblock&#41;; i++ &#41;
    {
            al = pTokenCode[i];

            if &#40; al &gt;= 0xA0 &#41;
            {
                    al -= 0x60;
            }

            pTokenCode[i] = al;

            BYTE bl = al;

            bl &amp;= 0x0F;

            if &#40; bl &gt;= 0x0A &#41;
            {
                    al -= 0x06;
                    pTokenCode[i] = al;
            }
    }

    dl = m_pDerivePinPtr[7];
    pTokenCode[6] = dl;

    for &#40; int i = 0; i &lt; GO3_CODE_LEN; i++ &#41;
    {
            for &#40; int j = 0; j &lt; &#40;GO3_CODE_LEN / 2&#41;; j++ &#41;
            {
                    al = pTokenCode2[j];
                    dl = al;
                    al &amp;= 0x0F;
                    dl &gt;&gt;= 4;

                    DWORD dwTmp1 = &#40;DWORD&#41; dl;
                    DWORD dwTmp2 = &#40;DWORD&#41; al;

                    dwTmp1 &amp;= 0x000000FF;
                    dwTmp2 &amp;= 0x000000FF;

                    dwTmp1 &lt;&lt;= 4;
                    al = c_table[dwTmp1 + dwTmp2];
                    pTokenCode2[j] = al;
            }

            dl = pTokenCode2[1];
            cl = pTokenCode2[2];

            bl = dl;
            al = cl;
            bl &amp;= 0x0F;
            al &amp;= 0x0F;

            bl &lt;&lt;= 4;
            cl &gt;&gt;= 4;
            bl += cl;

            cl =  pTokenCode2[0];
            pTokenCode2[2] = bl;

            bl = cl;
            bl &amp;= 0x0F;
            bl &lt;&lt;= 4;
            dl &gt;&gt;= 4;
            cl &gt;&gt;= 4;
            al &lt;&lt;= 4;
            bl += dl;
            cl += al;

            pTokenCode2[1] = bl;
            pTokenCode2[0] = cl;
    }

    #if 0 // digits only
    sprintf&#40;m_szTokenCode, &quot;&#37;02X&#37;02X&#37;02X&quot;, 
                    pTokenCode2[0], pTokenCode2[1], pTokenCode2[2]
                    &#41;;
    #endif

    //
    // optimized lookup convertion; see sprintf&#40;&#41; disabled above;
    //
    const static char g_HexToStr[0x10] = {
                                                    &#39;0&#39;, &#39;1&#39;, &#39;2&#39;, &#39;3&#39;, &#39;4&#39;,
                                                    &#39;5&#39;, &#39;6&#39;, &#39;7&#39;, &#39;8&#39;, &#39;9&#39;,
                                                    //
                                                    // from now on, should never happen; they&#39;re 
                                                    //  wrong if reached; the extra padding avoids 
                                                    //  runtime &quot;explosions&quot;;
                                                    //
                                                    &#39;A&#39;, &#39;B&#39;, &#39;C&#39;, &#39;D&#39;,&#39;E&#39;, &#39;F&#39;
                                            };

    //
    // loop unrolled, still for optimization purposes;
    //
    m_szTokenCode[0x00] = g_HexToStr[&#40;&#40;pTokenCode2[0] &gt;&gt; 4&#41; &amp; 0x0F&#41;];
    m_szTokenCode[0x01] = g_HexToStr[&#40;&#40;pTokenCode2[0] &gt;&gt; 0&#41; &amp; 0x0F&#41;];
    m_szTokenCode[0x02] = g_HexToStr[&#40;&#40;pTokenCode2[1] &gt;&gt; 4&#41; &amp; 0x0F&#41;];
    m_szTokenCode[0x03] = g_HexToStr[&#40;&#40;pTokenCode2[1] &gt;&gt; 0&#41; &amp; 0x0F&#41;];
    m_szTokenCode[0x04] = g_HexToStr[&#40;&#40;pTokenCode2[2] &gt;&gt; 4&#41; &amp; 0x0F&#41;];
    m_szTokenCode[0x05] = g_HexToStr[&#40;&#40;pTokenCode2[2] &gt;&gt; 0&#41; &amp; 0x0F&#41;];

    m_szTokenCode[0x06] = &#39;&#92;0&#39;;

    if &#40; szTokenCode != NULL &#41;
            strcpy&#40;szTokenCode, m_szTokenCode&#41;;

}

// ----------------------------------------------------------------------------
// Try to find time drift between token and localtime();
// This can be positive, or negative; depends on kind of time drift;
// Brute-force approach; FIXME
// ----------------------------------------------------------------------------
bool CDigipassGO3::Synchronize(LPCSTR szTarget)
{
TRACE("\tSynchronize()ing with '%s'…\n", szTarget);

    time_t start = time&#40;NULL&#41;;

    const size_t DAYS = 2 * &#40;24 * 60 * 60&#41;; // 2 days in seconds

    TRACE&#40;&quot;&#92;t&#92;tbackwards...&#92;n&quot;&#41;;

    HIT_KEY_TO_CONTINUE&#40;&#41;;

    //
    // backwards
    //
    for &#40; m_sync_delta = 0; m_sync_delta &lt; DAYS; m_sync_delta += GO3_PERIOD &#41;
    {
            m_szTokenCode[0] = &#39;&#92;0&#39;;

            GetOTP&#40;start - m_sync_delta&#41;;

            TRACE&#40;&quot;&#92;t&#92;tround: &#37;08u, &#37;s:&#37;s&#92;n&quot;, m_sync_delta, szTarget, m_szTokenCode&#41;;

            if &#40; strcmp&#40;szTarget, m_szTokenCode&#41; == 0 &#41;
            {
                    m_sync_delta = &#40;~&#40;DWORD&#41;m_sync_delta&#41; + 1; // negative, 2s-complement

                    TRACE&#40;&quot;&#92;t&#92;tSynchronize&#40;&#41; found negative drift!&#92;n&quot;&#41;;
                    return true;
            }
    }

    TRACE&#40;&quot;&#92;t&#92;tupwards...&#92;n&quot;&#41;;

    HIT_KEY_TO_CONTINUE&#40;&#41;;

    //
    // upwards
    //
    for &#40; m_sync_delta = 0; m_sync_delta &lt; DAYS; m_sync_delta += GO3_PERIOD &#41;
    {
            m_szTokenCode[0] = &#39;&#92;0&#39;;

            GetOTP&#40;start + m_sync_delta&#41;;

            TRACE&#40;&quot;&#92;t&#92;tround: &#37;08u, &#37;s:&#37;s&#92;n&quot;, m_sync_delta, szTarget, m_szTokenCode&#41;;

            if &#40; strcmp&#40;szTarget, m_szTokenCode&#41; == 0 &#41;
            {
                    TRACE&#40;&quot;&#92;t&#92;tSynchronize&#40;&#41; found positive drift!&#92;n&quot;&#41;;
                    return true;
            }
    }

    return false;

}

// ----------------------------------------------------------------------------

#ifndef _WIN32
int __argc;
char **__argv;
#endif // _WIN32

// ----------------------------------------------------------------------------

int main(int argc, char *argv[])
{
#ifndef _WIN32
__argc = argc;
__argv = argv;
#endif // _WIN32

    if &#40; argc &lt; ARGC_COUNT &#41;
    {
            printf&#40;&quot;&#92;tincomplete arguments: MK DEL DKEY TDKEY OFFSET SERIAL [TARGET]&#92;n&quot;&#41;;
            return -1;
    }

    bool bHasTarget = false;

    printf&#40;&quot;&#92;n&quot;&#41;;

    if &#40; argc == &#40;ARGC_COUNT + 1&#41; &#41;
    {
            bHasTarget = true;
            printf&#40;&quot;&#92;t&#92;tconvergence using &#39;&#37;s&#39;...&#92;n&quot;, TARGET&#41;;
    }


    CDigipassGO3 go3_token;

    if &#40; !go3_token.InitCtx&#40;MK, DEL, DKEY, TDKEY, OFFSET, SERIAL&#41; &#41;
    {
            printf&#40;&quot;&#92;t&#92;tcannot init token ctx...&#92;n&quot;&#41;;
            return -2;
    }

    printf&#40;&quot;&#92;n&quot;&#41;;

    time_t start = time&#40;NULL&#41;;

    if &#40; bHasTarget &#41;
    {
            if &#40; !go3_token.Synchronize&#40;TARGET&#41; &#41;
            {
                    printf&#40;&quot;&#92;t&#92;tSynchronize&#40;&#41; did not converge. aborted :&#40;&#92;n&quot;&#41;;
                    return -3;
            }
            else
            {
                    printf&#40;&quot;&#92;t&#92;tdrif: 0x&#37;08X...&#92;n&quot;, go3_token.GetTimeDrift&#40;&#41;&#41;;

                    start += go3_token.GetTimeDrift&#40;&#41;;

                    HIT_KEY_TO_CONTINUE&#40;&#41;;
            }
    }

    int          round    = 0;
    const DWORD  WAIT_MSEC = 51;

    while &#40; true &#41; {

            char *str_time = ctime&#40;&amp;start&#41;;
            str_time[24] = &#39;&#92;0&#39;;

            go3_token.GetOTP&#40;start&#41;;

            #if 0
            printf&#40;&quot;&#92;ttoken code &#40;&#39;&#37;s&#39;:&#37;03d&#41;: &#39;&#37;s&#39;...&#92;n&quot;, str_time, 
                                                    round, go3_token.GetOTP_Str&#40;&#41;
                            &#41;;
            #endif

            // for database manipulation
            printf&#40;&quot;&#37;d;&#37;s&#92;n&quot;, round, go3_token.GetOTP_Str&#40;&#41;&#41;;

            #if 0
            Sleep&#40;WAIT_MSEC&#41;;
            #endif

            start += &#40;CDigipassGO3::GO3_PERIOD&#41;;
            round++;

            if &#40; round &gt; &#40;&#40;72000 + 10 + 2400&#41; &#41; * 6&#41; // ~ 6 month
                    break;
            //if &#40; start &lt; 0 &#41; // time_t are long&#39;s in Win32; overflowed!
            //      break;
    }

    return 0;

}

// ----------------------------------------------------------------------------