<<< Date Index >>>     <<< Thread Index >>>

Digipass Go3 Token Dumper (at least for 2006)



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 "master-derived" 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() {
                        ResetState();
                        }

                static BYTE inline TO_I(char c) {
                        BYTE cc = (BYTE) toupper((BYTE)c);
                        return ( c > '9' /* hex chars */ ) ? (cc - '7') : (cc - 
'0');
                        }

                bool InitCtx(   LPCSTR szMK,
                                                LPCSTR szDEL,
                                                LPCSTR szDKEY,
                                                LPCSTR szTDKEY,
                                                LPCSTR szOFFSET,
                                                LPCSTR szSERIAL
                                                );

                void GetOTP(time_t start, char *GeneratedOTP = NULL);

                bool Synchronize(LPCSTR szTarget);

                time_t GetTimeDrift(void) {
                                return m_sync_delta;
                        }

                LPCSTR GetOTP_Str(void) {
                                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 = (1 * SEC_DELTA)
                        };

        private:
                void ResetState(void) {
                        memset(this, 0, sizeof(*this));
                        }

                void MakePreSecretFromSerial(DES_cblock &pre, BYTE ord);

                bool DeriveKeys(void);

                static bool ConvertHexStrToByteVector(LPCSTR szSTR, BYTE 
*pBase);

                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 ( len & 1 )
        {
                TRACE("\tmalformed hex_str not multiple of 2: '%s'...\n", 
szSTR);
                return false;
        }

        TRACE("\tconverting '%s' to bin...\n", szSTR);

        for ( size_t i = 0, j = 0; i < len; j++, i += 2 )
        {
                pBase[j] = (TO_I(szSTR[i]) << 4) + TO_I(szSTR[i + 1]);
        }

        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();
}

// ----------------------------------------------------------------------------
// 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 = (BYTE *) &pre;

        memcpy(p + (sizeof(DES_cblock) -SERIAL_LEN), m_ctx.vSERIAL, SERIAL_LEN);

        if ( ord == 0x01 ) {
                p[0] = 0x01;
        }
        else if ( ord == 0x02 ) {
                p[1] = 0x10;
        }
        else if ( ord == 0x03 ) {
                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(&des2, m_ctx.vMasterKey + sizeof(DES_cblock), 
sizeof(DES_cblock));

        DES_set_key_unchecked(&des1, &m_ctx.ks_master[0]);
        DES_set_key_unchecked(&des2, &m_ctx.ks_master[1]);

        DES_ecb3_encrypt((DES_cblock *)m_ctx.vDEL, &m_ctx.digipass_k[0], 
                                        &m_ctx.ks_master[0], 
&m_ctx.ks_master[1], &m_ctx.ks_master[0], 
                                        DES_ENCRYPT
                                        );

        DES_ecb3_encrypt(&m_ctx.digipass_k[0], &m_ctx.digipass_k[1], 
                                        &m_ctx.ks_master[0], 
&m_ctx.ks_master[1], &m_ctx.ks_master[0], 
                                        DES_ENCRYPT
                                        );

        DES_set_odd_parity(&m_ctx.digipass_k[0]);
        DES_set_odd_parity(&m_ctx.digipass_k[1]);

        DES_set_key_unchecked(&m_ctx.digipass_k[0], &m_ctx.ks_digipass[0]);
        DES_set_key_unchecked(&m_ctx.digipass_k[1], &m_ctx.ks_digipass[1]);

        //
        // ks_digipass[0] && ks_digipass[1] 
        //
        DES_cblock pre1;
                MakePreSecretFromSerial(pre1, 0x01);

        DES_cblock a;
        DES_cblock b;

        DES_ecb3_encrypt(&pre1, &a, 
                                &m_ctx.ks_digipass[0], &m_ctx.ks_digipass[1], 
&m_ctx.ks_digipass[0], 
                                DES_ENCRYPT
                                );

        for ( int i = 0; i < sizeof(DES_cblock); i++ )
        {
                DES_cblock tmp;
                        memcpy(&tmp, (BYTE *)&a + i, sizeof(DES_cblock) -i);
                        memcpy((BYTE *)&tmp + sizeof(DES_cblock) -i, 
m_ctx.vDES64KEY, i);

                DES_ecb3_encrypt(&tmp, &b, 
                                &m_ctx.ks_digipass[0], &m_ctx.ks_digipass[1], 
&m_ctx.ks_digipass[0],
                                DES_ENCRYPT
                                );

                BYTE al = *(BYTE *) &b;
                BYTE cl = m_ctx.vDES64KEY[i] ^ al;
                ((BYTE *)&m_ctx.secret1)[i] = cl;
        }

        DES_cblock pre2;
                MakePreSecretFromSerial(pre2, 0x02);

        DES_ecb3_encrypt(&pre2, &a, 
                                &m_ctx.ks_digipass[0], &m_ctx.ks_digipass[1], 
&m_ctx.ks_digipass[0], 
                                DES_ENCRYPT
                                );

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

                DES_ecb3_encrypt(&tmp, &b, 
                                                &m_ctx.ks_digipass[0], 
&m_ctx.ks_digipass[1], &m_ctx.ks_digipass[0],
                                                 DES_ENCRYPT
                                                );

                BYTE al = *(BYTE *) &b;
                BYTE cl = m_ctx.vA_TDES64KEY[i] ^ al;
                ((BYTE *)&m_ctx.secret2)[i] = cl;
        }

        DES_cblock pre3;
                MakePreSecretFromSerial(pre3, 0x03);

        DES_ecb3_encrypt(&pre3, &a, 
                                        &m_ctx.ks_digipass[0], 
&m_ctx.ks_digipass[1], &m_ctx.ks_digipass[0], 
                                        DES_ENCRYPT
                                        );

        for ( int i = 0; i < sizeof(DES_cblock); i++ )
        {
                DES_cblock tmp;
                        memcpy(&tmp, (BYTE *)&a + i, sizeof(DES_cblock) -i);
                        memcpy((BYTE *)&tmp + sizeof(DES_cblock) -i, 
m_ctx.vA_OFFSET, i);

                DES_ecb3_encrypt(&tmp, &b, 
                                                &m_ctx.ks_digipass[0], 
&m_ctx.ks_digipass[1], &m_ctx.ks_digipass[0],
                                                 DES_ENCRYPT
                                                );

                BYTE al = *(BYTE *) &b;
                BYTE cl = m_ctx.vA_OFFSET[i] ^ al;
                ((BYTE *)&m_ctx.secret3)[i] = cl;
        }

        DES_set_key_unchecked(&m_ctx.secret1, &m_ctx.ks_token[0]);
        DES_set_key_unchecked(&m_ctx.secret2, &m_ctx.ks_token[1]);

        m_pDerivePinPtr = (BYTE *)&m_ctx.secret3;

        TRACE("\tCDigipassGO3::DeriveKeys() done...\n");

        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 = (DWORD) (time_tm.tm_min * 60);

        dwTmpCalc31 += (DWORD) time_tm.tm_sec;

        uint64_t tmp = (uint64_t) dwTmpCalc31 * (uint64_t)0x38E38E39;

        tmp >>= 32;
        tmp >>= 3;


        BYTE calc1 = ((BYTE)(time_tm.tm_year / (TIME_WINDOW_SIZE / 10)) << 4) +
                                        (BYTE)(time_tm.tm_year % 0x0A);

        BYTE calc2 = ((BYTE)(time_tm.tm_hour / (TIME_WINDOW_SIZE / 10)) << 4) +
                                        (BYTE)(time_tm.tm_hour % 0x0A);

        BYTE calc3 = (((BYTE) tmp / 0x0A) * GO3_CODE_LEN) + (BYTE) tmp;

        BYTE calcA = ((BYTE)(time_tm.tm_mday / (TIME_WINDOW_SIZE / 10)) << 4) +
                                        (BYTE)(time_tm.tm_mday % 0x0A);

        time_tm.tm_mon++; // time_tm.tm_mon + 1; // ok
        BYTE calcB = ((BYTE)(time_tm.tm_mon / (TIME_WINDOW_SIZE / 10)) << 4) +
                                        (BYTE)(time_tm.tm_mon % 0x0A);

        // [0], [1], [2] - ok (secret3[0], secret3[1], secret3[2])
        m_pDerivePinPtr[3] = calc1;
        m_pDerivePinPtr[4] = calcB;
        m_pDerivePinPtr[5] = calcA;
        m_pDerivePinPtr[6] = calc2;
        m_pDerivePinPtr[7] = calc3;

        DES_ecb3_encrypt(&m_ctx.secret3, &token_code, 
                                                &m_ctx.ks_token[0], 
&m_ctx.ks_token[1], &m_ctx.ks_token[0], 
                                                DES_ENCRYPT
                                        );

        //
        // 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  = (BYTE *) &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 ( int i = 0; i < sizeof(DES_cblock); i++ )
        {
                al = pTokenCode[i];

                if ( al >= 0xA0 )
                {
                        al -= 0x60;
                }

                pTokenCode[i] = al;

                BYTE bl = al;

                bl &= 0x0F;

                if ( bl >= 0x0A )
                {
                        al -= 0x06;
                        pTokenCode[i] = al;
                }
        }

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

        for ( int i = 0; i < GO3_CODE_LEN; i++ )
        {
                for ( int j = 0; j < (GO3_CODE_LEN / 2); j++ )
                {
                        al = pTokenCode2[j];
                        dl = al;
                        al &= 0x0F;
                        dl >>= 4;

                        DWORD dwTmp1 = (DWORD) dl;
                        DWORD dwTmp2 = (DWORD) al;

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

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

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

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

                bl <<= 4;
                cl >>= 4;
                bl += cl;

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

                bl = cl;
                bl &= 0x0F;
                bl <<= 4;
                dl >>= 4;
                cl >>= 4;
                al <<= 4;
                bl += dl;
                cl += al;

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

        #if 0 // digits only
        sprintf(m_szTokenCode, "%02X%02X%02X", 
                        pTokenCode2[0], pTokenCode2[1], pTokenCode2[2]
                        );
        #endif

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

        //
        // loop unrolled, still for optimization purposes;
        //
        m_szTokenCode[0x00] = g_HexToStr[((pTokenCode2[0] >> 4) & 0x0F)];
        m_szTokenCode[0x01] = g_HexToStr[((pTokenCode2[0] >> 0) & 0x0F)];
        m_szTokenCode[0x02] = g_HexToStr[((pTokenCode2[1] >> 4) & 0x0F)];
        m_szTokenCode[0x03] = g_HexToStr[((pTokenCode2[1] >> 0) & 0x0F)];
        m_szTokenCode[0x04] = g_HexToStr[((pTokenCode2[2] >> 4) & 0x0F)];
        m_szTokenCode[0x05] = g_HexToStr[((pTokenCode2[2] >> 0) & 0x0F)];

        m_szTokenCode[0x06] = '\0';

        if ( szTokenCode != NULL )
                strcpy(szTokenCode, m_szTokenCode);
}

// ----------------------------------------------------------------------------
// 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(NULL);

        const size_t DAYS = 2 * (24 * 60 * 60); // 2 days in seconds

        TRACE("\t\tbackwards...\n");

        HIT_KEY_TO_CONTINUE();

        //
        // backwards
        //
        for ( m_sync_delta = 0; m_sync_delta < DAYS; m_sync_delta += GO3_PERIOD 
)
        {
                m_szTokenCode[0] = '\0';

                GetOTP(start - m_sync_delta);

                TRACE("\t\tround: %08u, %s:%s\n", m_sync_delta, szTarget, 
m_szTokenCode);

                if ( strcmp(szTarget, m_szTokenCode) == 0 )
                {
                        m_sync_delta = (~(DWORD)m_sync_delta) + 1; // negative, 
2s-complement

                        TRACE("\t\tSynchronize() found negative drift!\n");
                        return true;
                }
        }

        TRACE("\t\tupwards...\n");

        HIT_KEY_TO_CONTINUE();

        //
        // upwards
        //
        for ( m_sync_delta = 0; m_sync_delta < DAYS; m_sync_delta += GO3_PERIOD 
)
        {
                m_szTokenCode[0] = '\0';

                GetOTP(start + m_sync_delta);

                TRACE("\t\tround: %08u, %s:%s\n", m_sync_delta, szTarget, 
m_szTokenCode);

                if ( strcmp(szTarget, m_szTokenCode) == 0 )
                {
                        TRACE("\t\tSynchronize() found positive drift!\n");
                        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 ( argc < ARGC_COUNT )
        {
                printf("\tincomplete arguments: MK DEL DKEY TDKEY OFFSET SERIAL 
[TARGET]\n");
                return -1;
        }

        bool bHasTarget = false;

        printf("\n");

        if ( argc == (ARGC_COUNT + 1) )
        {
                bHasTarget = true;
                printf("\t\tconvergence using '%s'...\n", TARGET);
        }


        CDigipassGO3 go3_token;

        if ( !go3_token.InitCtx(MK, DEL, DKEY, TDKEY, OFFSET, SERIAL) )
        {
                printf("\t\tcannot init token ctx...\n");
                return -2;
        }

        printf("\n");

        time_t start = time(NULL);

        if ( bHasTarget )
        {
                if ( !go3_token.Synchronize(TARGET) )
                {
                        printf("\t\tSynchronize() did not converge. aborted 
:(\n");
                        return -3;
                }
                else
                {
                        printf("\t\tdrif: 0x%08X...\n", 
go3_token.GetTimeDrift());

                        start += go3_token.GetTimeDrift();

                        HIT_KEY_TO_CONTINUE();
                }
        }

        int          round    = 0;
        const DWORD  WAIT_MSEC = 51;

        while ( true ) {

                char *str_time = ctime(&start);
                str_time[24] = '\0';

                go3_token.GetOTP(start);

                #if 0
                printf("\ttoken code ('%s':%03d): '%s'...\n", str_time, 
                                                        round, 
go3_token.GetOTP_Str()
                                );
                #endif

                // for database manipulation
                printf("%d;%s\n", round, go3_token.GetOTP_Str());

                #if 0
                Sleep(WAIT_MSEC);
                #endif

                start += (CDigipassGO3::GO3_PERIOD);
                round++;

                if ( round > ((72000 + 10 + 2400) ) * 6) // ~ 6 month
                        break;
                //if ( start < 0 ) // time_t are long's in Win32; overflowed!
                //      break;
        }

        return 0;
}

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