Translate

Archives

Bash XOR A String

Here is an example of how to XOR a string variable using the Bash shell. Each character in the plaintext string is XOR’ed with decimal 90.

#!/usr/local/bin/bash

plaintext="abcdefg"

echo "Plaintext: $plaintext"

cyphertext=""
for ((i=0; i < ${#plaintext}; i++ ))
do
   ord=$(printf "%d" "'${plaintext:$i:1}")
   tmp=$(printf \$(printf '%03o' $((ord ^ 90)) ))
   ciphertext="${ciphertext}${tmp}"
done

echo "Ciphertext: $ciphertext"

# now XOR again and we should get the original string back
plaintext=""
for ((i=0; i < ${#ciphertext}; i++ ))
do
   ord=$(printf "%d" "'${ciphertext:$i:1}")
   tmp=$(printf \$(printf '%03o' $((ord ^ 90)) ))
   plaintext="${plaintext}${tmp}"
done

echo "Plaintext: $plaintext"


The key lines are the ord= and the tmp= lines. The first line converts the character returned by ${string:$index:1} into an ordinal number matching the ASCII chart. Note the use of the “‘…”” syntax. You need that leading single quote! The second line XORs the value of the ord variable with 90 and then converts the result back into a character.

This also works for ksh93 and should work for the zsh shell but I have not tested it.

3 comments to Bash XOR A String

  • fil

    Line 7 should be: ciphertext=””
    Otherwise the code does not work as written: if the plaintext contains letter Z (decimal 90), the letter is missing in the end.
    For example:

    Plaintext: aaZaa
    Ciphertext: ;;;;
    Plaintext: aaaa

  • fil

    Proposed improvement:


    cipher () {
    # Echo XOR cipher of $1, using 31 as a key (non-printing character, not expected in the input).
    # Note! The resulting value may contain control characters, hence the caller must set IFS to null to retrieve the result.
    # E.g. IFS=$'' result="$(cipher "$text")"
    local i
    for ((i=0; i<${#1}; i++))
    do printf \$(printf '%03o' $(( $(printf '%d' "'${1:$i:1}") ^ 31 )) )
    done
    }

    try () {
    plaintext="$1"
    echo "Plaintext: length ${#plaintext}"
    IFS=$'' ciphertext="$(cipher "$1" $2)"
    echo "Ciphertext: length ${#ciphertext}"
    IFS=$'' plaintext2="$(c "$ciphertext" $2)"
    echo "Plaintext: length ${#plaintext2}"
    if [ "$plaintext2" = "$plaintext" ]; then echo "OK"; else echo "NOK!"; fi
    }

    try "abcdefg"
    try $'a12b12c'

  • Russ Letson

    Hi,

    I am a relative beginner to bash (though have been using other languages for a long while).

    I am quite possibly confused but it seems like you commented out portions of the 2 for loops (???)

    I’m also having a problem understanding that the characters don’t seem to be converted to binary numbers…

    Thanks!