QB64 Phoenix Edition
SHA1 and TOTP - Printable Version

+- QB64 Phoenix Edition (https://qb64phoenix.com/forum)
+-- Forum: Chatting and Socializing (https://qb64phoenix.com/forum/forumdisplay.php?fid=11)
+--- Forum: General Discussion (https://qb64phoenix.com/forum/forumdisplay.php?fid=2)
+--- Thread: SHA1 and TOTP (/showthread.php?tid=2970)

Pages: 1 2


RE: SHA1 and TOTP - Ra7eN - 08-22-2024

ok FOUND the ROL,ROR
working on it now.... using the Phoenix version! didnt know it was out. I'm slow


RE: SHA1 and TOTP - SpriggsySpriggs - 08-22-2024

Perhaps this could be of some assistance?

GitHub - hellforever/sha1: SHA1 and HMAC-SHA1 implementation as an ANSI C library


RE: SHA1 and TOTP - Ra7eN - 08-22-2024

thanks! will be looking into that tonight.

this morning went meticulously through any qb64 issue, couple things I found was the _SHL and _SHR the right doesn't pad a "0" if needed _shl seems to work ok. over all things are working. but I just cannot get the right answer - just using abc for now.

I'll take a look at the c. I want to make this integrated into my pwm. Python is nice, but hate _shell-ing out

so made a simple "simulated" one

Code: (Select All)

' Simulate SHL (Shift Left) shiftedValue = (value * (2 ^ shiftAmount)) AND &HFFFFFFFF PRINT RIGHT$("00000000" + HEX$(shiftedValue), 8) ' Expected: 23456780

' Simulate SHR (Shift Right) shiftedValue = (value \ (2 ^ shiftAmount)) AND &HFFFFFFFF PRINT RIGHT$("00000000" + HEX$(shiftedValue), 8) ' Expected: 01234567


I'll take a look at the c and see what I can snag from it :-) I think I have it working, just one of the qb64 functions are not working like I want.  it is some tiny flaw somewhere


RE: SHA1 and TOTP - DSMan195276 - 08-22-2024

It's not a pure QB64 solution but I managed to hack this together for you from this C TOTP implementation. The only thing really 'missing' is a base32 converter, but that should be very easy to write in QB64 (and it looked like you already wrote that).

This is an example of using it from QB64 (I tested it with this website). To use it in your program you'll copy the `Declare Library` section into your code and then you can call the defined functions, I provided options to either have the C code get the time or for QB64 to provide the time (as Unix time):

Code: (Select All)

Declare Library "totp"
    Function getTOTPAtTime~&(hmacKey As String, keyLength As _Unsigned Long, timeStep As _Unsigned Long, currentTime As _Unsigned _Integer64)
    Function getTOTP~&(hmacKey As String, BYVAL keyLength As _Unsigned Long, BYVAL timeStep As _Unsigned Long)
End Declare

' The unencoded key
key$ = "Hello!" + CHR$(&HDE) + CHR$(&HAD) + CHR$(&HBE) + CHR$(&HEF)

Do
    Locate 2, 2
    ' Use $Right to add the leading zeros
    Print "Code: "; Right$("000000" + _Trim$(Str$(getTOTP(key$, LEN(key$), 30))), 6);
    _Limit 2
Loop Until Inkey$ <> ""

And copy this C code into a file named `totp.h` and place it next to your `.bas` file. This C code is very messy but it works fine for me:

Code: (Select All)
// MIT License

// Copyright (c) 2019 Weravech

#include <string.h>
#include <inttypes.h>
#include "time.h"

void TOTP(uint8_t* hmacKey, uint8_t keyLength, uint32_t timeStep);
uint32_t getCodeFromTimestamp(uint32_t timeStamp);

// Callable functions from QB64
uint32_t getTOTPAtTime(char *hmacKey, uint32_t keyLength, uint32_t timeStep, time_t timeStamp)
{
    TOTP((uint8_t *)hmacKey, keyLength, timeStep);
    return getCodeFromTimestamp(timeStamp);
}

uint32_t getTOTP(char *hmacKey, uint32_t keyLength, uint32_t timeStep)
{
    return getTOTPAtTime(hmacKey, keyLength, timeStep, time(NULL));
}

#define HASH_LENGTH 20
#define BLOCK_LENGTH 64

union _buffer {
  uint8_t b[BLOCK_LENGTH];
  uint32_t w[BLOCK_LENGTH/4];
} buffer;
union _state {
  uint8_t b[HASH_LENGTH];
  uint32_t w[HASH_LENGTH/4];
} state;

uint8_t bufferOffset;
uint32_t byteCount;
uint8_t keyBuffer[BLOCK_LENGTH];
uint8_t innerHash[HASH_LENGTH];

void init(void);
void initHmac(const uint8_t* secret, uint8_t secretLength);
uint8_t* result(void);
uint8_t* resultHmac(void);
void write(uint8_t);
void writeArray(uint8_t *buffer, uint8_t size);
uint32_t getCodeFromSteps(uint32_t steps);

// TOTP.c
uint8_t* _hmacKey;
uint8_t _keyLength;
uint8_t _timeZoneOffset;
uint32_t _timeStep;

// Init the library with the private key, its length and the timeStep duration
void TOTP(uint8_t* hmacKey, uint8_t keyLength, uint32_t timeStep) {
    _hmacKey = hmacKey;
    _keyLength = keyLength;
    _timeStep = timeStep;
}

// Generate a code, using the timestamp provided
uint32_t getCodeFromTimestamp(uint32_t timeStamp) {
    uint32_t steps = timeStamp / _timeStep;
    return getCodeFromSteps(steps);
}

// Generate a code, using the number of steps provided
uint32_t getCodeFromSteps(uint32_t steps) {
    // STEP 0, map the number of steps in a 8-bytes array (counter value)
    uint8_t _byteArray[8];
    _byteArray[0] = 0x00;
    _byteArray[1] = 0x00;
    _byteArray[2] = 0x00;
    _byteArray[3] = 0x00;
    _byteArray[4] = (uint8_t)((steps >> 24) & 0xFF);
    _byteArray[5] = (uint8_t)((steps >> 16) & 0xFF);
    _byteArray[6] = (uint8_t)((steps >> 8) & 0XFF);
    _byteArray[7] = (uint8_t)((steps & 0XFF));

    // STEP 1, get the HMAC-SHA1 hash from counter and key
    initHmac(_hmacKey, _keyLength);
    writeArray(_byteArray, 8);
    uint8_t* _hash = resultHmac();

    // STEP 2, apply dynamic truncation to obtain a 4-bytes string
    uint32_t _truncatedHash = 0;
    uint8_t _offset = _hash[20 - 1] & 0xF;
    uint8_t j;
    for (j = 0; j < 4; ++j) {
        _truncatedHash <<= 8;
        _truncatedHash  |= _hash[_offset + j];
    }

    // STEP 3, compute the OTP value
    _truncatedHash &= 0x7FFFFFFF;    //Disabled
    _truncatedHash %= 1000000;

    return _truncatedHash;
}
// sha1.c


#define SHA1_K0 0x5a827999
#define SHA1_K20 0x6ed9eba1
#define SHA1_K40 0x8f1bbcdc
#define SHA1_K60 0xca62c1d6

uint8_t sha1InitState[] = {
  0x01,0x23,0x45,0x67, // H0
  0x89,0xab,0xcd,0xef, // H1
  0xfe,0xdc,0xba,0x98, // H2
  0x76,0x54,0x32,0x10, // H3
  0xf0,0xe1,0xd2,0xc3  // H4
};

void init(void) {
  memcpy(state.b,sha1InitState,HASH_LENGTH);
  byteCount = 0;
  bufferOffset = 0;
}

uint32_t rol32(uint32_t number, uint8_t bits) {
  return ((number << bits) | (uint32_t)(number >> (32-bits)));
}

void hashBlock() {
  uint8_t i;
  uint32_t a,b,c,d,e,t;

  a=state.w[0];
  b=state.w[1];
  c=state.w[2];
  d=state.w[3];
  e=state.w[4];
  for (i=0; i<80; i++) {
    if (i>=16) {
      t = buffer.w[(i+13)&15] ^ buffer.w[(i+8)&15] ^ buffer.w[(i+2)&15] ^ buffer.w[i&15];
      buffer.w[i&15] = rol32(t,1);
    }
    if (i<20) {
      t = (d ^ (b & (c ^ d))) + SHA1_K0;
    } else if (i<40) {
      t = (b ^ c ^ d) + SHA1_K20;
    } else if (i<60) {
      t = ((b & c) | (d & (b | c))) + SHA1_K40;
    } else {
      t = (b ^ c ^ d) + SHA1_K60;
    }
    t+=rol32(a,5) + e + buffer.w[i&15];
    e=d;
    d=c;
    c=rol32(b,30);
    b=a;
    a=t;
  }
  state.w[0] += a;
  state.w[1] += b;
  state.w[2] += c;
  state.w[3] += d;
  state.w[4] += e;
}

void addUncounted(uint8_t data) {
  buffer.b[bufferOffset ^ 3] = data;
  bufferOffset++;
  if (bufferOffset == BLOCK_LENGTH) {
    hashBlock();
    bufferOffset = 0;
  }
}

void write(uint8_t data) {
  ++byteCount;
  addUncounted(data);

  return;
}

void writeArray(uint8_t *buffer, uint8_t size){
    while (size--) {
        write(*buffer++);
    }
}

void pad() {
  // Implement SHA-1 padding (fips180-2 ˜5.1.1)

  // Pad with 0x80 followed by 0x00 until the end of the block
  addUncounted(0x80);
  while (bufferOffset != 56) addUncounted(0x00);

  // Append length in the last 8 bytes
  addUncounted(0); // We're only using 32 bit lengths
  addUncounted(0); // But SHA-1 supports 64 bit lengths
  addUncounted(0); // So zero pad the top bits
  addUncounted(byteCount >> 29); // Shifting to multiply by 8
  addUncounted(byteCount >> 21); // as SHA-1 supports bitstreams as well as
  addUncounted(byteCount >> 13); // byte.
  addUncounted(byteCount >> 5);
  addUncounted(byteCount << 3);
}

uint8_t* result(void) {
  // Pad to complete the last block
  pad();

  // Swap byte order back
  uint8_t i;
  for (i=0; i<5; i++) {
    uint32_t a,b;
    a=state.w[i];
    b=a<<24;
    b|=(a<<8) & 0x00ff0000;
    b|=(a>>8) & 0x0000ff00;
    b|=a>>24;
    state.w[i]=b;
  }

  // Return pointer to hash (20 characters)
  return state.b;
}

#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5c

void initHmac(const uint8_t* key, uint8_t keyLength) {
  uint8_t i;
  memset(keyBuffer,0,BLOCK_LENGTH);
  if (keyLength > BLOCK_LENGTH) {
    // Hash long keys
    init();
    for (;keyLength--;) write(*key++);
    memcpy(keyBuffer,result(),HASH_LENGTH);
  } else {
    // Block length keys are used as is
    memcpy(keyBuffer,key,keyLength);
  }
  // Start inner hash
  init();
  for (i=0; i<BLOCK_LENGTH; i++) {
    write(keyBuffer[i] ^ HMAC_IPAD);
  }
}

uint8_t* resultHmac(void) {
  uint8_t i;
  // Complete inner hash
  memcpy(innerHash,result(),HASH_LENGTH);
  // Calculate outer hash
  init();
  for (i=0; i<BLOCK_LENGTH; i++) write(keyBuffer[i] ^ HMAC_OPAD);
  for (i=0; i<HASH_LENGTH; i++) write(innerHash[i]);
  return result();
}
Note that the C code does not give an option to change the digit length of the code. I believe that detail is hardcoded into `getCodeFromSteps` where it does a MOD 1000000 at the end. Assuming I'm correct on that it should be easy to support longer digit codes if needed by modifying the C code to let you pass in that value.


RE: SHA1 and TOTP - Ra7eN - 08-22-2024

I tried the c method, same issue I am currently experiencing. It produces good code, but not correct.
But I like the fact that qb64 can almost work with C

thanks for the idea. !!


RE: SHA1 and TOTP - DSMan195276 - 08-23-2024

(08-22-2024, 11:41 PM)Ra7eN Wrote: I tried the c method, same issue I am currently experiencing. It produces good code, but not correct.
But I like the fact that qb64 can almost work with C

thanks for the idea. !!
Can you clarify what issue you're having, did you try the code I posted? When I tested it, it produced correct TOTPs for me.


RE: SHA1 and TOTP - Ra7eN - 08-23-2024

(08-23-2024, 12:32 AM)DSMan195276 Wrote:
(08-22-2024, 11:41 PM)Ra7eN Wrote: I tried the c method, same issue I am currently experiencing. It produces good code, but not correct.
But I like the fact that qb64 can almost work with C

thanks for the idea. !!
Can you clarify what issue you're having, did you try the code I posted? When I tested it, it produced correct TOTPs for me.

It produced codes but not the correct one. I put my secretkey in FFXIV for example, and kept failing, went back to the python/qb64 worked perfect

I switched the following:
Code: (Select All)

key$ = "Hello!" + CHR$(&HDE) + CHR$(&HAD) + CHR$(&HBE) + CHR$(&HEF)

to
Code: (Select All)

key$ = "1234MYSECRETKEY567890" + CHR$(&HDE) + CHR$(&HAD) + CHR$(&HBE) + CHR$(&HEF)

again, codes kept popping up like normal. but they were not correct

Let me know if i did this wrong.

Thanks!


RE: SHA1 and TOTP - DSMan195276 - 08-23-2024

(08-23-2024, 12:43 AM)Ra7eN Wrote: It produced codes but not the correct one. I put my secretkey in FFXIV for example, and kept failing, went back to the python/qb64 worked perfect

I switched the following:
Code: (Select All)

key$ = "Hello!" + CHR$(&HDE) + CHR$(&HAD) + CHR$(&HBE) + CHR$(&HEF)

to
Code: (Select All)

key$ = "1234MYSECRETKEY567890" + CHR$(&HDE) + CHR$(&HAD) + CHR$(&HBE) + CHR$(&HEF)

again, codes kept popping up like normal. but they were not correct

Sorry, it's not super clear from your example, is `1234MYSECRETKEY567890` the base32 version or the binary version of your key? The C code does not do the decoding, so you have to provide it the decoded binary key.

Ex. The actual key for my test was `JBSWY3DPEHPK3PXP`, which when decoded as base32 produces the "Hello!" plus DEADBEEF string that I put in `key$`. The chances are high that your key will not be ASCII like my example one when decoded, so your key will just be a series of `CHR$()`s.


RE: SHA1 and TOTP - Ra7eN - 08-24-2024

(08-23-2024, 06:30 AM)DSMan195276 Wrote:
(08-23-2024, 12:43 AM)Ra7eN Wrote: It produced codes but not the correct one. I put my secretkey in FFXIV for example, and kept failing, went back to the python/qb64 worked perfect

I switched the following:
Code: (Select All)

key$ = "Hello!" + CHR$(&HDE) + CHR$(&HAD) + CHR$(&HBE) + CHR$(&HEF)

to
Code: (Select All)

key$ = "1234MYSECRETKEY567890" + CHR$(&HDE) + CHR$(&HAD) + CHR$(&HBE) + CHR$(&HEF)

again, codes kept popping up like normal. but they were not correct

Sorry, it's not super clear from your example, is `1234MYSECRETKEY567890` the base32 version or the binary version of your key? The C code does not do the decoding, so you have to provide it the decoded binary key.

Ex. The actual key for my test was `JBSWY3DPEHPK3PXP`, which when decoded as base32 produces the "Hello!" plus DEADBEEF string that I put in `key$`. The chances are high that your key will not be ASCII like my example one when decoded, so your key will just be a series of `CHR$()`s.
Ah good point, I did not decode the key first, I just ran the code as is. ' 1234MYSECRETKEY567890 ' this was just the represent my FFXIV key. As it did not work. However, this month long project has actually allowed me to create my own custom hash, and almost AES quality - not quite due to the lack of HMAC_SHA1 but still very happy with it. Will rewrite some of my code to accept this.
The TOTP feature, I found that I can just run a portable python and include it with my distro.; So it will run faily seamlessly. QB64 is just too challenging for me to get a TOTP. The following is almost exactly as per RFC 3174 . But for some reason, it will toss up a a code, but will not be correct.
But I will put it here for anyone else to take a stab at it. I am TOTALLY  exhausted. Its all per RFC 3174 , but yet qb will not throw the right code.

Code: (Select All)

Option _Explicit

Dim shstr As String
shstr$ = SHA1("abc")

Print shstr
'for debug copying to textfile since I can't copy from terminal window
Open "sha1.txt" For Output As #1
Print #1, shstr$
Close #1
End


Dim Shared hA As _Unsigned Long
Dim Shared hB As _Unsigned Long
Dim Shared hC As _Unsigned Long
Dim Shared hD As _Unsigned Long
Dim Shared hE As _Unsigned Long

Sub InitializeHashValues
    hA = &H67452301
    hB = &HEFCDAB89
    hC = &H98BADCFE
    hD = &H10325476
    hE = &HC3D2E1F0
End Sub


Function PadMessage$ (message As String)
    Dim messageLength As _Unsigned Long
    messageLength = Len(message) * 8 ' Length in bits

    message = message + Chr$(128) ' Append a '1' bit (0x80 in hex)

    While (Len(message) * 8) Mod 512 <> 448
        message = message + Chr$(0) ' Append '0' bits
    Wend

    Dim highPart As _Unsigned Long, lowPart As _Unsigned Long
    highPart = 0 ' For messages shorter than 2^32 bits, highPart is 0
    lowPart = messageLength And &HFFFFFFFF

    Dim lengthBytes As String

lengthBytes = CHR$((lowPart \ &H1000000) AND &HFF) + _
              CHR$((lowPart \ &H10000) AND &HFF) + _
              CHR$((lowPart \ &H100) AND &HFF) + _
              CHR$(lowPart AND &HFF)

    ' Ensure each part is correctly interpreted as 8 bits
  ' lengthBytes = Right$("00000000" + Hex$(Val(lengthBytes)), 8)
    message = message + String$(4, 0) + lengthBytes ' Append length

    PadMessage$ = message
End Function



Sub ProcessBlock (block As String)
    Dim i As _Unsigned Long
    Dim t As _Unsigned Long
    Dim TEMP As _Unsigned Long
    Dim W(79) As _Unsigned Long
    For i = 0 To 15
        W(i) = CVL(Mid$(block, i * 4 + 1, 4))
    Next

    For t = 16 To 79
        W(t) = _RoL(W(t - 3) Xor W(t - 8) Xor W(t - 14) Xor W(t - 16), 1)
    Next

    Dim aTemp As _Unsigned Long, bTemp As _Unsigned Long
    Dim cTemp As _Unsigned Long, dTemp As _Unsigned Long, eTemp As _Unsigned Long
    aTemp = hA: bTemp = hB: cTemp = hC: dTemp = hD: eTemp = hE

    For t = 0 To 79
        For t = 0 To 79
            Select Case t
                Case 0 To 19
                    TEMP = (_RoL(aTemp, 5) + ((bTemp And cTemp) Or ((Not bTemp) And dTemp)) + eTemp + W(t) + &H5A827999) And &HFFFFFFFF
                Case 20 To 39
                    TEMP = (_RoL(aTemp, 5) + (bTemp Xor cTemp Xor dTemp) + eTemp + W(t) + &H6ED9EBA1) And &HFFFFFFFF
                Case 40 To 59
                    TEMP = (_RoL(aTemp, 5) + ((bTemp And cTemp) Or (bTemp And dTemp) Or (cTemp And dTemp)) + eTemp + W(t) + &H8F1BBCDC) And &HFFFFFFFF
                Case 60 To 79
                    TEMP = (_RoL(aTemp, 5) + (bTemp Xor cTemp Xor dTemp) + eTemp + W(t) + &HCA62C1D6) And &HFFFFFFFF
            End Select
        Next
        eTemp = dTemp
        dTemp = cTemp
        cTemp = _RoL(bTemp, 30)
        bTemp = aTemp
        aTemp = TEMP
    Next

    hA = (hA + aTemp) And &HFFFFFFFF
    hB = (hB + bTemp) And &HFFFFFFFF
    hC = (hC + cTemp) And &HFFFFFFFF
    hD = (hD + dTemp) And &HFFFFFFFF
    hE = (hE + eTemp) And &HFFFFFFFF
End Sub


Function SHA1$ (message As String)
    ' Initialize the hash constants
    InitializeHashValues

    ' Preprocess the message (pad it)
    message = PadMessage(message)

    ' Process each 512-bit block
    Dim i As Long
    For i = 1 To Len(message) Step 64
        ProcessBlock Mid$(message, i, 64)
    Next

    ' Convert each part of the hash to a hexadecimal string and trim it
    Dim hashA As String, hashB As String, hashC As String, hashD As String, hashE As String
    hashA = LCase$(_Trim$(Hex$(hA)))
    hashB = LCase$(_Trim$(Hex$(hB)))
    hashC = LCase$(_Trim$(Hex$(hC)))
    hashD = LCase$(_Trim$(Hex$(hD)))
    hashE = LCase$(_Trim$(Hex$(hE)))

    ' Concatenate the final hash string explicitly
    Dim finalHash As String
    finalHash = hashA + hashB + hashC + hashD + hashE

    ' Return the concatenated and trimmed hash
    SHA1$ = finalHash
End Function




RE: SHA1 and TOTP - Kernelpanic - 08-24-2024

Why still use SHA1? Microsoft has withdrawn support for it since 2021 due to problems was with it.

AKTUALISIERUNG: SHA-1-signierte Inhalte werden zurückgezogen