TOTP 100% qb64 Yup - sorta LOL - Ra7eN - 06-02-2025
OK I cant do it any more - months of testing and I can no longer see the forest for the trees as they say.
I've been working on adding full TOTP generation natively in QB64 — no DLLs, no Python, just raw QB64. This is for a personal PWM it ahve been working on for years, I just keep adding to it LOL, and now I want an internal 2FA. I’ve made serious progress, but I’m hitting a brick wall with final output not matching the expected 6-digit codes (e.g., not matching 287082 with known test vectors like GEZDGNBVGY3TQOJQ... at counter 0).
https://totp.danhersam.com/
Here’s what I’ve already implemented:
- Full Base32 decoding
- SHA1 hashing (based on FIPS PUB 180-1, all QB64)
- HMAC-SHA1 (manual, with XOR'd opad/ipad)
- 8-byte big-endian counter
- All code modularized in a totp.bi file
- Verified Base32 decoded output = 20 bytes (correct)
- Confirmed HMAC length = 20 bytes (SHA1 digest)
- Offset and truncation logic implemented using RFC 4226 spec
- Constants like 30 sec interval, 6-digit truncation, etc.
I’ve tested this against Google Authenticator keys and various online generators — still getting invalid results. We even tried printing hashes in hex and stepping through the logic. Everything looks structurally sound.
I used this page for help.
https://datatracker.ietf.org/doc/html/rfc4226#section-7
I’m guessing the fault is here:
Either QB64’s string handling is off for binary bytes
Or something’s fishy in bit shifts / unsigned math in QB64
I’ve included the .BI file if anyone wants to tinker. I’m all ears if someone knows a way QB64 internally misbehaves with binary string math or if any fixes exist I missed. Would love to see this baked into native QB64 someday — or even just confirmed if it’s impossible without external help.
currently went back to
Code: (Select All)
SUB GENERATE_TOTP (encryptedTOTPSecret AS STRING)
DIM decryptedSecret AS STRING
DIM totp_code AS STRING
DIM cmd AS STRING
DIM F AS INTEGER
' Check for missing or clearly invalid secret
IF LEN(_TRIM$(encryptedTOTPSecret)) < 10 THEN
LWRITELN "`4Missing or invalid TOTP secret. Cannot generate code."
PAUSE 24
EXIT SUB
END IF
' Decrypt the stored secret
decryptedSecret = EncryptStr$(_TRIM$(encryptedTOTPSecret), vaultKey$, "d")
' Build the command to call the TOTP generator
cmd = "cmd /c py\generate_totp.exe " + decryptedSecret
SHELL _HIDE cmd
' Check if output file was created
IF NOT _FILEEXISTS("totp_code.txt") THEN
LWRITELN "`4TOTP generation failed. Output file not found."
PAUSE 24
EXIT SUB
END IF
' Read the generated TOTP code
F = FREEFILE
OPEN "totp_code.txt" FOR INPUT AS #F
LINE INPUT #F, totp_code
totp_code = DigitsOnly$(_TRIM$(totp_code))
CLOSE #F
' Sanity check output
IF LEN(totp_code) <> 6 OR DigitsOnly$(totp_code) <> totp_code THEN
LWRITELN "`4TOTP generation failed. Invalid output: " + totp_code
ELSE
_CLIPBOARD$ = totp_code
PRINT "TOTP code generated and copied to clipboard: " + totp_code
END IF
' Clean up
KILL "totp_code.txt"
PAUSE 24
END SUB
With python its super easy (which I converted to .exe for portability):
Code: (Select All)
import pyotp
import sys
def generate_totp(secret_key):
totp = pyotp.TOTP(secret_key)
return totp.now()
if __name__ == "__main__":
secret_key = sys.argv[1]
totp_code = generate_totp(secret_key)
with open("totp_code.txt", "w") as f:
f.write(totp_code)
Thanks in advance. You guys are who I come to when the engine starts smoking.
here is all the **cough** maigc , just can't seem to pull a rabbit LOL!!
Also line: 102 I have code=0 for testing. Comment out below to see it live. You will also need the following const if you turn this into an app to test
Code: (Select All)
' -- Constants used for totp.bi
CONST TOTP_INTERval = 30
CONST TOTP_DIGITS = 6
CONST TOTP_TIME_OFFSET = 0 ' Adjust if system time is skewed
FILE: TOTP.BI
Code: (Select All)
' genTOTP.bi - Internal TOTP generation module for QB64
' -- Helper: Base32 decoding
FUNCTION Base32Decode$ (inputStr AS STRING)
DIM alphabet AS STRING: alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
DIM buffer AS STRING, outputStr AS STRING
DIM i AS INTEGER, bits AS INTEGER, value AS LONG, ch AS STRING
FOR i = 1 TO LEN(inputStr)
ch = MID$(UCASE$(inputStr), i, 1)
value = INSTR(alphabet, ch) - 1
IF value >= 0 THEN
buffer = buffer + RIGHT$("00000" + BIN$(value AND 31), 5)
END IF
NEXT
FOR i = 1 TO LEN(buffer) STEP 8
IF i + 7 <= LEN(buffer) THEN
outputStr = outputStr + CHR$(VAL("&B" + MID$(buffer, i, 8)))
END IF
NEXT
Base32Decode$ = outputStr
END FUNCTION
FUNCTION BIN$ (value AS _UNSIGNED LONG)
DIM result AS STRING
DO
result = LTRIM$(STR$(value MOD 2)) + result
value = value \ 2
LOOP UNTIL value = 0
BIN$ = result
END FUNCTION
' -- Helper: Convert number to 8-byte big-endian
FUNCTION IntToBytes$ (counter AS _UNSIGNED _INTEGER64)
DIM i AS INTEGER
DIM b AS STRING
DIM byteVal AS _UNSIGNED _BYTE
FOR i = 7 TO 0 STEP -1
byteVal = (counter \ (2 ^ (i * 8))) AND &HFF
b = b + CHR$(byteVal)
NEXT
IntToBytes$ = b
END FUNCTION
FUNCTION GetUnixTime~&&
DIM y AS INTEGER, m AS INTEGER, d AS INTEGER
y = VAL(LEFT$(DATE$, 4))
m = VAL(MID$(DATE$, 6, 2))
d = VAL(RIGHT$(DATE$, 2))
GetUnixTime~&& = DaysSinceEpoch(y, m, d) * 86400 + TIMER
END FUNCTION
FUNCTION DaysSinceEpoch~& (y, m, d)
DIM days AS LONG, i AS INTEGER
DIM mdays(12) AS INTEGER
mdays(1) = 31: mdays(2) = 28: mdays(3) = 31: mdays(4) = 30
mdays(5) = 31: mdays(6) = 30: mdays(7) = 31: mdays(8) = 31
mdays(9) = 30: mdays(10) = 31: mdays(11) = 30: mdays(12) = 31
days = 0
FOR i = 1970 TO y - 1
IF (i MOD 4 = 0 AND i MOD 100 <> 0) OR (i MOD 400 = 0) THEN
days = days + 366
ELSE
days = days + 365
END IF
NEXT
IF (y MOD 4 = 0 AND y MOD 100 <> 0) OR (y MOD 400 = 0) THEN
mdays(2) = 29
END IF
FOR i = 1 TO m - 1
days = days + mdays(i)
NEXT
days = days + (d - 1)
DaysSinceEpoch~& = days
END FUNCTION
' -- TOTP generation
FUNCTION GenerateTOTP$ (base32Secret$)
DIM keyStr AS STRING, counter AS _UNSIGNED _INTEGER64
DIM hash AS STRING, offset AS INTEGER, code AS LONG
DIM binCounter AS STRING
DIM unixTime AS _UNSIGNED _INTEGER64
unixTime = GetUnixTime~&&
counter = (unixTime + TOTP_TIME_OFFSET) \ TOTP_INTERval
keyStr = Base32Decode$(base32Secret$)
counter = 0
'counter = (TIMER + TOTP_TIME_OFFSET) \ TOTP_INTERval
binCounter$ = IntToBytes$(counter)
hash$ = HMAC_SHA1$(keyStr$, binCounter$)
IF LEN(hash$) = 0 THEN
GenerateTOTP$ = "ERROR"
EXIT FUNCTION
END IF
offset = ASC(RIGHT$(hash$, 1)) AND &HF
DIM segment AS STRING
segment = MID$(hash$, offset + 1, 4)
code = BytesToLong~&(segment) AND &H7FFFFFFF
code = code MOD (10 ^ TOTP_DIGITS)
GenerateTOTP$ = RIGHT$("000000" + LTRIM$(STR$(code)), TOTP_DIGITS)
END FUNCTION
' === SHA1 Function ===
' Returns binary digest of the message
FUNCTION SHA1$ (message$)
' Based on FIPS PUB 180-1. Credit to open-source BASIC implementations.
DIM A AS _UNSIGNED LONG, B AS _UNSIGNED LONG, C AS _UNSIGNED LONG
DIM D AS _UNSIGNED LONG, E AS _UNSIGNED LONG, F AS _UNSIGNED LONG
DIM b1 AS _UNSIGNED LONG, b2 AS _UNSIGNED LONG, b3 AS _UNSIGNED LONG, b4 AS _UNSIGNED LONG
DIM K AS _UNSIGNED LONG, TEMP AS _UNSIGNED LONG
DIM H0 AS _UNSIGNED LONG: H0 = &H67452301
DIM H1 AS _UNSIGNED LONG: H1 = &HEFCDAB89
DIM H2 AS _UNSIGNED LONG: H2 = &H98BADCFE
DIM H3 AS _UNSIGNED LONG: H3 = &H10325476
DIM H4 AS _UNSIGNED LONG: H4 = &HC3D2E1F0
DIM padded AS STRING, chunk AS STRING
DIM W(80) AS _UNSIGNED LONG
DIM i AS INTEGER, j AS INTEGER
' Padding
padded$ = message$ + CHR$(&H80)
DO WHILE (LEN(padded$) MOD 64) <> 56
padded$ = padded$ + CHR$(0)
LOOP
' Append original length in bits
DIM bitLen AS _UNSIGNED _INTEGER64: bitLen = LEN(message$) * 8
FOR i = 7 TO 0 STEP -1
padded$ = padded$ + CHR$((bitLen \ (2 ^ (i * 8))) AND &HFF)
NEXT
' Process each 512-bit chunk
FOR i = 1 TO LEN(padded$) STEP 64
chunk = MID$(padded$, i, 64)
' Break chunk into 16 big-endian words
FOR j = 0 TO 15
b1 = ASC(MID$(chunk, j * 4 + 1, 1))
b2 = ASC(MID$(chunk, j * 4 + 2, 1))
b3 = ASC(MID$(chunk, j * 4 + 3, 1))
b4 = ASC(MID$(chunk, j * 4 + 4, 1))
W(j) = _SHL(b1, 24) OR _SHL(b2, 16) OR _SHL(b3, 8) OR b4
NEXT
' Extend words
FOR j = 16 TO 79
W(j) = _SHL(W(j - 3) XOR W(j - 8) XOR W(j - 14) XOR W(j - 16), 1) OR _
_SHR(W(j - 3) XOR W(j - 8) XOR W(j - 14) XOR W(j - 16), 31)
NEXT
A = H0: B = H1: C = H2: D = H3: E = H4
FOR j = 0 TO 79
SELECT CASE j
CASE j <= 19: F = (B AND C) OR ((NOT B) AND D): K = &H5A827999
CASE j <= 39: F = B XOR C XOR D: K = &H6ED9EBA1
CASE j <= 59: F = (B AND C) OR (B AND D) OR (C AND D): K = &H8F1BBCDC
CASE ELSE: F = B XOR C XOR D: K = &HCA62C1D6
END SELECT
TEMP = (_SHL(A, 5) OR _SHR(A, 27)) + F + E + K + W(j)
E = D: D = C
C = (_SHL(B, 30) OR _SHR(B, 2))
B = A: A = TEMP
NEXT
H0 = (H0 + A) AND &HFFFFFFFF
H1 = (H1 + B) AND &HFFFFFFFF
H2 = (H2 + C) AND &HFFFFFFFF
H3 = (H3 + D) AND &HFFFFFFFF
H4 = (H4 + E) AND &HFFFFFFFF
NEXT
' Output 20-byte binary hash
' Output 20-byte binary hash
SHA1$ = ""
FOR i = 0 TO 4
SELECT CASE i
CASE 0: TEMP = H0
CASE 1: TEMP = H1
CASE 2: TEMP = H2
CASE 3: TEMP = H3
CASE 4: TEMP = H4
END SELECT
SHA1$ = SHA1$ + CHR$((TEMP \ 2 ^ 24) AND &HFF) + _
CHR$((TEMP \ 2 ^ 16) AND &HFF) + _
CHR$((TEMP \ 2 ^ 8) AND &HFF) + _
CHR$(TEMP AND &HFF)
NEXT
END FUNCTION
FUNCTION HMAC_SHA1$ (inputKey$, inputMsg$)
CONST BLOCK_SIZE = 64
DIM i AS INTEGER
DIM oPad$, iPad$, keyStr$
' Ensure key is 20-byte binary SHA1 if too long
keyStr$ = inputKey$
IF LEN(keyStr$) > BLOCK_SIZE THEN keyStr$ = SHA1$(keyStr$)
keyStr$ = keyStr$ + STRING$(BLOCK_SIZE - LEN(keyStr$), CHR$(0))
FOR i = 1 TO BLOCK_SIZE
oPad$ = oPad$ + CHR$(ASC(MID$(keyStr$, i, 1)) XOR &H5C)
iPad$ = iPad$ + CHR$(ASC(MID$(keyStr$, i, 1)) XOR &H36)
NEXT
' SHA1$ must return 20-byte binary digest
HMAC_SHA1$ = SHA1$(oPad$ + SHA1$(iPad$ + inputMsg$))
END FUNCTION
FUNCTION BytesToLong~& (s AS STRING)
BytesToLong~& = (_SHL(ASC(MID$(s, 1, 1)), 24) OR _
_SHL(ASC(MID$(s, 2, 1)), 16) OR _
_SHL(ASC(MID$(s, 3, 1)), 8) OR _
ASC(MID$(s, 4, 1)))
END FUNCTION
I think we can do this - and don't tell me this has already been done, will hurt my feeling ahaha. I spent a long time on this - and it is not working
RE: TOTP 100% qb64 Yup - sorta LOL - aadityap0901 - 06-02-2025
This is actually one of the best thing out there to be a project.
I have worked on the SHA256 algorithm yesterday: Github: SHA2
and I made some functions for managing rightRotate, LongToHex, BytesToLong, LongToBits, and a couple more.
Here they are:
__ to bits:
Code: (Select All)
Function ByteToBits$ (__BYTE As _Unsigned _Byte)
Dim __I As _Unsigned _Byte
Dim __O$8
For __I = 0 To 7
If _ReadBit(__BYTE, __I) Then Asc(__O$8, 1 + __I) = 49 Else Asc(__O$8, 1 + __I) = 48
Next __I
ByteToBits$ = __O$8
End Function
Function IntegerToBits$ (__A As _Unsigned Integer)
Dim __I As _Unsigned _Byte
Dim __O$16
For __I = 0 To 15
If _ReadBit(__A, 15 - __I) Then Asc(__O$16, 1 + __I) = 49 Else Asc(__O$16, 1 + __I) = 48
Next __I
IntegerToBits$ = __O$16
End Function
Function LongToBits$ (__A As _Unsigned Long)
Dim __I As _Unsigned _Byte
Dim __O$32
For __I = 0 To 31
If _ReadBit(__A, 31 - __I) Then Asc(__O$32, 1 + __I) = 49 Else Asc(__O$32, 1 + __I) = 48
Next __I
LongToBits$ = __O$32
End Function
Function Integer64ToBits$ (__A As _Unsigned _Integer64)
Dim __I As _Unsigned _Byte
Dim __O$64
For __I = 0 To 63
If _ReadBit(__A, 63 - __I) Then Asc(__O$64, 1 + __I) = 49 Else Asc(__O$64, 1 + __I) = 48
Next __I
Integer64ToBits$ = __O$64
End Function
__ to hex:
Code: (Select All)
Function ByteToHex$ (__A~%%)
__H$ = Hex$(__A~%%)
ByteToHex$ = String$(2 - Len(__H$), 48) + __H$
End Function
Function IntegerToHex$ (__A~%)
__H$ = Hex$(__A~%)
IntegerToHex$ = String$(4 - Len(__H$), 48) + __H$
End Function
Function LongToHex$ (__A~&)
__H$ = Hex$(__A~&)
LongToHex$ = String$(8 - Len(__H$), 48) + __H$
End Function
Function Integer64ToHex$ (__A~&&)
__H$ = Hex$(__A~&&)
Integer64ToHex$ = String$(16 - Len(__H$), 48) + __H$
End Function
rightRotate:
Code: (Select All)
Function rightRotateLong~& (A~&, B~%%)
rightRotateLong~& = _SHR(A~&, B~%%) Or _SHL(A~&, 32 - B~%%)
End Function
Function rightRotateInteger64~& (A~&&, B~%%)
rightRotateInteger64~& = _SHR(A~&&, B~%%) Or _SHL(A~&&, 32 - B~%%)
End Function
reverseCVL: (an alternative to your BytesToLong~&)
Code: (Select All)
Function ReverseCVL~& (__A$4)
ReverseCVL~& = Asc(__A$4, 4) Or _SHL(Asc(__A$4, 3), 8) Or _SHL(Asc(__A$4, 2), 16) Or _SHL(Asc(__A$4, 1), 24)
End Function
The problem with using Bits is the Big-Endian and Little-Endian, I've had to debug them myself for minutes...
reverseCVI64: (for _integer64)
Code: (Select All)
Function ReverseCVI64~&& (__A$8)
ReverseCVI64~&& = Asc(__A$8, 8) Or _SHL(Asc(__A$8, 7), 8) Or _SHL(Asc(__A$8, 6), 16) Or _SHL(Asc(__A$8, 5), 24) Or _SHL(Asc(__A$8, 4), 32) Or _SHL(Asc(__A$8, 3), 40) Or _SHL(Asc(__A$8, 2), 48) Or _SHL(Asc(__A$8, 1), 56)
End Function
I might also suggest you a couple of things, to optimize your algorithms:
1. Use ASC(S$, I) instead of ASC(MID$(I$, I, 1)), it is faster
2. Declare variables static, which you think will not affect the output of your algorithm... if not assigned to zero.
3. While calculating SHA1$, you can do this to make it fast: (provided you are using H(0 to 4) instead of H0, H1, H2, H3, H4)
Code: (Select All)
SHA1$ = String$(20, 0)
For I = 0 To 4
Mid$(SHA1$, I * 4 + 1, 4) = MKL$(H(I))
Next I
You can refer to my sha256 algorithm, for optimizations.
I don't think anyone made this thing before, you are the first one.
RE: TOTP 100% qb64 Yup - sorta LOL - Ra7eN - 06-02-2025
Hey thanks man, seriously appreciate the time you took here. Your breakdown’s sharp and definitely shows the kind of detail this stuff needs. I’m gonna dig through the functions you posted — especially the bit/hex converters and that reverse CVL/CVI64 trick — those look like they might simplify some of the chaos I was running into with endianness and raw buffer parsing in my version.
Just curious though: would this be compatible with a self-contained TOTP function if I sub in SHA1 instead of SHA256? From what I’ve seen, the TOTP standard still relies on HMAC-SHA1 by default (I know SHA256 is an option but it's not default). Did you get your SHA256 version working for time-based codes yet, or are you thinking it could be ported?
Also side note, I totally agree — doing this from scratch in QB64 is half-pain, half-fun. But I come here when I’m stuck deep because y’all make it less painful ?
RE: TOTP 100% qb64 Yup - sorta LOL - Ra7eN - 06-02-2025
if you're curious what the beta looks like, you can grab it here.
Yes, I’m a total L.O.R.D. obsessive — I’ve built versions in Pascal, QB64, and PHP — and now I wanted a PWM that actually fits me. This one’s close to flawless, though a few things still need ironing out.
2FA is working (TOTP is handled externally via a Python EXE in the py folder)
Currently compiled for Windows 10 — not tested on Win7 or Linux yet
Encryption is... okay. It's offline and for personal use, so it’s good enough for now.
Next up, need help with an AES clone (or close to it), so if anyone has code ideas for that, I’m all ears. Right now, the so-so security is tied to the user.
Sorry if this post is out of place — just excited to show what I’ve been building and where it’s headed and any complex security help - if removed I get it LOL!
https://www.mediafire.com/file/0fgxho0qzjwfl0q/TPWM_LORD_2025_V0.27.01b.zip/file
RE: TOTP 100% qb64 Yup - sorta LOL - aadityap0901 - 06-03-2025
(06-02-2025, 08:23 PM)Ra7eN Wrote: Hey thanks man, seriously appreciate the time you took here. Your breakdown’s sharp and definitely shows the kind of detail this stuff needs. I’m gonna dig through the functions you posted — especially the bit/hex converters and that reverse CVL/CVI64 trick — those look like they might simplify some of the chaos I was running into with endianness and raw buffer parsing in my version.
Just curious though: would this be compatible with a self-contained TOTP function if I sub in SHA1 instead of SHA256? From what I’ve seen, the TOTP standard still relies on HMAC-SHA1 by default (I know SHA256 is an option but it's not default). Did you get your SHA256 version working for time-based codes yet, or are you thinking it could be ported?
Also side note, I totally agree — doing this from scratch in QB64 is half-pain, half-fun. But I come here when I’m stuck deep because y’all make it less painful ? I also ran in this reverseCVL issue a year ago when I was working on the previous version of SHA256...
This SHA256 is working correctly, and it can be used for TOTP.
(06-02-2025, 08:45 PM)Ra7eN Wrote: if you're curious what the beta looks like, you can grab it here.
Yes, I’m a total L.O.R.D. obsessive — I’ve built versions in Pascal, QB64, and PHP — and now I wanted a PWM that actually fits me. This one’s close to flawless, though a few things still need ironing out.
2FA is working (TOTP is handled externally via a Python EXE in the py folder)
Currently compiled for Windows 10 — not tested on Win7 or Linux yet
Encryption is... okay. It's offline and for personal use, so it’s good enough for now.
Next up, need help with an AES clone (or close to it), so if anyone has code ideas for that, I’m all ears. Right now, the so-so security is tied to the user.
Sorry if this post is out of place — just excited to show what I’ve been building and where it’s headed and any complex security help - if removed I get it LOL!
https://www.mediafire.com/file/0fgxho0qzjwfl0q/TPWM_LORD_2025_V0.27.01b.zip/file I have an AES clone attached here, which I use in a password manager...
It is not perfectly AES, but similar to it.
RE: TOTP 100% qb64 Yup - sorta LOL - Ra7eN - 06-03-2025
I'll have to check to see if i can use sha256, my understanding with google is that it prefers sha1 for compatibility across other 2FA apps.. I'll get back to you on that one.
RE: TOTP 100% qb64 Yup - sorta LOL - Ra7eN - 06-03-2025
(06-03-2025, 05:40 AM)aadityap0901 Wrote: I have an AES clone attached here, which I use in a password manager...
It is not perfectly AES, but similar to it. Here is my attempt.. it is pretty solid, gets rid of the dead wieght at the end. I noticed yours had some unprintables. maybe we can work together.. see if we can get a aes clone
Pretty basic
Code: (Select All)
secretMessage = SleeperCrypt$ ( "hello world", key, "e"["d"])
What it DOES NOT do:- GF(2^8) math (for mixcolumns) Way to slow for qb64
- Bit shifts > 1 bit I did find a way to emulate SHL/SHR after I did this, but for this simple script it does not do it
- Byte masking
Code: (Select All)
DIM SHARED SBox(0 TO 255) AS INTEGER
SUB InitSBox
DIM sData AS STRING
DIM i AS INTEGER
'had to redo all this because qb64 does not do:
' sData = sData + CHR$(&H63, &H7C, &H77, &H7B, &HF2, &H6B, &H6F, &HC5, &H30, &H1, &H67, &H2B, &HFE, &HD7, &HAB, &H76)
sData = ""
sData = sData + CHR$(&H63) + CHR$(&H7C) + CHR$(&H77) + CHR$(&H7B) + CHR$(&HF2) + CHR$(&H6B) + CHR$(&H6F) + CHR$(&HC5)
sData = sData + CHR$(&H30) + CHR$(&H01) + CHR$(&H67) + CHR$(&H2B) + CHR$(&HFE) + CHR$(&HD7) + CHR$(&HAB) + CHR$(&H76)
sData = sData + CHR$(&HCA) + CHR$(&H82) + CHR$(&HC9) + CHR$(&H7D) + CHR$(&HFA) + CHR$(&H59) + CHR$(&H47) + CHR$(&HF0)
sData = sData + CHR$(&HAD) + CHR$(&HD4) + CHR$(&HA2) + CHR$(&HAF) + CHR$(&H9C) + CHR$(&HA4) + CHR$(&H72) + CHR$(&HC0)
sData = sData + CHR$(&HB7) + CHR$(&HFD) + CHR$(&H93) + CHR$(&H26) + CHR$(&H36) + CHR$(&H3F) + CHR$(&HF7) + CHR$(&HCC)
sData = sData + CHR$(&H34) + CHR$(&HA5) + CHR$(&HE5) + CHR$(&HF1) + CHR$(&H71) + CHR$(&HD8) + CHR$(&H31) + CHR$(&H15)
sData = sData + CHR$(&H04) + CHR$(&HC7) + CHR$(&H23) + CHR$(&HC3) + CHR$(&H18) + CHR$(&H96) + CHR$(&H05) + CHR$(&H9A)
sData = sData + CHR$(&H07) + CHR$(&H12) + CHR$(&H80) + CHR$(&HE2) + CHR$(&HEB) + CHR$(&H27) + CHR$(&HB2) + CHR$(&H75)
sData = sData + CHR$(&H09) + CHR$(&H83) + CHR$(&H2C) + CHR$(&H1A) + CHR$(&H1B) + CHR$(&H6E) + CHR$(&H5A) + CHR$(&HA0)
sData = sData + CHR$(&H52) + CHR$(&H3B) + CHR$(&HD6) + CHR$(&HB3) + CHR$(&H29) + CHR$(&HE3) + CHR$(&H2F) + CHR$(&H84)
sData = sData + CHR$(&H53) + CHR$(&HD1) + CHR$(&H00) + CHR$(&HED) + CHR$(&H20) + CHR$(&HFC) + CHR$(&HB1) + CHR$(&H5B)
sData = sData + CHR$(&H6A) + CHR$(&HCB) + CHR$(&HBE) + CHR$(&H39) + CHR$(&H4A) + CHR$(&H4C) + CHR$(&H58) + CHR$(&HCF)
sData = sData + CHR$(&HD0) + CHR$(&HEF) + CHR$(&HAA) + CHR$(&HFB) + CHR$(&H43) + CHR$(&H4D) + CHR$(&H33) + CHR$(&H85)
sData = sData + CHR$(&H45) + CHR$(&HF9) + CHR$(&H02) + CHR$(&H7F) + CHR$(&H50) + CHR$(&H3C) + CHR$(&H9F) + CHR$(&HA8)
sData = sData + CHR$(&H51) + CHR$(&HA3) + CHR$(&H40) + CHR$(&H8F) + CHR$(&H92) + CHR$(&H9D) + CHR$(&H38) + CHR$(&HF5)
sData = sData + CHR$(&HBC) + CHR$(&HB6) + CHR$(&HDA) + CHR$(&H21) + CHR$(&H10) + CHR$(&HFF) + CHR$(&HF3) + CHR$(&HD2)
sData = sData + CHR$(&HCD) + CHR$(&H0C) + CHR$(&H13) + CHR$(&HEC) + CHR$(&H5F) + CHR$(&H97) + CHR$(&H44) + CHR$(&H17)
sData = sData + CHR$(&HC4) + CHR$(&HA7) + CHR$(&H7E) + CHR$(&H3D) + CHR$(&H64) + CHR$(&H5D) + CHR$(&H19) + CHR$(&H73)
sData = sData + CHR$(&H60) + CHR$(&H81) + CHR$(&H4F) + CHR$(&HDC) + CHR$(&H22) + CHR$(&H2A) + CHR$(&H90) + CHR$(&H88)
sData = sData + CHR$(&H46) + CHR$(&HEE) + CHR$(&HB8) + CHR$(&H14) + CHR$(&HDE) + CHR$(&H5E) + CHR$(&H0B) + CHR$(&HDB)
sData = sData + CHR$(&HE0) + CHR$(&H32) + CHR$(&H3A) + CHR$(&H0A) + CHR$(&H49) + CHR$(&H06) + CHR$(&H24) + CHR$(&H5C)
sData = sData + CHR$(&HC2) + CHR$(&HD3) + CHR$(&HAC) + CHR$(&H62) + CHR$(&H91) + CHR$(&H95) + CHR$(&HE4) + CHR$(&H79)
sData = sData + CHR$(&HE7) + CHR$(&HC8) + CHR$(&H37) + CHR$(&H6D) + CHR$(&H8D) + CHR$(&HD5) + CHR$(&H4E) + CHR$(&HA9)
sData = sData + CHR$(&H6C) + CHR$(&H56) + CHR$(&HF4) + CHR$(&HEA) + CHR$(&H65) + CHR$(&H7A) + CHR$(&HAE) + CHR$(&H08)
sData = sData + CHR$(&HBA) + CHR$(&H78) + CHR$(&H25) + CHR$(&H2E) + CHR$(&H1C) + CHR$(&HA6) + CHR$(&HB4) + CHR$(&HC6)
sData = sData + CHR$(&HE8) + CHR$(&HDD) + CHR$(&H74) + CHR$(&H1F) + CHR$(&H4B) + CHR$(&HBD) + CHR$(&H8B) + CHR$(&H8A)
sData = sData + CHR$(&H70) + CHR$(&H3E) + CHR$(&HB5) + CHR$(&H66) + CHR$(&H48) + CHR$(&H03) + CHR$(&HF6) + CHR$(&H0E)
sData = sData + CHR$(&H61) + CHR$(&H35) + CHR$(&H57) + CHR$(&HB9) + CHR$(&H86) + CHR$(&HC1) + CHR$(&H1D) + CHR$(&H9E)
sData = sData + CHR$(&HE1) + CHR$(&HF8) + CHR$(&H98) + CHR$(&H11) + CHR$(&H69) + CHR$(&HD9) + CHR$(&H8E) + CHR$(&H94)
sData = sData + CHR$(&H9B) + CHR$(&H1E) + CHR$(&H87) + CHR$(&HE9) + CHR$(&HCE) + CHR$(&H55) + CHR$(&H28) + CHR$(&HDF)
sData = sData + CHR$(&H8C) + CHR$(&HA1) + CHR$(&H89) + CHR$(&H0D) + CHR$(&HBF) + CHR$(&HE6) + CHR$(&H42) + CHR$(&H68)
sData = sData + CHR$(&H41) + CHR$(&H99) + CHR$(&H2D) + CHR$(&H0F) + CHR$(&HB0) + CHR$(&H54) + CHR$(&HBB) + CHR$(&H16)
FOR i = 0 TO 255
SBox(i) = ASC(sData, i + 1)
NEXT
END SUB
FUNCTION SleeperCrypt$ (plainText AS STRING, keyString AS STRING, method AS STRING)
DIM inputText AS STRING
inputText = plainText
DIM result AS STRING
DIM i AS INTEGER, j AS INTEGER, round AS INTEGER
DIM value AS INTEGER, keyByte AS INTEGER, keyPos AS INTEGER
DIM blockSize AS INTEGER: blockSize = 16
' Pad key to at least blockSize
IF LEN(keyString) < blockSize THEN
DO WHILE LEN(keyString) < blockSize
keyString = keyString + keyString
LOOP
END IF
keyString = LEFT$(keyString, blockSize)
method = LCASE$(method)
result = plainText
FOR round = 1 TO 5 ' Do 5 rounds for added strength
inputText = result
result = ""
FOR i = 1 TO LEN(inputText)
keyPos = ((i - 1) MOD blockSize) + 1
keyByte = ASC(keyString, keyPos)
value = ASC(inputText, i)
IF method = "e" THEN
value = value XOR keyByte
value = (value * 2) AND 255 ' Simulate ROL1
value = SBox(value)
ELSE
' Reverse S-Box
FOR j = 0 TO 255
IF SBox(j) = value THEN value = j: EXIT FOR
NEXT
value = (value \ 2) OR ((value AND 1) * 128) ' Simulate ROR1
value = value XOR keyByte
END IF
result = result + CHR$(value)
NEXT
NEXT
SleeperCrypt$ = result
END FUNCTION
RE: TOTP 100% qb64 Yup - sorta LOL - Jack - 06-03-2025
I have only briefly scanned this thread but I gather that in order to implement the Time-based One-Time Password you need some form of encryption, there's a public domain C library https://www.libtom.net/LibTomCrypt/
Quote:Ciphers Supported:- AES (aka Rijndael)
- Anubis (with optional tweak as proposed by the developers)
- Blowfish
- CAST5
- Camellia
- DES, two-key 3DES, 3DES
- KASUMI
- Khazad
- Multi2
- Noekeon
- RC2
- RC5
- RC6
- SAFER (K64, SK64, K128, SK128)
- SAFER+
- SEED
- Skipjack
- Twofish
- XTEA
maybe you can use that library or borrow some code/ideas
RE: TOTP 100% qb64 Yup - sorta LOL - Ra7eN - 06-04-2025
I redid the encryption on my app, remember I am not building a PWM for military use ahahahaha, Just something local but needs to be fairly strong. I call this AES LITE, because I thought it was ALMOST as good. Back when I wrote this - I thought it was amazing LOL, However recently after reading how AES performcs, and qb64 doesn't do a good job handling much o it, I found out it wasn't as good as I thought... So don't laugh. Any tips?? Greatly appreciated. Again it is for local and offline, so I think it does the trick.
But I like it. secure where I need it. In the app, each record has it's own key, so no two record will have the same key.
Code: (Select All)
' aesLite.bi
' AES-lite with XOR, round key derivation, and mode handling
' ================================
' Function: keyGen$
' Returns a 32-character random key from uppercase letters and numbers
' ================================
Function keyGen$ ()
Dim theKey As String, pool As String, ch As String * 1
pool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
Do
ch = Mid$(pool, Int(Rnd * Len(pool)) + 1, 1)
theKey = theKey + ch
Loop Until Len(theKey) = 32
keyGen$ = theKey
End Function
' ================================
' Function: StrReverse$
' Reverses a string manually for QB64 compatibility
' ================================
Function StrReverse$ (s As String)
Dim i As Integer, r As String
For i = Len(s) To 1 Step -1
r = r + Mid$(s, i, 1)
Next
StrReverse$ = r
End Function
' ================================
' Function: DeriveKey$
' Derives a round key from a base and seed key using XOR transformation
' ================================
Function DeriveKey$ (baseStr As String, seed As String)
Dim result As String
Dim i As Integer, ch As String * 1, sch As String * 1
Dim xorVal As Integer
For i = 1 To Len(baseStr)
ch = Mid$(baseStr, i, 1)
sch = Mid$(seed, ((i - 1) Mod Len(seed)) + 1, 1)
xorVal = Asc(ch) Xor Asc(sch) Xor (i Mod 255)
result = result + Chr$(xorVal Mod 256)
Next
DeriveKey$ = result
End Function
' ================================
' Function: encryptAESLite$
' AES-lite encryption/decryption based on mode
' ================================
Function encryptAESLite$ (text As String, keyString As String, mode As String)
Dim result As String, temp As String
Dim i As Integer, j As Integer
Dim ch As String * 1, kch As String * 1
Dim xorVal As Integer
Dim roundKey(0 To 3) As String * 32
roundKey(0) = keyString
For i = 1 To 3
roundKey(i) = DeriveKey$(roundKey(i - 1), keyString)
Next
result = text
If LCase$(mode) = "e" Then
For i = 0 To 3
temp = ""
For j = 1 To Len(result)
ch = Mid$(result, j, 1)
kch = Mid$(roundKey(i), ((j - 1) Mod 32) + 1, 1)
xorVal = Asc(ch) Xor Asc(kch) Xor (j + (i * 17))
temp = temp + Chr$(xorVal Mod 256)
Next
result = StrReverse$(temp)
Next
ElseIf LCase$(mode) = "d" Then
For i = 3 To 0 Step -1
result = StrReverse$(result)
temp = ""
For j = 1 To Len(result)
ch = Mid$(result, j, 1)
kch = Mid$(roundKey(i), ((j - 1) Mod 32) + 1, 1)
xorVal = Asc(ch) Xor Asc(kch) Xor (j + (i * 17))
temp = temp + Chr$(xorVal Mod 256)
Next
result = temp
Next
End If
encryptAESLite$ = result
End Function
' Detects tampering or wrong key use
' Add a 4-character checksum at the end of encrypted data, then verify it during decrypt.
Function AddCheckTag$ (text As String)
Dim i As Integer, sum As Integer
For i = 1 To Len(text)
sum = sum + Asc(Mid$(text, i, 1))
Next
sum = sum Mod 65536
AddCheckTag$ = text + MKI$(sum)
End Function
Function StripCheckTag$ (cipher As String, passed As Integer)
Dim tag As Integer, real As Integer
Dim dataStr As String, i As Integer
If Len(cipher) < 3 Then passed = 0: StripCheckTag$ = "": Exit Function
dataStr = Left$(cipher, Len(cipher) - 2)
tag = CVI(Right$(cipher, 2))
For i = 1 To Len(dataStr)
real = real + Asc(Mid$(dataStr, i, 1))
Next
real = real Mod 65536
If real = tag Then passed = -1 Else passed = 0
StripCheckTag$ = dataStr
End Function
' ================================
' Function: EncryptField$
' Encrypts data and adds checksum tag
' ================================
Function EncryptField$ (dataStr As String, keyStr As String)
EncryptField$ = AddCheckTag$(encryptAESLite$(dataStr, keyStr, "e"))
End Function
' ================================
' Function: DecryptField$
' Decrypts data and verifies checksum tag
' Returns 'passed' = -1 if OK, 0 if tampered/invalid
' ================================
Function DecryptField$ (cipher As String, keystr As String, passed As Integer)
DecryptField$ = StripCheckTag$(encryptAESLite$(cipher, keystr, "d"), passed)
End Function
'=== EXAMPLE USAGE ======
'----------------------------------------
'DIM myKey AS STRING, encrypted$ AS STRING, decrypted$ AS STRING, isGood AS INTEGER
'myKey = keyGen$
'encrypted$ = EncryptField$("topsecret123", myKey)
'decrypted$ = DecryptField$(encrypted$, myKey, isGood)
'IF isGood THEN
' PRINT "Decrypted OK: "; decrypted$
'ELSE
' PRINT "Invalid decryption or tampered data!"
'END IF
|