20 min read

In this article by Brenton J.W Blawat, author of the book Enterprise PowerShell Scripting Bootcamp, we will learn about string encryption and decryption. Large enterprises often have very strict security standards that are required by industry-specific regulations. When you are creating your Windows server scanning script, you will need to approach the script carefully with certain security concepts in mind. One of the most common situations you may encounter is the need to leverage sensitive data, such as credentials,in your script. While you could prompt for sensitive data during runtime, most enterprises want to automate the full script using zero-touch automation.

(For more resources related to this topic, see here.)

Zero-touch automation requires that the scripts are self-contained and have all of the required credentials and components to successfully run. The problem with incorporating sensitive data in the script, however, is that data can be obtained in clear text. The usage of clear text passwords in scripts is a bad practice, and violates many regulatory and security standards.

As a result, PowerShell scripters need a method to securely store and retrieve sensitive data for use in their scripts. One of the popular methods to secure sensitive data is to encrypt the sensitive strings. This article explores RijndaelManaged symmetric encryption, and how to use it to encrypt and decrypt strings using PowerShell.

In this article, we will cover the following topics:

  • Learn about RijndaelManaged symmetric encryption
  • Understand the salt, init, and password for the encryption algorithm
  • Script a method to create randomized salt, init, and password values
  • Encrypt and decrypt strings using RijndaelManaged encryption
  • Create an encoding and data separation security mechanism for encryption passwords

The examples in this article build upon each other. You will need to execute the script sequentially to have the final script in this article work properly.

RijndaelManaged encryption

When you are creating your scripts, it is best practice to leverage some sort of obfuscation, or encryption for sensitive data. There are many different strategies that you can use to secure your data. One is leveraging string and script encoding. Encoding takes your human readable string or script, and scrambles it to make it more difficult for someone to see what the actual code is. The downsides of encoding are that you must decode the script to make changes to it and decoding does not require the use of a password or passphrase. Thus, someone can easily decode your sensitive data using the same method you would use to decode the script.

The alternative to encoding is leveraging an encryption algorithm. Encryption algorithms provide multiple mechanisms to secure your scripts and strings. While you can encrypt your entire script, it’s most commonly used to encrypt the sensitive data in the scripts themselves, or answer files.

One of the most popular encryption algorithms to use with PowerShell is RijndaelManaged. RijndaelManaged is a symmetric block cipher algorithm, which was selected by United States National Institute of Standards and Technology (NIST) for its implementation of Advanced Encryption Standard (AES). When using RijndaelManaged for the standard of AES, it supports 128-bit, 192-bit, and 256-bit encryption.

In contrast to encoding, encryption algorithms require additional information to be able to properly encrypt and decrypt the string. When implementing the RijndaelManaged in PowerShell, the algorithm requires salt, a password, and the InitializationVector (IV). The salt is typically a randomized value that changes each time you leverage the encryption algorithm. The purpose of salt in a traditional encryption scenario is to change the encrypted value each time the encryption function is used. This is important in scenarios where you are encrypting multiple passwords or strings with the same value. If two users are using the same password, the encryption value in the database would also be the same. By changing the salt each time, the passwords, though the same value, would have different encrypted values in the database. In this article, we will be leveraging a static salt value.

The password typically is a value that is manually entered by a user, or fed into the script using a parameter block. You can also derive the password value from a certificate, active directory attribute values, or a multitude of other sources. In this article, we will be leveraging three sources for the password.

The InitializationVector (IV) is a hash generated from the IV string and is used for the EncryptionKey. The IV string is also typically a randomized value that changes each time you leverage the encryption algorithm. The purpose of the IV string is to strengthen the hash created by the encryption algorithm. This was created to thwart a hacker who is leveraging a rainbow attack using precalculated hash tables using no IV strings, or commonly used strings. Since you are setting the IV string, the number of hash combinations exponentially increases and it reduces the effectiveness of a rainbow attack. In this article, we will be using a static initialization vector value.

The implementation of randomization of the salt and initialization vector strings become more important in scenarios where you are encrypting a large set of data. An attacker can intercept hundreds of thousands of packets, or strings,which reveals an increasing amount of information about your IV. With this, the attacker can guess the IV and derive the password.

The most notable hack of IVs were with WiredEquivalentPrivacy (WEP) wireless protocol that used aweak, or small, initialization vector. After capturing enough packets, anIV hash could be guessed and a hacker could easily obtain the passphrase used on the wireless network.

Creating random salt, initialization vector, and passwords

As you are creating your scripts, you will want to make sure you use complex random values for the salt, IV string, and password. This is to prevent dictionary attacks where an individual may use common passwords and phrases to guess the salt, IV string, and password. When you create your salt and IVs, make sure they are a minimum of 10 random characters each. It is also recommended that you use a minimum of 30 random characters for the password.

To create random passwords in PowerShell, you can do the following:

Function create-password {
    # Declare password variable outside of loop.
    $password = ""
    # For numbers between 33 and 126
    For ($a=33;$a –le 126;$a++) {
        # Add the Ascii text for the ascii number referenced. 
        $ascii += ,[char][byte]$a
    }
    # Generate a random character form the $ascii character set.
    # Repeat 30 times, or create 30 random characters.
1..30 | ForEach { $password += $ascii | get-random }

    # Return the password
return $password
}
# Create four 30 character passwords
create-password
create-password
create-password
create-password

The output of this command would look like the following:

This function will create a string with 30 random characters for use with random password creation. You first start by declaring the create-password function. You then declare the $password variable for use within the function by setting it equal to “”.

The next step is creating a For command to loop through a set of numbers. These numbers represent ASCII character numbers that you can select from for the password. You then create the For command by writing For ($a=33; $a -le 126;$a++). This means starting at the number 33, increase the value by one ($a++), and continue until the number is less than or equal to 126. You then declare the $ascii variable and construct the variable using the += assignment operator. As the For loop goes through its iterations, it adds a character to the array values. The script then leverages the [char] or character value of the [byte] number contained in $a. After this section, the $ascii array will contain an array of all the ASCII characters with the byte values between 33 and 126.

You then continue to the random character generation. You declare the 1..30 command, which means for numbers 1 to 30, repeat the following command. You pipe this to ForEach {, which will designate for each of the 30 iterations. You then call the $ascii array and pipe it to | get-random cmdlet. The get-random cmdlet will randomly select one of the characters in the $ascii array. This value is then joined to the existing values in the $password string using the assignment operator +=. After the 30 iterations, there will be 30 random values in the $password variable. Lastly, you leverage return $password, to return this value to the script. After declaring the function, you call the function four times using create-password. This creates four random passwords for use.

To create strings that are less than 30 random characters in length, you can modify the 1..30 to be any value that you want. If you want 15 random character Salt and Initialization Vector, you would use 1..15 instead.

Encrypting and decrypting strings

To start using RijndaelManaged encryption, you need to import the .NET System.Security Assembly into your script. Much like importing a module to provide additional cmdlets, using .NET assemblies provide an extension to a variety of classes you wouldn’t normally have access to in PowerShell. Importing the assembly isn’t persistent. This means you will need to import the assembly each time you want to use it in a PowerShell session, or each time you want to run the script.

To load the .NET assembly, you can use the Add-Type cmdlet with the -AssemblyName parameter with the System.Security argument. Since the cmdlet doesn’t actually output anything to the screen, you may choose to print to the screen successful importing of the assembly.

To import the System.Security Assembly with display information, you can do the following:

Write-host "Loading the .NET System.Security Assembly For Encryption"
Add-Type -AssemblyNameSystem.Security -ErrorActionSilentlyContinue -ErrorVariable err
if ($err) {
Write-host "Error Importing the .NET System.Security Assembly."
    PAUSE
    EXIT
}
# if err is not set, it was successful.
if (!$err) {
    Write-host "Succesfully loaded the .NET System.Security Assembly For Encryption"
}

The output from this command looks like the following:

In this example, you successfully import the.NET System.SecurityAssembly for use with PowerShell. You first start by writing “Loading the .NET System.Security Assembly for Encryption” to the screen using the Write-host command. You then leverage the Add-Type cmdlet with the -AssemblyName parameter with the System.Security argument, the -ErrorAction parameter with the SilentlyContinue argument, and the -ErrorVariable parameter with the err argument. You then create an if statement to see if $err contains data. If it does, it will use Write-host cmdlet to print“Error Importing the .NET System.Security Assembly.” to the screen. It will PAUSE the script so the error can be read. Finally, it will exit the script. If $err is $null, designated by if (!$err) {, it will use the Write-host cmdlet to print “Successfully loaded the .NET System.Security Assembly for Encryption” to the screen. At this point, the script or PowerShell window is ready to leverageSystem.Security Assembly.

After you loadSystem.Security Assembly, you can start creating the encryption function. The RijndaelManaged encryption requires a four-step process to encrypt the strings which is represented in the preceeding diagram.

The RijndaelManaged encryption process is as follows:

  1. The process starts by creating the encryptor. The encryptor is derived from the encryption key (password and salt) and initialization vector.
  2. After you define the encryptor, you will need to create a new memory stream using the IO.MemoryStream object. A memory stream is what stores values in memory for use by the encryption assembly.
  3. Once the memory stream is open, you define a System.Security.Cryptography.CryptoStream object. The CryptoStream is the mechanism that uses the memory stream and the encryptor to transform the unencrypted data to encrypted data. In order to leverage the CryptoStream, you need to write data to the CryptoStream.
  4. The final step is to use the IO.StreamWriter object to write the unencrypted value into the CryptoStream. The output from this transformation is placed into MemoryStream. To access the encrypted value, you read the data in the memory stream.

To learn more about the System.Security.Cryptography.RijndaelManaged class, you can view the following MSDN article: https://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndaelmanaged(v=vs.110).aspx.

To create a script that encrypts strings using the RijndaelManaged encryption, you would perform the following:

Add-Type -AssemblyNameSystem.Security
function Encrypt-String { param($String, $Pass, $salt="CreateAUniqueSalt", $init="CreateAUniqueInit")
try{
        $r = new-Object System.Security.Cryptography.RijndaelManaged
    $pass = [Text.Encoding]::UTF8.GetBytes($pass)
        $salt = [Text.Encoding]::UTF8.GetBytes($salt)
        $init = [Text.Encoding]::UTF8.GetBytes($init) 

        $r.Key = (new-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, "SHA1", 50000).GetBytes(32)
        $r.IV = (new-Object Security.Cryptography.SHA1Managed).ComputeHash($init)[0..15]

        $c = $r.CreateEncryptor()
        $ms = new-Object IO.MemoryStream
        $cs = new-Object Security.Cryptography.CryptoStream $ms,$c,"Write"
        $sw = new-Object IO.StreamWriter $cs
        $sw.Write($String)
        $sw.Close()
        $cs.Close()
        $ms.Close()
        $r.Clear()
        [byte[]]$result = $ms.ToArray()
    }
catch { 
        $err = "Error Occurred Encrypting String: $_"
    }
if($err) {
        # Report Back Error
return $err
    } 
else {
return [Convert]::ToBase64String($result)
    }
} 
Encrypt-String "Encrypt This String""A_Complex_Password_With_A_Lot_Of_Characters"

The output of this script would look like the following:

This function displays how to encrypt a string leveraging the RijndaelManaged encryption algorithm. You first start by importing the System.Security assembly by leveraging Add-Type cmdlet, using the -AssemblyName parameter with the System.Security argument. You then declare the function of Encrypt-String. You include a parameter block to accept and set values into the function. The first value is $string, which is the unencrypted text. The second value is $pass, which is used for the encryption key. The third is a predefined $salt variable set to “CreateAUniqueSalt”. You then define the $init variable, which is set to “CreateAUniqueInit”.

After the parameter block, you declare try { to handle any errors in the .NET assembly. The first step is to declare the encryption class using new-Object cmdlet with the System.Security.Cryptography.RijndaelManaged argument. You place this object inside the $r variable.

You then convert the $pass, $salt, and $init values to the character encoding standard of UTF8 and store the character byte values in a variable. This is done specifying [Text.Encoding]::UTF8.GetBytes($pass) for the $pass variable, [Text.Encoding]::UTF8.GetBytes($salt) for the $salt variable, and [Text.Encoding]::UTF8.GetBytes($init) for the $init variable.

After setting the proper character encoding, you proceed to create the encryption key for the RijndalManaged encryption algorithm. This is done by setting the RijndaelManaged $r.Key attribute to the object created by (new-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, “SHA1”, 50000).GetBytes(32). This object leverages the Security.Cryptography.PasswordDeriveBytes class and creates a key using the $pass variable, $salt variable, “SHA1” hash name, and iterating the derivative 50000 times. Each iteration of this class generates a different key value, making it more complex to guess the key. You then leverage the .Get-Bytes(32) method to return the 32-byte value of the key.

The RijndaelManaged 256-bit encryption is a derivative of the 32 bytes in the key. 32 bytes times 8 bits per byte is 256bits.

To create the initialization vector for the algorithm, you set the RijndaelManaged$r.IV attribute to the object created by (new-Object Security.Cryptography.SHA1Managed).ComputeHash($init)[0..15]. This section of the code leverages Security.Cryptography.SHA1Managed and computes the hash based on the $init value. When you invoke the [0..15] range operator, it will obtain the first 16 bytes of the hash and place it into $r.IVattribute.

The RijndaelManaged default block size for the initialization vector is 128bits. 16 bytes times 8 bits per byte is 128bits.

After setting up the required attributes, you are now ready to start encrypting data. You first start by leveraging the $r RijndaelManaged object with the $r.Key and $r.IV attributes defined. You use the $r.CreateEncryptor() method to generate the encryptor.

Once you’ve generated the encryptor, you have to create a memory stream to do the encryption in memory. This is done by declaring new-Objectcmdlet, set to the IO.MemoryStream class, and placing the memory stream object in the $ms variable.

Next, you create CryptoStream. The CryptoStream is used to transform the unencrypted data into the encrypted data. You first declare the new-Object cmdlet with the Security.Cryptopgraphy.CryptoStream argument. You also define the memory stream of $ms, the encryptor of $c, and the operator of “Write” to tell the class to write unencrypted data to the encryption stream in memory.

After creating CryptoStream, you are ready to write the unencrypted data into the CryptoStream. This is done using the IO.StreamWriter class. You declare a new-Object cmdlet with the IO.StreamWriter argument, and define CryptoStream of $cs for writing.

Last, you take the unencrypted string stored in the $string variable, and pass it into the StreamWriter$sw with $sw.Write($String). The encrypted value is now stored in the memory stream. To stop the writing of data to the CryptoStream and MemoryStream, you close the StreamWriter with $sw.Close(), close the CryptoStream with $cs.Close() and the memory stream with $ms.Close(). For security purposes, you also clear out the encryptor data by declaring $r.Clear().

After the encryption process is done, you will need to export the memory stream to a byte array. This is done calling the $ms.ToArray() method and setting it to the$result variable with the [byte[]] data type. The contents are stored in a byte array in $result.

This section of the code is where you declare your catch { statement. If there were any errors in the encryption process, the script will execute this section. You declare the variable of $err with the“Error Occurred Encrypting String: $_” argument. The $_ will be the pipeline error that occurred during the try {} section. You then create an if statement to determine whether there is data in the $err variable. If there is data in $err, it returns the error string to the script.

If there were no errors, the script will enter the else { section of the script. It will convert the $result byte array to Base64String by leveraging [Convert]::ToBase64String($result). This converts the byte array to string for use in your scripts.

After defining the encryption function, you call the function for use. You first start by calling Encrypt-String followed by “Encrypt This String”. You also declare the second argument as the password for the encryptor, which is “A_Complex_Password_With_A_Lot_Of_Characters”. After execution, this example receives the encrypted value of hK7GHaDD1FxknHu03TYAPxbFAAZeJ6KTSHlnSCPpJ7c= generated from the function. Your results will vary depending on your salt, init, and password you use for the encryption algorithm.

Decrypting strings

The decryption of strings is very similar to the process you performed of encrypting strings. Instead of writing data to the memory stream, the function reads the data in the memory stream. Also, instead of using the .CreateEncryptor() method, the decryption process leverages the .CreateDecryptor() method.

To create a script that decrypts encrypted strings using the RijndaelManaged encryption, you would perform the following:

Add-Type -AssemblyNameSystem.Security
function Decrypt-String { param($Encrypted, $pass, $salt="CreateAUniqueSalt", $init="CreateAUniqueInit")

if($Encrypted -is [string]){
      $Encrypted = [Convert]::FromBase64String($Encrypted)
   }

   $r = new-Object System.Security.Cryptography.RijndaelManaged
   $pass = [System.Text.Encoding]::UTF8.GetBytes($pass)
   $salt = [System.Text.Encoding]::UTF8.GetBytes($salt)
   $init = [Text.Encoding]::UTF8.GetBytes($init) 

   $r.Key = (new-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, "SHA1", 50000).GetBytes(32)
   $r.IV = (new-Object Security.Cryptography.SHA1Managed).ComputeHash($init)[0..15]

   $d = $r.CreateDecryptor()
   $ms = new-Object IO.MemoryStream@(,$Encrypted)
   $cs = new-Object Security.Cryptography.CryptoStream $ms,$d,"Read"
   $sr = new-Object IO.StreamReader $cs

try {
       $result = $sr.ReadToEnd()
       $sr.Close()
       $cs.Close()
       $ms.Close()
       $r.Clear()
       Return $result
   }
   Catch {
       Write-host "Error Occurred Decrypting String: Wrong String Used In Script."
   }
}
Decrypt-String "hK7GHaDD1FxknHu03TYAPxbFAAZeJ6KTSHlnSCPpJ7c=""A_Complex_Password_With_A_Lot_Of_Characters".

The output of this script would look like the following:

This function displays how to decrypt a string leveraging the RijndaelManaged encryption algorithm. You first start by importing the System.Security assembly by leveraging the Add-Type cmdlet, using the -AssemblyName parameter with the System.Security argument. You then declare the Decrypt-String function. You include a parameter block to accept and set values for the function. The first value is $Encrypted, which is the encrypted text. The second value is the $pass which is used for the encryption key. The third is a predefined $salt variable set to “CreateAUniqueSalt”. You then define the $init variable, which is set to “CreateAUniqueInit”.

After the parameter block, you check to see if the encrypted value is formatted as a string by using if ($Encrypted -is [string]) {. If this evaluates to True, you convert the string to bytes using [Convert]::FromBase64String($Encrypted) and placing the encoded value in the $Encrypted variable. Next, you declare the decryption class using new-Object cmdlet with the System.Security.Cryptography.RijndaelManaged argument. You place this object inside of the $r variable.

You then convert the $pass, $salt, and $init values to the character encoding standard of UTF8 and store the character byte values in a variable. This is done specifying [Text.Encoding]::UTF8.GetBytes($pass) for the $pass variable, [Text.Encoding]::UTF8.GetBytes($salt) for the $salt variable, and [Text.Encoding]::UTF8.GetBytes($init) for the $init variable.

After setting the proper character encoding, you proceed to create the encryption key for the RijndaelManaged encryption algorithm. This is done by setting the RijndaelManaged $r.Key attribute to the object created by (new-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, “SHA1”, 50000).GetBytes(32). This object leverages the Security.Cryptography.PasswordDeriveBytes class and creates a key using the $pass variable, $salt variable, “SHA1” hash name, and iterating the derivative 50000 times. Each iteration of this class generates a different key value, making it more complex to guess the key. You then leverage the .get-bytes(32) method to return the 32-byte value of the key.

To create the initialization vector for the algorithm, you set the RijndaelManaged $r.IV attribute to the object created by (new-Object Security.Cryptography.SHA1Managed).ComputeHash($init)[0..15]. This section of the code leverages the Security.Cryptography.SHA1Managed class and computes the hash based on the $init value. When you invoke the [0..15] range operator, the first 16 bytes of the hash are obtained and placed into $r.IV attribute.

After setting up the required attributes, you are now ready to start decrypting data. You first start by leveraging the $r RijndaelManaged object with the $r.key and $r.IV attributes defined. You use the $r.CreateDecryptor() method to generate the decryptor.

Once you’ve generated the decryptor, you have to create a memory stream to do the decryption in memory. This is done by declaring new-Object cmdlet with the IO.MemoryStream class argument. You then reference the $encrypted values to place in the memory stream object with @(,$Encrypted), and store the populated memory stream in the $ms variable.

Next, you create CryptoStream, which CryptoStream is used to transform the encrypted data into the decrypted data. You first declare new-Object cmdlet with the Security.Cryptopgraphy.CryptoStream class argument. You also define the memory stream of $ms, the decryptor of $d, and the operator of “Read” to tell the class to read the encrypted data from the encryption stream in memory.

After creating CryptoStream, you are ready to read the decrypted datafrom CryptoStream. This is done using the IO.StreamReader class. You declare new-Object with the IO.StreamReader class argument, and define CryptoStream of $cs to read from.

At this point, you use try { to catch any error messages that are generated from reading the data in the StreamReader. You call $sr.ReadToEnd(), which calls the StreamReader and reads the complete decrypted value and places the datain the $result variable. To stop the reading of data to CryptoStream and MemoryStream, you close StreamWriter with $sw.Close(), close the CryptoStream with $cs.Close() and the memory stream with $ms.Close(). For security purposes, you also clear out the decryptor data by declaring $r.Clear(). If the decryption is successful, you return the value of $result to the script.

After defining the decryption function, you call the function for use. You first start by calling Decrypt-String followed by “hK7GHaDD1FxknHu03TYAPxbFAAZeJ6KTSHlnSCPpJ7c=”. You also declare the second argument as the password for the decryptor, which is “A_Complex_Password_With_A_Lot_Of_Characters”. After execution, you will receive the decrypted value of “Encrypt This String” generated from the function.

Summary

In this article, we learned about RijndaelManaged 256-bit encryption. We first started with the basics of the encryption process. Then, we proceeded into learning how to create randomized salt, init, and passwords in scripts. We ended the article with learning how to encrypt and decrypt strings.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here