Skip to content

Instantly share code, notes, and snippets.

@tiesont
Created January 21, 2015 18:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tiesont/bdad5a91c8055e23d8a3 to your computer and use it in GitHub Desktop.
Save tiesont/bdad5a91c8055e23d8a3 to your computer and use it in GitHub Desktop.
VisualBasic.NET implementation of Scott Lowe's OpenSslAes class.
Imports System.Collections.Generic
Imports System.IO
Imports System.Security.Cryptography
Imports System.Text
Namespace Security
''' <summary>
''' OpenSSL AES CBC 256 in .NET for interop with Ruby
''' </summary>
''' <remarks>Created by Scott Lowe</remarks>
''' <see href="https://gist.github.com/scottlowe/1411917#file-opensslaes-cs"/>
Public Class OpenSslAes
''' <summary>
''' Encrypts the specified plain text.
''' </summary>
''' <param name="plainText">The plain text.</param>
''' <param name="passphrase">The passphrase. Key and IV are derived from this value.</param>
''' <returns></returns>
Public Shared Function Encrypt(plainText As String, passphrase As String) As String
Dim key As Byte(), iv As Byte()
Dim salt = New Byte(7) {}
Dim rng As New RNGCryptoServiceProvider()
rng.GetNonZeroBytes(salt)
EvpBytesToKey(passphrase, salt, key, iv)
Dim encryptedBytes As Byte() = AesEncrypt(plainText, key, iv)
Dim encryptedBytesWithSalt = CombineSaltAndEncryptedData(encryptedBytes, salt)
Return Convert.ToBase64String(encryptedBytesWithSalt)
End Function
' OpenSSL prefixes the combined encrypted data and salt with "Salted__"
Private Shared Function CombineSaltAndEncryptedData(encryptedData As Byte(), salt As Byte()) As Byte()
Dim encryptedBytesWithSalt = New Byte(salt.Length + encryptedData.Length + 7) {}
Buffer.BlockCopy(Encoding.ASCII.GetBytes("Salted__"), 0, encryptedBytesWithSalt, 0, 8)
Buffer.BlockCopy(salt, 0, encryptedBytesWithSalt, 8, salt.Length)
Buffer.BlockCopy(encryptedData, 0, encryptedBytesWithSalt, salt.Length + 8, encryptedData.Length)
Return encryptedBytesWithSalt
End Function
''' <summary>
''' Decrypts the specified encrypted string.
''' </summary>
''' <param name="encrypted">The encrypted string.</param>
''' <param name="passphrase">The passphrase. Key and IV are derived from this value.</param>
''' <returns></returns>
Public Shared Function Decrypt(encrypted As String, passphrase As String) As String
Dim encryptedBytesWithSalt As Byte() = Convert.FromBase64String(encrypted)
Dim salt = ExtractSalt(encryptedBytesWithSalt)
Dim encryptedBytes = ExtractEncryptedData(salt, encryptedBytesWithSalt)
Dim key As Byte(), iv As Byte()
EvpBytesToKey(passphrase, salt, key, iv)
Return AesDecrypt(encryptedBytes, key, iv)
End Function
' Pull the data out from the combined salt and data
Private Shared Function ExtractEncryptedData(salt As Byte(), encryptedBytesWithSalt As Byte()) As Byte()
Dim encryptedBytes = New Byte(encryptedBytesWithSalt.Length - salt.Length - 9) {}
Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length + 8, encryptedBytes, 0, encryptedBytes.Length)
Return encryptedBytes
End Function
' The salt is located in the first 8 bytes of the combined encrypted data and salt bytes
Private Shared Function ExtractSalt(encryptedBytesWithSalt As Byte()) As Byte()
Dim salt = New Byte(7) {}
Buffer.BlockCopy(encryptedBytesWithSalt, 8, salt, 0, salt.Length)
Return salt
End Function
' Key derivation algorithm used by OpenSSL
'
' Derives a key and IV from the passphrase and salt using a hash algorithm (in this case, MD5).
'
' Refer to http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM
Private Shared Sub EvpBytesToKey(passphrase As String, salt As Byte(), ByRef key As Byte(), ByRef iv As Byte())
Dim concatenatedHashes = New List(Of Byte)(48)
Dim password As Byte() = Encoding.UTF8.GetBytes(passphrase)
Dim currentHash As Byte() = New Byte(-1) {}
Dim hasher = MD5.Create()
Dim enoughBytesForKey As Boolean = False
While Not enoughBytesForKey
Dim preHashLength As Integer = currentHash.Length + password.Length + salt.Length
Dim preHash As Byte() = New Byte(preHashLength - 1) {}
Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length)
Buffer.BlockCopy(password, 0, preHash, currentHash.Length, password.Length)
Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + password.Length, salt.Length)
currentHash = hasher.ComputeHash(preHash)
concatenatedHashes.AddRange(currentHash)
If concatenatedHashes.Count >= 48 Then
enoughBytesForKey = True
End If
End While
key = New Byte(31) {}
iv = New Byte(15) {}
concatenatedHashes.CopyTo(0, key, 0, 32)
concatenatedHashes.CopyTo(32, iv, 0, 16)
hasher.Clear()
hasher = Nothing
End Sub
Private Shared Function AesEncrypt(plainText As String, key As Byte(), iv As Byte()) As Byte()
Dim memoryStream As MemoryStream
Dim aesAlgorithm As RijndaelManaged = Nothing
Try
aesAlgorithm = New RijndaelManaged() With { _
.Mode = CipherMode.CBC, _
.KeySize = 256, _
.BlockSize = 128, _
.Key = key, _
.IV = iv _
}
Dim cryptoTransform = aesAlgorithm.CreateEncryptor(aesAlgorithm.Key, aesAlgorithm.IV)
memoryStream = New MemoryStream()
Using cryptoStream As CryptoStream = New CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write)
Using streamWriter As StreamWriter = New StreamWriter(cryptoStream)
streamWriter.Write(plainText)
streamWriter.Flush()
streamWriter.Close()
End Using
End Using
Finally
If aesAlgorithm IsNot Nothing Then
aesAlgorithm.Clear()
End If
End Try
Return memoryStream.ToArray()
End Function
Private Shared Function AesDecrypt(cipherText As Byte(), key As Byte(), iv As Byte()) As String
Dim aesAlgorithm As RijndaelManaged = Nothing
Dim plaintext As String
Try
aesAlgorithm = New RijndaelManaged() With { _
.Mode = CipherMode.CBC, _
.KeySize = 256, _
.BlockSize = 128, _
.Key = key, _
.IV = iv _
}
Dim decryptor As ICryptoTransform = aesAlgorithm.CreateDecryptor(aesAlgorithm.Key, aesAlgorithm.IV)
Using memoryStream As MemoryStream = New MemoryStream(cipherText)
Using cryptoStream As CryptoStream = New CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)
Using streamReader As StreamReader = New StreamReader(cryptoStream)
plaintext = streamReader.ReadToEnd()
streamReader.Close()
End Using
End Using
End Using
Finally
If aesAlgorithm IsNot Nothing Then
aesAlgorithm.Clear()
End If
End Try
Return plaintext
End Function
End Class
End Namespace
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment