PowerShell Cryptography Library

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.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
$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
[0],$key[1],$key[2],$key[3],$key[4],$key[5],$key[6],$key[7],$key[8] = XORBytes ("healed dr".ToCharArray()) $cipherBytes1[0..8]

## These other magic values come from looking at the output and making educated guesses
$key[9],$key[10] = XORBytes ("se".ToCharArray()) $cipherBytes1[49..50]
$key[11],$key[12],$key[13] = XORBytes ("ion".ToCharArray()) $cipherBytes2[91..93]
$key[14],$key[15],$key[16] = XORBytes ("gs ".ToCharArray()) $cipherBytes1[74..76]
$key[17],$key[18],$key[19] = 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:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
function Get-SubstitutedText($cipherText, $substitutions)
{
    $content = $(foreach($char in [char[]] $cipherText)
    {
        if($substitutions["$char"])
        {
            $substitutions["$char"]
        }
        else
        {
            "."
        }
    }
    )
   
    -join $content
}

function XORBytes($passwordBytes, $cipherBytes2)
{
    $combinedBytes = @()
    for($counter = 0; $counter -lt $cipherBytes2.Length; $counter++)
    {
        $combinedBytes += $passwordBytes[$counter % $passwordBytes.Length] -bxor $cipherBytes2[$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[0]
    }
    else
    {
        foreach($element in $string.ToCharArray())
        {
            combo ($string -replace $element,'') | foreach { $element + $_ }
            combo ($string -replace $element,'') | foreach { $_ }
        }
    }
   
}

One Response to “PowerShell Cryptography Library”

  1. Detecting Obfuscated PowerShell | Precision Computing writes:

    […] to do some variable analysis. Basic entropy is a pretty good start. When you combine that with letter frequency distribution, this creates a pretty good obfuscation […]

Leave a Reply