# _____ ___ ____ ____ _____ ____ ____ _____ _____ # |_ _/ _ \| _ \ / ___|| ____/ ___| _ \| ____|_ _| # | || | | | |_) | \___ \| _|| | | |_) | _| | | # | || |_| | __/ ___) | |__| |___| _ <| |___ | | # |_| \___/|_| |____/|_____\____|_| \_\_____| |_| # OFFENSIVEencfile.nim # Modified version of the original "encfile.nim". # This fork will be only supports encryption and not decryption. # However, it encodes all information necessary to decrypt using "encfile.nim" # It has no error handling. Good luck. In case of problem, check your password length. # - Eline. # Decryption HAS been tested as of 28 November 2023 (initial release). # TODO: use sysargs to encrypt files :p # i.e ./offensiveencfile input output password # or maybe only get password interactively. hmm... import nimcrypto import std/sysrand import std/streams const # default encryption/decryption buffer size - 64KB bufferSizeDef = 64 * 1024 # maximum password length (number of chars) # AES block size in bytes AESBlockSize = 16 # password stretching function proc stretch(passw: string, iv1: array[16, byte]): array[32, byte] = var digest: array[32, byte] copyMem(addr digest[0], unsafeAddr iv1[0], len(iv1)) var passwBytes: array[1024, byte] copyMem(addr passwBytes[0], unsafeAddr passw[0], len(passw)) for i in 1 .. 8192: var sha: sha512_256 sha.init() sha.update(digest) sha.update(passwBytes[0..len(passw)-1]) digest = sha.finish().data return digest # encrypt binary stream function # arguments: # fIn: input binary stream # fOut: output binary stream # passw: encryption password # bufferSize: encryption buffer size, must be a multiple of # AES block size (16) # using a larger buffer speeds up things when dealing # with long streams proc encryptStream*(fIn: Stream, fOut: Stream, passw: string, bufferSize: int) = # validate bufferSize if bufferSize mod AESBlockSize != 0: raise newException(OSError, "Buffer size must be a multiple of AES block size.") # generate external iv (used to encrypt the main iv and the # encryption key) # let iv1 = urandom(AESBlockSize) let initIv1 = urandom(AESBlockSize) var iv1: array[16, byte] for i in [0..15]: iv1[i]=initIv1[i] # stretch password and iv let key = stretch(passw, iv1) # generate random main iv var iv0 = urandom(AESBlockSize) # generate random internal key var intKey = urandom(32) # instantiate AES cipher var encryptor0: CBC[aes256] encryptor0.init(intKey, iv0) # instantiate HMAC-SHA256 for the ciphertext var hmac0: HMAC[sha512_256] hmac0.init(intKey) # instantiate another AES cipher var encryptor1: CBC[aes256] encryptor1.init(key, iv1) # encrypt main iv and key var plainText = newString(len(iv0)+len(intKey)) var c_iv_key = newString(len(iv0)+len(intKey)) copyMem(addr plainText[0], unsafeAddr iv0[0], len(iv0)) copyMem(addr plainText[0+len(iv0)], unsafeAddr intKey[0], len(intKey)) encryptor1.encrypt(plainText, c_iv_key) # calculate HMAC-SHA256 of the encrypted iv and key var hmac1: HMAC[sha512_256] hmac1.init(key) hmac1.update(c_iv_key) # write header fOut.write("AES") # write version (AES Crypt version 2 file format - # see https://www.aescrypt.com/aes_file_format.html) fOut.write([byte 2]) # reserved byte (set to zero) fOut.write([byte 0]) # setup "CREATED-BY" extension var cby = "Confidential " # write "CREATED-BY" extension length fOut.write([byte 0, cast[uint8](1+len("CREATED_BY")+len(cby))]) # write "CREATED-BY" extension fOut.write("CREATED_BY") fOut.write([byte 0]) fOut.write(cby) # write "container" extension length fOut.write([byte 0, 128]) # write "container" extension for i in 1 .. 128: fOut.write([byte 0]) # write end-of-extensions tag fOut.write([byte 0, 0]) # write the iv used to encrypt the main iv and the # encryption key fOut.write(iv1) # write encrypted main iv and key fOut.write(c_iv_key) # write HMAC-SHA256 of the encrypted iv and key fOut.write(hmac1.finish()) var fs16 = 0 # encrypt file while reading it var fdata = newString(bufferSize) var cText = newString(bufferSize) while true: # try to read bufferSize bytes let bytesRead = fIn.readData(addr fdata[0], bufferSize) # check if EOF was reached if bytesRead < bufferSize: # file size mod 16, lsb positions fs16 = bytesRead mod AESBlockSize # pad data (this is NOT PKCS#7!) # ...unless no bytes or a multiple of a block size # of bytes was read var padLen: int if bytesRead mod AESBlockSize == 0: padLen = 0 else: padLen = 16 - bytesRead mod AESBlockSize # todo handl the pading to get the nb AES block & file with padLen, restrict the input of encrypt to x block # fdata += bytes([padLen])*padLen for i in bytesRead..bytesRead+padLen: fdata[i]=cast[char](padLen) # encrypt data encryptor0.encrypt(fdata[0..bytesRead+padLen-1], cText) # update HMAC hmac0.update(cText[0..bytesRead+padLen-1]) # write encrypted file content fOut.write(cText[0..bytesRead+padLen-1]) break # ...otherwise a full bufferSize was read else: # encrypt data encryptor0.encrypt(fdata, cText) # update HMAC hmac0.update(cText) # write encrypted file content fOut.write(cText) # write plaintext file size mod 16 lsb positions fOut.write(cast[uint8](fs16)) # write HMAC-SHA256 of the encrypted file fOut.write(hmac0.finish()) # encrypt file function # arguments: # infile: plaintext file path # outfile: ciphertext file path # passw: encryption password # bufferSize: optional buffer size, must be a multiple of # AES block size (16) # using a larger buffer speeds up things when dealing # with big files # Default is 64KB. proc encryptFile*(infile: string, outfile: string, passw: string, bufferSize: int = bufferSizeDef) = try: let fIn = newFileStream(infile, mode = fmRead) defer: fIn.close() let fOut = newFileStream(outfile, mode = fmWrite) defer: fOut.close() encryptStream(fIn, fOut, passw, bufferSize) except CatchableError: let e = getCurrentException() msg = getCurrentExceptionMsg() echo "Inside checkIn, got exception ", repr(e), " with message ", msg #encryptFile("dza.png", "file.aes", "long-and-random-password", 1024) #decryptFile("file.aes", "fileDecrypt.png", "long-and-random-password", 1024)