#!/bin/bash
################################################################################
# file:		rc4.sh
# created:	15-05-2011
# modified:	2012 Mar 20
#
# https://secure.wikimedia.org/wikipedia/en/wiki/RC4
#
# NOTES:
#   - ord() & chr() from http://mywiki.wooledge.org/BashFAQ/071
#
# TODO:
#   - from stdin to stdout
#   - todo figure out a better way for all the conversions
#   - DECRYPTED / ENCRYPTED -> ciphertext
#   - possibility to enter the key as hex
#
################################################################################
[ ${BASH_VERSINFO[0]} -ne 4 ] && {
  echo -e "warning: bash version != 4, this script might not work properly!" 1>&2
  echo    "         you can bypass this check by commenting out lines $[${LINENO}-2]-$[${LINENO}+2]." 1>&2
  exit 1
}
declare     TITLE="rc4.sh -- the RC4 stream cipher"
declare -a  S=()
declare -ai KEY=()
declare -i  KEYLENGTH
# Two 8-bit index-pointers
declare -i  I
declare -i  J
# keystream
declare -i  K
declare -i  SWAPBYTE
declare -i  DEBUG=0
# mode
declare -i  ENCRYPT=1

function ord() {
  # ord() - converts ASCII character to its decimal value
  [ ${#1} -ne 1 -o ${#} -ne 1 ] && echo "${FUNCNAME}(): error!" 1>&2
  printf '%d' "'${1}"
  return ${?}
}

function chr() {
  # chr() - converts decimal value to its ASCII character representation
  printf \\$(printf '%03o' ${1})
  return ${?}
}

function usage() {
  cat 0<<-EOF
	${TITLE}

	usage: ${0##*/} options

	options:
	  -d		decrypt (the default is to encrypt)
	  -h		this help
	  -k key	key as ASCII
	  -c ciphertext	ciphertext as hexadecimal (e.g. "1021BF0420")
	  -p plaintext	plaintext
	  -x		debug mode
EOF
} # usage()

while getopts "dhk:c:p:x" OPTION
do
  case "${OPTION}" in
    "d") ENCRYPT=0 ;;
    "k")
      ASCIIKEY="${OPTARG}"
      KEYLENGTH=${#ASCIIKEY}
    ;;
    "c")
      CIPHERTEXT="${OPTARG}"
      LENGTH=$[${#CIPHERTEXT}/2]
    ;;
    "p")
      PLAINTEXT="${OPTARG}"
      LENGTH=${#PLAINTEXT}
    ;;
    "x") DEBUG=1 ;;
    "h"|*)
      usage
      exit 0
    ;;
  esac
done

# sanity checks
[ -z "${ASCIIKEY}" ] && {
  echo "error: no key provided!" 1>&2
  exit 1
}
[ ${KEYLENGTH} -lt 1 -o ${KEYLENGTH} -gt 256 ] && {
  echo "error: invalid keylength!" 1>&2
  exit 1
}
if (( ${ENCRYPT} )) && [ -n "${CIPHERTEXT}" ]
then
  echo "error: mode is encrypt and you provided the ciphertext?" 1>&2
  exit 1
elif (( ${ENCRYPT} )) && [ -z "${PLAINTEXT}" ]
then
  echo "error: mode is encrypt and no plaintext provided!" 1>&2
  exit 1
elif (( ! ${ENCRYPT} )) && [ -n "${PLAINTEXT}" ]
then
  echo "error: mode is decrypt and you provided the plaintext?" 1>&2
  exit 1
elif (( ! ${ENCRYPT} )) && [ -z "${CIPHERTEXT}" ]
then
  echo "error: mode is decrypt and no ciphertext provided!" 1>&2
  exit 1
elif (( ! ${ENCRYPT} )) && [ $[ ${#CIPHERTEXT} % 2 ] -ne 0 ]
then
  echo "error: invalid ciphertext!" 1>&2
  exit 1
#TODO: CHECK CIPHERTEXT FOR HEX
fi

# translate the ASCII key to decimal
for ((I=0; I<${KEYLENGTH}; I++))
do
  KEY[I]=`ord "${ASCIIKEY:${I}:1}"`
done

cat 0<<-EOF 1>&2
	${TITLE}

	key (ASCII):	"${ASCIIKEY}"
	keylength:	$[KEYLENGTH*8] bits
	plaintext:	${PLAINTEXT:+"'"}${PLAINTEXT:-n/a}${PLAINTEXT:+"'"}

	running the key-scheduling algorithm (KSA)...
EOF

for I in {0..255}
do
  S[I]=${I}
done

# The key-scheduling algorithm (KSA)
J=0
for I in {0..255}
do
  # Bash: "Within an expression, shell variables may also be referenced by name without using the parameter expansion  syntax."
  J=$[ ( J + S[I] + KEY[ I % KEYLENGTH ] ) % 256 ]
  SWAPBYTE=${S[I]}
  # S-Box (Substitution-box)
  S[I]=${S[J]}
  S[J]=${SWAPBYTE}
done

echo "running the pseudo-random generation algorithm (PRGA)..." 1>&2

# The pseudo-random generation algorithm (PRGA)
for ((I=0, J=0, COUNTER=0; COUNTER<${LENGTH}; COUNTER++))
do
  I=$[ ++I		% 256 ]
  J=$[ ( J + S[I] )	% 256 ]
  SWAPBYTE=${S[I]}
  S[I]=${S[J]}
  S[J]=${SWAPBYTE}
  # keystream
  K=$[ S[ ( S[I] + S[J] ) % 256 ] ]
  # decimal to hexadecimal
  printf -v K_HEX '%.2x' ${K}

  # these are the same for both encrypt and decrypt.
  (( ${DEBUG} )) && cat 0<<-EOF 1>&2
	DEBUG (character $[COUNTER+1]/${LENGTH}):
	  I=${I} J=${J} S[I]=${S[I]} S[J]=${S[J]}
	  keystream byte (hex)		= ${K_HEX}
EOF
  # encrypt or decrypt?
  if (( ${ENCRYPT} ))
  then # encrypt
    CHAR="${PLAINTEXT:${COUNTER}:1}"
    CHARCODE=`ord "${CHAR}"`
    # "As with any stream cipher, these can be used for encryption by combining it with the plaintext using bit-wise exclusive-or"
    ENCRYPTED_CHARCODE=$[ CHARCODE ^ K ]
    #ENCRYPTED_CHAR=`chr ${ENCRYPTED_CHARCODE}`
    printf -v ENCRYPTED_CHARCODE_HEX '%.2x' ${ENCRYPTED_CHARCODE}
    (( ${DEBUG} )) && cat 0<<-EOF 1>&2
	  ENCRYPTED_CHARCODE (dec)	= ${ENCRYPTED_CHARCODE}
	  ENCRYPTED_CHARCODE (hex)	= ${ENCRYPTED_CHARCODE_HEX}
	  CHAR (dec)			= ${CHARCODE}
	  CHAR				= "${CHAR}"
EOF
    CIPHERTEXT="${CIPHERTEXT}${ENCRYPTED_CHARCODE_HEX}"
  else # decrypt

    # NOTE: we don't need or want the ciphertext as a character, since it can
    #       be anything.
    ENCRYPTED_CHARCODE_HEX=${CIPHERTEXT:$[COUNTER*2]:2}
    # hexadecimal to decimal
    # http://unstableme.blogspot.com/2007/12/hex-to-decimal-conversion-bash-newbie.html
    let ENCRYPTED_CHARCODE=0x${ENCRYPTED_CHARCODE_HEX}

    DECRYPTED_CHARCODE=$[ ENCRYPTED_CHARCODE ^ K ]
    DECRYPTED_CHAR=`chr ${DECRYPTED_CHARCODE}`
    (( ${DEBUG} )) && cat 0<<-EOF 1>&2
	  ENCRYPTED_CHARCODE (dec)	= ${ENCRYPTED_CHARCODE}
	  ENCRYPTED_CHARCODE (hex)	= ${ENCRYPTED_CHARCODE_HEX}
	  DECRYPTED_CHAR		= "${DECRYPTED_CHAR}"
EOF
    PLAINTEXT="${PLAINTEXT}${DECRYPTED_CHAR}"
  fi
done

if (( ${ENCRYPT} ))
then
  echo -e "\nciphertext (hex):\t${CIPHERTEXT^^}"
  # some error checking
  #[ $[ ${#CIPHERTEXT} % 2 ] -ne 0 ] && echo "error: invalid ciphertext!" 1>&2
else
  echo -e "\nplaintext:\t\"${PLAINTEXT}\""
fi

# from https://secure.wikimedia.org/wikipedia/en/wiki/Rc4#Test_vectors
if (( ${DEBUG} )) && (( ${ENCRYPT} ))
then
  if [ "${ASCIIKEY}" = "Key" -a "${PLAINTEXT}" = "Plaintext" ]
  then
    echo -e "DEBUG: SHOULD BE:\tBBF316E8D940AF0AD3"
  elif [ "${ASCIIKEY}" = "Wiki" -a "${PLAINTEXT}" = "pedia" ]
  then
    echo -e "DEBUG: SHOULD BE:\t1021BF0420"
  elif [ "${ASCIIKEY}" = "Secret" -a "${PLAINTEXT}" = "Attack at dawn" ]
  then
    echo -e "DEBUG: SHOULD BE:\t45A01F645FC35B383552544B9BF5"
  fi
fi

exit 0

