Created
May 16, 2015 11:04
-
-
Save pyllyukko/48cee9f4fc7412e1ca4d to your computer and use it in GitHub Desktop.
RC4 stream cipher algorithm written in Bash
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
################################################################################ | |
# file: rc4.sh | |
# created: 15-05-2011 | |
# modified: 2014 Sep 04 | |
# | |
# https://secure.wikimedia.org/wikipedia/en/wiki/RC4 | |
# | |
# NOTES: | |
# - ord() & chr() from http://mywiki.wooledge.org/BashFAQ/071 | |
# | |
# TODO: | |
# - todo figure out a better way for all the conversions | |
# - optimize =) | |
# - improve the s-box drawing thingie to only print changed bytes | |
# | |
################################################################################ | |
[ ${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 | |
} | |
set -u | |
shopt -s nocasematch | |
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 | |
declare -i NCHARS=1 | |
declare CIPHERTEXT= | |
declare HL1="\033[44;1;31m" | |
declare HL2="\033[1m" | |
STDIN_TYPE=$(stat -L -c %F /proc/self/fd/0) | |
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 | |
case ${1} in | |
# this is stupid, but i couldn't figure out any other way to do this. we | |
# use "echo -en" to print the decrypted ciphertext. | |
10) | |
echo -n '\n' | |
;; | |
*) | |
printf \\$(printf '%03o' ${1}) | |
;; | |
esac | |
return ${?} | |
} | |
function usage() { | |
cat 0<<-EOF | |
${TITLE} | |
usage: ${0##*/} options | |
this script encrypts and decrypts data with the RC4 stream cipher algorithm. the script reads data from stdin and outputs the encrypted or decrypted data to stdout. if you're decrypting, the encrypted data must be hex encoded (e.g. "1021BF0420"). | |
so to encrypt data, you can do: | |
echo -n "pedia" | ./rc4.sh -k Wiki | |
and to decrypt, do: | |
echo -n "1021BF0420" | ./rc4.sh -d -k Wiki | |
you can try the test vector's from wikipedia (https://en.wikipedia.org/wiki/RC4#Test_vectors): | |
echo -n "Plaintext" | bash ./rc4.sh -k Key | |
echo -n "pedia" | bash ./rc4.sh -k Wiki | |
echo -n "Attack at dawn" | bash ./rc4.sh -k Secret | |
options: | |
-d decrypt (the default is to encrypt) | |
-h this help | |
-k key key | |
you can provide the key as hex by prefixing with '0x', | |
it is otherwise interpreted as ASCII. | |
-x debug mode | |
EOF | |
# -c ciphertext ciphertext as hexadecimal (e.g. "1021BF0420") | |
# #-p plaintext plaintext | |
} # usage() | |
function draw_s-box() { | |
# $1 = I | |
# $2 = J | |
local -i I=0 | |
local K_HEX | |
local NO_COLOUR | |
local HL | |
if [ ${#} -ne 2 -o -z "${1}" -o -z "${2}" ] | |
then | |
echo "${FUNCNAME}(): error!" 1>&2 | |
return 1 | |
fi | |
#for ((I=0; I<=46; I++)) | |
#do | |
# echo -n "-" | |
#done | |
#echo -n $'\n' | |
for ((I=0; I<256; I++)) | |
do | |
printf -v K_HEX '%.2x' ${S[I]} | |
HL="" | |
NO_COLOUR="" | |
# colours | |
# http://tldp.org/LDP/abs/html/colorizing.html | |
if [ ${I} -eq ${1} -o ${I} -eq ${2} ] | |
then | |
# I or J | |
HL="\033[1m" | |
NO_COLOUR="\033[0m" | |
elif [ ${I} -eq $(( ( S[${1}] + S[${2}] ) % 256 )) ] | |
then | |
# keystream byte | |
HL="${HL1}" | |
NO_COLOUR="\033[0m" | |
fi | |
# print the value | |
echo -en "${HL}${K_HEX}${NO_COLOUR} " 1>&2 | |
if [ $(( I % 16 )) -eq 15 ] | |
then | |
# newline | |
echo -n $'\n' | |
fi 1>&2 | |
done | |
echo -n $'\n' 1>&2 | |
return 0 | |
} # draw_s-box() | |
while getopts "dhk:c:p:x" OPTION | |
do | |
case "${OPTION}" in | |
"d") | |
ENCRYPT=0 | |
NCHARS=2 | |
;; | |
"k") | |
ASCIIKEY="${OPTARG}" | |
if [ "${ASCIIKEY:0:2}" = "0x" ] # key provided as hex | |
then | |
KEYLENGTH=$[ ( ${#ASCIIKEY} - 2 ) / 2 ] | |
#echo "DEBUG: keylength=${KEYLENGTH}" | |
else # key provided as ASCII | |
KEYLENGTH=${#ASCIIKEY} | |
fi | |
;; | |
#"c") | |
# # ciphertext provided -> decrypt mode | |
# CIPHERTEXT="${OPTARG}" | |
# LENGTH=$[${#CIPHERTEXT}/2] | |
# ENCRYPT=0 | |
# NCHARS=2 | |
#;; | |
#"p") | |
# # plaintext provided -> encrypt mode | |
# PLAINTEXT="${OPTARG}" | |
# LENGTH=${#PLAINTEXT} | |
# ENCRYPT=1 | |
# NCHARS=1 | |
#;; | |
"x") DEBUG=1 ;; | |
"h"|*) | |
usage | |
exit 0 | |
;; | |
esac | |
done | |
if [ ${#} -eq 0 ] | |
then | |
usage | |
exit 0 | |
fi | |
# 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 | |
#fi | |
for ((I=0; I<${KEYLENGTH}; I++)) | |
do | |
if [ "${ASCIIKEY:0:2}" = "0x" ] # key provided as hex | |
then | |
# hex to dec | |
let KEY[I]=0x${ASCIIKEY:$[(I+1)*2]:2} | |
#echo "DEBUG: KEY[${I}]=${KEY[I]}" | |
else # key provided as ASCII | |
# translate the ASCII key to decimal | |
KEY[I]=`ord "${ASCIIKEY:${I}:1}"` | |
#echo "DEBUG: KEY[${I}]=${ASCIIKEY:${I}:1}" | |
fi | |
done | |
cat 0<<-EOF 1>&2 | |
${TITLE} | |
key: ${ASCIIKEY} | |
keylength: $[KEYLENGTH*8] bits (${KEYLENGTH} bytes) | |
running the key-scheduling algorithm (KSA)... | |
EOF | |
S=( {0..255} ) | |
# The key-scheduling algorithm (KSA) | |
J=0 | |
for I in {0..255} | |
do | |
J=$[ ( J + S[I] + KEY[ I % KEYLENGTH ] ) % 256 ] | |
SWAPBYTE=${S[I]} | |
# S-Box (Substitution-box) | |
S[I]=${S[J]} | |
S[J]=${SWAPBYTE} | |
if (( ${DEBUG} )) && [ ${I} -eq 0 ] | |
then | |
echo " DEBUG: I=${I} J=${J} S[${I}]=${S[I]} S[${J}]=${S[J]}" 1>&2 | |
fi | |
done | |
echo -e "running the pseudo-random generation algorithm (PRGA)...\n" 1>&2 | |
if (( ${DEBUG} )) | |
then | |
clear | |
fi 1>&2 | |
# The pseudo-random generation algorithm (PRGA) | |
I=0 | |
J=0 | |
COUNTER=0 | |
#for ((I=0, J=0, COUNTER=0; COUNTER<${LENGTH}; COUNTER++)) | |
while read -rs -d "" -n ${NCHARS} | |
do | |
# end-of-transmission | |
# ./rc4.sh: line 256: [: `)' expected, found | |
#if [ "${STDIN_TYPE}" = "character special file" -a "${REPLY}" = $'\x04' ] | |
if \ | |
[ "${STDIN_TYPE}" = "character special file" ] && \ | |
[[ "${REPLY}" == $'\x04' ]] | |
then | |
echo "EOT received" 1>&2 | |
break | |
fi | |
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. | |
if (( ${DEBUG} )) | |
then | |
echo -en "\033[1;1H" | |
# cat 0<<-EOF 1>&2 | |
# DEBUG (character $[COUNTER+1]/${LENGTH}): | |
# I=${I} J=${J} S[I]=${S[I]} S[J]=${S[J]} | |
#EOF | |
printf "DEBUG (character #$((++COUNTER))):\n" | |
printf " I=%3d J=%3d S[I]=${HL2}%.2x\033[0m S[J]=${HL2}%.2x\033[0m\n" ${I} ${J} ${S[I]} ${S[J]} | |
echo -e " keystream byte (hex)\t= ${HL1}${K_HEX}\033[0m" | |
fi 1>&2 | |
# encrypt or decrypt? | |
if (( ${ENCRYPT} )) | |
then # encrypt | |
#CHAR="${PLAINTEXT:${COUNTER}:1}" | |
CHAR="${REPLY}" | |
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} | |
if (( ${DEBUG} )) | |
then | |
# cat 0<<-EOF 1>&2 | |
# ENCRYPTED_CHARCODE (dec) = ${ENCRYPTED_CHARCODE} | |
# ENCRYPTED_CHARCODE (hex) = ${ENCRYPTED_CHARCODE_HEX} | |
# CHAR (dec) = ${CHARCODE} | |
# CHAR = "${CHAR}" | |
# | |
#EOF | |
printf " ciphertext (dec)\t= %3d\n" ${ENCRYPTED_CHARCODE} | |
printf " ciphertext (hex)\t= %s\n" ${ENCRYPTED_CHARCODE_HEX} | |
printf " plaintext (dec)\t= %3d\n" ${CHARCODE} | |
printf " plaintext (hex)\t= %.2x\n" ${CHARCODE} | |
# https://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters | |
# TODO: for some reason space (0x20) displays as """ | |
if [ ${CHARCODE} -ge 32 -a ${CHARCODE} -le 126 ] | |
then | |
printf " plaintext\t\t= \"%c\"\n\n" "${CHAR}" | |
else | |
printf " plaintext\t\t= \" \"\n\n" | |
fi | |
draw_s-box "${I}" "${J}" | |
fi 1>&2 | |
CIPHERTEXT="${CIPHERTEXT}${ENCRYPTED_CHARCODE_HEX}" | |
PLAINTEXT+="${CHAR}" | |
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} | |
ENCRYPTED_CHARCODE_HEX=${REPLY} | |
# check that the input was hex encoded | |
if [[ ! ${ENCRYPTED_CHARCODE_HEX} =~ ^[0-9a-f]{2}$ ]] | |
then | |
echo "error: ciphertext not in hex! aborting." 1>&2 | |
exit 1 | |
fi | |
# hexadecimal to decimal | |
# http://unstableme.blogspot.com/2007/12/hex-to-decimal-conversion-bash-newbie.html | |
# ./rc4.sh: line 331: let: ENCRYPTED_CHARCODE=0xa: syntax error: invalid arithmetic operator (error token is "") | |
let ENCRYPTED_CHARCODE=0x${ENCRYPTED_CHARCODE_HEX} | |
DECRYPTED_CHARCODE=$[ ENCRYPTED_CHARCODE ^ K ] | |
DECRYPTED_CHAR=`chr ${DECRYPTED_CHARCODE}` | |
if (( ${DEBUG} )) | |
then | |
# cat 0<<-EOF 1>&2 | |
# ENCRYPTED_CHARCODE (dec) = ${ENCRYPTED_CHARCODE} | |
# ENCRYPTED_CHARCODE (hex) = ${ENCRYPTED_CHARCODE_HEX} | |
# DECRYPTED_CHAR = "${DECRYPTED_CHAR}" | |
#EOF | |
printf " ciphertext (dec)\t= %3d\n" ${ENCRYPTED_CHARCODE} | |
printf " ciphertext (hex)\t= %s\n" ${ENCRYPTED_CHARCODE_HEX} | |
printf " plaintext (dec)\t= %d\n" ${DECRYPTED_CHARCODE} | |
printf " plaintext (hex)\t= %.2x\n" ${DECRYPTED_CHARCODE} | |
if [ ${DECRYPTED_CHARCODE} -ge 32 -a ${DECRYPTED_CHARCODE} -le 126 ] | |
then | |
printf " plaintext\t\t= \"%c\"\n\n" ${DECRYPTED_CHAR} | |
else | |
printf " plaintext\t\t= \" \"\n\n" | |
fi | |
fi 1>&2 | |
PLAINTEXT+="${DECRYPTED_CHAR}" | |
fi | |
done | |
if (( ${ENCRYPT} )) | |
then | |
# some error checking | |
if [ $[ ${#CIPHERTEXT} % 2 ] -ne 0 ] | |
then | |
echo "error: invalid ciphertext!" 1>&2 | |
fi | |
# print the end result | |
echo -en "\nciphertext (hex):\t" 1>&2 | |
echo -e "${CIPHERTEXT^^}" | |
echo -en "\nplaintext:\t\t" 1>&2 | |
echo -e "${PLAINTEXT}" | |
else | |
# decrypted | |
echo -en "\nplaintext:\t" 1>&2 | |
echo -en "${PLAINTEXT}" | |
#printf "%s" ${PLAINTEXT} | |
echo -n $'\n' 1>&2 | |
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 1>&2 | |
exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment