AES streaming file encoder/decoder
Posted: Mon Aug 02, 2010 6:20 pm
There are a few wrinkles associated with streaming AES for files, and I don't think there's a sample on the forums. So here's one, it's tested successfully on files of all types from 5 bytes on up to 1gb. CRC32 File Fingerprint of the decrypted file matches that of the original in all cases. The gui isn't bulletproof, you'd want to add an Abort button to the window and put appropriate code in the threads for a graceful exit, but it shows the method:
A couple of things to keep in mind:
1) For 256bit encoding, your key and initialization vector should be 32 bytes in length.
2) For 256bit encoding, you should choose a chunk size which is a multiple of 2 and at least 32 bytes.
3) All your streamed buffers must be of equal size, including the last one. This goes for both Encode and Decode.
4) When you decode a file, you need to read all buffers in at the full chunk size. However, you will only want to write the last buffer out to the target file at its original (usually less-than-chunksize) size. Otherwise your target file will not be the same as the original file you encrypted. How do you know what this size was? What I did was write it out to the end of the target file at the encryption stage, and then read it in again at the decryption stage. This way you can AddCipherBuffer at the full chunksize for the decrypt but only write out those decoded bytes that matter. The remainder of the last buffer can safely be discarded.
Code: Select all
; Demo: Streaming AES file encode/decode
; netmaestro August 2010
; Purebasic 4.51
Declare Encode(void)
Declare Decode(void)
OpenWindow(0,0,0,200,120,"Cryptor!",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
ButtonGadget(0, 50,20,100,20,"Encrypt a file")
ButtonGadget(1, 50,50,100,20,"Decrypt a file")
CreateStatusBar(0, WindowID(0))
AddStatusBarField(200)
StatusBarProgress(0,0,0,#PB_ProgressBar_Smooth,0,100)
Repeat
EventID = WaitWindowEvent()
Select EventID
Case #PB_Event_Gadget
Select EventGadget()
Case 0
CreateThread(@Encode(), 0)
Case 1
CreateThread(@Decode(), 0)
EndSelect
EndSelect
Until EventID = #PB_Event_CloseWindow
End
Procedure Encode(void)
chunksize = 4096
rawfilename$ = OpenFileRequester("Please choose a file to encrypt:","","",0)
encfilename$ = rawfilename$+".enc"
If OpenFile(0, rawfilename$)
If CreateFile(1, encfilename$)
DisableGadget(0,1)
DisableGadget(1,1)
length.q = Lof(0)
numparts = length/chunksize
lastchunksize = length%chunksize
If lastchunksize
numparts+1
lastchunk = numparts
Else
lastchunk = 0
EndIf
*raw = AllocateMemory(chunksize)
*secure = AllocateMemory(chunksize)
StartAESCipher(0, ?key, 256, ?iVector, #PB_Cipher_CBC|#PB_Cipher_Encode)
For i=1 To numparts
If i=lastchunk
FillMemory(*raw, chunksize, 0, #PB_Byte)
ReadData(0, *raw, lastchunksize)
Else
ReadData(0, *raw, chunksize)
EndIf
AddCipherBuffer(0, *raw, *secure, chunksize)
WriteData(1, *secure, chunksize)
prog.d = i/numparts*100
StatusBarProgress(0,0, Int(prog))
Delay(1)
Next
If lastchunksize
WriteLong(1, lastchunksize)
Else
WriteLong(1, chunksize)
EndIf
FinishCipher(0)
CloseFile(0)
CloseFile(1)
FreeMemory(*raw)
FreeMemory(*secure)
MessageRequester("Finished", "Encrypted file "+encfilename$+" saved",#MB_ICONINFORMATION)
StatusBarProgress(0,0,0)
DisableGadget(0,0)
DisableGadget(1,0)
EndIf
EndIf
EndProcedure
Procedure Decode(void)
chunksize = 4096
encfilename$ = OpenFileRequester("Please choose a file to decrypt:","","",0)
rawfilename$ = SaveFileRequester("Please choose a new name for the decrypted file","","",0)
If OpenFile(0, encfilename$)
FileSeek(0, Lof(0)-4)
lastchunksize = ReadLong(0)
FileSeek(0, 0)
If CreateFile(1, rawfilename$)
DisableGadget(0,1)
DisableGadget(1,1)
length.q = Lof(0)
numparts = length/chunksize
*raw = AllocateMemory(chunksize)
*secure = AllocateMemory(chunksize)
StartAESCipher(0, ?key, 256, ?iVector, #PB_Cipher_CBC|#PB_Cipher_Decode)
For i=1 To numparts
ReadData(0, *secure, chunksize)
AddCipherBuffer(0, *secure, *raw, chunksize)
If i=numparts
WriteData(1, *raw, lastchunksize)
Else
WriteData(1, *raw, chunksize)
EndIf
prog.d = i/numparts*100
StatusBarProgress(0,0, Int(prog))
Delay(1)
Next
FinishCipher(0)
CloseFile(0)
CloseFile(1)
FreeMemory(*raw)
FreeMemory(*secure)
MessageRequester("Finished", "Decrypted file "+rawfilename$+" saved",#MB_ICONINFORMATION)
StatusBarProgress(0,0,0)
DisableGadget(0,0)
DisableGadget(1,0)
EndIf
EndIf
EndProcedure
DataSection
key:
Data.b $8C,$15,$51,$2C,$0C,$8A,$0A,$D8,$07,$E4,$21,$A2,$8E,$83,$A3,$88,$8A,$CA,$FB,$E1
Data.b $7B,$A3,$6B,$D6,$BC,$F7,$E6,$CD,$FE,$B5,$D7,$B3
iVector:
Data.b $08,$0C,$96,$48,$33,$51,$35,$80,$0C,$A9,$42,$1E,$11,$E0,$83,$C7,$C4,$C6,$E1,$E4
Data.b $2E,$40,$81,$0A,$24,$70,$00,$10,$08,$B3,$64,$21
EndDataSection
1) For 256bit encoding, your key and initialization vector should be 32 bytes in length.
2) For 256bit encoding, you should choose a chunk size which is a multiple of 2 and at least 32 bytes.
3) All your streamed buffers must be of equal size, including the last one. This goes for both Encode and Decode.
4) When you decode a file, you need to read all buffers in at the full chunk size. However, you will only want to write the last buffer out to the target file at its original (usually less-than-chunksize) size. Otherwise your target file will not be the same as the original file you encrypted. How do you know what this size was? What I did was write it out to the end of the target file at the encryption stage, and then read it in again at the decryption stage. This way you can AddCipherBuffer at the full chunksize for the decrypt but only write out those decoded bytes that matter. The remainder of the last buffer can safely be discarded.