A single mdAesCtr.bas module contains an implementation of a simple to use openssl compatible AES 256-bit encryption/decryption in Counter (CTR) mode, using CNG API functions available in Win7 and later.
Sample usage
Just copy/paste mdAesCtr.bas from Source code section below to your project and will be able to strongly encrypt a user-supplied text with a custom password by calling AesEncryptString like this
encrypted = AesEncryptString(userText, password)
To decrypt the original text use AesDecryptString function with the same password like this
origText = AesDecryptString(encrypted, password)
These functions use sane defaults for salt and cipher strength that you don't have to worry about. These also encode/expect the string in encrypted in base-64 format so it can be persisted/mailed/transported as a simple string.
Advanced usage
Both string functions above use AesCryptArray worker function to encrypt/decrypt UTF-8 byte-arrays of the original strings. You can directly call AesCryptArray if you need to process binary data or need to customize AES salt and/or AES key length (strength) parameters.
Function AesCryptArray also allows calculating detached HMAC-SHA256 on the input/output data ("detached" means hashes has to be stored separately, supports both encrypt-then-MAC and MAC-then-encrypt) when used like this
When contents to be encrypted does not fit in (32-bit) memory you can expose private pvCryptoAesCtrInit/Terminate/Crypt functions so these can be used to implement read/process/write loop on paged original content.
'--- https://gist.github.com/wqweto/42a6c1de16cc87e9bab2ac9f3c9d8510
'--- already too long to fit in 25000 characters post limit
More samples
Code:
Option Explicit
Private Sub TestEncrypt()
Dim sPass As String
Dim sText As String
Dim sEncr As String
sPass = "password123"
sText = "this is a test"
sEncr = AesEncryptString(sText, sPass)
Debug.Assert sText = AesDecryptString(sEncr, sPass)
Debug.Print "Result (Base64): " & sEncr
Debug.Print "Raw byte-array: " & StrConv(FromBase64Array(sEncr), vbUnicode)
Debug.Print "Decrypted: " & AesDecryptString(sEncr, sPass)
End Sub
Private Sub TestHmac()
Dim baEncr() As Byte
Dim baHmacEncr(0 To 31) As Byte
Dim baHmacDecr(0 To 31) As Byte
baEncr = ToUtf8Array("test message")
baHmacEncr(0) = 0 '--- 0 -> generate hash before encrypting
AesCryptArray baEncr, ToUtf8Array("pass"), Hmac:=baHmacEncr
baHmacDecr(0) = 1 '--- 1 -> decrypt and generate hash after that
AesCryptArray baEncr, ToUtf8Array("pass"), Hmac:=baHmacDecr
Debug.Assert InStrB(1, baHmacDecr, baHmacEncr) = 1
Debug.Print "baHmacDecr: " & StrConv(baHmacDecr, vbUnicode)
Debug.Print "baHmacEncr: " & StrConv(baHmacEncr, vbUnicode)
End Sub
cheers,
</wqw>
Last edited by wqweto; Jan 28th, 2022 at 05:54 AM.
Reason: VBA compatibility
Hello,
This looks very intimidating -- which makes it amazing! However, I am having trouble testing it out in a 64-bit system; it keeps presenting a type mismatch error. Would it be possible to post a 64-bit version?
Thanks so much!
I am developing on Windows 7 32bit only because a few other items I have here need that environment to compile. I do have a Win 7 x64 here which I will try also a bit later today and let you know.
@Krammig: Yes, there was a problem w/ Windows 7 support but now mdAesCtr.bas above is fixed (turned out there is no support for BCRYPT_HASH_REUSABLE_FLAG flag on Windows 7).
@Krammig: Yes, there was a problem w/ Windows 7 support but now mdAesEcb.bas above is fixed (turned out there is no support for BCRYPT_HASH_REUSABLE_FLAG flag on Windows 7).
thank you!
I'm not a programmer,is vb6 fans, This link example is C++。
Where are the vb6 examples?
Since there are no VB6 examples nowadays we just use whatever C/C++ examples we can find. Can you find a real programmer to do this job for you?
Or you can use "openssl enc -aes-128-cbc -in encrypt.txt -iv 313233343536 -K 313233343536 -d -out decrypted.txt" to decrypt. (Use -d parameter to decrypt).
This is brilliant with so little code. I can encrypt and decrypt entirely in VB but my problem is that I need to decrypt an encryption which was done in java. This requires a salt and a secret key. I can see where the salt is set but not the secret key. Can you tell me how I can set the secret key for decryption which was used for the encryption.
This is brilliant with so little code. I can encrypt and decrypt entirely in VB but my problem is that I need to decrypt an encryption which was done in java. This requires a salt and a secret key. I can see where the salt is set but not the secret key. Can you tell me how I can set the secret key for decryption which was used for the encryption.
Thanks
Rob
This is hardly going to match Java implementation. It is currently matching 1-to-1 the WinZip AES encryption scheme where the module was actually extracted from. Unfortunately it deviates a bit from most "standard" AES-in-Counter-Mode implementations in the wild which vex me now. I'll probably delete this thread althogether in the future and reimplement a compatible enough scheme.
You might find sample BCrypt API usage helpful though to be able to re-implemented Java's implementation. The code after '-- generate RFC 2898 based derived key comment is dealing with the "secret key". You can try skipping the key derivation from the password text and directly use your byte-array for specific custom key to initialize AES.
I was wondering if someone could help, i am struggling with something. By the way this code is pretty awesome! thank you
In the code there is this line
Debug.Print FromUtf8Array(FromBase64Array(sEncr))
I want to be able to store some text in the format that this outputs, however i need to be able to decrypt that string back into its original form later.
i m seriously struggling to figure out how to do this.
So for example.
Using the above code example you get the following outputs
rYuhuCOvpPncA2tEGeg= ????#????kD?
this is a test
i would like to encrypt the original string and store the second line, then when a user enters their password their original text is restored, in this case "this is a test"
What you marked above is the raw byte-array dumped to Immediate Window with Debug.Print FromUtf8Array(FromBase64Array(sEncr)).
Place this pair of AesEncrypt/DecryptByteArray byte-array encrypting functions in a separate module
Code:
Option Explicit
Public Function AesEncryptByteArray(sText As String, sPassword As String) As Byte()
Dim baData() As Byte
Dim sError As String
baData = ToUtf8Array(sText)
If Not AesCryptArray(baData, ToUtf8Array(sPassword), Error:=sError) Then
Err.Raise vbObjectError, , sError
End If
AesEncryptByteArray = baData
End Function
Public Function AesDecryptByteArray(baData() As Byte, sPassword As String) As String
Dim sError As String
If Not AesCryptArray(baData, ToUtf8Array(sPassword), Error:=sError) Then
Err.Raise vbObjectError, , sError
End If
AesDecryptByteArray = FromUtf8Array(baData)
End Function
Private Sub Form_Load()
Dim sPass As String
Dim sText As String
Dim baEncr() As Byte
sPass = "password123"
sText = "this is a test"
baEncr = AesEncryptByteArray(sText, sPass)
Debug.Print baEncr
Debug.Print AesDecryptByteArray(baEncr, sPass)
End Sub
These are using the same AesCryptArray function underneath but skip base-64 encoding and return raw byte-arrays.
I have not programmed in over 10 years and am struggling with some basic things. i am re learning much of what i have forgotten but it will take some tie i guess.
I am trying to update some of the applications i wrote years ago but am struggling with something.
What i am to do is:
1) Allow the user to specify some text and a password
2) Encrypt and write that encrypted text into a text file
3) Later the user should be able to Specify the password and retrieve that text from the file decrypted
I can write the encrypted test to a file, but when i try to retrieve it, it fails
Write value to file
Code:
Private Sub Command1_Click()
Dim sPass As String
Dim sText As String
Dim baEncr() As Byte
baEncr = AesEncryptByteArray("Encrypt me", "Password123")
Open "C:\users\abc\Desktop\a.txt" For Output As #1
Print #1, baEncr
Close #1
End Sub
Read from file
Code:
Private Sub Command2_Click()
Dim LineText As Byte
Dim LT As String
Com1.ShowOpen
Open Com1.FileName For Input As #2
Do Until EOF(2) ' Repeat until end of file...
Line Input #2, LT ' Read a line from the file.
Debug.Print LT
LineText = AesEncryptByteArray(LT, "Password123")
Debug.Print AesDecryptByteArray(LT, "Password123")
Loop
Close #2
End Sub
Appreciate all your help bud, sorry if this is basic, i am trying to relearn everything after many years away from it all.
I have it working now, i was going crazy trying to figure out why i kept on getting a type mismatch error, then it occured to me, when i defined my variable, i did not define it as an array
ie i had
dim LineText as byte
instead on
Dim LineText() as byte
A single mdAesCtr.bas module contains an implementation of a simple to use AES 256-bit encryption/decryption in CTR mode, using API functions (CNG) available in Win7 and later.
Sample usage
Just copy/paste mdAesCtr.bas from Source code section below to your project and will be able to strongly encrypt a user-supplied text with a custom password by calling AesEncryptString like this
encrypted = AesEncryptString(userText, password)
To decrypt the original text use AesDecryptString function with the same password like this
origText = AesDecryptString(encrypted, password)
These functions use sane defaults for salt and cypher strength that you don't have to worry about. These also encode/expect the string in encrypted in base-64 format so it can be persisted/mailed/transported as a simple string.
Advanced usage
Both string functions above use AesCryptArray worker function to encrypt/decrypt UTF-8 byte-arrays of the original strings. You can directly call AesCryptArray if you need to process binary data or need to customize AES salt and/or AES key length (strength) parameters.
Function AesCryptArray also allows calculating out-of-band HMAC-SHA1 hashes on the input/output binary data (OOB means hashes has to be stored separately) when used like this
When contents to be encrypted does not fit in (32-bit) memory you can expose private pvCryptoAesInit/Terminate/Crypt functions so these can be used to implement read/process/write loop on paged original content.
Private Const MS_PRIMITIVE_PROVIDER As String = "Microsoft Primitive Provider"
Private Const BCRYPT_CHAIN_MODE_ECB As String = "ChainingModeECB"
Private Const BCRYPT_ALG_HANDLE_HMAC_FLAG As Long = 8
'--- for CryptStringToBinary
Private Const CRYPT_STRING_BASE64 As Long = 1
'--- for WideCharToMultiByte
Private Const CP_UTF8 As Long = 65001
'--- for FormatMessage
Private Const FORMAT_MESSAGE_FROM_SYSTEM As Long = &H1000
Private Const FORMAT_MESSAGE_IGNORE_INSERTS As Long = &H200
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
Private Declare Function BCryptOpenAlgorithmProvider Lib "bcrypt" (phAlgorithm As Long, ByVal pszAlgId As Long, ByVal pszImplementation As Long, ByVal dwFlags As Long) As Long
Private Declare Function BCryptCloseAlgorithmProvider Lib "bcrypt" (ByVal hAlgorithm As Long, ByVal dwFlags As Long) As Long
Private Declare Function BCryptGetProperty Lib "bcrypt" (ByVal hObject As Long, ByVal pszProperty As Long, pbOutput As Any, ByVal cbOutput As Long, cbResult As Long, ByVal dwFlags As Long) As Long
Private Declare Function BCryptSetProperty Lib "bcrypt" (ByVal hObject As Long, ByVal pszProperty As Long, ByVal pbInput As Long, ByVal cbInput As Long, ByVal dwFlags As Long) As Long
Private Declare Function BCryptGenerateSymmetricKey Lib "bcrypt" (ByVal hAlgorithm As Long, phKey As Long, pbKeyObject As Any, ByVal cbKeyObject As Long, pbSecret As Any, ByVal cbSecret As Long, ByVal dwFlags As Long) As Long
Private Declare Function BCryptDestroyKey Lib "bcrypt" (ByVal hKey As Long) As Long
Private Declare Function BCryptEncrypt Lib "bcrypt" (ByVal hKey As Long, pbInput As Any, ByVal cbInput As Long, ByVal pPaddingInfo As Long, ByVal pbIV As Long, ByVal cbIV As Long, pbOutput As Any, ByVal cbOutput As Long, pcbResult As Long, ByVal dwFlags As Long) As Long
Private Declare Function BCryptDeriveKeyPBKDF2 Lib "bcrypt" (ByVal pPrf As Long, pbPassword As Any, ByVal cbPassword As Long, pbSalt As Any, ByVal cbSalt As Long, ByVal cIterations As Long, ByVal dwDummy As Long, pbDerivedKey As Any, ByVal cbDerivedKey As Long, ByVal dwFlags As Long) As Long
Private Declare Function BCryptCreateHash Lib "bcrypt" (ByVal hAlgorithm As Long, phHash As Long, ByVal pbHashObject As Long, ByVal cbHashObject As Long, pbSecret As Any, ByVal cbSecret As Long, ByVal dwFlags As Long) As Long
Private Declare Function BCryptDestroyHash Lib "bcrypt" (ByVal hHash As Long) As Long
Private Declare Function BCryptHashData Lib "bcrypt" (ByVal hHash As Long, pbInput As Any, ByVal cbInput As Long, ByVal dwFlags As Long) As Long
Private Declare Function BCryptFinishHash Lib "bcrypt" (ByVal hHash As Long, pbOutput As Any, ByVal cbOutput As Long, ByVal dwFlags As Long) As Long
#If Not ImplUseShared Then
Private Declare Function CryptStringToBinary Lib "crypt32" Alias "CryptStringToBinaryW" (ByVal pszString As Long, ByVal cchString As Long, ByVal dwFlags As Long, ByVal pbBinary As Long, ByRef pcbBinary As Long, ByRef pdwSkip As Long, ByRef pdwFlags As Long) As Long
Private Declare Function CryptBinaryToString Lib "crypt32" Alias "CryptBinaryToStringW" (ByVal pbBinary As Long, ByVal cbBinary As Long, ByVal dwFlags As Long, ByVal pszString As Long, pcchString As Long) As Long
Private Declare Function WideCharToMultiByte Lib "kernel32" (ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpWideCharStr As Long, ByVal cchWideChar As Long, lpMultiByteStr As Any, ByVal cchMultiByte As Long, ByVal lpDefaultChar As Long, ByVal lpUsedDefaultChar As Long) As Long
Private Declare Function MultiByteToWideChar Lib "kernel32" (ByVal CodePage As Long, ByVal dwFlags As Long, lpMultiByteStr As Any, ByVal cchMultiByte As Long, ByVal lpWideCharStr As Long, ByVal cchWideChar As Long) As Long
Private Declare Function FormatMessage Lib "kernel32" Alias "FormatMessageA" (ByVal dwFlags As Long, lpSource As Long, ByVal dwMessageId As Long, ByVal dwLanguageId As Long, ByVal lpBuffer As String, ByVal nSize As Long, Args As Any) As Long
Private Function pvCryptoAesInit(uCrypto As UcsZipCryptoType, baPass() As Byte, baSalt() As Byte, ByVal lKeyLen As Long, nPassVer As Integer) As Boolean
Dim baDerivedKey() As Byte
Dim lDummy As Long '--- discarded
Dim hResult As Long
Dim sApiSource As String
'--- init member vars
uCrypto.Nonce(0) = 0
uCrypto.Nonce(1) = 0
uCrypto.EncrData = vbNullString
uCrypto.EncrPos = 0
'--- generate RFC 2898 based derived key
On Error GoTo EH_Unsupported '--- CNG API missing on XP
Many thanks for this cool class.
First I noted that the returned encrypted string has line feed (LF &H10) character at the end of it, I fixed that by (I am not sure if this the right way or not!) subtracting 2 of lSize variable
Private Sub TestEncrypt()
Dim sPass As String
Dim sText As String
Dim sEncr As String
sPass = "password123"
sText = "1234"
sEncr = AesEncryptString(sText, sPass)
Debug.Print sEncr
Debug.Print AesDecryptString(sEncr, sPass)
End Sub
Yes, this is a problem with this code and it's currently not compatible with openssl's implementation in both key derivation (expanding the password to 32 bytes keys) and probably the counter mode (this is using WinZip's construction).
I'll have to come up with a completely new submission based on this signature in PHP sources when time permits, so that this would allow passing the key as byte-array and more modes too (like GCM, CCM, OCB)
I'm not sure about equivalent php implementation but before calling openssl_encrypt you'll probably have to prepare key/IV using pbkdf2 for key derivation based on the password and some random 8 bytes salt.
Here is an XP compatible implementation of AesEncrypt/DecryptString functions: mdAesCbc.bas implements AES-256 in CBC mode and PBKDF2 w/ SHA-512 using only legacy wincrypto API functions.
I am using only AesDecryptString function on my server, so I get an error that $salt is not defined, also for some reason "\n" is not working so I replaced it with the HTML equivalent.
Yes, that would happen if $encr is invalid (i.e. does not contain the openssl prefix) so in this case in VB6 version the $salt is just an empty byte-array.
Not sure how to initialize it to an array with no elements in php but empty string seems to do the job (tweaked my post above).
I implement the encryption using your module in my commercial application and it works ok till the update for a client with Windows 7 where he reported he can not log in the system!
I found that the library is failing with Windows 7, though when I debugged the problem it raises an error at :
Code:
If Not AesCryptArray(baData, ToUtf8Array(sPassword), baSalt, Error:=sError) Then
Err.Raise vbObjectError, , sError
End If
And to my surprise, the error is:
[0] completed successfully
One more thing you may want to check or enlighten me if I am not aware of it:
The generated encrypted string sometimes contains unsafe characters which don't work if I pass it as URL parameters for a PHP script to parse it!
I overcome it using the following:
AesEncryptString returns a base64 encoded result, the same way base64_encode encodes the result in the PHP code above and the same way final -a parameter to openssl.exe encodes the output in base64.
What you want to use in your URLs would be base64url -- a slight modification to base64 which allows base64 encoded result to be used without additional URLs escape.
Here is an explanation of the difference beteen base64 and base64url.
For simple base64->base64url conversion you need two replacements on the encoded string: + to - (dash) and / to _ (underscore).
AesEncryptString returns a base64 encoded result, the same way base64_encode encodes the result in the PHP code above and the same way final -a parameter to openssl.exe encodes the output in base64.
What you want to use in your URLs would be base64url -- a slight modification to base64 which allows base64 encoded result to be used without additional URLs escape.
Here is an explanation of the difference beteen base64 and base64url.
For simple base64->base64url conversion you need two replacements on the encoded string: + to - (dash) and / to _ (underscore).