! Copyright (C) 2013 Fred Alger ! Some parts Copyright (C) 2008 Doug Coleman. ! See http://factorcode.org/license.txt for BSD license. USING: accessors arrays assocs combinators crypto.aes.utils generalizations grouping kernel locals math math.bitwise math.ranges memoize namespaces sequences sequences.private sequences.unrolled ; IN: crypto.aes CONSTANT: AES_BLOCK_SIZE 16 ! FIPS-197 AES ! input block, state, output block -- 4 32-bit words CONSTANT: FIPS-197 { { 128 10 } ! aes-128 -- Key(4) Block(4) Rounds(10) { 192 12 } ! aes-192 -- Key(6) Block(4) Rounds(12) { 256 14 } ! aes-256 -- Key(8) Block(4) Rounds(14) } dup 256 [ dup sbox nth rot set-nth ] with each-integer ; ! applies sbox to each byte of word : subword ( word -- word' ) [ gb0 sbox nth ] keep [ gb1 sbox nth ] keep [ gb2 sbox nth ] keep gb3 sbox nth >ui32 ; ! applies inverse sbox to each byte of word : inv-subword ( word -- word' ) [ gb0 inv-sbox nth ] keep [ gb1 inv-sbox nth ] keep [ gb2 inv-sbox nth ] keep gb3 inv-sbox nth >ui32 ; : rotword ( n -- n ) 8 bitroll-32 ; ! round constants, 2^n over GF(2^8) : rcon ( -- array ) { 0x00 0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80 0x1b 0x36 } ; : (rcon-nth) ( n -- rcon[n] ) rcon nth 24 shift ; ! Galois field product related : xtime ( x -- x' ) [ 1 shift ] [ 0x80 bitand 0 = 0 0x1b ? ] bi bitxor 8 bits ; ! generate t-box :: set-t ( T i -- ) i sbox nth :> a1 a1 xtime :> a2 a1 a2 bitxor :> a3 a3 a1 a1 a2 >ui32 i T set-nth a1 a1 a2 a3 >ui32 i 0x100 + T set-nth a1 a2 a3 a1 >ui32 i 0x200 + T set-nth a2 a3 a1 a1 >ui32 i 0x300 + T set-nth ; MEMO:: t-table ( -- array ) 1024 0 dup 256 [ set-t ] with each-integer ; ! generate inverse t-box :: set-d ( D i -- ) i inv-sbox nth :> a1 a1 xtime :> a2 a2 xtime :> a4 a4 xtime :> a8 a8 a1 bitxor :> a9 a9 a2 bitxor :> ab a9 a4 bitxor :> ad a8 a4 a2 bitxor bitxor :> ae ab ad a9 ae >ui32 i D set-nth ad a9 ae ab >ui32 i 0x100 + D set-nth a9 ae ab ad >ui32 i 0x200 + D set-nth ae ab ad a9 >ui32 i 0x300 + D set-nth ; MEMO:: d-table ( -- array ) 1024 0 dup 256 [ set-d ] with each-integer ; :: (transform) ( a0 a1 a2 a3 table -- word' ) a0 a1 a2 a3 [ 0x100 + ] [ 0x200 + ] [ 0x300 + ] tri* [ table nth ] 4 napply bitxor bitxor bitxor ; inline : t-transform ( a0 a1 a2 a3 -- word' ) t-table (transform) ; : d-transform ( a0 a1 a2 a3 -- word' ) d-table (transform) ; ! key schedule ! expands an 128/192/256 bit key into an 176/208/240 byte schedule SYMBOL: aes-expand-inner HOOK: key-expand-round aes-expand-inner ( temp i -- temp' ) SINGLETON: aes-128-key SINGLETON: aes-256-key : (add-rcon) ( word rcon-ndx -- word' ) (rcon-nth) [ rotword subword ] dip bitxor ; M: aes-128-key key-expand-round ( temp i -- temp' ) 4 /mod 0 = swap and [ (add-rcon) ] when* ; ERROR: aes-192-256-not-implemented ; M: aes-256-key key-expand-round ( temp i -- temp' ) aes-192-256-not-implemented ; : (key-sched-round) ( output temp i -- output' ) key-expand-round [ dup 4th-from-end ] dip bitxor suffix! ; inline : (sched-interval) ( K Nr -- seq ) [ length ] dip 1 + 4 * [a,b) ; ! over the interval Nk...Nb(Nr + 1) : (init-round) ( out -- out temp quot ) [ ] [ last ] [ length 6 > [ aes-256-key ] [ aes-128-key ] if ] tri ; ! K -- input key (byte-array), Nr -- number of rounds ! output: sched, Nb(Nr+1) byte key schedule : (expand-enc-key) ( K Nr -- sched ) [ bytes>words ] dip [ drop (init-round) ] [ (sched-interval) ] 2bi [ [ aes-expand-inner set ] dip [ (key-sched-round) dup last ] each ] with-scope drop ; TUPLE: aes-state nrounds key state ; : ( nrounds key state -- aes-state ) \ aes-state boa ; ! grabs the 4n...4(n+1) words of the key : (key-at-nth-round) ( nth aes -- seq ) [ 4 * dup 4 + ] [ key>> ] bi* ; SYMBOL: aes-strategy HOOK: (expand-key) aes-strategy ( K Nr -- sched ) HOOK: (first-round) aes-strategy ( aes -- aes' ) HOOK: (counter) aes-strategy ( nrounds -- seq ) HOOK: (round) aes-strategy ( state -- ) HOOK: (add-key) aes-strategy ( aes -- aes' ) HOOK: (final-round) aes-strategy ( aes -- aes' ) SINGLETON: aes-decrypt SINGLETON: aes-encrypt ! rotates the 2nd row left by one element ! rotates the 3rd row left by two elements ! rotates the 4th row left by three elements ! ! Kind of ugly because the algorithm is specified and ! implemented in terms of columns. This approach is very ! efficient in terms of execution and only requires one new ! word to implement. ! ! The alternative is to split into arrays of bytes, transpose, ! rotate each row n times, transpose again, and then ! smash them back into 4-byte words. :: (shift-rows) ( c0 c1 c2 c3 -- c0' c1' c2' c3' ) c3 gb0 c2 gb1 c1 gb2 c0 gb3 >ui32 ! c0' c0 gb0 c3 gb1 c2 gb2 c1 gb3 >ui32 ! c1' c1 gb0 c0 gb1 c3 gb2 c2 gb3 >ui32 ! c2' c2 gb0 c1 gb1 c0 gb2 c3 gb3 >ui32 ; ! c3' :: (unshift-rows) ( c0 c1 c2 c3 -- c0' c1' c2' c3' ) c1 gb0 c2 gb1 c3 gb2 c0 gb3 >ui32 ! c0' c2 gb0 c3 gb1 c0 gb2 c1 gb3 >ui32 ! c1' c3 gb0 c0 gb1 c1 gb2 c2 gb3 >ui32 ! c2' c0 gb0 c1 gb1 c2 gb2 c3 gb3 >ui32 ; ! c3' : (add-round-key) ( key state -- state' ) 4 [ bitxor ] unrolled-2map ; : add-round-key ( aes n -- aes' ) over (key-at-nth-round) swap [ (add-round-key) ] change-state ; : add-final-round-key ( aes -- aes' ) dup nrounds>> add-round-key ; : add-first-round-key ( aes -- aes' ) 0 add-round-key ; : aes-round ( state -- ) dup first4-unsafe { [ first-diag t-transform ] [ second-diag t-transform ] [ third-diag t-transform ] [ fourth-diag t-transform ] } 4 ncleave set-first4-unsafe ; : shift-rows ( state -- state' ) first4 (shift-rows) 4array ; : unshift-rows ( state -- state' ) first4 (unshift-rows) 4array ; : final-round ( state -- state' ) 4 [ subword ] unrolled-map shift-rows ; : (do-round) ( aes -- aes' ) [ state>> (round) ] keep ; M: aes-encrypt (expand-key) (expand-enc-key) ; M: aes-encrypt (first-round) add-first-round-key ; M: aes-encrypt (counter) 0 swap (a,b) ; M: aes-encrypt (round) aes-round ; M: aes-encrypt (final-round) [ final-round ] change-state add-final-round-key ; M:: aes-decrypt (expand-key) ( K Nr -- sched ) K Nr (expand-enc-key) dup length :> key-length [ [ 4 >= ] [ key-length 4 - < ] bi and [ subword ui32-rev> d-transform ] when ] map-index ; M: aes-decrypt (first-round) ( aes -- aes' ) add-final-round-key ; M: aes-decrypt (counter) ( nrounds -- seq ) 0 swap (a,b) ; M: aes-decrypt (final-round) ( aes -- aes' ) [ [ inv-subword ] map unshift-rows ] change-state add-first-round-key ; M: aes-decrypt (round) ( state -- ) dup first4-unsafe { [ -first-diag d-transform ] [ -fourth-diag d-transform ] [ -third-diag d-transform ] [ -second-diag d-transform ] } 4 ncleave set-first4-unsafe ; : (aes-crypt) ( aes -- aes' ) (first-round) [ dup nrounds>> (counter) [ [ (do-round) ] dip add-round-key drop ] with each ] keep (final-round) ; : (aes-expand-key) ( key -- nrounds expanded-key ) [ (nrounds) ] keep over (expand-key) ; : (aes-crypt-block-inner) ( nrounds key block -- crypted-block ) (aes-crypt) state>> ; : (aes-crypt-block) ( key block -- output-block ) [ (aes-expand-key) ] dip bytes>words (aes-crypt-block-inner) ; PRIVATE> : aes-encrypt-block ( key block -- output ) [ aes-encrypt aes-strategy set (aes-crypt-block) ] with-scope [ ui32> 4array reverse ] map concat ; : aes-decrypt-block ( key block -- output ) [ aes-decrypt aes-strategy set (aes-crypt-block) ] with-scope [ ui32> 4array reverse ] map concat ;