### PowerShell Cryptography Library

Mon, Oct 19, 2009 5-minute read

When playing with cryptography challenges (don’t we all?,) you end up leaning on a bunch of common tasks. For example, substituting all text in a string with a set of replacements (substitution ciphers,) XORing strings together, applying dictionary-based algorithms, investigating word frequency, and more.

PowerShell lends itself really well to these challenges, and I’ve developed a small cryptography library along the way. Here it is. Your job, as a cryptographer, is to uncover the hidden comments :) (Hint: don’t spend very long. Some problems are unbreakable.)

For example, here’s the script I used (interactively) to solve an “XOR Cipher” at http://cryptolib.com/challenges.php.

``````\$cipherHex1 = @"
3526532A122A183D4D0E4A2E3713101954013E2B3E2246235728
573553064832781F164A481F2A2B396350341E2B5E3A5E1C4861
301C11544F1D22363863413F192A4A36520A0D2D2D13044A001A
252025335E2F142F5A355A
"@
\$cipherHex2 = @"
32335734163A5136510E41612818114A4912252C2722462F1820
4B7956014E283C180D4D001522312724573416225C794C074C2C
3D0E435A4F063B29383012251B214C315A1C41283618435E5212
2531342D556612205B2B461F592837131019481225212F225B2A
"@

\$words = @{}
Get-Content dictfull.txt | % { \$words[\$_] = \$true }

## Turn the cipher text into a single string
\$cipherText1 = \$cipherHex1 -replace "`r`n",""
\$cipherText2 = \$cipherHex2 -replace "`r`n",""

## Convert hex to decimal
\$cipherBytes1 = \$cipherText1 -split '(..)' | ? { \$_ } | % { [Convert]::ToInt32(\$_, 16) }
\$cipherBytes2 = \$cipherText2 -split "(..)" | ? { \$_ } | % { [Convert]::ToInt32(\$_, 16) }

## Drop the key by XORing the two cipher texts together
\$plainTextCombined = XORBytes \$cipherBytes1[0..19] \$cipherBytes2[0..19]

## Initialize the key
\$key = ,0x20*20

## First break comes by scanning through the entire dictionary, guessing the
## first word of one plaintext, then seeing if that corresponds to a key that makes
## the second plaintext look reasonable
\$output = \$(foreach(\$word in \$words.GetEnumerator() | % { \$_.Key })
{
\$testString = XORBytes \$plainTextCombined[0..(\$word.Length - 1)] (\$word.ToCharArray())
(-join [char[]]\$testString) + " : \$word"
}
)

## Output the list to a file, then clean the file to include only alpha-numeric
## characters.
\$output > first_word.txt
\$alphaOnly = gc first_word.txt | select-string '^[a-zA-Z ]+ : .*\$'
\$alphaOnly > first_word_alpha.txt

## The first break comes from seeing that "healed dr" corresponds to "operation".
## Make a guess of that as the first 9 elements of the key.
\$k
ey,\$key,\$key,\$key,\$key,\$key,\$key,\$key,\$key = XORBytes ("healed dr".ToCharArray()) \$cipherBytes1[0..8]

## These other magic values come from looking at the output and making educated guesses
\$key,\$key = XORBytes ("se".ToCharArray()) \$cipherBytes1[49..50]
\$key,\$key,\$key = XORBytes ("ion".ToCharArray()) \$cipherBytes2[91..93]
\$key,\$key,\$key = XORBytes ("gs ".ToCharArray()) \$cipherBytes1[74..76]
\$key,\$key,\$key = XORBytes ("Fit".ToCharArray()) \$cipherBytes2[37..39]

## Display the currently-guessed plain texts alongside eachother,
## including the key and index into the cipher text.
for(\$counter = 0; \$counter -lt \$cipherBytes2.Count; \$counter++)
{
\$keyChar = \$key[\$counter % 20]
"{0} {1} {2} {3}" -f \$counter,([char] \$keyChar),([char] (\$cipherBytes1[\$counter] -bxor \$keyChar)),([char] (\$cipherBytes2[\$counter] -bxor \$keyChar))
}

-join [char[]] (XORBytes \$key \$cipherBytes1)
-join [char[]] (XORBytes \$key \$cipherBytes2)
-join [char[]] \$key
``````

And the library:

``````unction Get-SubstitutedText(\$cipherText, \$substitutions)
{
\$content = \$(foreach(\$char in [char[]] \$cipherText)
{
if(\$substitutions["\$char"])
{
\$substitutions["\$char"]
}
else
{
"."
}
}
)

-join \$content
}
{
\$combinedBytes = @()
for(\$counter = 0; \$counter -lt \$cipherBytes2.Length; \$counter++)
{
}

\$combinedBytes
}

function Search-DictionaryForWord(\$pattern, \$floating)
{
if(-not \$floating) { \$pattern = "^\$pattern`\$" }
Select-String \$pattern wordlist.txt
}

function Search-DictionaryForPattern(\$pattern, \$floating)
{
\$substituted = ""
\$toFind = ""

if(-not \$floating) { \$toFind += "^" }

foreach(\$char in \$pattern.ToLower().ToCharArray())
{
if(\$char -eq ".")
{
\$toFind += "."
continue
}

\$subIndex = \$substituted.IndexOf(\$char)

if(\$subIndex -ge 0)
{
\$toFind += "\" + (\$subIndex + 1)
}
else
{
if(-not \$substituted)
{
\$toFind += "(.)"
}
else
{
\$toFind += "([^\$substituted])"
}
\$substituted += \$char
}
}

if(-not \$floating) { \$toFind += '\$' }
Write-Verbose \$toFind
Select-String \$toFind wordlist.txt
}

function Measure-LetterFrequency(\$cipherText)
{
\$frequentLetters = " etnoriasfindhdlcumwfgypbvkjxqz"

"`nLetter Frequencies:`n"
\$groups = [char[]] \$cipherText.ToLower() | group | Sort -Desc Count

\$groupNumber = 0
\$(foreach(\$group in \$groups)
{
\$group | Select Name,
@{
Name = "Replacement";
Expression = { \$frequentLetters[\$groupNumber] } },
Count,
@{
Name = "Percent";
Expression = { "{0:..%}" -f (\$_.Count / \$cipherText.Length) } }
\$groupNumber++
}) | ft -auto | out-string | % { \$_.Trim() }
}

function Measure-BigraphFrequency(\$cipherText)
{
\$frequentBigraphs = "th","he","an","in","er","on","re",
"ed","nd","ha","at","en","es","of","nt","ea","ti",
"to","io","le","is","ou","ar","as","de","rt","ve"

\$cipherText = \$cipherText -replace " ",""
\$cipherText = \$cipherText -replace "\W",""

"`nBigraph Frequencies:`n"
\$groups =
@((\$cipherText -split '(..)') +
(\$cipherText.Remove(0,1) -split '(..)')) |
? { \$_.Trim().Length -eq 2 } | group | Sort -Desc Count

\$groupNumber = 0
\$(foreach(\$group in \$groups)
{
\$group | Select Name,
@{
Name = "Replacement";
Expression = { \$frequentBigraphs[\$groupNumber] } },
Count
\$groupNumber++
}) | ft -auto | out-string | % { \$_.Trim() }
}

function Measure-DoubleLetterFrequency(\$cipherText)
{
\$frequentDoubles = "ss","ee","tt","ff","ll","mm","oo"

"`nDouble Frequencies:`n"
\$groups = [Regex]::Matches(\$cipherText, '(.)\1') | % { \$_.Value } |
group | Sort -Desc Count

\$groupNumber = 0
\$(foreach(\$group in \$groups)
{
\$group | Select Name,
@{
Name = "Replacement";
Expression = { \$frequentDoubles[\$groupNumber] } },
Count
\$groupNumber++
}) | ft -auto | out-string | % { \$_.Trim() }

"`nOther groups:"
"\$(\$frequentDoubles[\$groupNumber..(\$frequentDoubles.Length - 1)])"
}

function Measure-InitialFrequency(\$cipherText)
{
\$frequentInitialLetters = "toawbcdsfmrhiyeglnoujk"

"`nInitial letters:`n"
\$groups = -split \$cipherText.ToLower() | % { ([char[]] \$_)[< /span>0] } | group | Sort -Desc Count

\$groupNumber = 0
\$(foreach(\$group in \$groups)
{
\$group | Select Name,
@{
Name = "Replacement";
Expression = { \$frequentInitialLetters[\$groupNumber] } },
Count,
@{
Name = "Percent";
Expression = { "{0:..%}" -f (\$_.Count / \$groups.Count) } }
\$groupNumber++
}) | ft -auto | out-string | % { \$_.Trim() }
}

function combo(\$string)
{
if(\$string.Length -eq 2)
{
\$string
\$string[-1] + \$string
}
else
{
foreach(\$element in \$string.ToCharArray())
{
combo (\$string -replace \$element,'') | foreach { \$element + \$_ }
combo (\$string -replace \$element,'') | foreach { \$_ }
}
}

}
``````