Hex2bytes: length of iterable must be even

Hi there, it seems hex2bytes is really not safe, but depending on the concrete number, it will throw an error. Workaround: using "0" * string(65537, base=16), but this looks really weird.

Can someone explain why such a basic function does not work as described in its own documentation?

julia> hex2bytes(string(65537, base=16))
ERROR: ArgumentError: length of iterable must be even
Stacktrace:
 [1] hex2bytes!(dest::Vector{UInt8}, itr::Base.CodeUnits{UInt8, String})
   @ Base ./strings/util.jl:847
 [2] hex2bytes!
   @ ./strings/util.jl:831 [inlined]
 [3] hex2bytes(s::String)
   @ Base ./strings/util.jl:825
 [4] top-level scope
   @ REPL[57]:1

I guess at least, the documentation should be updated, it currently reads like above

julia> s = string(12345, base = 16)
"3039"

julia> hex2bytes(s)
2-element Vector{UInt8}:
 0x30
 0x39

what do you think?

string(n,base=b, pad=Int(ceil(log(b,n)))+(1-iseven(Int(ceil(log(b,n)))))) # :innocent:
1 Like

This behavior corresponds precisely to its documentation, which states:

each successive pair of hexadecimal digits in itr gives the value of one byte in the return vector.

(emphasis added). i.e. it is documented on working on pairs of hexadecimal digits, since this is the canonical way to represent bytes as hex.

If you are trying to parse arbitrary hexadecimal numbers that aren’t representations of byte sequences (i.e. which don’t consist of paired hex digits), then you’re doing something different from what hex2bytes was intended for. What is your application?

I agree that the example in the docs with string(n, base=16) is misleading, however.

3 Likes

Thank you for the help!

I am trying to get a number to hex bytes and run into this error because I followed the example in the documentation.

this is very impressive :slight_smile: thank you so much

given that cryptic code, I only feel slightly safer, but I feel at least a little safer :slight_smile:

Use digits:

julia> digits(65537, base=256)
3-element Vector{Int64}:
 1
 0
 1

julia> digits(12345, base=256)
2-element Vector{Int64}:
 57
 48

You’ll have to manually convert to bytes though.

EDIT: 256 instead of 16, of course :person_facepalming:

1 Like

Do you just want something like digits(UInt8, n, base=256) or perhaps num2bytes(n) = reinterpret(UInt8, [n])? Or if you just want particular bytes you can use shift and mod operations on n.

In general, I would avoid the intermediate step of converting the number to a string and then parsing the string.

1 Like
julia> hex2bytes(lpad((s=string(65537, base=16);s),(length(s)+1)Ă·2*2,'0'))
3-element Vector{UInt8}:
 0x01
 0x00
 0x01

But it is still awkward.

thank you both,
everything is indeed clarified already - just the example in the documentation should get an update

my background: I am using JWTs to create authentication pipeline and there the exponent of the private key needs to be given in hex2bytes(int2hexstr(my_exponent)), because JWTs expects this

I am now running with

function int2hexstr(n)
    str = string(n, base=16)
    iseven(length(str)) ? str : "0" * str
end

Just don’t create unnecessary intermediate strings:

julia> UInt8.(digits(65537, base=256))
3-element Vector{UInt8}:
 0x01
 0x00
 0x01

But in general I agree with @stevengj here - shifting and building it that way is likely better, depending on the ultimate usecase of this.

1 Like

Have you considered using JWT.jl?

Padding unknown (or heck, even known) incoming data in a cryptographic context “to make the algorithm work” is NOT a good idea. Generally, neither is implementing a seemingly simple padding operation - you can introduce subtle bugs that leave your code vulnerable.

2 Likes

just now I understood that this actually does the same, so yeah, no need to go via a hexstr

yes I am using that package.

There is no API for creating the modulus and exponent, so I use openssl and need to do the conversions myself. I guess that is life

If you don’t mind me asking, but how are you generating the input in the first place? Going via regular Int64 or similar things seems much more of a hassle instead of directly generating an array of bytes to pass to e.g. openssl.

for JWTs keys need to be given under yourdomain/jwks in a json format which includes modulus and exponend base64 encoded with some extra url handling.

While there is an easy way to extract the modulus from a private key using openssl, I couldn’t find anything like that for the exponent, hence I am just assuming the default exponent will be used.

So here the answer: I am indeed using plain Int64 value as the source for my exponent because this is the best readable so that I or others can later check that this is really the default exponent.

final comment from my side :wink:

(EDIT: for further tips and tricks please ping me per private message or open a new discourse thread, so that this one is not further stacked up)

It’s just for fun. Follow the (serious) adivices of others

1 Like