I reviewed the AES FIPS-197 document and I like it! The data manipulation involved is convoluted and complex, so it is difficult to achieve it in an efficient way, even in assembler language. In the case of Batch files, we can use some text substitution tricks in order to compress more operations into FOR commands, so they run faster. However, most of the modifications I propose can not be easily included in the existent code unless it be extensively modified, so I decided to write a complete working program that include all modifications, at least in a preliminary version. In the case of complex data manipulations it is easier to review working code instead of read descriptions about it. I only developed the encrypt part in the program; I will complete the decrypt part later.
Some of my modifications can be directly included in the existing code, like the generation of factor tables, so the program will drastically reduce its size and run a little bit faster. I tried to completely eliminate these constants and insert the equivalent operation in the SET /A commands, but I can't find such equivalent operation. The AES documentation specify that is "modulo 0x11B", but this doesn't works. @Magialisk: how did you calculated the G2, G3, etc. constants? Why you don't use the original operation instead of the precomputed constants?
Nov-18-2013: I modified the code below in these points:
- Generate Galois constants via a formula instead of converting pre-defined values (strings).
- Achieve the BinToHex conversion via Batch code (instead of JScript).
- Added a multi-process encryption method.
Code: Select all
@echo off
setlocal DisableDelayedExpansion
set "bang=!"
setlocal EnableDelayedExpansion
rem Multi-process dispatcher:
set "param=%~1"
if "!param:~0,1!" equ ":" (
shift
goto !param:~1!
)
rem Implementation of AES FIPS-197 cryptographic algorithm, multi-process encryption version
rem http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
rem Antonio Perez Ayala
if "%~2" neq "" goto begin
echo AES.BAT file.ext hexKey [pidNum]
echo/
echo file.ext Name of file to encrypt/decrypt:
echo - Not .aes extension: encrypt any file, produce file.ext.aes
echo - .aes extension: decrypt an .aes file, produce original file.ext
echo hexKey String of 32, 48 or 64 hexadecimal digits, the same used in encrypt
echo must be used for decrypt.
echo pidNum Number of parallel encryption processes, defaults to CPU cores - 1.
goto :EOF
:begin
rem To trace intermediate results, delete the next line
set TRACE=rem
if not exist "%~1" echo File not found & goto :EOF
set "fileName=%~N1"
set fileExt=%~X1
set fileSize=%~Z1
set "key=%~2"
for %%a in (64 48 32) do (
if "!key:~%%a,1!" equ "" (
set /A b=%%a-1
for %%b in (!b!) do if "!key:~%%b,1!" neq "" set keyLen=%%a& goto setParams
)
)
echo Bad key lenght & goto :EOF
:setParams
set /A Nb=4, blockLen=8*Nb-2, Nk=keyLen/8, keyLen-=2, Nr=Nk+6, N=NUMBER_OF_PROCESSORS-1
if "%~3" neq "" set N=%~3
if %N% lss 1 set N=1
rem Define a decimal-to-hexadecimal conversion table for one byte
set i=0
for %%a in (0 1 2 3 4 5 6 7 8 9 a b c d e f) do (
for %%b in (0 1 2 3 4 5 6 7 8 9 a b c d e f) do (
set "H[!i!]=%%a%%b"
set /A i+=1
)
)
rem Byte variables are individual. 32-bits variables have 4 separated byte parts named: var0, var1, var2, var3
rem Define pre-computed round constants for key scheduler algorithm
set i=1
for %%a in (01 02 04 08 10 20 40 80 1b 36) do set /A "Rcon[!i!]=0x%%a, i+=1"
rem Define pre-computed Rijndael S-box values for SubBytes() function and key scheduler algorithm
set i=0
for %%s in ("637c777bf26b6fc53001672bfed7ab76"
"ca82c97dfa5947f0add4a2af9ca472c0"
"b7fd9326363ff7cc34a5e5f171d83115"
"04c723c31896059a071280e2eb27b275"
"09832c1a1b6e5aa0523bd6b329e32f84"
"53d100ed20fcb15b6acbbe394a4c58cf"
"d0efaafb434d338545f9027f503c9fa8"
"51a3408f929d38f5bcb6da2110fff3d2"
"cd0c13ec5f974417c4a77e3d645d1973"
"60814fdc222a908846eeb814de5e0bdb"
"e0323a0a4906245cc2d3ac629195e479"
"e7c8376d8dd54ea96c56f4ea657aae08"
"ba78252e1ca6b4c6e8dd741f4bbd8b8a"
"703eb5664803f60e613557b986c11d9e"
"e1f8981169d98e949b1e87e9ce5528df"
"8ca1890dbfe6426841992d0fb054bb16"
) do (
set "s=%%~s"
for /L %%j in (0,2,%blockLen%) do set /A "s[!i!]=0x!s:~%%j,2!, i+=1"
)
rem AES: KeyExpansion(byte key[4*Nk], word w[Nb*(Nr+1)], Nk)
:KeyExpansion
rem AES: begin
rem Split the key string in bytes
set i=0
for /L %%j in (0,2,%keyLen%) do set /A "key[!i!]=0x!key:~%%j,2!, i+=1"
rem AES: word temp
rem AES: i = 0
rem AES: while (i < Nk)
rem AES: w[i] = word(key[4*i], key[4*i+1], key[4*i+2], key[4*i+3])
rem AES: i = i+1
rem AES: end while
set /A NkM1=Nk-1, j=0
for /L %%i in (0,1,%NkM1%) do (
for %%b in (0 1 2 3) do for %%j in (!j!) do set /A "w%%b[%%i]=!key[%%j]!, j+=1"
)
if %Nk% gtr 6 (
rem Define the "else if" macro for this case that is used below
set "else if Nk gtr 6 and iModNk equ 4 set temp to SubWord[temp]=) else if !bang!iModNk!bang! equ 4 ( for %%b in (0 1 2 3) do set
/A temp%%b=s[!bang!temp%%b!bang!]"
)
rem AES: i = Nk
rem AES: while (i < Nb * (Nr+1)]
rem AES: temp = w[i-1]
rem AES: if (i mod Nk = 0)
rem AES: temp = SubWord(RotWord(temp)) xor Rcon[i/Nk]
rem AES: else if (Nk > 6 and i mod Nk = 4)
rem AES: temp = SubWord(temp)
rem AES: end if
rem AES: w[i] = w[i-Nk] xor temp
rem AES: i = i + 1
rem AES: end while
set /A wLen=Nb*(Nr+1)-1
for /L %%i in (%Nk%,1,%wLen%) do (
set /A "iM1=%%i-1, iModNk=%%i%%Nk, iDivNk=%%i/Nk, iMNk=%%i-Nk"
for /F "tokens=1-3" %%j in ("!iM1! !iDivNk! !iMNk!") do (
rem j=i-1, k=i/Nk, l=i-Nk
rem temp = w[i-1]:
for %%b in (0 1 2 3) do set "temp%%b=!w%%b[%%j]!"
if !iModNk! equ 0 (
rem RotWord(temp^):
for /F "tokens=1,2,3,4" %%a in ("!temp1! !temp2! !temp3! !temp0!") do (
rem temp = SubWord(...^) xor Rcon[i/Nk]:
set /A "temp0=s[%%a] ^ Rcon[%%k], temp1=s[%%b], temp2=s[%%c], temp3=s[%%d]"
)
%else if Nk gtr 6 and iModNk equ 4 set temp to SubWord[temp]%
)
rem w[i] = w[i-Nk] xor temp:
for %%b in (0 1 2 3) do set /A "w%%b[%%i]=w%%b[%%l] ^ temp%%b"
)
)
rem AES: end KeyExpansion
rem This section show the key expansion
%TRACE% for /L %%i in (0,1,%wLen%) do (
%TRACE% set "line=%%i- "
%TRACE% for %%b in (0 1 2 3) do for %%h in (!w%%b[%%i]!) do set "line=!line!!H[%%h]!"
%TRACE% echo !line!>&2
%TRACE% )
%TRACE% echo/>&2
if /I "%fileExt%" equ ".aes" goto Decryption
:Encryption
rem Define multiplication tables for multiplying by 2 and 3 over the
rem Rijndael Galois finite field. Required for MixColumns() function.
for /L %%x in (0,1,255) do set /A "x=%%x, G3%%x=x, x<<=1, x^=(x>>8)*0x11B, G2%%x=x,G3%%x^=x"
rem Convert input file to hexadecimal ASCII digits, AND
rem encrypt hexadecimal digits to AES format via multi-process method
echo %time% - Start conversion AND encryption of file
call :BinToHex "%fileName%%fileExt%"
echo %time% - End conversion AND encryption
goto :EOF
:BinToHex binFile
set "tempFile=%temp%\BinToHex.tmp"
del "%tempFile%" 2>NUL
fsutil file createnew "%tempFile%" %filesize% >NUL
del pipeFile?.txt 2>NUL
del "%fileName%%fileExt%.aes" 2>NUL
set /A a=0, record=-1
set "output="
for /F "skip=1 tokens=1,2 delims=: " %%b in ('fc /B "%~1" "%tempFile%"') do (
set /A b=0x%%b
if !a! neq !b! (
set /A c=b-a
for /L %%i in (1,1,!c!) do (
set "output=!output!00"
if "!output:~31,2!" neq "" (
set /A record+=1, nextTurn=record %% N
echo !output!>> pipeFile!nextTurn!.txt
set "output="
rem Multi-process starter:
if !record! lss %N% start "" /B "%~F0" :Encrypt !nextTurn!
)
)
)
set /A a=b+1
set "output=!output!%%c"
if "!output:~31,2!" neq "" (
set /A record+=1, nextTurn=record %% N
echo !output!>> pipeFile!nextTurn!.txt
set "output="
rem Multi-process starter:
if !record! lss %N% start "" /B "%~F0" :Encrypt !nextTurn!
)
)
if !a! neq %filesize% (
set /A c=filesize-a
for /L %%i in (1,1,!c!) do (
set "output=!output!00"
if "!output:~31,2!" neq "" (
set /A record+=1, nextTurn=record %% N
echo !output!>> pipeFile!nextTurn!.txt
set "output="
)
)
)
if "!output!" neq "" (
set pad=0
for /L %%i in (2,2,30) do (
if "!output:~%%i,2!" equ "" (
set "output=!output!00"
set /A pad+=2
)
)
set /A record+=1, nextTurn=record %% N
echo !output!>> pipeFile!nextTurn!.txt
REM echo !output! !pad!
)
del "%tempFile%"
:waitForEncryption
if exist pipeFile?.txt goto waitForEncryption
exit /b
:Encrypt turn
set /A NbM1=Nb-1, NkM1=Nk-1, NrM1=Nr-1
call :Cipher %1 < pipeFile%1.txt
del pipeFile%1.txt
exit
rem AES: Cipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
:Cipher turn
rem AES: begin
rem AES: byte state[4,Nb]
rem AES: state = in
set "in="
set /P in=
if not defined in goto endCipher
set p=0
for /L %%c in (0,1,%NbM1%) do (
for /L %%r in (0,1,%NkM1%) do (
for %%p in (!p!) do set /A "state[%%r][%%c]=0x!in:~%%p,2!, p+=2"
)
)
%TRACE% call :ShowMatrix "round[0].input " state >&2
rem AES: AddRoundKey(state, w[0, Nb-1]^)
for /L %%c in (0,1,%NbM1%) do (
for %%b in (0 1 2 3) do (
set /A "state[%%b][%%c]^=w%%b[%%c]"
%TRACE% set sch[%%b][%%c]=!w%%b[%%c]!
)
)
%TRACE% call :ShowMatrix "round[0].k_sch " sch >&2
rem AES: for round = 1 step 1 to Nr–1
rem AES: ShiftRows(state^)
rem AES: SubBytes(state^)
rem AES: MixColumns(state^)
rem AES: AddRoundKey(state, w[round*Nb, (round+1^)*Nb-1]^)
rem AES: end for
for /L %%i in (1,1,%NrM1%) do (
%TRACE% echo/>&2
%TRACE% call :ShowMatrix "round[%%i].start " state >&2
rem ShiftRows(state^):
set a=0
for %%s in ("0 1 2 3" "1 2 3 0" "2 3 0 1" "3 0 1 2") do (
for /F "tokens=1-5" %%a in ("!a! %%~s") do (
for /F "tokens=1-4" %%B in ("!state[%%a][%%b]! !state[%%a][%%c]! !state[%%a][%%d]! !state[%%a][%%e]!") do (
rem SubBytes(state^):
set /A "state[%%a][0]=s[%%B], state[%%a][1]=s[%%C], state[%%a][2]=s[%%D], state[%%a][3]=s[%%E]"
)
)
set /A a+=1
)
%TRACE% call :ShowMatrix "round[%%i].s_row " state >&2
rem MixColumns(state^):
set /A l=%%i*Nb
for /L %%c in (0,1,%NbM1%) do (
for /F "tokens=1-4" %%F in ("!state[0][%%c]! !state[1][%%c]! !state[2][%%c]! !state[3][%%c]!") do (
for %%m in ("0=G2 G3 + +" "1=+ G2 G3 +" "2=+ + G2 G3" "3=G3 + + G2") do (
for /F "tokens=1-5 delims== " %%A in (%%m) do (
rem AddRoundKey(state, w[round*Nb, (round+1^)*Nb-1]^):
set /A "state[%%A][%%c]=(%%B%%F) ^^ (%%C%%G) ^^ (%%D%%H) ^^ (%%E%%I) ^^ (w%%A[!l!])"
)
)
)
set /A l+=1
)
%TRACE% call :ShowMatrix "round[%%i].m_col " state >&2
REM This section show SCH matrix
%TRACE% set /A l=%%i*Nb
%TRACE% for /L %%c in (0,1,%NbM1%) do (
%TRACE% for %%b in (0 1 2 3) do (
%TRACE% for %%l IN (!l!) do set sch[%%b][%%c]=!w%%b[%%l]!
%TRACE% )
%TRACE% set /A l+=1
%TRACE% )
%TRACE% call :ShowMatrix "round[%%i].k_sch " sch >&2
)
rem AES: ShiftRows(state^)
rem AES: SubBytes(state^)
rem ShiftRows(state^):
set a=0
for %%s in ("0 1 2 3" "1 2 3 0" "2 3 0 1" "3 0 1 2") do (
for /F "tokens=1-5" %%a in ("!a! %%~s") do (
for /F "tokens=1-4" %%B in ("!state[%%a][%%b]! !state[%%a][%%c]! !state[%%a][%%d]! !state[%%a][%%e]!") do (
rem SubBytes(state^):
set /A "state[%%a][0]=s[%%B], state[%%a][1]=s[%%C], state[%%a][2]=s[%%D], state[%%a][3]=s[%%E]"
)
)
set /A a+=1
)
rem AES: AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
set /A l=Nr*Nb
for /L %%c in (0,1,%NbM1%) do (
for %%b in (0 1 2 3) do (
set /A "state[%%b][%%c]^^=w%%b[!l!]"
%TRACE% for %%l in (!l!) do set sch[%%b][%%c]=!w%%b[%%l]!
)
set /A l+=1
)
%TRACE% echo/>&2
%TRACE% call :ShowMatrix "round[-].k_sch " sch >&2
%TRACE% call :ShowMatrix "round[-].outpt " state >&2
%TRACE% echo =============================================== >&2
rem AES: out = state
call :ShowMatrix "" state >> "%fileName%%fileExt%.aes"
goto Cipher
rem AES: end Cipher
:endCipher
exit /B
:ShowMatrix Header: Matrix
set "line=%~1"
for /L %%j in (0,1,3) do (
for /L %%i in (0,1,3) do (
for %%h in (!%2[%%i][%%j]!) do (
set "line=!line!!H[%%h]!"
)
)
)
echo !line!
exit /B
:Decryption
REM Under development
rem Define pre-computed multiplication tables for multiplying by 9, 0xb (11), 0xd (13) and 0xe (14) over the
rem Rijndael Galois finite field. Required for InvMixColumns() function.
rem Factor: 9 b d e
for /L %%x in (0,1,255) do (
rem bit 0 [1]: 1 1 1 0
set /A "x=%%x, G9[%%x]= Gb[%%x]= Gd[%%x]=x"
rem bit 1 [2]: 0 1 0 1
set /A "x<<=1, x^=(x>>8)*0x11b, Gb[%%x]^=x, Ge[%%x]=x"
rem bit 2 [4]: 0 0 1 1
set /A "x<<=1, x^=(x>>8)*0x11b, Gd[%%x]^=x,Ge[%%x]^=x"
rem bit 3 [8]: 1 1 1 1
set /A "x<<=1, x^=(x>>8)*0x11b, G9[%%x]^=x,Gb[%%x]^=x,Gd[%%x]^=x,Ge[%%x]^=x"
)
goto :EOF
There are other parts of the original code that just can not be optimized, like the multi-process control method. If we analyze the purpose of the multi-process feature we would conclude that this technique allows to encrypt/decrypt a file in a fraction of the time that it would take with just one process. Of course, this goal is achieved dividing the file by the number of processes, that is to say, if there are 3 parallel tasks, each one would process 1/3 of the file. Although in real life this fraction is not exact, the idea is correct. We may develop an equivalent but much simpler method that consist in split the input file in N parts of the same size and then start N parallel tasks, so each one process one part. When all tasks ends, the generated parts are just joined. This method does not waste CPU resources in a complex controller and allows to use all available CPU cores in the encrypt/decript process.
Although the objective of this thread is to develop this method in a pure Batch program, the final hexadecimal to binary conversion can not be achieved with Batch commands, so if VBS (or JScript) code is already included in the program in order to achieve this conversion, why not add more code to the non-Batch section? I will also develop a JScript version of the encrypt/decrypt routines just to complete this topic; it is expected that this version be the fastest one.
Antonio