AES file encryption

Windows specific questions.
Post Reply
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

AES file encryption

Post by deltarho[1859] »

With my RSA-ECDSA it lacks having AES encryption. Senders and recipients will have to arrange that themselves. I will have some FreeBASIC code to add AES but that may a while coming.

I do, however, have AES in PowerBASIC code in the form of functions, static and dynamic libraries and standalone GUI's. So, RSA-ECDSA can be augmented fairly quickly by using a PowerBASIC dll.

I decided to open a thread about that dll because you can add AES to any of your own code very easily. I must stress that it only does encryption/decryption for your personal files. It should not be used for sending encrypted data over the internet. For that we should use authenticated encryption. I have PowerBASIC code for that but is not needed for RSA-ECDSA as that is about authentication. Needless to say the dll underwent extensive testing with files of varying lengths and different types of files. It uses a 256KB buffer so we can encrypt files of any size, 6GB if you want.

The dll is easy to use.

AES( <AES strength>, <File>, <password>, <stretch> )

The stretch is an integer employed to inject entropy into a limited entropy password. With <stretch> = 20, for example, we effectively increase the passwords entropy by 20 bits ie make it over one million times stronger (2^20). If the stretching takes 250ms then an attacker, with the same PC power, will only be able to try four guesses of our password per second. The stretch cannot be jumped over because is uses a hash function and a random salt with the final hash being used as our encryption key. The method was got from Cryptography Engineering ISBN: 978-0-470-47424-2.

However, I doubt if anyone is more strongly against using passwords than I am. If, for our password, we used '123456', the most commonly used password at the time of writing, then a dictionary attack, with the above example, would break our code in 250 milliseconds. The reason being that, from a dictionary attack perspective, '123456' has no entropy so stretching it by 20 bits give us 20 bits which even a 1997 laptop would not find intimidating. So, we need to stretch something which has some decent entropy in the first place. I am afraid that I part company with Bruce Schneier on this one.

For small files sizes we can 'push the boat out' with the stretch values but the above argument still holds - with a weak entropy password we will still be 'blown out of the water'. If we stretched for 10 minutes then an attacker would break our code in 10 minutes. It is as simple as that.

The dll, however, will accept binary strings as passwords. For example, if we gave it a 64 random character hex string then the dll will convert that into a 256 binary key. Each byte will have an entropy of 8 bits and so will be saturated. It cannot, therefore, be stretched and so isn't. If we give the dll a stretch value it will be ignored.

Not any old hex string will do: This has to be satisfied ( in PB speak )

Code: Select all

If ( sPassword <> "" ) And ( Len( sPassword ) Mod 2 = 0 ) And ( Len( sPassword ) >= 64 ) And IsTrue Hex( sPassword ) Then
The 'IsTrue Hex( sPassword )' returns true if password is wholly hexadecimal.

A variety of key derivation functions have been suggested over the years and their principle aim is stretching and provide a key which is uniformly distributed. Well, a random number generator is uniformly distributed so if we generate our hex string with one then it will be uniformly distributed and not requiring any stretching; if the above criteria is satisfied. Obviously I would recommend a CPRNG rather than a PRNG; such as Microsoft's BCryptGenRandom function.

In use, we could have

Code: Select all

AES( AES128, <File>, "E12344C847455AF8F55D9F7E3F2B76A44A034099E824B35A14DBE49C2071D476", 0 )
A quantum computer will not be able to brute force that password. Needless, we cannot commit that to memory and a good place for it is a password manager.

The following code will generate a cryptographically random 64 character hex string and place it on the clipboard. If you have a password manager then have it open ready to accept the password being Pasted to it because the clipboard will be emptied 10 seconds after the password is created. Even though the clipboard has been emptied the password will still exist in the clipboard manager's database and will need to be removed. If you do not have a password manager then Paste it elsewhere and secure using your current protocol.

HexGen.bas

Code: Select all

#Include once "windows.bi"
#Include Once "win\bcrypt.bi"
#inclib "bcrypt"
 
' Sub by leopardpm
Sub SaveToClipBoard(Text As String)       'write Text to the clipboard
  Var Num=GlobalAlloc(GMEM_MOVEABLE,Len(Text)+1)
  memcpy(GlobalLock(num), @text[0], Len(Text)+1)
  Var Chars=GlobalLock(Num)
  GlobalUnlock(Num)
  OpenClipboard(0)
  EmptyClipboard()
  SetClipboardData(CF_TEXT,Chars)
  CloseClipboard()
End Sub
 
Dim Ss BCRYPT_ALG_HANDLE ptr hRand
Dim As String Password
Dim HexByte( 1 to 64 ) As uByte
Dim As Long i
Dim As Double t
 
BCryptOpenAlgorithmProvider(@hRand, BCRYPT_RNG_ALGORITHM, 0, 0)
BCryptGenRandom(hRand, @HexByte(1), 64, 0)
BCryptCloseAlgorithmProvider(hRand, 0)
 
For i = 1 To 64
  Password += Mid( "0123456789ABCDEF", HexByte(i)\16 + 1, 1 )
Next
 
SaveToClipBoard( Password )
 
Locate 10, 20
Print "Clipboard will be emptied in 10 seconds"
t = timer
Do
Loop Until Timer >= t + 10
 
OpenClipboard(0)
EmptyClipboard()
CloseClipboard()
 
Print
Locate 12, 6
Print "If you have a clipboard manager you will have to remove the password"
 
Sleep
When a file, say MyData.dat,is encrypted the ciphertext will be called MyData.dat.aes. On decryption we simply use MyData.dat.aes and the plaintext will be called MyData.dat.

On paper AES256 should be about 30% slower than AES128. When I first looked at AES I was getting more like 20%. However, that is for the encryption time and not the time to read and write data as well. When we look at the whole operation, reading, encrypting and writing then there is very difference between AES128 and AES256. The difference will 'open up' if faster drives are used and I found this to be true when moving tests from my internal HDD to my SSD system drive.

As mentioned the minimum OS requirement is Windows Vista. If you have Windows 7 or later and have AES-NI on your CPU then you will get hardware encryption automatically. If you run CPU-Z you will see AES in the 'Instructions' box if you have AES-NI.

This is what I am getting for a 100MB file. The times are in seconds and each are the average of 10 tests. The filecache was cleared before each test.

Code: Select all

               Stretch
        0    16   18   20   22
HDD
AES128 0.84 0.89 1.08 1.86 5.00
AES192 0.85 0.90 1.10 1.84 4.97
AES256 0.84 0.88 1.10 1.88 4.97
 
SSD
AES128 0.34 0.53 0.61 1.37 4.47
AES192 0.41 0.50 0.61 1.40 4.46
AES256 0.35 0.47 0.58 1.36 4.47
So, with my HDD and AES-NI I am getting about 120MBs per second and aout 285MBs per second with my SSD.

Here is a usage example.

Code: Select all

#include once "windows.bi"
 
const AES128 = 128
const AES192 = 192
Const AES256 = 256
 
Dim As Any Ptr library = DyLibLoad( "EncDecStretchCNG.dll" )
If library = 0  Then
  Print "Failed to load EncDecStretchCNG.dll"
  end 1
End If
 
Dim AES As Function( as long, ByRef as ZString, byref As zString, as long ) as long
AES = DyLibSymbol( library, "EncDecStretchCNG" )
If AES = 0  Then
  Print "Could not retrieve the entry point of EncDecStretchCNG"
  End 1
End If
 
' ***** Use your own file *****
 
Dim As Double t
t = Timer
AES( AES256, "100MB.txt", "0D2D775DEE07C6966E3600851323C9A6C30578B66118D34D7E7130DAA7A6386D", 0 )
t = Timer - t
Print t
print "Done"
sleep

Here is a zip of the library: EncDecStretchCNG.zip

Technical Note:

The header of the encrypted file includes 16 bytes for the random IV (Initial Values) and 32 bytes for the random salt if stretching is employed.
Last edited by deltarho[1859] on Feb 19, 2018 11:51, edited 2 times in total.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

There was a bug in the hex key generator; it sometimes fell short of 64 characters. The method was ported from PowerBASIC which has a command called CEIL which FB does not have. I messed up on the translation. Fixed!
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

I forgot to mention that the dll has return values. Most of them were used during development and trap such things as invalid parameter which, obviously, helps development.

From a users point of view the two relevant ones are:

-1 : File not found
-5: Target is a zero byte file

allowing you to recover gracefully.

All errors are trapped so if you see any other values then let me know.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

I am sure that I don't need to mention this but I will anyway. The code above has the password hardwired. This is for illustrative purposes and testing. In practice, we should be requested for a password. An easy way to do that is via an input box and to get a password manager to fill that for us.
Last edited by deltarho[1859] on Feb 19, 2018 19:26, edited 1 time in total.
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: AES file encryption

Post by srvaldez »

thank you deltarho[1859]
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

DR: Did I mention a pricing plan?
Other: This is FreeBASIC.
DR: Oh, yeah. Well, at least I cannot be asked for a refund.
Other: There is that.

A few years ago a user of mine messed about with an app's ini file and screwed it up so badly that the app crashed. Fortunately, if no ini file existed the app built one with 'factory' settings. All the user had to do then was delete the ini file and restart the app . Of course, he lost all all his user settings and serves him right to. <smile> I then changed the app to use an encrypted ini file. So, the app needs a password then? Nope. The app used something in the binary, hashed it and used the hash as an encryption key. I cannot go into detail as I would then have to shoot you. There was no risk involved as the app had nothing to do with security and no one in their right mind would have an interest in hacking into it. The uses for encryption are boundless.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

Run this to find out whether you have ASE-NI or not. If not Windows will use software. Notice that both Intel and AMD have AES-NI in their later models.

Code: Select all

Function GetAESNI() As Long
Dim CPUManID As String * 12
Dim CPUManIDPtr As Ubyte Ptr
Dim result As Ulong

  ' Get Manufactures Id String
  CPUManIDPtr = Cast( Ubyte Ptr, @CPUManID)
  Asm
    mov eax, 0 ' Get Vendor Id
    cpuid
    mov eax, dword Ptr [CPUManIDPtr]
    mov [eax], ebx
    mov [eax + 4], edx
    mov [eax + 8], ecx
  End Asm
  If CPUManID = "GenuineIntel" Or CPUManID = "AuthenticAMD" Then
    ' Is AES supported?
    Asm
      mov eax, 1 ' Get features
      cpuid
      Test ecx, &h02000000 ' Bit 25
      jz 0f
      mov dword Ptr [result], -1
  0:
    End Asm
    Return result
  End If
  Return result
End Function

If GetAESNI = -1 Then
  Print "You have AES-NI"
Else
  Print "You have not got AES-NI"
End If

Sleep
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

I finally got around to porting AES from PowerBASIC to FreeBASIC. It was by far the most difficult port so far. I ended up paraphrasing a quip by Bernard Shaw: PowerBASIC and FreeBASIC are two BASICs divided by a common language. <laugh>

The function presented here, AES, has exactly the same parameters as AES in the opening post ie

AES( <AES strength>, <File>, <password>, <stretch> )

However, it has a new feature: Password/Passkey verification. Instead of doing 2^n stetches we do '2^n - 1' stretches. We then prepend the hash so far with 64 null bytes, SHA256 block length, and hash that. This hash, the verification data, is saved in the header of the ciphertext file. We then calculate the final stretching iterate and use that as our encryption key. Because of the properties of hash functions the saved value is independent of the final iterate so cannot be used to get the final iterate. On decryption the process is the same. If the verification data does not equate with the saved data then the password is invalid. This method was also got from Cryptography Engineering ISBN: 978-0-470-47424-2.

Password/Passkey verification is still used if we use an appropriate binary key; which doesn't normally need stretching. We could, inadvertently, pass the wrong key from our password manager. The stretching here uses n = 4. On my machine the 'overhead' is about 70 microseconds plus the time to read/write the verification data to disk; done at the same time as reading/writing the random initial values (IV) and salt. So, there is no performance hit in verifying an appropriate binary key.

AES() returns -6 for a Password/Passkey failing the verification test.

It should be noted that the Password/Passkey may be correct on decryption but the stretch value may be wrong. This, obviously, would also lead to a -6 error.

Needless to say I still advocate a binary key over an Ascii key, any day of the week. With a 256 key we have an uncrackable Passkey and very nearly free of any stretching allowing the code to get on with the job without delay.

I was tempted to remove the first parameter, <AES strength>, since AES128 is only marginally faster than AES256 when disk access is taken into account. However, that is on my machine - your mileage may differ. Software encryption, as opposed to AES-NI, will reduce the relative dominance of disk accessing and will 'open up' the difference; as if the drives were faster. Drives which are faster will see the difference open up - such as modern SSDs which are probably much faster than mine. The buffer size has been kept at 256KB so we will still be able to work on very large files.

The header of the encrypted file now includes 16 bytes for the random IV (Initial Values), 32 bytes for the random salt and 32 bytes for the verification data. I did not mention this above but the ciphertext data length is the plaintext data length padded to the next AES block size of 16 bytes. If the plaintext is exactly divisible by sixteen then padding still occurs at 16 bytes. So, the padding will between 1 and 16 bytes inclusively. The ciphertext file then may be up to 96 bytes larger than the plaintext file.

Just remember we pass <filepath> to AES() for encryption and pass <filepath.aes> to AES() for decryption.

Just in case we make a mistake the stretch is limited 24. Try 24 on a small file - you will not do it again. 16 is about right for my machine adding just less than 300ms to no stretching. According to Moore's Law of one bit, a doubling, every two years then it will be 16 years before 24 is about right and Moore's Law is slowing down. The current recommendation by the NIST for password entropy is 112 bits. If your password is at that level then an extra 16 bits will really scupper an attacker. A random alpha-numeric password of 19 characters is just over 113 bits but, I must admit, I struggle to remember more than 16. <Large grin> Of course, with a binary key we don't have to think about such matters for a very long time. (256 - 112) puts us well into next century. Persuaded yet?

Attached is a zipped folder, AES_Libraries, including: AES.bi, folders 32bit and 64bit with respective static libraries libAES.a.

AES_Libraries

The 32 bit version of libAES.a, the larger of the two oddly enough, is only 12.6KB.

Example:

Code: Select all

#Include Once "AES.bi"
Const AES256 = 256
 
AES( AES256, "MyTopSecretStuff.txt", "Don'tHaveAPasswordManager", 16 )
Print "Done"
Sleep 
32 bit and 64 bit run pretty much at the same speed.

There is little difference between gas and gcc.

The FB version is about 18% faster than the PB version. I have often found this. PB is no slouch but is often left behind by FB.

Here is the source code package including AES() and supporting functions IsHex() and Hash().

Code: Select all

#Include Once "file.bi"
#Include Once "windows.bi"
#Inclib "bcrypt"
#Include Once "win/wincrypt.bi"
#Inclib "crypt32"

#define STATUS_SUCCESS &h00000000
Const BlockSize = 16
Const BufferSize = 256*1024
Const AES128 = 128
Const AES192 = 192
Const AES256 = 256

' Following courtesy of Mr Swiss
#Define IsFalse(e)  ( Not CBool(e) )
#Define IsTrue(e)   ( CBool(e) )

Function IsHex(s As String) As Long
  Dim i As Long
 
  For i = 0 To Len(s) - 1
    Select Case s[i]
    Case 48 To 57, 65 To 70, 97 To 102
    Case Else
      Return False
    End Select
  Next i
  Return true
 
End Function

Function Hash( hashalg As lpcwstr, stext As String, index As Long, mode As Long, final As Long, pbsecret As String = Chr(0)) As String

Static phalg(1 To 8) As Byte Ptr
Static phhash(1 To 8) As Byte Ptr
Dim sbinary As String
Dim shex As String
Dim nlength As Long

  ' Initialize section
 
  If phhash(index) = 0 Then ' will be the Case On, And perhaps only, first pass
    If pbsecret = Chr(0) Then ' Not hmac
      BcryptOpenAlgorithmprovider @phalg(index), hashalg, 0, 0 ' we want phalg(index)
      BcryptCreatehash phalg(index), @phhash(index), null, 0, 0, 0, 0 ' we want phhash(index)
    Else ' Is hmac
      BcryptOpenAlgorithmprovider @phalg(index), hashalg, 0, bcrypt_alg_handle_hmac_flag ' we want phalg(index)
      If Right$( pbsecret, 1 ) <> Chr(0) Then ' ascii Not forced
        ' are we going To use pbsecret As ascii Or Binary?
        If ( Len( pbsecret ) Mod 2 = False ) And IsHex( pbsecret ) = true Then
          nlength = Len(pbsecret)\2
          sbinary = Space$( nlength )
          CryptStringToBinarya Strptr(pbsecret), Len(pbsecret), crypt_string_hexraw, Strptr(sbinary), @nlength, 0, 0
          pbsecret = sbinary
        End If
      Else
        pbsecret = Left$( pbsecret, Len(pbsecret) - 1)
      End If
      BcryptCreatehash phalg(index), @phhash(index), null, 0, Strptr( pbsecret ), Len( pbsecret ), 0 ' we want phhash(index)
    End If
  End If

  ' update section
 
  BcryptHashData( phhash(index), Strptr( stext), Len( stext ), 0 )

  ' finalization section
 
  If final = true Then ' finalize hash
    Dim As Ulong lhashlength
    Dim As Ulong lresult
    Dim sbinhash As String
   
    BcryptGetProperty phalg(index), bcrypt_hash_length, Cast( Puchar, @lhashlength ), 4, @lresult, 0
    sbinhash = String$( lhashlength, 0 )
    BcryptFinishHash phhash(index), Strptr( sbinhash ), lhashlength, 0
    BcryptDestroyHash phhash(index)
    BcryptCloseAlgorithmprovider phalg(index), 0
    phhash(index) = 0 ' ensures a New hash Is created On another pass of hash() With This index
    If mode = 0 Then
      Return sbinhash
    Else
      nlength = Len(sbinhash)*2 + 1 ' + 1 To accomodate a null terminator
      shex = Space$( nlength )
      CryptBinaryToStringa Strptr(sbinhash), Len(sbinhash), crypt_string_hexraw + crypt_string_nocrlf, _
        Strptr(shex), @nlength ' at msdn nlength 'Out' Is Len(sbinhash) * 2, so
      Return Ucase$( Left$( shex, nlength ) )
    End If
  End If

End Function

Function AES( AESstrength As Long, sInFile As String, sPassword As String, lStretch As Long ) As Long
' If sInFile has an aes extension Then decrypt Else encrypt

Dim As BCRYPT_ALG_HANDLE Ptr hRand, hAESAlg
Dim As BCRYPT_KEY_HANDLE Ptr hKey
Dim OutText() As Byte
Dim As Long i, Final, lFileLen, FinalSection, SectionCount, IsInputEncrypted, LenInText, LenOutText, nLength
Dim As Ulong ntStatus, lError, dwResult, phHashAES, hIn, hOut
Dim sIV As String * 16
Dim sSalt As String * 32
Dim As String sBinary, sOutFile, sDummy, sKey
Dim As String*32 sKeyVerify, sSavedKeyVerify
Dim As String * 262144 pbdata
Dim As Uinteger nbloaded
 
  AESstrength /= 8
  lStretch += 2
 
  ' File Input/Output section
 
  If Not Fileexists( sInfile ) Then ' Unable To find file
    Function = -1
    Goto TidyUp
  End If
 
  hIn = Freefile
  Open sInfile For Binary As #hIn
    lFilelen = Lof( hIn )
     If lFileLen = 0 Then ' Zer0 Byte file
    Close #hIn
    Function = -5
    Goto TidyUp
  End If
 
  If Lcase( Right( sInFile, 4 ) ) <> ".aes" Then
    sOutFile = sInFile & ".aes"
    ' eg f:\folder\myapp.Exe ==> f:\folder\myapp.Exe.aes
    IsInputEncrypted = False
  Else
    sOutFile = Left$( sInFile, Len( sInFile ) - 4 )
    ' eg f:\folder\myapp.Exe.aes ==> f:\folder\myapp.Exe
    IsInputEncrypted = True
  End If
 
  ' Read Or generate header items
 
  If IsInputEncrypted = True Then
    Get #hIn,, sIV
    Get #hIn,, sSalt
    Get #hIn,, sSavedKeyVerify
  Else
    ntStatus = BCryptOpenAlgorithmProvider(@hRand, BCRYPT_RNG_ALGORITHM, "", 0) ' Prepare For Random number generation
    If ntStatus <> STATUS_SUCCESS Then lError = 10 : Goto ErrorTrap
    ntStatus = BCryptGenRandom(hRand, Strptr(sIV), BlockSize, 0)
    If ntStatus <> STATUS_SUCCESS Then lError = 20 : Goto ErrorTrap
    ntStatus = BCryptGenRandom(hRand, Strptr(sSalt), 32, 0)
    If ntStatus <> STATUS_SUCCESS Then lError = 30 : Goto ErrorTrap
    ntStatus = BCryptCloseAlgorithmProvider(hRand, 0)
    If ntStatus <> STATUS_SUCCESS Then lError = 40 : Goto ErrorTrap
  End If
 
  ' Are we going To use sPassword As ascii Or Binary?
 
  If ( sPassword <> "" ) And ( Len( sPassword ) Mod 2 = 0 ) And ( Len( sPassword ) >= 2 * AESStrength ) And IsHex( sPassword ) <> 0 Then
    ' Treat As a Binary String
    nLength = Len(sPassword)\2
    sBinary = Space$( nlength )
    CryptStringToBinarya Strptr(sPassword), Len(sPassword), crypt_string_hexraw, Strptr(sBinary), @nLength, 0, 0
    sPassword = sBinary
  End If
 
  ' Use SHA256 And stretch sPassword
 
  ' Algorithm employed x0 = 0 : xi = Hash( xi-1 + sPassword + sSalt ) For i = 1 To 2^lStretch
  ' From Cryptography Engineering: ISBN 978-0-470-47424-2
 
  ' Calculate 2^lStretch - 1 hashes
 
  For i = 1 To 2^lStretch - 1
    sDummy = sKey + sPassword + sSalt
    sKey = Hash( Wstr("SHA256"), sDummy, 1, False, False )
  Next
 
  ' Calculate password verification Data
 
  sDummy = String$(64,0) + sKey + sPassword + sSalt
  sKeyVerify = Hash( Wstr("SHA256"), sDummy, 1, False, True )
 
  If IsTrue( IsInputEncrypted ) Then
    If sSavedKeyVerify <> sKeyVerify Then ' Password failed
      Function = -6
      Goto TidyUp
    End If
  End If
 
  ' Now calculate final iterate
 
  sDummy = sKey + sPassword + sSalt
  sKey = Hash( Wstr("SHA256"), sDummy, 1, False, True )
 
  ' Set up Output file
 
  If IsTrue( Fileexists(sOutFile) ) Then
    Kill sOutFile
  End If
  hOut = Freefile
  Open sOutFile For Binary As hOut
  If Err Then
    Function = Err
    Goto TidyUp
  End If
 
  If IsFalse( IsInputEncrypted ) Then ' Save Header items
    Put #hOut, , sIV
    Put #hOut, , sSalt
    Put #hOut, , sKeyVerify
  End If
 
  ' AES section
 
  ntStatus = BCryptOpenAlgorithmProvider( @hAESAlg, BCRYPT_AES_ALGORITHM, "", 0 ) ' We want hAESAlg
  If ntStatus <> STATUS_SUCCESS Then lError = 50 : Goto ErrorTrap
  ntStatus = BCryptGenerateSymmetricKey( hAESAlg, @hKey, 0, 0, Strptr(sKey), AESstrength, 0 ) ' We want hKey
  If ntStatus <> STATUS_SUCCESS Then lError = 60 : Goto ErrorTrap
 
  ' Encryption/Decryption section
 
  Redim OutText( 1 To BufferSize ) As Byte
  LenOutText = BufferSize
  LenInText = BufferSize
 
  If IsTrue( IsInputEncrypted ) Then
    lFileLen -= 80 ' To account For sIV, sSalt And sSavedKeyVerify
  End If
  FinalSection = lFileLen\BufferSize
  If lFileLen > FinalSection*BufferSize Then FinalSection += 1
 
  ' Encryption/Decryption Loop
 
  Do
    SectionCount += 1
    Get #hIn,, pbdata, , nbLoaded
    If SectionCount = FinalSection Then
      Final = BCRYPT_BLOCK_PADDING
    End If
 
    If IsFalse( IsInputEncrypted ) Then ' Encrypt
      If Final = BCRYPT_BLOCK_PADDING Then
        ntStatus = BCryptEnCrypt( hKey, Strptr(pbData), nbLoaded, 0, Cast( Byte Ptr, @sIV ), BlockSize, 0, 0, @LenOutText, Final )  'We want LenOutText
        If ntStatus <> 0 Then lError = 70 : Goto ErrorTrap
       
        ' If we are On blocksize boundary we will need BufferSize + BlockSize otherwise GPF
        If IsFalse( LenOutText Mod BlockSize ) Then Redim OutText( 1 To BufferSize + BlockSize) As Byte
        LenInText = nbLoaded
      End If
 
      ' LenOutText = BufferSize unless revised above
      ntStatus = BCryptEnCrypt( hKey, Strptr(pbData), LenInText, 0, Cast( Byte Ptr, @sIV ), BlockSize, @OutText(1), LenOutText, @dwResult, Final )
      If ntStatus <> 0 Then lError = 80 : Goto ErrorTrap
 
    Else ' Decrypt
      If Final = BCRYPT_BLOCK_PADDING Then
        ntStatus = BCryptDecrypt( hKey, Strptr(pbData), nbLoaded, 0, Cast( Byte Ptr, @sIV ), BlockSize, 0, 0, @LenOutText, Final )  ' We want LenOutText
        If ntStatus <> 0 Then lError = 90 : Goto ErrorTrap
        If IsFalse( LenOutText Mod BlockSize ) Then Redim OutText( 1 To BufferSize + BlockSize) As Byte
        LenInText = nbLoaded
      End If
 
      ntStatus = BCryptDecrypt( hKey, Strptr(pbData), LenInText, 0, Cast( Byte Ptr, @sIV ), BlockSize, @OutText(1), LenOutText, @dwResult, Final )
      If ntStatus <> 0 Then lError = 100 : Goto ErrorTrap
    End If
 
    ' If Output file Is an exact multiple of bufferSize
    ' Then dwResult will be zero And we have nothing To dump
 
    If dwResult > 0 Then
      If IsTrue( Final ) Then
        Redim Preserve OutText(1 To dwResult) As Byte
      End If
      Put #hOut, , OutText()
    End If
 
  Loop Until Final
 
Goto TidyUp

ErrorTrap:

MessageBox ( 0, "Error at Position " + Str(lError) + " " + Hex(ntStatus), "EncDec", MB_OK )
 
TidyUp:
  If hAESAlg <> 0 Then BCryptCloseAlgorithmProvider( hAESAlg, 0 )
  If hKey <> 0 Then BCryptDestroyKey( hKey )
  Close hIn
  Close hOut
 
End Function
Last edited by deltarho[1859] on Mar 21, 2018 7:05, edited 3 times in total.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: AES file encryption

Post by dodicat »

Hi DeltaRho[]

Code: Select all

You have not got AES-NI 
Snookered.

Instead of all those goto's (which personally I am quite in favour of), you could use macros instead of labels.
But you'll have to test to see if they give the required results.
(Also I had to:
#define STATUS_SUCCESS 1)
It was giving an error here.
Your snippet de gotoed (I have undefined goto only, if your macros work you could rub every goto out with your ide)

Code: Select all

#Include Once "file.bi"
#Include Once "windows.bi"
#Inclib "bcrypt"
#Include Once "win/wincrypt.bi"
#Inclib "crypt32"

Const BlockSize = 16
Const BufferSize = 256*1024
Const AES128 = 128
Const AES192 = 192
Const AES256 = 256

' Following courtesy of Mr Swiss
#Define IsFalse(e)  ( Not CBool(e) )
#Define IsTrue(e)   ( CBool(e) )

Function IsHex(s As String) As Long
  Dim i As Long
  
  For i = 0 To Len(s) - 1
    Select Case s[i]
    Case 48 To 57, 65 To 70, 97 To 102
    Case Else
      Return False
    End Select
  Next i
  Return true
  
End Function

Function Hash( hashalg As lpcwstr, stext As String, index As Long, mode As Long, final As Long, pbsecret As String = Chr(0)) As String

Static phalg(1 To 8) As Byte Ptr
Static phhash(1 To 8) As Byte Ptr
Dim sbinary As String
Dim shex As String
Dim nlength As Long

  ' Initialize section
  
  If phhash(index) = 0 Then ' will be the Case On, And perhaps only, first pass
    If pbsecret = Chr(0) Then ' Not hmac
      BcryptOpenAlgorithmprovider @phalg(index), hashalg, 0, 0 ' we want phalg(index)
      BcryptCreatehash phalg(index), @phhash(index), null, 0, 0, 0, 0 ' we want phhash(index)
    Else ' Is hmac
      BcryptOpenAlgorithmprovider @phalg(index), hashalg, 0, bcrypt_alg_handle_hmac_flag ' we want phalg(index)
      If Right$( pbsecret, 1 ) <> Chr(0) Then ' ascii Not forced
        ' are we going To use pbsecret As ascii Or Binary?
        If ( Len( pbsecret ) Mod 2 = False ) And IsHex( pbsecret ) = true Then
          nlength = Len(pbsecret)\2
          sbinary = Space$( nlength )
          CryptStringToBinarya Strptr(pbsecret), Len(pbsecret), crypt_string_hexraw, Strptr(sbinary), @nlength, 0, 0
          pbsecret = sbinary
        End If
      Else
        pbsecret = Left$( pbsecret, Len(pbsecret) - 1)
      End If
      BcryptCreatehash phalg(index), @phhash(index), null, 0, Strptr( pbsecret ), Len( pbsecret ), 0 ' we want phhash(index)
    End If
  End If

  ' update section
  
  BcryptHashData( phhash(index), Strptr( stext), Len( stext ), 0 )

  ' finalization section
  
  If final = true Then ' finalize hash
    Dim As Ulong lhashlength
    Dim As Ulong lresult
    Dim sbinhash As String
    
    BcryptGetProperty phalg(index), bcrypt_hash_length, Cast( Puchar, @lhashlength ), 4, @lresult, 0
    sbinhash = String$( lhashlength, 0 )
    BcryptFinishHash phhash(index), Strptr( sbinhash ), lhashlength, 0
    BcryptDestroyHash phhash(index) 
    BcryptCloseAlgorithmprovider phalg(index), 0
    phhash(index) = 0 ' ensures a New hash Is created On another pass of hash() With This index
    If mode = 0 Then
      Return sbinhash
    Else
      nlength = Len(sbinhash)*2 + 1 ' + 1 To accomodate a null terminator
      shex = Space$( nlength )
      CryptBinaryToStringa Strptr(sbinhash), Len(sbinhash), crypt_string_hexraw + crypt_string_nocrlf, _
        Strptr(shex), @nlength ' at msdn nlength 'Out' Is Len(sbinhash) * 2, so
      Return Ucase$( Left$( shex, nlength ) )
    End If
  End If

End Function

Function AES( AESstrength As Long, sInFile As String, sPassword As String, lStretch As Long ) As Long
' If sInFile has an aes extension Then decrypt Else encrypt
'======================  LABELS To MACROS ======================
#macro TidyUp
  If hAESAlg <> 0 Then BCryptCloseAlgorithmProvider( hAESAlg, 0 ):
  If hKey <> 0 Then BCryptDestroyKey( hKey ):
  Close hIn:
  Close hOut:
  exit function
  #endmacro
 #macro  ErrorTrap
MessageBox ( 0, "Error at Position " + Str(lError) + " " + Hex(ntStatus), "EncDec", MB_OK )
TidyUp
#endmacro

  #undef goto
  #define goto 
  #define STATUS_SUCCESS 1
'==============================================================

Dim As BCRYPT_ALG_HANDLE Ptr hRand, hAESAlg
Dim As BCRYPT_KEY_HANDLE Ptr hKey
Dim OutText() As Byte
Dim As Long i, Final, lFileLen, FinalSection, SectionCount, IsInputEncrypted, LenInText, LenOutText, nLength
Dim As Ulong ntStatus, lError, dwResult, phHashAES, hIn, hOut
Dim sIV As String * 16
Dim sSalt As String * 32
Dim As String sBinary, sOutFile, sDummy, sKey
Dim As String*32 sKeyVerify, sSavedKeyVerify
Dim As String * 262144 pbdata
Dim As Uinteger nbloaded
 
  AESstrength /= 8
  lStretch += 2
 
  ' File Input/Output section
 
  If Not Fileexists( sInfile ) Then ' Unable To find file
    Function = -1
    Goto TidyUp
  End If
  
  hIn = Freefile
  Open sInfile For Binary As #hIn
    lFilelen = Lof( hIn )
     If lFileLen = 0 Then ' Zer0 Byte file
    Close #hIn
    Function = -5
    Goto TidyUp
  End If
 
  If Lcase( Right( sInFile, 4 ) ) <> ".aes" Then
    sOutFile = sInFile & ".aes"
    ' eg f:\folder\myapp.Exe ==> f:\folder\myapp.Exe.aes
    IsInputEncrypted = False
  Else
    sOutFile = Left$( sInFile, Len( sInFile ) - 4 )
    ' eg f:\folder\myapp.Exe.aes ==> f:\folder\myapp.Exe
    IsInputEncrypted = True
  End If
  
  ' Read Or generate header items
  
  If IsInputEncrypted = True Then
    Get #hIn,, sIV
    Get #hIn,, sSalt
    Get #hIn,, sSavedKeyVerify
  Else
    ntStatus = BCryptOpenAlgorithmProvider(@hRand, BCRYPT_RNG_ALGORITHM, "", 0) ' Prepare For Random number generation
    If ntStatus <> STATUS_SUCCESS Then lError = 10 : Goto TidyUp
    ntStatus = BCryptGenRandom(hRand, Strptr(sIV), BlockSize, 0)
    If ntStatus <> STATUS_SUCCESS Then lError = 20 : Goto TidyUp
    ntStatus = BCryptGenRandom(hRand, Strptr(sSalt), 32, 0)
    If ntStatus <> STATUS_SUCCESS Then lError = 30 : Goto TidyUp
    ntStatus = BCryptCloseAlgorithmProvider(hRand, 0)
    If ntStatus <> STATUS_SUCCESS Then lError = 40 : Goto TidyUp
  End If
 
  ' Are we going To use sPassword As ascii Or Binary?
 
  If ( sPassword <> "" ) And ( Len( sPassword ) Mod 2 = 0 ) And ( Len( sPassword ) >= 2 * AESStrength ) And IsHex( sPassword ) <> 0 Then
    ' Treat As a Binary String
    nLength = Len(sPassword)\2
    sBinary = Space$( nlength )
    CryptStringToBinarya Strptr(sPassword), Len(sPassword), crypt_string_hexraw, Strptr(sBinary), @nLength, 0, 0
    sPassword = sBinary
  End If
 
  ' Use SHA256 And stretch sPassword
 
  ' Algorithm employed x0 = 0 : xi = Hash( xi-1 + sPassword + sSalt ) For i = 1 To 2^lStretch
  ' From Cryptography Engineering: ISBN 978-0-470-47424-2
 
  ' Calculate 2^lStretch - 1 hashes 
  
  For i = 1 To 2^lStretch - 1
    sDummy = sKey + sPassword + sSalt
    sKey = Hash( Wstr("SHA256"), sDummy, 1, False, False )
  Next
 
  ' Calculate password verification Data
  
  sDummy = String$(64,0) + sKey + sPassword + sSalt
  sKeyVerify = Hash( Wstr("SHA256"), sDummy, 1, False, True )
  
  If IsTrue( IsInputEncrypted ) Then
    If sSavedKeyVerify <> sKeyVerify Then ' Password failed
      Function = -6
      Goto TidyUp
    End If
  End If
 
  ' Now calculate final iterate
  
  sDummy = sKey + sPassword + sSalt
  sKey = Hash( Wstr("SHA256"), sDummy, 1, False, True )
 
  ' Set up Output file
  
  If IsTrue( Fileexists(sOutFile) ) Then
    Kill sOutFile
  End If
  hOut = Freefile
  Open sOutFile For Binary As hOut
  If Err Then
    Function = Err
    Goto TidyUp
  End If
 
  If IsFalse( IsInputEncrypted ) Then ' Save Header items
    Put #hOut, , sIV
    Put #hOut, , sSalt
    Put #hOut, , sKeyVerify
  End If
 
  ' AES section
 
  ntStatus = BCryptOpenAlgorithmProvider( @hAESAlg, BCRYPT_AES_ALGORITHM, "", 0 ) ' We want hAESAlg
  If ntStatus <> STATUS_SUCCESS Then lError = 50 : Goto TidyUp
  ntStatus = BCryptGenerateSymmetricKey( hAESAlg, @hKey, 0, 0, Strptr(sKey), AESstrength, 0 ) ' We want hKey
  If ntStatus <> STATUS_SUCCESS Then lError = 60 : Goto TidyUp
 
  ' Encryption/Decryption section
 
  Redim OutText( 1 To BufferSize ) As Byte
  LenOutText = BufferSize
  LenInText = BufferSize
  
  If IsTrue( IsInputEncrypted ) Then
    lFileLen -= 80 ' To account For sIV, sSalt And sSavedKeyVerify
  End If
  FinalSection = lFileLen\BufferSize
  If lFileLen > FinalSection*BufferSize Then FinalSection += 1
 
  ' Encryption/Decryption Loop
  
  Do
    SectionCount += 1
    Get #hIn,, pbdata, , nbLoaded
    If SectionCount = FinalSection Then
      Final = BCRYPT_BLOCK_PADDING
    End If
 
    If IsFalse( IsInputEncrypted ) Then ' Encrypt
      If Final = BCRYPT_BLOCK_PADDING Then
        ntStatus = BCryptEnCrypt( hKey, Strptr(pbData), nbLoaded, 0, Cast( Byte Ptr, @sIV ), BlockSize, 0, 0, @LenOutText, Final )  'We want LenOutText
        If ntStatus <> 0 Then lError = 70 : Goto ErrorTrap
        
        ' If we are On blocksize boundary we will need BufferSize + BlockSize otherwise GPF
        If IsFalse( LenOutText Mod BlockSize ) Then Redim OutText( 1 To BufferSize + BlockSize) As Byte
        LenInText = nbLoaded
      End If
 
      ' LenOutText = BufferSize unless revised above
      ntStatus = BCryptEnCrypt( hKey, Strptr(pbData), LenInText, 0, Cast( Byte Ptr, @sIV ), BlockSize, @OutText(1), LenOutText, @dwResult, Final )
      If ntStatus <> 0 Then lError = 80 : Goto ErrorTrap
 
    Else ' Decrypt
      If Final = BCRYPT_BLOCK_PADDING Then
        ntStatus = BCryptDecrypt( hKey, Strptr(pbData), nbLoaded, 0, Cast( Byte Ptr, @sIV ), BlockSize, 0, 0, @LenOutText, Final )  ' We want LenOutText
        If ntStatus <> 0 Then lError = 90 : Goto ErrorTrap
        If IsFalse( LenOutText Mod BlockSize ) Then Redim OutText( 1 To BufferSize + BlockSize) As Byte
        LenInText = nbLoaded
      End If
 
      ntStatus = BCryptDecrypt( hKey, Strptr(pbData), LenInText, 0, Cast( Byte Ptr, @sIV ), BlockSize, @OutText(1), LenOutText, @dwResult, Final )
      If ntStatus <> 0 Then lError = 100 : Goto ErrorTrap
    End If
 
    ' If Output file Is an exact multiple of bufferSize
    ' Then dwResult will be zero And we have nothing To dump
 
    If dwResult > 0 Then
      If IsTrue( Final ) Then
        Redim Preserve OutText(1 To dwResult) As Byte
      End If
      Put #hOut, , OutText()
    End If
 
  Loop Until Final
  
Goto TidyUp

'ErrorTrap:

'MessageBox ( 0, "Error at Position " + Str(lError) + " " + Hex(ntStatus), "EncDec", MB_OK )
  
'TidyUp:
  'If hAESAlg <> 0 Then BCryptCloseAlgorithmProvider( hAESAlg, 0 )
  'If hKey <> 0 Then BCryptDestroyKey( hKey )
 ' Close hIn
  'Close hOut
  
End Function


 
But your GOTO's are much neater.
Why I don't have AES-NI I don't know.

Processor: Intel(R) Core(TM)2 Duo CPU E8400 @ 3.00GHz (2 CPUs), ~3.0GHz
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

I am a fan of macros but not labels to macros - they bloat the source code and the resulting exe.

That source code was moved from a test bed program which had STATUS_SUCCESS - must have fallen off the back of the lorry.

It is actually 'STATUS_SUCCESS = 0x00000000 ' ie Zero errors. It should be in one of the bi files but it isn't. 'STATUS_INVALID_SIGNATURE' = 0xC000A000 is not in the bi files either. NTSTATUS Values

Your CPU was introduced in the first quarter of 2008. Intel proposed AES-NI in 2008 but it did not 'hit the streets' until 2010. AMD introduced it in 2011.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: AES file encryption

Post by dodicat »

Goto is better, I agree.


#define STATUS_SUCCESS &h00000000 would be better then.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

This has been added to RSA-ECDSA to give AES-RSA-ECDSA giving access to a GUI method of encryption. This is handy for encrypting data with no intention of sending the ciphertext over the internet. If your data is sensitive enough to be encrypted then public key encryption should be employed if you do send ciphertext over the internet. To my mind we have a contradiction otherwise. At the mentioned link is a link to AES-RSA-ECDSA.exe - some, if not most of you, may have no interest, or the time, to compile your own.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

Bug fix in source code package. It has nothing to do with the encryption/decryption but how errors are handled.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AES file encryption

Post by deltarho[1859] »

The opening post used a PB dll. I ported the code to FB in post #8. I have no idea if anyone compiled that or not but what is certain is that nobody compiled the code after the bug fix in my last post because there was no way that it would compile.

I keep forgetting that when I copy and paste from either poseidonFB or WinFBE what we get is not what we see. I used to paste into FBIde and format but that GPF'd every time I closed it. poseidonFB has a facility to format on the fly so I now use that. I am working on a new way of encryption/decryption and was looking at the post above and realized that some functions were duplicated. Clearly I had not ripped all the code out when I pasted the properly formatted code. We would then have a duplicated definition.

I am sure that someone would have mentioned the duplication - perhaps not. The code above has been corrected and will now compile.

The idea that I am now looking at uses a 256 bit random binary key so there is no need now for stretching. The key is only used once so does not have to be put into a password manager. In fact we don't even see the password. We have then a one time pad which is impossible to crack.

Instead of AES( <AES strength>, <File>, <password>, <stretch> ) we now simply have AES( <File> ) with <AES strength> hard wired at AES256.

Decryption is just as straightforward ( AES( <File.enc> ), and, again, we don't see the password here either.

I am not an expert on Internet communications but I think what I am doing is similar to Transport Layer Security (TLS) except the above is for our own encryption and not to be sent anywhere; so there is no authentication aspect.
Post Reply