-
-
Save Darryl-G/d1039c2407262cb6d735c3e7a730ee86 to your computer and use it in GitHub Desktop.
Powershell Basic Routines for Encrypt and Decrypt AES 256 CBC using OpenSSL EVP
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
#PowerShell to create an gibberishaes(and openssl) compatible aes string with salt | |
#Salted__8bitsalt/aesstring | |
#thanks for .netcode -> http://stackoverflow.com/questions/5452422/openssl-using-only-net-classes | |
# | |
# This outputs the same ciphertext as: echo -n "SomePlainText"|/usr/bin/openssl enc -A -e -aes-256-cbc -a -pass pass:ThePassword | |
# For decrypt: echo "[cipherText]"|/usr/bin/openssl base64 -d|/usr/bin/openssl enc -A -d -aes-256-cbc -pass pass:ThePassword | |
function OpenSSLEncrypt($passphrase, $plainText) | |
{ | |
# generate salt | |
[byte[]] $key | |
[byte[]] $iv; | |
[byte[]] $salt = RandomByteArray | |
$rng = (new-Object Security.Cryptography.RNGCryptoServiceProvider); | |
$res = DeriveKeyAndIV $passphrase $salt | |
$key = $res.key | |
$iv = $res.iv | |
# encrypt bytes | |
[byte[]] $encryptedBytes = EncryptStringToBytesAes $plainText $key $iv; | |
$encryptedBytes = $encryptedBytes[1..33] # Increase this if you enter longer plaintext e.g. 128, 256 etc. | |
# add salt as first 8 bytes | |
[byte[]] $encryptedBytesWithSalt | |
$encryptedBytesWithSalt = ([Text.Encoding]::ASCII.GetBytes("Salted__")) | |
$encryptedBytesWithSalt += $salt | |
$encryptedBytesWithSalt += $encryptedBytes | |
# base64 encode | |
return [Convert]::ToBase64String($encryptedBytesWithSalt) | |
} | |
function DeriveKeyAndIV($passphrase, $salt) | |
{ | |
# generate key and iv | |
$concatenatedHashes | |
[byte[]] $password = [Text.Encoding]::UTF8.GetBytes($passphrase); | |
[byte[]] $currentHash =@() | |
$md5 = new-object System.Security.Cryptography.MD5CryptoServiceProvider | |
[bool] $enoughBytesForKey = $false; | |
# See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM | |
while (!$enoughBytesForKey) | |
{ | |
[byte[]] $preHash = @() | |
$preHash = $currentHash | |
$preHash += $password | |
$preHash += $salt | |
$currentHash = $md5.ComputeHash($preHash); | |
$concatenatedHashes += $currentHash; | |
if ($concatenatedHashes.Count -ge 48) | |
{ | |
$enoughBytesForKey = $true; | |
} | |
} | |
$key = $concatenatedHashes[0..31] | |
$iv = $concatenatedHashes[32..(32+15)] | |
$md5.Clear(); | |
$md5 = $null; | |
$value = New-Object -TypeName PSObject -Property @{ | |
key = $key | |
iv = $iv | |
} | |
$value | |
} | |
function EncryptStringToBytesAes($plainText, $key, $iv) | |
{ | |
# Check arguments. | |
if ($plainText -eq $null -or $plainText.Length -le 0){ | |
throw new-object ArgumentNullException("plainText");} | |
if ($key -eq $null -or $key.Length -le 0){ | |
throw new-object ArgumentNullException("key");} | |
if ($iv -eq $null -or $iv.Length -le 0){ | |
throw new-object ArgumentNullException("iv");} | |
# Declare the stream used to encrypt to an in memory | |
# array of bytes. | |
$msEncrypt; | |
# Declare the RijndaelManaged object | |
# used to encrypt the data. | |
$aesAlg = new-Object System.Security.Cryptography.RijndaelManaged | |
try | |
{ | |
# Create a RijndaelManaged object | |
# with the specified key and IV. | |
$aesAlg = new-object System.Security.Cryptography.RijndaelManaged | |
$aesAlg.Mode = [System.Security.Cryptography.CipherMode]::CBC | |
$aesAlg.KeySize = 256 | |
$aesAlg.BlockSize = 128 | |
$aesAlg.key = $key | |
$aesAlg.IV = $iv | |
# Create an encryptor to perform the stream transform. | |
[System.Security.Cryptography.ICryptoTransform] $encryptor = $aesAlg.CreateEncryptor($aesAlg.Key, $aesAlg.IV); | |
# Create the streams used for encryption. | |
$msEncrypt = new-Object System.IO.MemoryStream | |
$csEncrypt = new-object System.Security.Cryptography.CryptoStream($msEncrypt, $encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write) | |
$swEncrypt = new-object System.IO.StreamWriter($csEncrypt) | |
#Write all data to the stream. | |
$swEncrypt.Write($plainText); | |
$swEncrypt.Flush(); | |
$swEncrypt.Close(); | |
} | |
finally | |
{ | |
# Clear the RijndaelManaged object. | |
if ($aesAlg -ne $null){ | |
$aesAlg.Clear();} | |
} | |
# Return the encrypted bytes from the memory stream. | |
return $msEncrypt.ToArray(); | |
} | |
function RandomByteArray([int] $length = 8) | |
{ | |
$array = @() | |
for($i=0;$i -lt $length;$i++) | |
{ | |
$array += [math]::Round($(Get-Random -Minimum 50.1 -Maximum 190.1)) | |
} | |
return $array | |
} | |
function OpenSSLDecrypt([String] $passphrase, [String] $encrypted) { | |
# base 64 decode | |
[byte[]] $encryptedBytesWithSalt = [Convert]::FromBase64String($encrypted); | |
# extract salt (first 8 bytes of encrypted) | |
[byte[]] $salt = $encryptedBytesWithSalt[8..15] | |
[byte[]] $encryptedBytes = $encryptedBytesWithSalt[($salt.length + 8 )..($encryptedBytesWithSalt.Length)] | |
#$encryptedBytes+=0 | |
# get key and iv | |
$res = DeriveKeyAndIV $passphrase $salt | |
[byte[]] $key = $res.key | |
[byte[]] $iv = $res.iv | |
return DecryptStringFromBytesAes $encryptedBytes $key $iv | |
} | |
function DecryptStringFromBytesAes([byte[]] $cipherText, [byte[]] $key, [byte[]] $iv) { | |
# Check arguments. | |
if ($cipherText -eq $null -or $cipherText.Length -le 0){ | |
throw new-object ArgumentNullException("cipherText");} | |
if ($key -eq $null -or $key.Length -le 0){ | |
throw new-object ArgumentNullException("key");} | |
if ($iv -eq $null -or $iv.Length -le 0){ | |
throw new-object ArgumentNullException("iv");} | |
# Declare the stream used to encrypt to an in memory | |
# array of bytes. | |
[System.IO.MemoryStream] $msDecrypt | |
# Declare the RijndaelManaged object | |
# used to encrypt the data. | |
[System.Security.Cryptography.RijndaelManaged] $aesAlg = new-Object System.Security.Cryptography.RijndaelManaged | |
[String] $plainText="" | |
try { | |
# Create a RijndaelManaged object | |
# with the specified key and IV. | |
$aesAlg = new-object System.Security.Cryptography.RijndaelManaged | |
$aesAlg.Mode = [System.Security.Cryptography.CipherMode]::CBC | |
$aesAlg.KeySize = 256 | |
$aesAlg.BlockSize = 128 | |
$aesAlg.key = $key | |
$aesAlg.IV = $iv | |
# Create an encryptor to perform the stream transform. | |
[System.Security.Cryptography.ICryptoTransform] $decryptor = $aesAlg.CreateDecryptor($aesAlg.Key, $aesAlg.IV); | |
# Create the streams used for encryption. | |
$msDecrypt = new-Object System.IO.MemoryStream @(,$cipherText) | |
$csDecrypt = new-object System.Security.Cryptography.CryptoStream($msDecrypt, $decryptor, [System.Security.Cryptography.CryptoStreamMode]::Read) | |
$srDecrypt = new-object System.IO.StreamReader($csDecrypt) | |
#Write all data to the stream. | |
$plainText = $srDecrypt.ReadToEnd() | |
$srDecrypt.Close() | |
$csDecrypt.Close() | |
$msDecrypt.Close() | |
} | |
finally { | |
# Clear the RijndaelManaged object. | |
if ($aesAlg -ne $null){ | |
$aesAlg.Clear() | |
} | |
} | |
# Return the Decrypted bytes from the memory stream. | |
return $plainText | |
} | |
###example### | |
$passphrase = "some password" | |
$plaintext = "A lot of dummy plaintext," | |
$encryptedText="$(OpenSSLEncrypt $passphrase $plainText)".Trim() | |
$encryptedText | |
$(OpenSSLDecrypt $passphrase $encryptedText) |
>>> $plaintext = "Salted__Write-Host 'Test'"
=> "Salted__Write-Host 'Test'"
>>> $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC")
=> 16
>>> $ciphertext_raw = openssl_encrypt($plaintext, $cipher, 'g3tR3kT', ($options=OPENSSL_RAW_DATA), $iv);
=> b"\x17E| *4╗´\vÀÏühCh3Jû_ú¶®‗\x04▄╩\x1F¡ıë@f"
>>> $hmac = hash_hmac('sha256', $ciphertext_raw, 'g3tR3kT', ($as_binary=true));
=> b"'─\võT\\Í$\*°Ûƶ\x05_Älù¤H\6\x13î·9ظ\x11\x15"
>>> $ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );
=> "3de9ftLKKcJr9e5P8KYnCyfEC+RUXFzWJFwq+OqS9AVfjmyXz0hcNhOM+jmd9xEVMPGJjqEAMMwxVNwpBw9il+GJjusefhIsMWkKQK2iCRE="
Json is now:
{"key":"g3tR3kT","text":"3de9ftLKKcJr9e5P8KYnCyfEC+RUXFzWJFwq+OqS9AVfjmyXz0hcNhOM+jmd9xEVMPGJjqEAMMwxVNwpBw9il+GJjusefhIsMWkKQK2iCRE="}
Error:
PS:
$response = (Invoke-RestMethod "http://$IPV4/test.json")
Write-Host $response.key
Write-Host $response.text
$decrypt = $(OpenSSLDecrypt $response.key $response.text)
Output:
Hey Kyle,
You have to remember that my code is for directly decrypting from an
openssl encryption using the openssl command line.
This includes salting by default as per openssl default salt string
"Salted__".
Take a look at the heading of the PowerShell encryption function and it
shows what openssl command line can be used to generate the same encrypted
output.
As well as salt, there's also a difference between the openssl binary
implementation and the PHP function.
The key and the password are not the same thing.
The specific issue is explained here:
https://www.php.net/manual/en/function.openssl-encrypt.php#104438
You need an example that adds the 8bit salt "Salted__" into the PHP
encryption call.
Hope that helps.
Best
Darryl
…On Wed, 18 Aug 2021, 22:28 Kyle @ Iezon, ***@***.***> wrote:
***@***.**** commented on this gist.
------------------------------
Hi dude, using this with openssl_encrypt in PHP
openssl_encrypt('Write-Host "Test"', 'aes-256-cbc', 'SOME_KEY', $options=0, openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc')), $tag)
=> "W61oGlJYdkXhe6+12dLzZ4oVKb6g9gv+2lKsXulpStI="
Does not seem to work.
$response = (Invoke-RestMethod "http://$IPV4/test.json")
$decrypt = $(OpenSSLDecrypt $response.key $response.text)
=> Exception calling "ReadToEnd" with "0" argument(s): "Padding is invalid and cannot be removed."
At line:178 char:9 +
$plainText = $srDecrypt.ReadToEnd()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : CryptographicException
Json:
{"key":"SOME_KEY","text":"W61oGlJYdkXhe6+12dLzZ4oVKb6g9gv+2lKsXulpStI="}
Any ideas? Thanks man.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<https://gist.github.com/d1039c2407262cb6d735c3e7a730ee86#gistcomment-3865274>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AJOKVBP6NHS3PIENQ6FKCB3T5QQYTANCNFSM5CM4I2OA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&utm_campaign=notification-email>
.
Alas, PowerShell 7 throws errors:
InvalidOperation: C:\tmp\tmp.ps1:205
Line |
205 | $encryptedText=$(OpenSSLEncrypt $passphrase $plainText).Trim()
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| You cannot call a method on a null-valued expression.
OperationStopped: C:\tmp\tmp.ps1:150
Line |
150 | throw new-object ArgumentNullException("cipherText");}
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Value cannot be null. (Parameter 'cipherText')
Alas, PowerShell 7 throws errors:
InvalidOperation: C:\tmp\tmp.ps1:205 Line | 205 | $encryptedText=$(OpenSSLEncrypt $passphrase $plainText).Trim() | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | You cannot call a method on a null-valued expression. OperationStopped: C:\tmp\tmp.ps1:150 Line | 150 | throw new-object ArgumentNullException("cipherText");} | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Value cannot be null. (Parameter 'cipherText')
Hey @sergeevabc
That's annoying.
Here's how it works in 7 from what I can see:
$encryptedText="$(OpenSSLEncrypt $passphrase $plainText)".Trim()
Just wrap the call to the function in double quotes.
Hopefully that's it. The Trim is just there to ensure that any whitespace is removed off the end.
Best
Darryl
Updated the code for Powershell 7 as per the previous comment.
@Darryl-G, it works now, thank you.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi dude, using this with
openssl_encrypt
in PHPDoes not seem to work.
Json:
Any ideas? Thanks man.