# VBForums CodeBank > CodeBank - Visual Basic 6 and earlier >  [VB6/VBA] Simple AES 256-bit password protected encryption

## wqweto

*Simple AES 256-bit password protected encryption*

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

*AesCryptArray baEncr, ToUtf8Array("pass"), Hmac:=baHmacEncr*

(See _More samples_ section below)

*Stream usage*

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.

*Implementation*

This implementation used to be based on WinZip AES-encrypted archives as implemented in ZipArchive project but now is compatible with openssl enc command when using aes-256-ctr cipher.

*Source code*



```
'--- https://gist.github.com/wqweto/42a6c1de16cc87e9bab2ac9f3c9d8510
'--- already too long to fit in 25000 characters post limit
```

*More samples*



```
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>

----------


## ChenLin

It's incredible that no other DLLs and LIBs are used. Only one class can compress files!

----------


## Elemints

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!

----------


## wqweto

It works ok in VB6 on x64 here.

----------


## Krammig

Thanks for sharing, but not working for me here. I have attached the test project. 

It is throwing a runtime error -214... etc  Then it says [0] The operation completed successfully. 

AesEncryptString

Could be I have done something wrong   :Smilie: 

Look forward to your feedback.
cheers

----------


## wqweto

Your project works ok here. Windows 10 version 1803

Which OS are you testing this on?

cheers,
</wqw>

----------


## Krammig

Thanks for checking it out so quickly.

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.

----------


## wqweto

@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).

FYI, here is a diff of the changes, along with err handling fixes.

cheers,
</wqw>

----------


## Krammig

> @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).
> 
> FYI, here is a diff of the changes, along with err handling fixes.
> 
> cheers,
> </wqw>



Thanks for that, 

Cheers

----------


## xiaoyao

how to encode a Binary file(or decode) ,can you give me a example？，please,
thank you.

----------


## wqweto

Try using AesCryptArray  function like this

baData = ReadBinaryFile("c:\path\to\input.file")
AesCryptArray baData, ToUtf8Array("pass")
WriteBinaryFile "c:\path\to\encrypted.file", baData
cheers,
</wqw>

----------


## ln_0

please，A file that USES a command “openssl enc -aes-128-cbc -in plain.txt -out encrypt.txt -iv 313233343536 -K 313233343536 -p -salt”
how decrypt?
(Can't  English)

----------


## wqweto

Try reading this code: https://docs.microsoft.com/en-us/win...-data-with-cng

cheers,
</wqw>

----------


## ln_0

thank you！
I'm not a programmer，is vb6 fans, This link example is C++。
Where are the vb6 examples？

----------


## wqweto

> 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).

cheers,
</wqw>

----------


## ln_0

ok，Thanks for your advice

----------


## IWASHERE

Hi,

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

----------


## wqweto

> Hi,
> 
> 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.

cheers,
</wqw>

----------


## BuntyK

Hi everyone,

Hope you are all well.

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" 

can this be achieved?

Many thanks,

----------


## wqweto

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



```
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.

cheers,
</wqw>

----------


## BuntyK

Hi wqweto,

You my friend are a NINJA.

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


```
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


```
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.

Any ideas what i am doing wrong?

Thanks,

----------


## Arnoutdv

```
 Open "C:\users\abc\Desktop\a.txt" For Output As #1
        Print #1, baEncr
        Close #1
```

You are saving a byte array using file IO meant for textual data.
Open the file in binary mode and use the Put statement

The same goes for reading the file, you are using text based file IO

----------


## BuntyK

Thank you Arnoutdv and everyone,

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

its now working.

You guys are awesome!!!

Thank you again.

----------


## labmany

> *Simple AES 256-bit password protected encryption*
> 
> 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)*
> ...


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


```
ToBase64Array = Left$(ToBase64Array, lSize - 2) 'Original code was ToBase64Array = Left$(ToBase64Array, lSize)
```

Second and most important for me is that the returend string is not equal to what a PHP code is returning, here is the code:


```
<!DOCTYPE html>
<html>
<body>

<?php

echo '6NH7/w==     This is the VB generated one <br>';
$encrypted = openssl_encrypt('1234', 'AES-256-CTR', 'password123', false, 'SaltVb6CryptoAes');
echo $encrypted;
echo '<br>';
echo openssl_decrypt($encrypted, 'AES-256-CTR', 'password123', false, 'SaltVb6CryptoAes'); 

?>


</body>
</html>
```

You can test the above PHP code here :
https://www.w3schools.com/Php/phptry...ryphp_compiler

The VB6 code :


```
    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
```

I do appreciate if you can tell what's wrong!

----------


## wqweto

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)

cheers,
</wqw>

----------


## wqweto

The last revision is openssl compatible equivalent to using this from command-line to encrypt



```
c:> openssl enc -aes-256-ctr -pbkdf2 -md sha512 -pass pass:{sPassword} -in {sText}.file -a
```

. . . and this to decrypt



```
c:> openssl enc -d -aes-256-ctr -pbkdf2 -md sha512 -pass pass:{sPassword} -in {sEncr}.file -a
```

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.

cheers,
</wqw>

----------


## wqweto

Here is the equivalent php implementation with compatible arguments for the pbkdf2 key derivation



```
<?php
const CIPHER = "AES-256-CTR";
const KEYLEN = 32;
const IVLEN = 16;
const KDF_SALTLEN = 8;
const KDF_ITER = 10000;
const KDF_HASH = "SHA512";
const OPENSSL_MAGIC = "Salted__";
const OPENSSL_MAGICLEN = 8;

function AesEncryptString($text, $password)
{
    $salt = openssl_random_pseudo_bytes(KDF_SALTLEN);
    $derived = openssl_pbkdf2($password, $salt, KEYLEN + IVLEN, KDF_ITER, KDF_HASH);
    $encr = openssl_encrypt($text, CIPHER, substr($derived, 0, KEYLEN), OPENSSL_RAW_DATA, substr($derived, KEYLEN, IVLEN));
    return base64_encode(OPENSSL_MAGIC . $salt . $encr);
}

function AesDecryptString($encr, $password)
{
    $encr = base64_decode($encr);
    $salt = "";
    if (substr($encr, 0, OPENSSL_MAGICLEN) == OPENSSL_MAGIC) {
        $salt = substr($encr, OPENSSL_MAGICLEN, KDF_SALTLEN);
        $encr = substr($encr, OPENSSL_MAGICLEN + KDF_SALTLEN);
    }
    $derived = openssl_pbkdf2($password, $salt, KEYLEN + IVLEN, KDF_ITER, KDF_HASH);
    return openssl_decrypt($encr, CIPHER, substr($derived, 0, KEYLEN), OPENSSL_RAW_DATA, substr($derived, KEYLEN, IVLEN));
}

$encr = AesEncryptString("this is a PHP test това е проба", "password123");
echo $encr . "\n";
echo AesDecryptString($encr, "password123") . "\n";
?>
```

cheers,
</wqw>

----------


## labmany

> Here is the equivalent php implementation with compatible arguments for the pbkdf2 key derivation
> 
> 
> 
> ```
> <?php
> const CIPHER = "AES-256-CTR";
> const KEYLEN = 32;
> const IVLEN = 16;
> ...


Thank you very much.
I do appreciate your help.

----------


## wqweto

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.

cheers,
</wqw>

----------


## labmany

Thanks.

Also, I guess it is SAFE (by safe here I mean URL compatible) to pass decrypted data as parameters, right?

----------


## labmany

Sorry!

----------


## labmany

Updated the PHP code a bit!

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.



```
<?php
const CIPHER = "AES-256-CTR";
const KEYLEN = 32;
const IVLEN = 16;
const KDF_SALTLEN = 8;
const KDF_ITER = 10000;
const KDF_HASH = "SHA512";
const OPENSSL_MAGIC = "Salted__";
const OPENSSL_MAGICLEN = 8;

function AesEncryptString($text, $password)
{
    $salt = openssl_random_pseudo_bytes(KDF_SALTLEN);
    $derived = openssl_pbkdf2($password, $salt, KEYLEN + IVLEN, KDF_ITER, KDF_HASH);
    $encr = openssl_encrypt($text, CIPHER, substr($derived, 0, KEYLEN), OPENSSL_RAW_DATA, substr($derived, KEYLEN, IVLEN));
    return base64_encode(OPENSSL_MAGIC . $salt . $encr);
}

function AesDecryptString($encr, $password)
{
    $encr = base64_decode($encr);
    if (substr($encr, 0, OPENSSL_MAGICLEN) == OPENSSL_MAGIC) {
        $salt = substr($encr, OPENSSL_MAGICLEN, KDF_SALTLEN);
        $encr = substr($encr, OPENSSL_MAGICLEN + KDF_SALTLEN);
    }
    if(!isset($salt)){
        $salt = openssl_random_pseudo_bytes(KDF_SALTLEN);
    }
    $derived = openssl_pbkdf2($password, $salt, KEYLEN + IVLEN, KDF_ITER, KDF_HASH);
    return openssl_decrypt($encr, CIPHER, substr($derived, 0, KEYLEN), OPENSSL_RAW_DATA, substr($derived, KEYLEN, IVLEN));
}

$encr = AesEncryptString("this is a PHP test това е проба", "password123");
echo $encr . '<br>';
echo AesDecryptString($encr, "password123") . '<br>';
?>
```

----------


## wqweto

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).

cheers,
</wqw>

----------


## labmany

Thanks a lot.
I do appreciate your efforts.

----------


## labmany

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 :



```
    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

Any clue?

----------


## wqweto

This bug mysteriously resurfaced apparently, combined with poor error handling.

All fixed now!

Diff of changes is here.

cheers,
</wqw>

----------


## labmany

> This bug mysteriously resurfaced apparently, combined with poor error handling.
> 
> All fixed now!
> 
> Diff of changes is here.
> 
> cheers,
> </wqw>


Many thanks.
It works ok now.

----------


## labmany

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:


```
    Dim baSalt(0 To KDF_SALTLEN - 1) As Byte
    
    '/ + ^ []{} .. etc which are not URL compatible
    Randomize Timer
    baSalt(0) = Int(Rnd(1) * 25) + 65: baSalt(1) = Int(Rnd(1) * 25) + 97: baSalt(2) = Int(Rnd(1) * 25) + 65: baSalt(3) = Int(Rnd(1) * 25) + 97: baSalt(4) = Int(Rnd(1) * 25) + 65: baSalt(5) = Int(Rnd(1) * 25) + 97: baSalt(6) = Int(Rnd(1) * 25) + 65
```

instead of the original code:


```
    Dim baSalt(0 To KDF_SALTLEN - 1) As Byte
    Call RtlGenRandom(baSalt(0), KDF_SALTLEN)
```

I am not sure if this is the right way but at least it works for me!

----------


## wqweto

*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).

cheers,
</wqw>

----------


## labmany

> *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).
> 
> cheers,
> </wqw>


Thanks again, that was helpful and informative  :Smilie:

----------


## volkeru

This is a really wonderful thread! I unfortunately discovered it far too late. Otherwise I could have saved myself many days of work. Although I have not yet contributed anything myself, I would like to express my heartfelt thanks to all who contribute here!

----------


## volkeru

> 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.
> 
> cheers,
> </wqw>


Unfortunately, it only works for me with strings of less than 16 characters. I have tested it under Windows XP with VB6 and it always gives a runtime error and the message "[234] There is more data available" when I want to encrypt strings with more than 15 characters. Has anyone ever tested this with more than 15 characters? The CTR code, on the other hand, works with strings of any length.

Edit: Very strange! The exception is not fired with every string, but only with some. The following string gives the error: "testtesttesttest" with password "12345678901234567890123456789012" whereas "testtesttesttestt" does not give an error. I think there is still a bug to fix ;-)

----------


## volkeru

I'll give myself the answer: The problem arises from the *CryptEncrypt* function. The number of bytes to be encoded specified in *pdwDataLen* can be identical to the buffer size *dwBufLen*. And that is obviously a problem. Here an excerpt from the documentation of the function:




> If the buffer allocated for pbData is not large enough to hold the encrypted data, GetLastError returns ERROR_MORE_DATA and stores the required buffer size, in bytes, in the DWORD value pointed to by pdwDataLen.


With a number of 16 bytes to encode and a buffer length of 16 bytes, pdwDataLen returns the number of 32 bytes required. Therefore, the error ERROR_MORE_DATA is returned.

However, if I change line



```
    lPadSize = (lSize + AES_BLOCK_SIZE - 1) And -AES_BLOCK_SIZE
```

in the code to



```
    lPadSize = (lSize + AES_BLOCK_SIZE) And -AES_BLOCK_SIZE
```

the error no longer occurs. This is because a buffer of 32 bytes is then reserved for a data length of 16 bytes, etc. I have now tested 4096 encryptions and decryptions with random strings from 1 to 4096 bytes in length and everything worked fine.

----------


## wqweto

*@volkeru:* Your analysis is correct. Thanks for that!

PKCS #7 padding for plaintext of size multiple of AES_BLOCK_SIZE (16) is not 0 but 16 i.e. this is the most inefficient case where tha last 16 bytes are padded with 15, 15, 15, 15, .... so the complete AES block is wasted.

Fixed in latest commit, here is the diff.

Edit: Another fix for empty string handling in both encrypt and decrypt.

cheers,
</wqw>

----------


## volkeru

@wqweto: Thank you so much for the fast answer and the modification to the code! And thank you very much for sharing your work!

----------


## IanFletcher

I'm trying to create a SHA256 object from MS Access VBA, and it won't work. See the attached code sample for details. I'm running Access 2016 on a Windows machine with .NET 4.8. I believe I've tried everything obvious. Any suggestions would be much appreciated.



```
Public Function Base64_HMACSHA256(ByVal sTextToHash As String, ByVal sSharedSecretKey As String) As String
    Dim asc As Object, enc As Object
    Dim TextToHash() As Byte
    Dim SharedSecretKey() As Byte
    Set asc = CreateObject("System.Text.UTF8Encoding")
    'Set enc = CreateObject("System.Security.Cryptography.HMACSHA256") 'THIS SUCCESSFULLY CREATES THE OBJECT
    'Set enc = CreateObject("System.Security.Cryptography.SHA256") 'IHF 02/03/22 'CAN'T CREATE OBJECT
    'Set enc = CreateObject("System.Security.Cryptography.SHA256CryptoServiceProvider") 'IHF 02/03/22 'CAN'T CREATE OBJECT
    'Set enc = CreateObject("System.Security.Cryptography.RSACryptoServiceProvider") 'CAN'T CREATE OBJECT
    TextToHash = asc.Getbytes_4(sTextToHash)
    SharedSecretKey = asc.Getbytes_4(sSharedSecretKey)    
    enc.Key = SharedSecretKey 
    Dim bytes() As Byte
    bytes = enc.ComputeHash_2((TextToHash))
    Base64_HMACSHA256 = EncodeBase64(bytes)
    Set asc = Nothing
    Set enc = Nothing
End Function
```

----------


## zerbes

Hi,

first of all: great forum. Thanks for the living the spirit of sharing and supporting.

I tried the latest code-version of mdAesCbc.bas in my excel and in general it worked! That's already great!  :Smilie:  . 

Unfortunately i need encoding string in excel and let these decode in another system / software. That software uses AES256, CBC, IV = 0000000000000000, no salt (as it seems). The results are matching with these from several AES-encyption / decryption websites.

1. Can i anyhow specifiy the IV itself to make the results of encryption here match with the external software and making these two "work" together?

2. Can the salt be set to 0 / "" without problems?

Thank you in advance,

regards

zerbes

----------


## wqweto

1. You have to specify both key and IV in principle. Here a password is used to generate both key and IV. The generation uses a random salt on the password too and the salt is output in final result too (along with ciphertext obviously).

This is done in a compatible way so that the receiving party can use openssl.exe to decrypt the message provided that they know the password only.

2. You need salt only for some password being used. You dont need salt nor password when you want to specify both key and IV for the AES in CBC mode to use.

----------


## zerbes

Hi, 

thanks for your support. The receiving party can't use any binaries to decrypt my encrypted strings, i can just use the application-layer with a provided class and methods. Let me note the results here:

external system:

AES256-key: C&F)J@NcRfUjXn2r5u7x!A%D*G-KaPdS
Plain-String: www.vbforums.com
IV: 0000000000000000
Salt used: no

results in encrypted string: *bTaRHvRreoUIVuqQt7/BZ4Ez5YGh61nx66EzSJK5fgk=*
Same result is received using the values on several websites.

The Excel returns encrypted string: _Result (Base64):_  *U2FsdGVkX18aB5L57pS8944M9OlMeCe6XLrJAvN0YsM=*and this string can't neither be decrypted using a website nor the implementation on the external system. The Message seen when trying refers anyhow to "bad padding".

I have a general understanding of how the encryption works, still i am not seeing what i need to adjust to make the VBA create encrypted strings that can be decrypted by the external system using a *specifyable IV* and *without salt*.

----------


## volkeru

I'm using *wqweto*'s mdAesCbc.bas to encrypt my data end decrypt them with the PHP script from *labmany*. That works great!

One of my users now has the software running on Linux with wine and found that it does not work there. On wine, mdAesCbc.bas also generates encrypted data blocks, but these can no longer be decrypted with the PHP script. They always result in an empty string there. Apparently the advapi32.dll on linux wine is not compatible with the one under Windows. Does anyone know more about this? It would be interesting to know what the cause is and whether it can be fixed somehow...

Many greetings, Volker

----------


## Niya

> I'm using *wqweto*'s mdAesCbc.bas to encrypt my data end decrypt them with the PHP script from *labmany*. That works great!
> 
> One of my users now has the software running on Linux with wine and found that it does not work there. On wine, mdAesCbc.bas also generates encrypted data blocks, but these can no longer be decrypted with the PHP script. They always result in an empty string there. Apparently the advapi32.dll on linux wine is not compatible with the one under Windows. Does anyone know more about this? It would be interesting to know what the cause is and whether it can be fixed somehow...
> 
> Many greetings, Volker


Sounds more like a Wine problem. This might be something the Wine devs would want to know about but I will leave it to the crypto experts to determine if that is indeed the case.

----------


## wqweto

> Hi, 
> 
> thanks for your support. The receiving party can't use any binaries to decrypt my encrypted strings, i can just use the application-layer with a provided class and methods. Let me note the results here:
> 
> external system:
> 
> AES256-key: C&F)J@NcRfUjXn2r5u7x!A%D*G-KaPdS
> Plain-String: www.vbforums.com
> IV: 0000000000000000
> ...


I just tweaked the mdAesCbc.bas above to accept byte-arrays for second parameter which is treated directly as the AES256-CBC key and IV (no PBKDF2 derivation and no salt is stored/used).

For instance this code



```
    Dim baKey()     As Byte
    Dim sText       As String
    Dim sEncr       As String
    
    baKey = StrConv("C&F)J@NcRfUjXn2r5u7x!A%D*G-KaPdS", vbFromUnicode)
    sText = "www.vbforums.com"
    sEncr = AesEncryptString(sText, baKey)
    Debug.Print sEncr
```

. . . produces *+hYo90qObfy5tAy0HjtioXnc2o3P1JMgBLyzXDHoRbM=* while this code



```
    Dim baKey()     As Byte
    Dim sText       As String
    Dim sEncr       As String
    
    baKey = StrConv("C&F)J@NcRfUjXn2r5u7x!A%D*G-KaPdS0000000000000000", vbFromUnicode)
    sText = "www.vbforums.com"
    sEncr = AesEncryptString(sText, baKey)
    Debug.Print sEncr
```

. . . produces *bTaRHvRreoUIVuqQt7/BZ4Ez5YGh61nx66EzSJK5fgk=* so the IV is optional and can be passed in the same byte-array immediately after the 32-byte key.

The module is VBA/TB compatible now incl. x64 support.

cheers,
</wqw>

----------


## volkeru

> Sounds more like a Wine problem. This might be something the Wine devs would want to know about but I will leave it to the crypto experts to determine if that is indeed the case.


The problem has been reported repeatedly in various places since autumn '21. It seemed to work before, but then it was apparently "engineered out of order". It doesn't seem to interest the wine developers. Unfortunately, there has not been a single reaction so far. I have now also checked again, but I don't expect any reaction either: https://forum.winehq.org/viewtopic.php?f=8&t=35667

----------


## wqweto

> The problem has been reported repeatedly in various places since autumn '21. It seemed to work before, but then it was apparently "engineered out of order". It doesn't seem to interest the wine developers. Unfortunately, there has not been a single reaction so far. I have now also checked again, but I don't expect any reaction either: https://forum.winehq.org/viewtopic.php?f=8&t=35667


It's not that it doesn't interest the Wine developers to fix this but the fixme warning cited in the bug report make no sense and it's not apparent how to dig into the root cause of the problem.

Take a look yourself which GNUTLS cipher is designated 23: https://cs.github.com/gnutls/gnutls?q=GNUTLS_CIPHER+23

CHACHA20 is not supported by Crypto API so it's not needed for AES -- the problem is elsewhere, the fixme messages are false lead.

At this point only a small C application (a single main function) which clearly produces invalid result under Wine can convince anyone to debug the problem IMO.

cheers,
</wqw>

----------


## volkeru

> It's not that it doesn't interest the Wine developers to fix this but the fixme warning cited in the bug report make no sense and it's not apparent how to dig into the root cause of the problem.


Well, the fixme warning is the only one issued in relation to the encryption problem with wine. If you deactivate the encryption, this message also disappears. The non-functioning of the VST plug-ins is certainly also related to the faulty encryption.




> Take a look yourself which GNUTLS cipher is designated 23: https://cs.github.com/gnutls/gnutls?q=GNUTLS_CIPHER+23


Okay, you're right. AES256-CBC should be number 5, not 23, but why does the message about algorithm 23 disappear if you deactivate encryption 5? That's strange!




> the fixme messages are false lead.


Yeah, that's what I also think now.




> At this point only a small C application (a single main function) which clearly produces invalid result under Wine can convince anyone to debug the problem IMO.


On the other hand, we have provided more than enough information so that anyone can easily reproduce the problem within a few minutes. Actually, no C procedure is required for this. It is enough to encrypt something with AES256-CBC using wine and then decrypt it with Windows - or vice versa. My software is designed for Windows and not for wine. That's why my engagement in this matter is quite limited.

Cheers, Volker

----------


## volkeru

I have now solved the problem with the incompatible AES256-CBC encryption under wine with a workaround: Since the AES256-CTR encryption in wine is compatible with the encryption on Windows (and thus also PHP), I now always encrypt with CTR by default and only use a fallback to CBC for old Windows versions that do not yet support CTR. Of course, the PHP script has to be adapted correspondingly so that it supports both encryptions. Not a particularly nice or clean solution, but it works.

----------


## RMMamun

Hi wqweto, Thanks for your very helpful contribution. I am working on a VB6 project and need your help, do you have a version for ECB mode and PKCS5Padding padding method? I have a specification for communicating with a device with AES algorithm, ECB mode, PKCS5Padding padding method. Thanks a lot.

----------


## wqweto

You cannot use PKCS#5 padding with AES as it only supports 8 bytes of padding (AES uses 16 bytes blocks) - What is the difference between PKCS#5 padding and PKCS#7 padding

Using ECB mode outside of other cryptographic constructs (e.g. CTR/GCM modes) is a very bad idea - The ECB Penguin.

If you *have* to deal with such bullsh*t you can try tweaking mdAesCbc.bas source by replacing CRYPT_MODE_CBC constant with this one

Private Const CRYPT_MODE_ECB                As Long = 2
. . . everywhere in the module's source code and hope your original assignment was meant to use PKCS#7 in first place.

Edit: JFYI, in latest revision of mdAesCbc.bas module there is a new optional *CipherMode* parameter to *AesChunkedInit* function which optionally accepts CRYPT_MODE_ECB const as declared above (when not specified defaults to CRYPT_MODE_CBC to keep current behavior).

cheers,
</wqw>

----------


## hinditutorpoint

unsupported encryption throw runtime error
-2147221504

----------


## wqweto

> unsupported encryption throw runtime error
> -2147221504


FYI, it says win7 and later in the first sentence of OP so this is normal runtime error to get on XP.

----------


## Juggler_IN

@Wqweto; Can you share the binary read and write codes: ReadBinaryFile and WriteBinaryFile?

baData = ReadBinaryFile("c:\path\to\input.file")
AesCryptArray baData, ToUtf8Array("pass")
WriteBinaryFile "c:\path\to\encrypted.file", baData

----------


## wqweto

Here you go



```
'--- mdBinaryFile.bas
Option Explicit

#Const HasPtrSafe = (VBA7 <> 0) Or (TWINBASIC <> 0)

#If HasPtrSafe Then
Private Declare PtrSafe Function DeleteFile Lib "kernel32" Alias "DeleteFileA" (ByVal lpFileName As String) As Long
#Else
Private Declare Function DeleteFile Lib "kernel32" Alias "DeleteFileA" (ByVal lpFileName As String) As Long
#End If

Public Function ReadBinaryFile(sFile As String) As Byte()
    Dim baBuffer()      As Byte
    Dim nFile           As Integer

    On Error GoTo EH
    baBuffer = vbNullString
    nFile = FreeFile
    Open sFile For Binary Access Read Shared As nFile
    If LOF(nFile) > 0 Then
        ReDim baBuffer(0 To LOF(nFile) - 1) As Byte
        Get nFile, , baBuffer
    End If
    Close nFile
    ReadBinaryFile = baBuffer
EH:
End Function

Public Sub WriteBinaryFile(sFile As String, baBuffer() As Byte)
    Dim nFile           As Integer
    
    Call DeleteFile(sFile)
    nFile = FreeFile
    Open sFile For Binary Access Write Shared As nFile
    If UBound(baBuffer) >= 0 Then
        Put nFile, , baBuffer
    End If
    Close nFile
End Sub
```

Can be used like this



```
Private Sub TestBinary()
    Const IN_FILE As String = "D:\TEMP\aaa.txt"
    Dim baData() As Byte
    
    baData = ReadBinaryFile(IN_FILE)
    AesCryptArray baData, ToUtf8Array("pass")
    WriteBinaryFile IN_FILE & ".encrypted", baData
    
    baData = ReadBinaryFile(IN_FILE & ".encrypted")
    AesCryptArray baData, ToUtf8Array("pass")
    WriteBinaryFile IN_FILE & ".decrypted", baData
End Sub
```

Btw, just updated the the mdAesCtr.bas with some small fixes.

cheers,
</wqw>

----------


## Tizio

Hi wqweto
thanks for the excellent work and for sharing the code
cheers
Tiz

----------

