Lazy Crypto Reloaded

simplicity.jpg

After figuring out how to design secure ciphers, cryptographers tried to find how to make secure ciphers as simple as possible. Permutation-based cryptography is the latest trend in symmetric cryptography. For example, the SHA-3 winner Keccak is based on a simple construction that iterates XORs of message chunks with the internal state with permutations of the said state. The Keccak team extended this simplistic construction to other crypto functionalities such as pseudorandom generation and authenticated encryption. With permutation-based ciphers, you get rid of key schedules, for example. Overall, it’s less stuff to design, less stuff to code, to debug, and thus hopefully fewer design flaws and fewer bugs. This approach has been successful so far with Keccak, and in SipHash and NORX.

A related trend is Even-Mansour block ciphers. Given a block, these XOR some key with the block, apply a permutation, and XOR some key again. A bunch of papers appeared this year about this construction and some of its variants. A quick C-f of http://eprint.iacr.org/2015 already gives you Multi-Key Security: The Even-Mansour Construction Revisited, From Related-Key Distinguishers to Related-Key-Recovery on Even-Mansour Constructions, The Iterated Random Permutation Problem with Applications to Cascade Encryption, or Tweaking Even-Mansour Ciphers.

So when I had little time to design a simple, low-security cipher to encrypt IPv4 addresses in a format-preserving way, I created an Even-Mansour-ish cipher. Recycling even further, as a permutation I just took a variant of SipHash’s core function:



def rotl(b, r):
    return ((b << r) & 0xff) | (b >> (8 - r))


def permute_fwd(state):
    (b0, b1, b2, b3) = state
    b0 += b1
    b2 += b3
    b0 &= 0xff
    b2 &= 0xff
    b1 = rotl(b1, 2)
    b3 = rotl(b3, 5)
    b1 ^= b0
    b3 ^= b2
    b0 = rotl(b0, 4)
    b0 += b3
    b2 += b1
    b0 &= 0xff
    b2 &= 0xff
    b1 = rotl(b1, 3)
    b3 = rotl(b3, 7)
    b1 ^= b2
    b3 ^= b0
    b2 = rotl(b2, 4)
    return (b0, b1, b2, b3)


def permute_bwd(state):
    (b0, b1, b2, b3) = state
    b2 = rotl(b2, 4)
    b1 ^= b2
    b3 ^= b0
    b1 = rotl(b1, 5)
    b3 = rotl(b3, 1)
    b0 -= b3
    b2 -= b1
    b0 &= 0xff
    b2 &= 0xff
    b0 = rotl(b0, 4)
    b1 ^= b0
    b3 ^= b2
    b1 = rotl(b1, 6)
    b3 = rotl(b3, 3)
    b0 -= b1
    b2 -= b3
    b0 &= 0xff
    b2 &= 0xff
    return (b0, b1, b2, b3)


def xor4(x, y):
    return [(x[i] ^ y[i]) & 0xff for i in (0, 1, 2, 3)]


def encrypt(key, ip):
    """16-byte key, ip string like '192.168.1.2'"""
    k = [struct.unpack('<B', x)[0] for x in key]
    try:
        state = [int(x) for x in ip.split('.')]
    except ValueError:
        raise
    state = xor4(state, k[:4])
    state = permute_fwd(state)
    state = xor4(state, k[4:8])
    state = permute_fwd(state)
    state = xor4(state, k[8:12])
    state = permute_fwd(state)
    state = xor4(state, k[12:16])
    return '.'.join(str(x) for x in state)


def decrypt(key, ip):
    """16-byte key, encrypted ip string like '215.51.199.127'"""
    k = [struct.unpack('<B', x)[0] for x in key]
    try:
        state = [int(x) for x in ip.split('.')]
    except ValueError:
        raise
    state = xor4(state, k[12:16])
    state = permute_bwd(state)
    state = xor4(state, k[8:12])
    state = permute_bwd(state)
    state = xor4(state, k[4:8])
    state = permute_bwd(state)
    state = xor4(state, k[:4])
    return '.'.join(str(x) for x in state)

This is probably secure enough for what I was up to—encrypting IPv4 addresses to IPv4 addresses to sanitize some logs. Frankly I’ve not tried very hard to analyze this thing’s security, let alone prove it. Anyway, you shouldn’t use it for other purposes: as it is it’s just a dumb single-block ECB, and 32-bit blocks allow straightforward codebook attacks, if you’ve an encryption or decryption oracle.

Don’t roll your own crypto, they say.

Edit (201506091116): Frank Denis ported the cipher to C: https://github.com/jedisct1/c-ipcrypt.

Leave a Reply