0

I'm trying to compute an xor in bash, and the simplest way I have found so far is to use the $(( )) construct, like this:

$ printf '0x%X\n' $(( 0xA ^ 0xF ))
0x5

However, I need to xor two 128-bit numbers (32 hexadecimal characters) and it looks like the (( )) construct does not support such large numbers. Is there a way that (( )) can handle it?

$ printf '0x%X\n' $(( 0xA ^ 0xF ))
0x5
$ printf '0x%X\n' $(( 0xAA ^ 0xFF ))
0x55
$ printf '0x%X\n' $(( 0xAAA ^ 0xFFF ))
0x555
$ printf '0x%X\n' $(( 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ))
0x5555555555555555

There are only 16 5's instead of 32 as expected from the size of the input.

My input will be text strings without the 0x.

E.g.

x=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
y=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

printf '0x%X\n' $(( 0x$x ^ 0x$y ))
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Rusty Lemur
  • 1,697
  • 1
  • 21
  • 54
  • 1
    No, bash's built-in integer math cannot handle a 128-bit integer. – Charles Duffy Sep 21 '22 at 15:32
  • 1
    That said, do you _need_ it to be one 128-bit integer instead of smaller ones? You can just append your results together, after all. It might be helpful if your input came from a variable instead of being hardcoded in the example so we could see the format of that variable and, where appropriate, break it down into smaller strings. One of the nice things about bitwise operations &c... – Charles Duffy Sep 21 '22 at 15:33
  • Thanks Charles, I've updated the post with an example of my input. – Rusty Lemur Sep 21 '22 at 15:54
  • Fixed length? Do you know the number of digits will always be constant? (I took a quick shot at writing an answer earlier, but dealing with cases when the number of digits was different between the inputs took it over my timebox; mind, at work right now, so it'll be a bit until I have a chance again) – Charles Duffy Sep 21 '22 at 15:55
  • Yes, they will always be 32 characters that are hexadecimal digits. – Rusty Lemur Sep 21 '22 at 15:56
  • okay, great. I may get to it later, or someone else may answer first, but that allows for some pretty straightforward workarounds. – Charles Duffy Sep 21 '22 at 15:57
  • ...actually, do you want to move your last edit into an answer? I'd upvote it. – Charles Duffy Sep 21 '22 at 16:16
  • 1
    (To strengthen that: Answers shouldn't be included in questions, and the consensus on meta is that when they're answered in a question, it's good practice for someone to edit it back out and move the content into a community-wiki answer; if I do that, though, you won't get reputation from any upvotes that answer gets, so it's really better if you do it yourself). See [What is the appropriate action when the answer to a question is added to that question?](https://meta.stackoverflow.com/questions/267434) on [meta]. – Charles Duffy Sep 21 '22 at 16:45
  • Thanks Charles, I've moved it to a new answer. – Rusty Lemur Sep 21 '22 at 19:14

4 Answers4

2

With Charles' advice, I have arrived at this (which is very similar to chrslg's answer):

printf '0x%016X%016X\n' $(( 0x${x:0:16} ^ 0x${y:0:16} )) $(( 0x${x:16} ^ 0x${y:16} ))

And a more portable version per Fravadona's suggestion:

printf '0x%08X%08X%08X%08X\n' $(( 0x${x:0:8} ^ 0x${y:0:8} )) $(( 0x${x:8:8} ^ 0x${y:8:8} )) $(( 0x${x:16:8} ^ 0x${y:16:8} )) $(( 0x${x:24} ^ 0x${y:24} ))
Rusty Lemur
  • 1,697
  • 1
  • 21
  • 54
1

Bash can't handle such large numbers (though I don't have a reference for that).

Instead, you could offload to another language like Python, which has arbitrarily large integers:

x=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
y=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
python3 -c "print(format(0x$x ^ 0x$y, '#x'))"

Output:

0x55555555555555555555555555555555

Note: I'm assuming the inputs are trusted and only contain hex digits. If that's not guaranteed, this solution is totally unsecure since it doesn't sanitize anything. You could do the validation in Bash first with something like regex, or if you want to do it in Python, you could look at How do I access command line arguments? and Convert hex string to integer in Python.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
  • 1
    If it weren't for the injection risk if `x` or `y` is able to contain other content (`x='0 + __import__("os").system("rm -rf ~")'` f/e), I'd be all-in on this answer. Might be worth making it a little longer to pass x and y out-of-band from the code? – Charles Duffy Sep 21 '22 at 19:29
  • (that, and this is missing zero-padding if the result doesn't have the same number of digits as the input; that's a thing KamilCuk's answer gets right, even though it completely punts on passing the data from shell to python) – Charles Duffy Sep 21 '22 at 19:30
  • @Charles Thanks for bringing that up. I should have mentioned it before. I added a note. OP didn't mention whether the inputs might be invalid or malicious, so I don't want to stress too much, but it's absolutely worth mentioning. – wjandrea Sep 21 '22 at 23:31
  • @Charles Why do you bring up padding though? OP didn't mention padding, the code in the question doesn't do it, and Kamil's code also doesn't do it. – wjandrea Sep 21 '22 at 23:34
  • 1
    OP's own answer does implement padding, but you're completely right that they didn't specify; I overread. – Charles Duffy Sep 21 '22 at 23:59
1

Is there a way that (( )) can handle it?

No. You would have to patch Bash with some big-number support and recompile it yourself.

Use python.

$ python -c 'print("{:X}".format(0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))'
55555555555555555555555555555555
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
1

Do it in two parts. Anyway, that the whole point of xor and hex, you could to it digit by digit if you wanted to. Here, since it is 32 digits numbers, and we have a 64 bits aka 16 hex digits precision arithmetics...

A=0123456789ABCDEF0123456789ABCDEF
B=ABCDEFABCDEF01234012345678956789

printf "0x" ; printf "%016X" $((0x${A:0:16} ^ 0x${B:0:16})) $((0x${A:16:16} ^ 0x${B:16:16})) ; printf "\n"

=>

0xAAEEAACC4444CCCC41317131F13EAA66

(as it should)

chrslg
  • 9,023
  • 5
  • 17
  • 31
  • Thanks chrslg. I think some additional formatting is needed for zero-padding the second xor to handle when there are leading 0s in the xor result. Consider this input: `A=00000000111111110000000011111111` `B=00000000111100000000000011110000` leads to this output: `0x11111111` – Rusty Lemur Sep 21 '22 at 19:28
  • Indeed. I should've used %016X instead of %X to fill the holes with 0. I update my answer. As I can see, you already did that in yours. – chrslg Sep 21 '22 at 19:34