An ECB/CBC detection oracle


In [ ]:
function decryptAES128ECBWithKey(ciphertext::String, key::String)
    decrypt = chomp(
        readstring(
            pipeline(`echo -n $ciphertext`,
                     `base64 --decode`,
                     `openssl enc -nopad -d -aes-128-ecb -K $key`,
                     `base64`)))
    return decrypt
end

# takes bytes and returns bytes
function decryptAES128ECBWithKey(cipherbytes::Array{UInt8,1}, keybytes::Array{UInt8,1})
    ciphertext = base64encode(cipherbytes)
    keytext = bytes2hex(keybytes)
    return base64decode(decryptAES128ECBWithKey(ciphertext, keytext))
end

function encryptAES128ECBWithKey(plaintext::String, key::String)
    encrypt = chomp(
        readstring(
            pipeline(`echo -n $plaintext`,
                     `base64 --decode`,
                     `openssl enc -nopad -e -aes-128-ecb -K $key`,
                     `base64`)))
    return encrypt
end

function encryptAES128ECBWithKey(plaintextbytes::Array{UInt8,1}, keybytes::Array{UInt8,1})
    plaintext = base64encode(plaintextbytes)
    keytext = bytes2hex(keybytes)
    return base64decode(encryptAES128ECBWithKey(plaintext, keytext))
end

function encryptCBCByHand(ptbytes::Array{UInt8,1}, blocksize, ivbytes::Array{UInt8,1}, keybytes::Array{UInt8,1})
    cipherbytes = Array{UInt8,1}()
    nblocks = convert(Int64, ceil(length(ptbytes)/blocksize))
    ptbytes = padToNBytes(ptbytes, blocksize*nblocks)
    lastblock = ivbytes
    for ii in 1:nblocks
        bytestoencrypt = ptbytes[((ii-1)*blocksize+1):(ii*blocksize)] $ lastblock
        encryptedbytes = encryptAES128ECBWithKey(bytestoencrypt, keybytes)
        append!(cipherbytes, encryptedbytes)
        lastblock = encryptedbytes
    end
    return cipherbytes
end;

function decryptCBCByHand(ctbytes::Array{UInt8,1}, blocksize, ivbytes::Array{UInt8,1}, keybytes::Array{UInt8,1})
    ptbytes = Array{UInt8,1}()
    nblocks = convert(Int64, length(ctbytes)/blocksize)
    lastblock = ivbytes
    for ii in 1:nblocks
        bytestodecrypt = ctbytes[((ii-1)*blocksize+1):(ii*blocksize)]
        decryptedbytes = decryptAES128ECBWithKey(bytestodecrypt, keybytes) $ lastblock
        append!(ptbytes, decryptedbytes)
        lastblock = bytestodecrypt
    end
    return ptbytes
end;

In [ ]:
function padToNBytes(a::Array{UInt8,1}, nbytes)
    newa = Array{UInt8,1}(nbytes)
    ntopad = nbytes - length(a)
    newa[1:length(a)] = a
    newa[(length(a)+1):nbytes] = UInt8(ntopad)
    return newa
end

function padToNBytes(a::String, nbytes)
    return String(padToNBytes(stringToBytes(a), nbytes))
end;

In [ ]:
function stringToBytes(X::String)
    return [UInt8(X[ii]) for ii in 1:length(X)]
end;

In [ ]:
function encryptionOracle(ptbytes::Array{UInt8, 1})
    blocksize=16
    keybytes = rand(0x00:0xff, blocksize)
    
    ptbytespadded = rand(0x00:0xff, rand(5:10))
    append!(ptbytespadded, ptbytes)
    append!(ptbytespadded, rand(0x00:0xff, rand(5:10)))
    
    nblocks = convert(Int64, ceil(length(ptbytespadded)/blocksize))
    ptbytespadded = padToNBytes(ptbytespadded, nblocks*blocksize)
    
    if rand() < 0.5
        println("ecb")
        return encryptAES128ECBWithKey(ptbytespadded, keybytes)
    else
        println("cbc")
        iv = rand(0x00:0xff, blocksize)
        return encryptCBCByHand(ptbytespadded, blocksize, iv, keybytes)
    end
end;

Given a thing that is either encrypting with ECB or CBC, we can figure out which its doing by probing it with a repeating block of plaintext which will, under ECB, even with the random padding, still result in some of the encrypted blocks being identical.


In [ ]:
probe = repeat("abcdefghijklmnop", 32)

In [ ]:
ctbytes = encryptionOracle(stringToBytes(probe));

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 [ ]:
(ciphertype, pexactsame, interblocksimilarity) = isThisECBOrCBC(ctbytes)

In [ ]:
using Gadfly;
using DataFrames;

In [ ]:
plot(DataFrame(similarity=interblocksimilarity), x="similarity", Geom.histogram)

Under CBC the interblock similarities will be distributed around 0.5. Under ECB, there will be a large fraction of pairs with similarity=1.0


In [ ]:
ctbytes = encryptionOracle(stringToBytes(probe));

In [ ]:
(ciphertype, pexactsame, interblocksimilarity) = isThisECBOrCBC(ctbytes)

In [ ]:
plot(DataFrame(similarity=interblocksimilarity), x="similarity", Geom.histogram)