Correct use of AES in CBC mode with a textual password

Share your advanced PureBasic knowledge/code with the community.
MarkOtt
User
User
Posts: 28
Joined: Sat Feb 11, 2017 8:33 pm
Location: Switzerland

Correct use of AES in CBC mode with a textual password

Post by MarkOtt »

Example code for encrypting/decrypting strings (files see below), using AESEncoder() and AESDecoder().

Using the PureBasic AESEncoder/AESDecoder in CBC mode in a correct way is not too easy.
(Topic is inspired by "walbus", see http://www.purebasic.fr/english/viewtop ... =7&t=68776 )

@walbus: Thanks, your AES OFB Cryptor helped understanding the situation.

A textual password (unicode) should be converted to binary hash with 32 Bytes for full complexity 256^32.
The InitializationVector with 16 Bytes should be changed for every new string/file.
The InitializationVector should be included in the ciphered data to be transfered to the recipient.
Data below 16 Bytes should be padded to 16 Bytes for AES-CBC encoding.

Here is my fully working demo code to start the criticism (save in PB 5.61 as UTF8 file format):

Code: Select all

;Using PureBasic 5.61 AESEncoder/AESDecoder in CBC mode for strings, with a textual password

;AES CBC 256 Bit  Encode/Decode  (Unicode ready)

Key$ = "The answer is 42 (Σ)."   ;Textual password

String$ = "This is a test for AES CBC. The quick brown fox jumps over the lazy dog (Σ)."   ;Data to be crypted
;String$ = "abcΣ"   ;test for less than 16 Bytes


;Create input buffer from unicode string (or do analogous for file input)
OriginalByteSize.q = StringByteLength(String$)
If OriginalByteSize < 16   ;minimum 16 bytes for AES CBC encoding
  *UncipheredData = AllocateMemory(16)
  FillMemory(*UncipheredData, 16, $20, #PB_Character)  ;padding with blanks
  PokeS(*UncipheredData, String$, -1, #PB_String_NoZero)
Else
  *UncipheredData = AllocateMemory(OriginalByteSize)
  PokeS(*UncipheredData, String$, -1, #PB_String_NoZero)
EndIf
;ShowMemoryViewer(*UncipheredData, MemorySize(*UncipheredData))


;Create InitializationVector with 16 Bytes (to be changed for every new string/file)
*InitVect = AllocateMemory(16)
If OpenCryptRandom() And *InitVect
  CryptRandomData(*InitVect, 16)
  CloseCryptRandom()
Else
  Debug "ERROR: Cryptography API not available"
  End
EndIf
;ShowMemoryViewer(*InitVect, MemorySize(*InitVect))


;Create CryptoKey with 32 Bytes (Unicode ready)
;Binary Hash with complexity 256^32  ( http://www.purebasic.fr/english/viewtopic.php?f=7&t=68776 )
UseSHA3Fingerprint()
KeySalted$ = Key$ + "dS8g%68D5_fg8df,5gR6:5dFg+8d5f7;6g57d6f?sD675)f7s6" ; add some salt
KeyHash$ = Fingerprint(@KeySalted$, StringByteLength(KeySalted$), #PB_Cipher_SHA3, 256)
Debug KeyHash$
;Create 32 Bytes (values) from Unicode-Hex-String
*KeyHash = AllocateMemory(32)
For i = 0 To 31 : PokeA(*KeyHash+i, Val("$"+PeekS(@KeyHash$+4*i,2,#PB_Unicode))) : Next
;ShowMemoryViewer(*KeyHash, MemorySize(*KeyHash))


;Encode AES in CBC mode
*CipheredData = AllocateMemory(MemorySize(*UncipheredData))  
If Not AESEncoder(*UncipheredData, *CipheredData, MemorySize(*UncipheredData), *KeyHash, 256, *InitVect, #PB_Cipher_CBC)
  Debug "ERROR: AESEncoder"
  End
EndIf
;ShowMemoryViewer(*CipheredData, MemorySize(*CipheredData))


;Save InitVector and size of original data to the beginning of crypted data
If CreateFile(0, "Crypted.aes")
  WriteQuad(0, OriginalByteSize)  ; Size of original data
  WriteData(0, *InitVect, 16)     ; InitVector
  WriteData(0, *CipheredData, MemorySize(*CipheredData))  ; Crypted data
  CloseFile(0)
Else
  Debug "ERROR: WriteFile"
  End
EndIf


;;;;;;;;;;; Alice --> Bob ;;;;;;;;;;;;;


;Reread crypted data
If ReadFile(0, "Crypted.aes")
  OriginalByteSize2.q = ReadQuad(0)  ; Size of original data
  *InitVect2 = AllocateMemory(16)  
  ReadData(0, *InitVect2, 16)        ; Original InitVector
  DataSize2.q = Lof(0)-8-16
  *CipheredData2 = AllocateMemory(DataSize2)  
  ReadData(0, *CipheredData2, DataSize2)  ; Crypted data
  CloseFile(0)
Else
  Debug "ERROR: ReadFile"
  End
EndIf
;Debug OriginalByteSize2
;ShowMemoryViewer(*InitVect2, MemorySize(*InitVect2))
;ShowMemoryViewer(*CipheredData2, MemorySize(*CipheredData2))

  
;Decode AES in CBC mode
*DecipheredData2 = AllocateMemory(MemorySize(*CipheredData2))  
If Not AESDecoder(*CipheredData2, *DecipheredData2, MemorySize(*CipheredData2), *KeyHash, 256, *InitVect2, #PB_Cipher_CBC)
  Debug "ERROR: AESDecoder"
  End
EndIf
Debug PeekS(*DecipheredData2, OriginalByteSize2/SizeOf(Character), #PB_Unicode)


;Don't forget to release all allocated memory
Best regards. Markus
Last edited by MarkOtt on Tue Jan 30, 2018 3:23 pm, edited 1 time in total.
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: Correct use of AES in CBC mode with a textual password

Post by walbus »

Yeah, two little things
It is better to append the IV to the end of the file
If you want to encrypt files inside yourself, it is annoying in the front

The AES Crypter does not work with file lengths of less than 16 bytes, even without CBC, which is often overlooked.

At the end of the file you can add a hash as defacto checksum.
With this you can check if a file could be decrypted successfully and without errors

You can also encrypt a small separate extender at the end of the file saparat
This way you can check if the file is a valid and encrypted file without having to read and decrypt it completely.

Normally, a crypt extender should contain :
IV as binary
Padding to blur the actual file length
The actual file length
A hash of data to check its integrity
A separately encrypted small extender to check if it is an encrypted file at all and if it can be decrypted.
The complete extender must not be identifiable or analysable as such,
it may only consist of randomly appearing bytes, without any gap
The complette file container must be self-monitoring


So the whole thing is quite complex
Small things are usually overlooked, which makes encryption vulnerable
It takes great care to avoid all conceivable weak points and to make no mistakes.

A typical misconduct is that you can encrypt a thousand different files, for example.
But one or more of them are not decipherable anymore.
Then you have to look for the mistake
It is anything but easy to find a bug in an encrypted file LOL
MarkOtt
User
User
Posts: 28
Joined: Sat Feb 11, 2017 8:33 pm
Location: Switzerland

Re: Correct use of AES in CBC mode with a textual password

Post by MarkOtt »

Example code for encrypting/decrypting strings (for files see below), using AESEncoder() and AESDecoder().

@walbus
Thanks for the additional information.

(16 Bytes padding for AES CBC encoding was already there.)

In the following code I have added:
- A hash/checksum to check the integrity of original data.
- The original data length (was already there, but is crypted now).
- Encoding of information extension (InitVector, OriginalDataLength, Checksum).
- Padding with random data to blur the real data section and extension data.

Please remember that this is not a full program, it is demo code to show the needed steps for AES CBC encoding (save in PB 5.61 as utf8 file format):

Code: Select all


;Using PureBasic 5.61 AESEncoder/AESDecoder in CBC mode for strings, with a textual password

;AES CBC 256 Bit  Encode/Decode  (Unicode ready)

Key$ = "The answer is 42 (Σ)."   ;Textual password

String$ = "This is a test for AES CBC. The quick brown fox jumps over the lazy dog (Σ)."   ;Data to be crypted
;String$ = "abcΣ"   ;test for less than 16 Bytes


;Create input buffer from Unicode string (or do analogous for file input)
OriginalByteSize.q = StringByteLength(String$)
If OriginalByteSize < 16       ;minimum 16 bytes for AES CBC encoding
  *UncipheredData = AllocateMemory(16)
  FillMemory(*UncipheredData, 16, $20, #PB_Character)  ;padding with blanks
  PokeS(*UncipheredData, String$, -1, #PB_Unicode | #PB_String_NoZero)
Else
  *UncipheredData = AllocateMemory(OriginalByteSize)
  PokeS(*UncipheredData, String$, -1, #PB_Unicode | #PB_String_NoZero)
EndIf
;ShowMemoryViewer(*UncipheredData, MemorySize(*UncipheredData))


;Create InitializationVector with 16 Bytes (to be changed for every new string/file)
*InitVect = AllocateMemory(16)
If OpenCryptRandom() And *InitVect
  CryptRandomData(*InitVect, 16)
  CloseCryptRandom()
Else
  Debug "ERROR: Cryptography API not available"
  End
EndIf
;ShowMemoryViewer(*InitVect, MemorySize(*InitVect))


;Create CryptoKey with 32 Bytes (Unicode ready)
;Binary Hash with complexity 256^32  ( http://www.purebasic.fr/english/viewtopic.php?f=7&t=68776 )
UseSHA3Fingerprint()
KeySalted$ = Key$ + "dS8g%68D5_fg8df,5gR6:5dFg+8d5f7;6g57d6f?sD675)f7s6" ; add some salt
KeyHash$ = Fingerprint(@KeySalted$, StringByteLength(KeySalted$), #PB_Cipher_SHA3, 256)
;Create 32 Bytes (values) from Unicode-Hex-String
*KeyHash = AllocateMemory(32)
For i = 0 To 31 : PokeA(*KeyHash+i, Val("$"+PeekS(@KeyHash$+4*i,2,#PB_Unicode))) : Next
;ShowMemoryViewer(*KeyHash, MemorySize(*KeyHash))


;Encode AES in CBC mode
*CipheredData = AllocateMemory(MemorySize(*UncipheredData))  
If Not AESEncoder(*UncipheredData, *CipheredData, MemorySize(*UncipheredData), *KeyHash, 256, *InitVect, #PB_Cipher_CBC)
  Debug "ERROR: AESEncoder"
  End
EndIf
;ShowMemoryViewer(*CipheredData, MemorySize(*CipheredData))


;Create extension data to transfer some information for verifying correct decryption:
; InitializationVector, OriginalDataSize, OriginalDataHash (SHA3)
*ExtData = AllocateMemory(88)
CopyMemory(*InitVect, *ExtData, 16)          ; InitializationVector (16 Bytes)
PokeQ(*ExtData+16, OriginalByteSize)         ; OriginalDataSize (8 Bytes)
CheckSum$ = StringFingerprint(String$, #PB_Cipher_SHA3, 256, #PB_Unicode)
PokeS(*ExtData+24, CheckSum$, 64, #PB_Ascii | #PB_String_NoZero)   ; OriginalDataHash (64 Bytes in Ascii)
;Crypt the extension data
*CryptedExtData = AllocateMemory(88)
AESEncoder(*ExtData, *CryptedExtData, MemorySize(*ExtData), ?KeyForExt, 256, ?IvectForExt, #PB_Cipher_CBC)
DataSection
  KeyForExt:
  Data.b $09 , $a9 , $20 , $40 , $35 , $b8 , $a1 , $5b , $52 , $2e , $03 ,$d5 , $34 , $11 , $00 , $08
  Data.b $11 , $b8 , $31 , $61 , $26 , $c3 , $32 , $64 , $d9 , $f3 , $01 ,$a4 , $27 , $61 , $56 , $29
  IvectForExt:
  Data.b $3d , $ae , $ba , $43 , $9d , $9e , $b5 , $30 , $b4 , $23 , $da ,$80 , $2d , $9f , $ac , $45
EndDataSection
;ShowMemoryViewer(*CryptedExtData, MemorySize(*CryptedExtData))


;Create some random data to help blurring the real data section
*BlurData = AllocateMemory(128)
If OpenCryptRandom() And *BlurData
  CryptRandomData(*BlurData, 128)
  CloseCryptRandom()
Else
  Debug "ERROR: Cryptography API not available"
  End
EndIf
;ShowMemoryViewer(*BlurData, MemorySize(*BlurData))


;Save crypted data to a file (including extension data and random data for blurring real data size)
If CreateFile(0, "Crypted.aes")
  WriteData(0, *BlurData, 16)        ; some random data to blur the real data section
  WriteData(0, *CipheredData, MemorySize(*CipheredData))  ; Crypted original data
  WriteData(0, *BlurData+16, 16)     ; some random data to blur the real data section
  WriteData(0, *CryptedExtData, 88)  ; Crypted extensio data; InitVect, OriginalDataSize, OriginalDataHash
  WriteData(0, *BlurData+32, 64)     ; some random data to blur the real data section
  CloseFile(0)
Else
  Debug "ERROR: WriteFile"
  End
EndIf


;;;;;;;;;;; Alice --> Bob ;;;;;;;;;;;;;


;Reread crypted data
If ReadFile(0, "Crypted.aes")
  *CryptedExtData2 = AllocateMemory(88)
  FileSeek(0, Lof(0)-64-88)           ; End - BlurBytes - ExtensionData
  ReadData(0, *CryptedExtData2, 88)   ; Crypted extension data; InitVect, OriginalDataSize, OriginalDataHash
  *ExtData2 = AllocateMemory(88)      ; Decrypted extension data
  AESDecoder(*CryptedExtData2, *ExtData2, MemorySize(*CryptedExtData2), ?KeyForExt, 256, ?IvectForExt, #PB_Cipher_CBC)
  *InitVect2 = AllocateMemory(16)  
  CopyMemory(*ExtData2, *InitVect2, 16)                    ; Original InitVector
  OriginalByteSize2.q = PeekQ(*ExtData2+16)                ; Original data size
  OriginalDataHash2.s = PeekS(*ExtData2+24, 64, #PB_Ascii) ; Original data hash (CheckSum)
  FileSeek(0, 16)                     ; Beginning + BlurBytes
  CipheredDataSize2.q = OriginalByteSize2  :  If CipheredDataSize2 < 16  : CipheredDataSize2 = 16  :  EndIf
  *CipheredData2 = AllocateMemory(CipheredDataSize2)  
  ReadData(0, *CipheredData2, CipheredDataSize2)          ; Crypted data
  CloseFile(0)
Else
  Debug "ERROR: ReadFile"
  End
EndIf
;Debug OriginalByteSize2
;ShowMemoryViewer(*InitVect2, MemorySize(*InitVect2))
;ShowMemoryViewer(*CipheredData2, MemorySize(*CipheredData2))

  
;Decode AES in CBC mode
*DecipheredData2 = AllocateMemory(MemorySize(*CipheredData2))  
If Not AESDecoder(*CipheredData2, *DecipheredData2, MemorySize(*CipheredData2), *KeyHash, 256, *InitVect2, #PB_Cipher_CBC)
  Debug "ERROR: AESDecoder"
  End
EndIf
OriginalString$ = PeekS(*DecipheredData2, OriginalByteSize2/SizeOf(Character), #PB_Unicode)
Debug OriginalString$
Debug "Current hash: " + StringFingerprint(OriginalString$, #PB_Cipher_SHA3, 256, #PB_Unicode)
Debug "Original hash: " + OriginalDataHash2


;Don't forget to release all allocated memory
Edited: replaced checksum calculation CRC32 with SHA3
Last edited by MarkOtt on Tue Jan 30, 2018 3:25 pm, edited 3 times in total.
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: Correct use of AES in CBC mode with a textual password

Post by walbus »

Yeah, looks good so far
But the devil is mostly in the details
If the IV in the extender is corrupted, you get a wrong file length back, it is huge, positive or negative, because decrypting fails.
You must ensure that a damaged extender is detected
If the determined file length is negative or larger than the whole container, you have to cut off in padding.
An error status is then reported accordingly
Whenever decryption is still possible, decryption must be used.
CRC32 is not a good idea, it doesn't belong in a code like this
As far as the whole container has to monitor itself, it must not crash.

What do you do if an encrypted file is larger than the available free Ram :wink:
MarkOtt
User
User
Posts: 28
Joined: Sat Feb 11, 2017 8:33 pm
Location: Switzerland

Re: Correct use of AES in CBC mode with a textual password

Post by MarkOtt »

@walbus
Thank you very much for commenting critically on the code.
These are good points for improvement.
CRC32 is not a good idea, it doesn't belong in a code like this
I have choosen CRC32 deliberately because of its speed and because it is only used to detect randomly corrupted data, not for something security related. Seen from this perspective, what would you choose instead?
What do you do if an encrypted file is larger than the available free Ram
This example is for encoding strings, what should not create memory shortage on actual systems; and it can be controlled by verifying the memory allocations.
But I have planned to make a second example for (large) files, using StartAESCipher() with AddCipherBuffer() to get some kind of file-to-file stream.
(As soon as I have some more time, today the holidays are over and I have to go back to work.)

Best regards. Markus
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: Correct use of AES in CBC mode with a textual password

Post by walbus »

Now CRC32 is no cryptographic hash function and has only a variance of 4 bytes.
Whether this works or not, just replace it with SHA3-256

There is a very nice and fast SHA3 module from Wilbert, this is always the first choice.

I always use the block method, the block size is often 16384 and is variable depending on the file size.
I do not use AddCypher
I always use the AES crypter to create a new IV from the original IV, for each block a new one.
This makes it very easy to replace or add the AES crypter with another block based crypter.
You can also program CBC mode yourself, you can find an example here
http://www.purebasic.fr/english/viewtop ... 12&t=69709

CBC mode is also not always the best, there are several methods which do not have the weak points of CBC mode.
But this goes beyond the scope here and it becomes increasingly complicated and difficult to understand

You have to try coding a real tool later on
It's very difficult to get the bug-free version.
The worst of these are errors or omissions that make encryption vulnerable.
Negligence in encryption software for the community is a sin
It's snake oil.
You have to weigh everything possible and try not to build in any mistakes.
Unfortunately, most of the time you are not always pointed out by others to errors, which can have different reasons.
MarkOtt
User
User
Posts: 28
Joined: Sat Feb 11, 2017 8:33 pm
Location: Switzerland

Re: Correct use of AES in CBC mode with a textual password

Post by MarkOtt »

@walbus

Changed CRC32 to SHA3 in the last demo code.
(I will try to include your other suggestions as soon as possible.)

As mentioned above, this topic covers the use of AES encoding closely as provided in PureBasic 5.61.
(This makes sense as I am not able to verify the correctness of other extended solutions.)

But I know that you have some very good other solutions. It might be very helpful if you could give an appropriate overview by listing the important links here ;-)
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: Correct use of AES in CBC mode with a textual password

Post by walbus »

Yep, but look, try understanding exactely what CBC mode does, and how it works
There are many other possibilities 8)
MarkOtt
User
User
Posts: 28
Joined: Sat Feb 11, 2017 8:33 pm
Location: Switzerland

Re: Correct use of AES in CBC mode with a textual password

Post by MarkOtt »

Here is my example code for encrypting/decrypting files (instead of strings), using StartAESCipher() and AddCipherBuffer().

The textual password (unicode) will be converted to binary hash with 32 Bytes for full complexity 256^32.
The InitializationVector with 16 Bytes will be changed for every new file.
The InitializationVector will be included in the ciphered data to be transfered to the recipient.
Data below 16 Bytes will be padded to 16 Bytes to be compatible with AES CBC encoding.

Save the code in PB 5.61 as UTF8 file format:

Code: Select all

;Using PureBasic 5.61 AESEncoder/AESDecoder in CBC stream mode for file encryption, with a textual password

;AES CBC 256 Bit  Encode/Decode  (Unicode ready)

Key$ = "The answer is 42 (Σ)."   ;Textual password


;Get original file to encrypt
FileOriginal$ = OpenFileRequester("Select file to encrypt",GetCurrentDirectory(),"All files (*.*)|*.*",0)
If FileOriginal$ = ""
  Debug "ERROR: No input file selected"
  End
EndIf
;New encrypted file name
FileCrypted$ = FileOriginal$ + ".aes"
;New decrypted file name
FileDecrypted$ = GetPathPart(FileOriginal$) + GetFilePart(FileOriginal$,#PB_FileSystem_NoExtension) +
                 "-decryped" + "." + GetExtensionPart(FileOriginal$)


;Create InitializationVector with 16 Bytes (to be changed for every new string/file)
*InitVect = AllocateMemory(16)
If OpenCryptRandom() And *InitVect
  CryptRandomData(*InitVect, 16)
  CloseCryptRandom()
Else
  Debug "ERROR: Cryptography API not available"
  End
EndIf
;ShowMemoryViewer(*InitVect, MemorySize(*InitVect))


;Create CryptoKey with 32 Bytes (Unicode ready)
;Binary Hash with complexity 256^32  ( http://www.purebasic.fr/english/viewtopic.php?f=7&t=68776 )
UseSHA3Fingerprint()
KeySalted$ = Key$ + "dS8g%68D5_fg8df,5gR6:5dFg+8d5f7;6g57d6f?sD675)f7s6" ; add some salt
KeyHash$ = Fingerprint(@KeySalted$, StringByteLength(KeySalted$), #PB_Cipher_SHA3, 256)
;Create 32 Bytes (values) from Unicode-Hex-String
*KeyHash = AllocateMemory(32)
For i = 0 To 31 : PokeA(*KeyHash+i, Val("$"+PeekS(@KeyHash$+4*i,2,#PB_Unicode))) : Next
;ShowMemoryViewer(*KeyHash, MemorySize(*KeyHash))


;Create some random data to help blurring the real data section
*BlurData = AllocateMemory(128)
If OpenCryptRandom() And *BlurData
  CryptRandomData(*BlurData, 128)
  CloseCryptRandom()
Else
  Debug "ERROR: Cryptography API not available"
  End
EndIf
;ShowMemoryViewer(*BlurData, MemorySize(*BlurData))


;Create extension data to transfer some information for verifying correct decryption:
; InitializationVector, OriginalFileSize, OriginalFileHash (SHA3)
*ExtData = AllocateMemory(88)
CopyMemory(*InitVect, *ExtData, 16)          ; InitializationVector (16 Bytes)
PokeQ(*ExtData+16, FileSize(FileOriginal$))     ; OriginalFileSize (8 Bytes)
CheckSum$ = FileFingerprint(FileOriginal$, #PB_Cipher_SHA3, 256)
PokeS(*ExtData+24, CheckSum$, 64, #PB_Ascii | #PB_String_NoZero)   ; OriginalDataHash (64 Bytes Ascii)
;Crypt the extension data
*CryptedExtData = AllocateMemory(88)
AESEncoder(*ExtData, *CryptedExtData, MemorySize(*ExtData), ?KeyForExt, 256, ?IvectForExt, #PB_Cipher_CBC)
DataSection
  KeyForExt:
  Data.b $09 , $a9 , $20 , $40 , $35 , $b8 , $a1 , $5b , $52 , $2e , $03 ,$d5 , $34 , $11 , $00 , $08
  Data.b $11 , $b8 , $31 , $61 , $26 , $c3 , $32 , $64 , $d9 , $f3 , $01 ,$a4 , $27 , $61 , $56 , $29
  IvectForExt:
  Data.b $3d , $ae , $ba , $43 , $9d , $9e , $b5 , $30 , $b4 , $23 , $da ,$80 , $2d , $9f , $ac , $45
EndDataSection
;ShowMemoryViewer(*CryptedExtData, MemorySize(*CryptedExtData))


;Encode file with AES in CBC mode (including extension data and random data for blurring real data size)
#FileBlockSize = 4096   ; min. 16 Bytes for AES CBC cipher  (4096 is NTFS block size)
FileOriginalNumber = ReadFile(#PB_Any, FileOriginal$)  ; file to encrypt
FileCryptedNumber = CreateFile(#PB_Any, FileCrypted$)  ; encrypted file (new)
*UncipheredData = AllocateMemory(#FileBlockSize)
*CipheredData = AllocateMemory(#FileBlockSize)
If FileOriginalNumber And FileCryptedNumber
  WriteData(FileCryptedNumber, *BlurData, 16)  ; some random data to blur the real data section
  ;Begin file encryption
  CipherNumber = StartAESCipher(#PB_Any, *KeyHash, 256, *InitVect, #PB_Cipher_Encode | #PB_Cipher_CBC)
  While Not Eof(FileOriginalNumber)
    FileReadBytes = ReadData(FileOriginalNumber, *UncipheredData, #FileBlockSize)
    If FileReadBytes > 0
      If FileReadBytes < 16 : FileReadBytes = 16 : EndIf  ; min. 16 Bytes for AES CBC cipher
      AddCipherBuffer(CipherNumber, *UncipheredData, *CipheredData, FileReadBytes)
      WriteData(FileCryptedNumber, *CipheredData, FileReadBytes)
    EndIf
  Wend
  FinishCipher(CipherNumber)
  ;End file encryption
  WriteData(FileCryptedNumber, *BlurData+16, 16)     ; some random data to blur the real data section
  WriteData(FileCryptedNumber, *CryptedExtData, 88)  ; crypted extensio data; InitVect, OriginalDataSize, OriginalDataHash
  WriteData(FileCryptedNumber, *BlurData+32, 64)     ; some random data to blur the real data section
  CloseFile(FileOriginalNumber)
  CloseFile(FileCryptedNumber)
Else
  Debug "ERROR: File could not be opened or created"
  End
EndIf

Debug "File encrypted, original hash: " + CheckSum$


;;;;;;;;;;; Alice --> Bob ;;;;;;;;;;;;;


;Decode encrypted file with AES in CBC mode
FileCryptedNumber = ReadFile(#PB_Any, FileCrypted$)        ; encrypted file
FileDecryptedNumber = CreateFile(#PB_Any, FileDecrypted$)  ; decrypted file (new)
*CipheredData = AllocateMemory(4096)
*UncipheredData = AllocateMemory(4096)
If FileCryptedNumber And FileDecryptedNumber
  *CryptedExtData2 = AllocateMemory(88)
  FileSeek(FileCryptedNumber, Lof(FileCryptedNumber)-64-88)  ; End - BlurBytes - ExtensionData
  ReadData(FileCryptedNumber, *CryptedExtData2, 88)  ; Crypted extension data; InitVect, OriginalDataSize, OriginalDataHash
  *ExtData2 = AllocateMemory(88)                     ; Decrypted extension data
  AESDecoder(*CryptedExtData2, *ExtData2, MemorySize(*CryptedExtData2), ?KeyForExt, 256, ?IvectForExt, #PB_Cipher_CBC)
  *InitVect2 = AllocateMemory(16)  
  CopyMemory(*ExtData2, *InitVect2, 16)                      ; Original InitVector
  OriginalByteSize2.q = PeekQ(*ExtData2+16)                  ; Original file size
  OriginalDataHash2.s = PeekS(*ExtData2+24, 64, #PB_Ascii)   ; Original file hash (CheckSum)
  FileSeek(FileCryptedNumber, 16)                            ; Beginning + BlurBytes
  ;Begin file decryption
  CipherNumber = StartAESCipher(#PB_Any, *KeyHash, 256, *InitVect2, #PB_Cipher_Decode | #PB_Cipher_CBC)
  If OriginalByteSize2 < 16  ; min. 16 Bytes for AES CBC cipher
    ReadData(FileCryptedNumber, *CipheredData, 16)
    AddCipherBuffer(CipherNumber, *CipheredData, *UncipheredData, 16)
    WriteData(FileDecryptedNumber, *UncipheredData, OriginalByteSize2)
  Else                       ; file size is greater equal 16 bytes
    While TotalBytesRead < OriginalByteSize2
      If OriginalByteSize2 - TotalBytesRead > #FileBlockSize   ; full block available
        BytesToRead = #FileBlockSize
      Else                           ; remaining bytes, less than one block available
        BytesToRead = OriginalByteSize2 - TotalBytesRead
      EndIf
      FileReadBytes = ReadData(FileCryptedNumber, *CipheredData, BytesToRead)
      TotalBytesRead + FileReadBytes
      If FileReadBytes > 0
        AddCipherBuffer(CipherNumber, *CipheredData, *UncipheredData, FileReadBytes)
        WriteData(FileDecryptedNumber, *UncipheredData, FileReadBytes)
      EndIf
    Wend
  EndIf
  FinishCipher(CipherNumber)
  ;End file decryption
  CloseFile(FileDecryptedNumber)
  CloseFile(FileCryptedNumber)
Else
  Debug "ERROR: File could not be opened or created"
  End
EndIf

Debug "File decrypted, current hash: " + FileFingerprint(FileDecrypted$, #PB_Cipher_SHA3, 256)


;Don't forget to release all allocated memory
Best regards. Markus
Post Reply