Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

TOTP seeds: Converting Hex to Base32

Status
Not open for further replies.

CBellucci

Programmer
Apr 20, 2007
38
US
I need to convert a hex number to a base32 number for creating a QR code to use Google Authenticator (or like) for Multi-Factor Authentication. I've found lots of online tools, but no code I can actually put in my program.

Example: If I get a hex number like this: df235768fe4365e6fa3a0a49325d14fb1bc24be1
I need to convert it to the base32 equivalent: 34RVO2H6INS6N6R2BJETEXIU7MN4ES7B

Perhaps I need to go from hex to decimal to base32? Anyone have any advice?

Many thanks in advance!
 
Yep, found that. Was really looking for "real code" to do it.
 
CBellucci,

This is a porting of the C implementation pointed by Wikipedia - only the encoder, so far.

Code:
LOCAL Demo AS Base32

m.Demo = CREATEOBJECT("Base32")

? m.Demo.Encode(0hdf235768fe4365e6fa3a0a49325d14fb1bc24be1)
? m.Demo.Encode("Hello, world")

DEFINE CLASS Base32 AS Custom

	Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="

	FUNCTION Encode (Source AS String) AS String

		LOCAL ToEncode AS String
		LOCAL SourceSegment AS String
		LOCAL SegmentSize AS Integer
		LOCAL EncodedIndex AS Integer
		LOCAL ARRAY EncodedSegment(8)

		LOCAL Encoded AS String

		m.Encoded = ""

		m.SourceSegment = LEFT(m.Source, 5)
		m.ToEncode = SUBSTR(m.Source, 6)

		DO WHILE LEN(m.SourceSegment) > 0 

			STORE .NULL. TO m.EncodedSegment

			m.SegmentSize = LEN(m.SourceSegment)
			
			IF m.SegmentSize = 5
				m.EncodedSegment(8) = BITAND(ASC(RIGHT(m.SourceSegment, 1)), 0x1f)
				m.EncodedSegment(7) = BITRSHIFT(BITAND(ASC(RIGHT(m.SourceSegment, 1)), 0xe0), 5)
			ENDIF

			IF m.SegmentSize >= 4
				m.EncodedSegment(7) = BITOR(BITLSHIFT(BITAND(ASC(SUBSTR(m.SourceSegment, 4, 1)), 0x03), 3), NVL(m.EncodedSegment(7), 0))
				m.EncodedSegment(6) = BITRSHIFT(BITAND(ASC(SUBSTR(m.SourceSegment, 4, 1)), 0x7c), 2)
				m.EncodedSegment(5) = BITRSHIFT(BITAND(ASC(SUBSTR(m.SourceSegment, 4, 1)), 0x80), 7)
			ENDIF

			IF m.SegmentSize >= 3
				m.EncodedSegment(5) = BITOR(BITLSHIFT(BITAND(ASC(SUBSTR(m.SourceSegment, 3, 1)), 0x0f), 1), NVL(m.EncodedSegment(5), 0))
				m.EncodedSegment(4) = BITRSHIFT(BITAND(ASC(SUBSTR(m.SourceSegment, 3, 1)), 0xf0), 4)
			ENDIF

			IF m.SegmentSize >= 2
				m.EncodedSegment(4) = BITOR(BITLSHIFT(BITAND(ASC(SUBSTR(m.SourceSegment, 2, 1)), 0x01), 4), NVL(m.EncodedSegment(4), 0))
				m.EncodedSegment(3) = BITRSHIFT(BITAND(ASC(SUBSTR(m.SourceSegment, 2, 1)), 0x3e), 1)
				m.EncodedSegment(2) = BITRSHIFT(BITAND(ASC(SUBSTR(m.SourceSegment, 2, 1)), 0xc0), 6)
			ENDIF

			m.EncodedSegment(2) = BITOR(BITLSHIFT(BITAND(ASC(LEFT(m.SourceSegment, 1)), 0x07), 2), NVL(m.EncodedSegment(2), 0))
			m.EncodedSegment(1) = BITRSHIFT(BITAND(ASC(LEFT(m.SourceSegment, 1)), 0xf8), 3)

			FOR m.EncodedIndex = 1 TO 8
				m.Encoded = m.Encoded + SUBSTR(This.Alphabet, NVL(m.EncodedSegment(m.EncodedIndex), 32) + 1, 1)
			ENDFOR

			m.SourceSegment = LEFT(m.ToEncode, 5)
			m.ToEncode = SUBSTR(m.ToEncode, 6)

		ENDDO

		RETURN m.Encoded

	ENDFUNC

ENDDEFINE
 
I don't know why you use double length codes, but maybe that's API specific.

Edit:
I forgot the comment on Google Authenticator:
Wikipedia about Google Authenticator said:
The service provider (Google) generates an 80-bit secret key for each user (whereas RFC 4226 §4 requires 128 bits and recommends 160 bits).[4] This is provided as a 16, 26 or 32 character base32 string or as a QR code.

128bit doesn't fit a multiple of 5 bits, so it's be padded to 130 bits = 26 base32 characters. To avoid that I'd also go for 160 bit = 32 Base32 characters.
It's convenient the FAQ code I wrote does not depend on specific String or bit lengths. So...
:End of Edit

Anyway, I'd neither convert hex to base32 directly nor go through decimal, but use binary, Q(10) or Q(20).

For example with the function as declared in my FAQ I get [tt]34RVO2H6INS6N6R2BJETEXIU7MN4ES7B[/tt] as necessary.

Code:
lcSecret = ''+0hdf235768fe4365e6fa3a0a49325d14fb1bc24be1
_cliptext = Base32Encode(lcSecret)
? _cliptext

You can also generate a random 20 char long string by using any character or directly create a random 32 char long base32 string by only using A-Z and 2-7, then you never need Base32Encode. The secret only needs to be generated once for the user to have it. Generating a QR Code from it is a topic of its own, I didn't as the Google Authenticator apps for Window Phone and Android also allow you to enter that code manually.

But there also is a QR Barcode image library in the VFPX project on GitHub:
Bye, Olaf.

Olaf Doschke Software Engineering
 
Btw, as ASC and SUBSTR etc also work on the binary data type the Base2Encode function also works with [tt]lcSecret = 0hdf235768fe4365e6fa3a0a49325d14fb1bc24be1[/tt] and if you have some variable hexdigitstring='df235768fe4365e6fa3a0a49325d14fb1bc24be1' as string of hex digits you can use EVAL to convert that to binary: lcString = EVAL('0h'+hexdigitsstring). Also notice Craig Boyds HMAC() function returns a string that's neither binary nor a hex digits string but simply the ASCII bytes the HMAC Sha function creates, VFP does not really need a distinction between a text string and a binary string, the only difference in the types Q and C in tables or cursors are about no codepage conversion so Q is pretty much the same as C NOCPTRAN, but it isn't displayed as the ASCII or ANSI characters of VFPs current codepage, the string bytes are shown as hex digits, it's not really a string of double length with 0-9 and a-f characters.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top