Reinventing the Wheel in PowerShell: PoshCiphers - Part 1
Introduction
Anyone who knows me knows I am a huge fan of PowerShell and a firm believer in reinventing the wheel with PowerShell in order to learn. So, this last weekend I bought a new book (The Code Book). After reading a few chapters and reminiscing on doing the Krypton challenges from OverTheWire, I decided I wanted to reinvent the wheel. Of course to further challenge myself I wanted to do it right and make a genuine PowerShell module.
If you just want to jump right into the module its posted on my GitHub as PoshCiphers.
For brevity I am only explaining the core parts of the module related to the cryptography and not the complete module. I hope my comments and some googling are enough for anyone who has questions about anything I haven't covered from the module.
Update
The module now performs brute forcing for Caesar and Vigenère ciphers. There have also been significant changes to the functions and their names so please use the versions on GitHub.
First, How do you make a PowerShell module?
Since I had never created a PowerShell module, I had to learn how to do that first. While I have read a few PowerShell books, most of them were very light on the modules and proper way to create them. Thankfully, there were various blogs that helped me tackle the first challenge. There even is a PowerShell module called PowerRails that helps build the scaffolding for a module.
Good Old Caesar
The first and easiest cipher to build support for in my module was the classic Caesar cipher. The Caesar cipher are simple rotation based monoalphabetic cipher. Traditionally, the Caesar cipher is a rotation of three. This means if you take the word Example
and rotate it by three it becomes Hadpsoh
.
When I first did the Krypton challenges I was very inexperienced compared to now and made a python script that was crude to say the least in order to work through most the challenges.
ciphertext = "QRWW KHEH VWHA DPSO H"
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
rot = 3
deciphered = ""
str(rot)
for char in ciphertext:
if char in alpha:
deciphered += alpha[(alpha.index(char)-rot)%(len(alpha))]
print "[-] Deciphered: " + deciphered
As anyone can see from the python script it was limited. First, it could only handle uppercase ciphertext. Second, its re-usability was terrible.
Deciphering in PowerShell
I choose to approach the functions for the Caesar cipher by building the support to decipher it first.
The math behind it
In order to decipher a Caesar cipher. First the letters have to be converted to numbers such as A=0,B=1,C=2,etc. Then subtracting the rotation from the letter. To account for when the result is not between 0-25 using the modulus of the rotated letter and 26 will provide the proper result. In case I am terrible at explaining the math please look at the Wikipedia entry for Caesar ciphers.
PowerShell'n that math
First, I wanted to build a function to handle the modulus portion of the algorithm. This part was easy I simply made a function called Get-Modulus
that took two parameters $Dividend
and $Divisor
. I then simply calculated the modulus and returned it.
$Modulus = ($Dividend % $Divisor + $Divisor) % $Divisor
Return $Modulus
Get-Modulus
Function Get-Modulus
{
<#
.Synopsis
Returns the modulus of a dividend and divisor.
.Description
Returns the modulus of a dividend and divisor.
.Parameter Dividend
The dividend to use.
.Parameter Divisor
The divisor to use.
.Example
Get-Modulus -Dividend 5 -Divisor 2
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $True, Position=0, ValueFromPipeline=$True)]
[Int] $Dividend,
[Parameter(Mandatory = $True, Position=1, ValueFromPipeline=$True)]
[Int] $Divisor
)
Process
{
$Modulus = ($Dividend % $Divisor + $Divisor) % $Divisor
}
End
{
#Returns the Modulus of the dividend and divisor
Return $Modulus
}
}
Next, I had to apply the modulus to the formula and write it in the PowerShell way. Additionally, I wanted to account for both uppercase and lowercase this time.
(Get-Modulus -Dividend $($_ - 65 - $Rotation) -Divisor 26) + 65
The above snippet is for uppercase letters and there is some additions in the formula over what I mentioned earlier. You might be noticing the 65s thrown around in there and this is because I am converting the letters into their ASCII code but in order for the formula to work I have to subtract the ASCII code for A to make the letters match the 0-25 range, and of course I have to add the ASCII code back to make it a valid ASCII code again. I also had to do it again for lowercase letters which looks like the snippet below.
(Get-Modulus -Dividend $($_ - 97 - $Rotation) -Divisor 26) + 97
Get-RotDecipher
Function Get-RotDecipher
{
<#
.Synopsis
Deciphers message(s) that has been enciphered with a rotation based cipher.
.Description
Deciphers message(s) that has been enciphered with a rotation based cipher.
.Parameter Ciphertext
The enciphered message(s) to be deciphered.
.Parameter Rotation
The rotation to use for deciphering.
Default value is 13.
.Parameter Strip
Removes whitespaces from the ciphertext message(s).
.Example
Get-RotDecipher -Ciphertext "Rknzcyr" -Rotation 13
Plaintext Ciphertext Rotation
--------- ---------- --------
Example Rknzcyr 13
.Example
Get-RotDecipher -Ciphertext "Rknzcyr Jvgu Fcnprf" -Rotation 13 -Strip
Plaintext Ciphertext Rotation
--------- ---------- --------
ExampleWithSpaces RknzcyrJvguFcnprf 13
.LINK
https://github.com/stackcrash/PoshCiphers
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $True, Position=0, ValueFromPipeline=$True)]
[String[]] $Ciphertext,
[Parameter(Mandatory = $False, Position=1)]
[Int] $Rotation = 13,
[Parameter()]
[Switch]$Strip
)
Begin
{
#Create an array list to store results in
$DecipheredMessages = New-Object System.Collections.ArrayList
}
Process
{
#Loop through each ciphertext
ForEach ($Message in $Ciphertext)
{
#Create an array list to store deciphered characters in
$Deciphered = New-Object System.Collections.ArrayList
If ($Strip)
{
#Remove whitespaces
$Message = $Message -replace '\s', ''
}
#Loop though each character in the message
ForEach ($Character in $Message.ToCharArray())
{
#Convert the character to ASCII code
Switch ([Byte]$Character)
{
#Decipher uppercase characters
{$_ -ge 65 -and $_ -le 90} { $Deciphered.Add([Char]((Get-Modulus -Dividend $($_ - 65 - $Rotation) -Divisor 26) + 65)) | Out-Null }
#Decipher lowercase characters
{$_ -ge 97 -and $_ -le 122} { $Deciphered.Add([Char]((Get-Modulus -Dividend $($_ - 97 - $Rotation) -Divisor 26) + 97)) | Out-Null }
#Pass through symbols and numbers
Default { $Deciphered.Add($Character) | Out-Null }
}
}
#Add results of the decipher
$DecipheredMessages.Add(([PSCustomObject]@{
'Plaintext' = $Deciphered -join ""
'Ciphertext' = $Message
'Rotation' = $Rotation
})) | Out-Null
}
}
End
{
#Return the results
Return $DecipheredMessages
}
}
Enciphering in PowerShell
After solving the challenge of deciphering, enciphering was easy since all I had to do is add the rotation instead of subtracting it. The change is simple enough so the uppercase formula looks like the snippet below.
(Get-Modulus -Dividend $($_ - 65 + $Rotation) -Divisor 26) + 65
And the lowercase formula.
(Get-Modulus -Dividend $($_ - 97 + $Rotation) -Divisor 26) + 97
Get-RotEncipher
Function Get-RotEncipher
{
<#
.Synopsis
Enciphers plaintext message(s) with a rotation based cipher.
.Description
Enciphers plaintext message(s) with a rotation based cipher.
.Parameter Plaintext
The plaintext message(s) to be enciphered.
.Parameter Rotation
The rotation to use for enciphering.
Default value is 13.
.Parameter Spacing
The amount of characters to insert spaces between in the ciphertext.
Default value is 0.
.Parameter Strip
Removes whitespaces from the plaintext message(s).
.Example
Get-RotEncipher -Plaintext "Example" -Rotation 13
Plaintext Ciphertext Rotation
--------- ---------- --------
Example Rknzcyr 13
.Example
Get-RotEncipher -Plaintext "Example With Spaces" -Rotation 13 -Strip
Plaintext Ciphertext Rotation
--------- ---------- --------
ExampleWithSpaces RknzcyrJvguFcnprf 13
.Example
Get-RotEncipher -Plaintext "Example With Spaces" -Rotation 13 -Spacing 4
Plaintext Ciphertext Rotation
--------- ---------- --------
Example With Spaces Rknz cyrJ vguF cnpr f 13
.LINK
https://github.com/stackcrash/PoshCiphers
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $True, Position=0, ValueFromPipeline=$True)]
[String[]] $Plaintext,
[Parameter(Mandatory = $False, Position=1)]
[Int] $Rotation = 13,
[Parameter(Mandatory = $False, Position=2)]
[String] $Spacing = 0,
[Parameter()]
[Switch]$Strip
)
Begin
{
#Create an array list to store results in
$EncipheredMessages = New-Object System.Collections.ArrayList
}
Process
{
#Loop through each message
ForEach ($Message in $Plaintext)
{
#Create an array list to store enciphered characters in
$Enciphered = New-Object System.Collections.ArrayList
If ($Strip)
{
#Remove whitespaces
$Message = $Message -replace '\s', ''
}
#Loop though each character in the message
ForEach ($Character in $Message.ToCharArray())
{
#Convert the character to ASCII code
Switch ([Byte]$Character)
{
#Encipher uppercase characters
{$_ -ge 65 -and $_ -le 90} { $Enciphered.Add([Char]((Get-Modulus -Dividend $($_ - 65 + $Rotation) -Divisor 26) + 65)) | Out-Null }
#Encipher lowercase characters
{$_ -ge 97 -and $_ -le 122} { $Enciphered.Add([Char]((Get-Modulus -Dividend $($_ - 97 + $Rotation) -Divisor 26) + 97)) | Out-Null }
#Pass through symbols and numbers
Default { $Enciphered.Add($Character) | Out-Null }
}
}
#Join the results of the encipher
$Ciphertext = $Enciphered -join ""
#Check is spacing is used
If ($Spacing -ge 1)
{
#Remove existing whitespaces
$Ciphertext = $Ciphertext -replace '\s', ''
#Split the ciphertext into the desired spacing
$Ciphertext = ([RegEx]::Matches($Ciphertext, ".{1,$Spacing}") | ForEach-Object { $_.Value }) -join ' '
}
#Add results of the encipher
$EncipheredMessages.Add(([PSCustomObject]@{
'Plaintext' = $Message
'Ciphertext' = $Ciphertext
'Rotation' = $Rotation
})) | Out-Null
}
}
End
{
#Return the array of PowerShell objects
Return $EncipheredMessages
}
}
Vive la Vigenère
The next cipher to build support for was the Vigenère cipher which is rotation based like the Caesar cipher. Unlike the Caesar cipher it is a polyalphabetic cipher that uses a repeated keyword to rotate between multiple sets of rotated alphabets.
Enciphering first this time
This time I choose to tackle the challenge from the enciphering side first.
More math fun
The Wikipedia entry definitely gives a better description of the math behind it than I can. But I will try my best to explain the math in PowerShell.
PowerShell'n the math again
Once again I needed to create a function to handle a specific part of the cipher. This time it was a function to handle creating the alphabets from the key to use in the enciphering. There are generally two approaches to this. One is you generate a table of all 26 possible alphabets and perform lookups based on the letter in the key. The second and probably easiest is to simply generate an array with the start point for each of the alphabets based on the key.
The function Get-VigFilter
was a little more complicated than the modulus function for the Caesar cipher so I will break it down to the two core parts. This time the function only takes one parameter called $Key
which as you can guess is the key being used. Next it loops through each letter in the key and looks at uppercase and lowercase letters while ignoring other characters.
($_ - 65) % 26
The above is the algorithm to generate the 0-25 value for the letter. Lowercase is identical except it uses 97 instead of the 65.
($_ - 97) % 26
Even though the function accounts for uppercase and lowercase in reality the Vigenère cipher alphabet is case-insensitive to uppercase or lowercase.
Get-VigFilter
Function Get-VigFilter
{
<#
.Synopsis
Returns an array of numbers representing the vigenere key.
.Description
Returns an array of numbers representing the vigenere key.
.Parameter Key
The key to use.
.Example
Get-VigFilter -Key "password"
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $True, Position=0, ValueFromPipeline=$True)]
[String] $Key
)
Begin
{
$KeyArray = New-Object System.Collections.ArrayList
}
Process
{
ForEach ($Character in $Key.ToCharArray())
{
Switch ([Byte]$Character)
{
#Uppercase characters
{$_ -ge 65 -and $_ -le 90} { $KeyArray.Add((($_ - 65) % 26)) | Out-Null }
#Lowercase characters
{$_ -ge 97 -and $_ -le 122} { $KeyArray.Add((($_ - 97) % 26)) | Out-Null }
#Ignore symbols and numbers
Default { Out-Null }
}
}
}
End
{
Return $KeyArray
}
}
Since the function that generates the array was complete, I needed to implement that into a function to perform the encipher.
$Filter = Get-VigFilter -Key $Key
$FilterIndex = 0
The above snippet is calling the function to generate the array and also setting a different variable to zero. The variable above called $FilterIndex
is the beginning of the key and it will increment each enciphered letter because the key is repeated over and over in order to encipher a full alphabet. Next I had to integrate this all into the rest of the math for the cipher.
$Enciphered.Add([Char](($_ - 65 + $Filter[$FilterIndex % $Filter.Length]) % 26 + 65)) | Out-Null
$FilterIndex += 1
Above is the complete snippet for an uppercase letter but don't worry I will break it down. First, we do the same subtraction of the ASCII code for A in order to reduce the letter to the value between 0-25. Next the value from the key array is added in order to perform the rotation based on the appropriate part of the key. Finally, the modulo of 26 is applied and we add back the ASCII value of A. Again this is to make the result a valid letter between A-Z. $Filter[$FilterIndex % $Filter.Length]
is used to provide a simple mechanism for repeating the key without the need for any additional logic or resetting the key index.
$Enciphered.Add([Char](($_ - 97 + $Filter[$FilterIndex % $Filter.Length]) % 26 + 97)) | Out-Null
$FilterIndex += 1
Lowercase letters are handled identically except once again the ASCII of A is swapped with a.
Get-VigEncipher
Function Get-VigEncipher
{
<#
.Synopsis
Enciphers plaintext message(s) with a Vigenere cipher.
.Description
Enciphers plaintext message(s) with a Vigenere cipher.
.Parameter Plaintext
The plaintext message(s) to be enciphered.
.Parameter Key
The key to use in the enciphering.
Note: The key is case-insensitive.
.Parameter Spacing
The amount of characters to insert spaces between in the ciphertext.
Default value is 0.
.Parameter Strip
Removes whitespaces from the plaintext message(s).
.Example
Get-VigEncipher -Plaintext "Example" -Key "password"
Plaintext Ciphertext Key
--------- ---------- ---
Example Txselzv password
.Example
Get-VigEncipher -Plaintext "Example With Spaces" -Key "password" -Strip
Plaintext Ciphertext Key
--------- ---------- ---
Examplewithspaces TxselzvZxtzKlothh password
.Example
Get-VigEncipher -Plaintext "Example With Spaces" -Key "password" -Spacing 4
Plaintext Ciphertext Key
--------- ---------- ---
Example With Spaces Txse lzvZ xtzK loth h password
.LINK
https://github.com/stackcrash/PoshCiphers
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $True, Position=0, ValueFromPipeline=$True)]
[String[]] $Plaintext,
[Parameter(Mandatory = $True, Position=1)]
[String] $Key,
[Parameter(Mandatory = $False, Position=2)]
[String] $Spacing = 0,
[Parameter()]
[Switch]$Strip
)
Begin
{
#Create an array list to store results in
$EncipheredMessages = New-Object System.Collections.ArrayList
}
Process
{
#Loop through each message
ForEach ($Message in $Plaintext)
{
#Create an array list to store enciphered characters in
$Enciphered = New-Object System.Collections.ArrayList
#Get the Vigenere table for the key
$Filter = Get-VigFilter -Key $Key
#Set the index value to use with the filter
$FilterIndex = 0
If ($Strip)
{
#Remove whitespaces
$Message = $Message -replace '\s', ''
}
#Loop though each character in the message
ForEach ($Character in $Message.ToCharArray())
{
#Convert the character to ASCII code
Switch ([Byte]$Character)
{
#Encipher uppercase characters
{$_ -ge 65 -and $_ -le 90}
{
$Enciphered.Add([Char](($_ - 65 + $Filter[$FilterIndex % $Filter.Length]) % 26 + 65)) | Out-Null
$FilterIndex += 1
}
#Encipher lowercase characters
{$_ -ge 97 -and $_ -le 122}
{
$Enciphered.Add([Char](($_ - 97 + $Filter[$FilterIndex % $Filter.Length]) % 26 + 97)) | Out-Null
$FilterIndex += 1
}
#Pass through symbols and numbers
Default { $Enciphered.Add($Character) | Out-Null }
}
}
#Join the results of the encipher
$Ciphertext = $Enciphered -join ""
#Check is spacing is used
If ($Spacing -ge 1)
{
#Remove existing whitespaces
$Ciphertext = $Ciphertext -replace '\s', ''
#Split the ciphertext into the desired spacing
$Ciphertext = ([RegEx]::Matches($Ciphertext, ".{1,$Spacing}") | ForEach-Object { $_.Value }) -join ' '
}
#Add results of the encipher
$EncipheredMessages.Add(([PSCustomObject]@{
'Plaintext' = $Message
'Ciphertext' = $Ciphertext
'Key' = $Key
})) | Out-Null
}
}
End
{
#Return the array of PowerShell objects
Return $EncipheredMessages
}
}
Deciphering in PowerShell
Deciphering the Vigenère cipher is almost identical to enciphering. The difference is I manipulated the key array to make a key array specifically for the deciphering.
$Filter = Get-VigFilter -Key $Key | ForEach-Object { (26 - $_) % 26 }
Basically, the above snippet simply generates the key array and then loops through each element subtracting it from 26 and applying the modulo of 26. This alters the array so that it can be applied in the same formulas as the enciphering but return the deciphered value instead of the enciphered value.
Get-VigDecipher
Function Get-VigDecipher
{
<#
.Synopsis
Deciphers message(s) that has been enciphered with a Vigenere cipher.
.Description
Deciphers message(s) that has been enciphered with a Vigenere cipher.
.Parameter Ciphertext
The enciphered message(s) to be deciphered.
.Parameter Key
The key to use in the deciphering.
Note: The key is case-insensitive.
.Parameter Strip
Removes whitespaces from the ciphertext message(s).
.Example
Get-VigDecipher -Ciphertext "Txselzv" -Key "password"
Plaintext Ciphertext Key
--------- ---------- ---
Example Txselzv password
.Example
Get-VigDecipher -Ciphertext "Txse lzvZ xtzK loth h" -Key "password" -Strip
Plaintext Ciphertext Key
--------- ---------- ---
ExampleWithSpaces TxselzvZxtzKlothh password
.LINK
https://github.com/stackcrash/PoshCiphers
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $True, Position=0, ValueFromPipeline=$True)]
[String[]] $Ciphertext,
[Parameter(Mandatory = $True, Position=1)]
[String] $Key,
[Parameter()]
[Switch]$Strip
)
Begin
{
#Create an array list to store results in
$DecipheredMessages = New-Object System.Collections.ArrayList
}
Process
{
#Loop through each ciphertext
ForEach ($Message in $Ciphertext)
{
#Create an array list to store deciphered characters in
$Deciphered = New-Object System.Collections.ArrayList
#Get the Vigenere table for the key
$Filter = Get-VigFilter -Key $Key | ForEach-Object { (26 - $_) % 26 }
#Set the index value to use with the filter
$FilterIndex = 0
If ($Strip)
{
#Remove whitespaces
$Message = $Message -replace '\s', ''
}
#Loop though each character in the message
ForEach ($Character in $Message.ToCharArray())
{
#Convert the character to ASCII code
Switch ([Byte]$Character)
{
#Encipher uppercase characters
{$_ -ge 65 -and $_ -le 90}
{
$Deciphered.Add([Char](($_ - 65 + $Filter[$FilterIndex % $Filter.Length]) % 26 + 65)) | Out-Null
$FilterIndex += 1
}
#Encipher lowercase characters
{$_ -ge 97 -and $_ -le 122}
{
$Deciphered.Add([Char](($_ - 97 + $Filter[$FilterIndex % $Filter.Length]) % 26 + 97)) | Out-Null
$FilterIndex += 1
}
#Pass through symbols and numbers
Default { $Deciphered.Add($Character) | Out-Null }
}
}
#Add results of the decipher
$DecipheredMessages.Add(([PSCustomObject]@{
'Plaintext' = $Deciphered -join ""
'Ciphertext' = $Message
'Key' = $Key
})) | Out-Null
}
}
End
{
#Return the results
Return $DecipheredMessages
}
}
The end is here
With the functions to encipher and decipher both the Caesar and Vigenère ciphers completed I wrapped it all into a module and performed a few test to make sure it all worked together appropriately.
Or is it
I am proud of the results of my first module, but I definitely want to do more with it hence the Part 1 in the title. My current goal is to build in brute forcing for both of the ciphers. After that I want to build support for some other ciphers commonly found in CTFs because the ultimate end goal is to make a robust module for cryptography portions of CTFs. If anyone has specific request feel free to do a feature request on the GitHub project for the module.