Byte-at-a-time ECB decryption (Simple)


In [ ]:
include("cryptofuncs.jl");

In [ ]:
suffix = """Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkgaGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUgYnkK"""

In [ ]:
suffixbytes = base64decode(suffix);

In [ ]:
function ECBEncryptor(ptbytes::Array{UInt8, 1}, keybytes::Array{UInt8,1}=rand(0x00:0xff, 16))
    blocksize = length(keybytes)
    nblocks = convert(Int64, ceil(length(ptbytes)/blocksize))
    ptbytespadded = padToNBytes(ptbytes, nblocks*blocksize)    
    return encryptAES128ECBWithKey(ptbytespadded, keybytes)
end;

In [ ]:
blocksize=16
randomkey = rand(0x00:0xff, blocksize)
function encryptionOracle(ptbytes::Array{UInt8,1})
    newptbytes = copy(ptbytes)
    append!(newptbytes, suffixbytes)
    return ECBEncryptor(newptbytes, randomkey)
end;

First, learn the blocksize.


In [ ]:
pt = stringToBytes("A")
ctlength = length(encryptionOracle(pt))
newctlength = ctlength
while newctlength == ctlength
    append!(pt, pt[1])
    newctlength = length(encryptionOracle(pt))
end
blocksize = newctlength - ctlength

Next, identify the cipher type.


In [ ]:
function isThisECBOrCBC(ctbytes::Array{UInt8,1}, blocksize=16)
    nblocks = convert(Int64, length(ctbytes)/blocksize)
    interblocksimilarity = []
    for ii in 1:(nblocks-1)
        for jj in (ii+1):nblocks
            blockii = ctbytes[((ii-1)*blocksize+1):(ii*blocksize)]
            blockjj = ctbytes[((jj-1)*blocksize+1):(jj*blocksize)]
            samebits = ~(blockii $ blockjj)
            similarity = 0
            for cc in samebits
                ccbits = bits(cc)
                for dd in 1:length(ccbits)
                    if ccbits[dd]=='1'
                        similarity+=1
                    end
                end
            end
            push!(interblocksimilarity, similarity/(8*blocksize))
        end
    end
    pexactsame = sum(interblocksimilarity.==1)/length(interblocksimilarity)
    cipher = "cbc"
    if pexactsame > 0.5
        cipher = "ecb"
    end
    return (cipher, pexactsame, interblocksimilarity)
end;

In [ ]:
probe = repeat("abcdefghijklmnop", 32)
ctbytes = encryptionOracle(stringToBytes(probe))
isThisECBOrCBC(ctbytes)

Because the oracle is appending some unknown ciphertext to the end of its input, the $pexactsame$ threshold might have needed to be adjusted, but there are still too many identical blocks for this to be CBC.


In [ ]:
probebytes = stringToBytes(repeat("A", blocksize))
for ii in 1:blocksize
    targetctbytes = encryptionOracle(probebytes[1:(blocksize-ii)])
    for xx in 0x00:0xff
        if isascii(Char(xx))
            probebytes[blocksize] = xx
            ctcandidate = encryptionOracle(probebytes) # the first block of this should match targetctbytes when xx is right
            if ctcandidate[1:blocksize] == targetctbytes[1:blocksize]
                println("found position $ii")
                if ii < blocksize
                    for jj in 1:(length(probebytes)-1)
                        probebytes[jj] = probebytes[jj+1] # shift the bytes back so we can crack the next position
                    end
                end
                break
            end
        end        
    end
end

In [ ]:
String(probebytes)

So now we've cracked the first block. Since this is ECB maybe now we can crack the key and be done, but I don't know how to do that. Or we can pump out the second block so that the byte we're learning is the last byte of the second block, and so on.


In [ ]:
nblocks = convert(Int64, length(encryptionOracle(stringToBytes("")))/blocksize)
decrypt = Array{UInt8,1}()
blockpt = stringToBytes(repeat("A", blocksize)) # set the initial blockpt to be repeating As
for kk in 1:nblocks
    for ii in 1:blocksize
        targetctbytes = encryptionOracle(stringToBytes(repeat("A", blocksize-ii))) # this is wasteful.  sorry.
        for xx in 0x00:0xff
            if isascii(Char(xx))
                blockpt[blocksize] = xx
                ctcandidate = encryptionOracle(blockpt) 
                if ctcandidate[1:blocksize] == targetctbytes[((kk-1)*blocksize+1):(kk*blocksize)]
                    println("found position $ii in block $kk") # TODO: fail if we fail to get here for any xx
                    append!(decrypt, xx)
                    for jj in 1:(length(probebytes)-1)
                        blockpt[jj] = blockpt[jj+1] # shift the bytes back so we can crack the next position
                    end
                    break
                end
            end        
        end
    end
end

In [ ]:
String(decrypt)

Beginning to sense a theme here.