Skip to main content Link Search Menu Expand Document (external link)

Iznogood

Un algorithme de chiffrement est donné par cette classe Python

class IZNOGOOD:
    def __init__(self, k):
        self.k = self._b2n(k)
        self.nr = 8
        self.S = [12, 5, 6, 11, 9, 0, 10, 13, 3, 14, 15, 8, 4, 7, 1, 2]
        self.pi = [
            [0x2,0x4,0x3,0xf,0x6,0xa,0x8,0x8,0x8,0x5,0xa,0x3,0x0,0x8,0xd,0x3,
             0x1,0x3,0x1,0x9,0x8,0xa,0x2,0xe,0x0,0x3,0x7,0x0,0x7,0x3,0x4,0x4],
            [0xa,0x4,0x0,0x9,0x3,0x8,0x2,0x2,0x2,0x9,0x9,0xf,0x3,0x1,0xd,0x0,
             0x0,0x8,0x2,0xe,0xf,0xa,0x9,0x8,0xe,0xc,0x4,0xe,0x6,0xc,0x8,0x9],
            [0x4,0x5,0x2,0x8,0x2,0x1,0xe,0x6,0x3,0x8,0xd,0x0,0x1,0x3,0x7,0x7,
             0xb,0xe,0x5,0x4,0x6,0x6,0xc,0xf,0x3,0x4,0xe,0x9,0x0,0xc,0x6,0xc],
            [0xc,0x0,0xa,0xc,0x2,0x9,0xb,0x7,0xc,0x9,0x7,0xc,0x5,0x0,0xd,0xd,
             0x3,0xf,0x8,0x4,0xd,0x5,0xb,0x5,0xb,0x5,0x4,0x7,0x0,0x9,0x1,0x7],
            [0x9,0x2,0x1,0x6,0xd,0x5,0xd,0x9,0x8,0x9,0x7,0x9,0xf,0xb,0x1,0xb,
             0xd,0x1,0x3,0x1,0x0,0xb,0xa,0x6,0x9,0x8,0xd,0xf,0xb,0x5,0xa,0xc],
            [0x2,0xf,0xf,0xd,0x7,0x2,0xd,0xb,0xd,0x0,0x1,0xa,0xd,0xf,0xb,0x7,
             0xb,0x8,0xe,0x1,0xa,0xf,0xe,0xd,0x6,0xa,0x2,0x6,0x7,0xe,0x9,0x6],
            [0xb,0xa,0x7,0xc,0x9,0x0,0x4,0x5,0xf,0x1,0x2,0xc,0x7,0xf,0x9,0x9,
             0x2,0x4,0xa,0x1,0x9,0x9,0x4,0x7,0xb,0x3,0x9,0x1,0x6,0xc,0xf,0x7],
            [0x0,0x8,0x0,0x1,0xf,0x2,0xe,0x2,0x8,0x5,0x8,0xe,0xf,0xc,0x1,0x6,
             0x6,0x3,0x6,0x9,0x2,0x0,0xd,0x8,0x7,0x1,0x5,0x7,0x4,0xe,0x6,0x9],
            [0xa,0x4,0x5,0x8,0xf,0xe,0xa,0x3,0xf,0x4,0x9,0x3,0x3,0xd,0x7,0xe,
             0x0,0xd,0x9,0x5,0x7,0x4,0x8,0xf,0x7,0x2,0x8,0xe,0xb,0x6,0x5,0x8],
        ]
        self.rk = self.pi
        for r in range(self.nr + 1):
            for i in range(32):
                self.rk[r][i] ^= self.k[i]

    def _S(self, s):
        return [ self.S[x] for x in s ]
        
    def _P(self, s):
        r = []
        for j in range(32):
            b = 0
            for i, x in enumerate(s):
                if i == j: continue
                b ^= x
            r.append(b)
        return r
    
    def _addKey(self, a, r):
        return [ x ^ y for x, y in zip(a, self.rk[r]) ]
        
    def _n2b(self, v):
        L = []
        for i in range (0, len(v), 2):
            a, b = v[i], v[i + 1]
            L.append( b ^ (a << 4) )
        return bytes(L)
    
    def _b2n(self, v):
        L = []
        for x in v:
            L.append( (x >> 4) & 0xf )
            L.append( x & 0xf )
        return L
    
    def encrypt(self, m):
        s = self._b2n(m)
        for i in range (self.nr - 1):
            s = self._addKey(s, i)
            s = self._S(s)
            s = self._P(s)
        s = self._addKey(s, self.nr - 1)
        s = self._S(s)
        s = self._addKey(s, self.nr)
        return self._n2b(s)

if __name__ == "__main__":
    KP = 1
    flag = open("flag.txt", "rb").read()

    k = os.urandom(16)
    E = IZNOGOOD(k)

    P = [ flag[i:i+16] for i in range(0, len(flag), 16) ]
    C = [ E.encrypt(p) for p in P ]

    for i in range(len(P)):
        if i < KP: print(P[i].hex(), C[i].hex())
        else:      print("?" * 32, C[i].hex())

On nous donne également une paire clair/chiffré:

464353437b6661343232653333393434 07b0d32c8a6a25dc782d2ee20acd53f3
???????????????????????????????? ba596368fc650d3d08ffbfb2bda27f28
???????????????????????????????? 68be7f31b109babfb667aabb92a119cd
???????????????????????????????? acf46fe7220bf34cb2fe740c5773b354

Il faut déchiffrer l’ensemble du message.

L’algorithme est composé (à la manière d’AES):

  • d’un XOR avec une clé (en plusieurs rounds)
  • d’une boîte de substitution
  • d’une phase de “mélange”

En regardant de plus près, la phase de mélange peut se réécrire. Chaque élément est remplacé par le XOR des autres, donc c’est équivalent à le XOR-er avec le XOR de tout le vecteur:

def _P(self, s):
    sum = s[0] ^ s[1] ^ ... ^ s[32]
    for i in range(len(s)):
        s[i] ^= sum

On voit alors que si un oracle nous donnait la valeur de sum pour chacun des rounds, chaque élément de sortie ne dépendrait que de l’élément d’entrée au même indice dans le tableau et dans les clés. On peut alors chercher les clés possibles éléments par éléments (on doit explorer 16 possibilités, 32 fois, et non pas 2^128 clés).

Comme les éléments sont sur 4 bits et qu’il y a 7 rounds de mélange, il n’y a que 2^28 possibilités pour ces 7 sommes: il suffit alors de toutes les essayer, et de revalider les clés trouvées:

  • pour chaque combinaison de sommes
  • pour chacun des 32 indices du tableau, chercher les valeurs possibles de key[i]
  • tester les différentes combinaisons de clés entières correspondant aux possibilités de key[i]

On trouve que les sommes sont e, 9, 5, 4, 5, a, f (en hexadécimal) et plusieurs clés possibles parmi les 573 millions de possibilités pour ces sommes. Pour les autres choix de sommes, il y a très peu de clés à essayer.

L’ensemble tourne en quelques minutes sur un CPU ordinaire.

package main

import (
    "fmt"
    "math/bits"
)

func main() {
    oracleStart := uint(0)

    var sIn [8]uint16
    var sOut [8]uint16
    var output [][8]uint16

    //in := "464353437b6661343232653333393434"
    //out := "07b0d32c8a6a25dc782d2ee20acd53f3"
    sIn = [8]uint16{
          0x3464, 0x3435, 0x66b7, 0x4316,
          0x2323, 0x3356, 0x9333, 0x4343,
    }
    sOut = [8]uint16{
          0x0b70, 0xc23d, 0xa6a8, 0xcd52,
          0xd287, 0x2ee2, 0xdca0, 0x3f35,
    }

    output = [][8]uint16{
          sOut,
          // ba59 6368 fc65 0d3d 08ff bfb2 bda2 7f28
          {0x95ab, 0x8636, 0x56cf, 0xd3d0, 0xff80, 0x2bfb, 0x2adb, 0x82f7},
          // 68be 7f31 b109 babf b667 aabb 92a1 19cd
          {0xeb86, 0x13f7, 0x901b, 0xfbab, 0x766b, 0xbbaa, 0x1a29, 0xdc91},
          // acf4 6fe7 220b f34c b2fe 740c 5773 b354
          {0x4fca, 0x7ef6, 0xb022, 0xc43f, 0xef2b, 0xc047, 0x3775, 0x453b},
    }

    for oracle := oracleStart; oracle < 1<<28; oracle++ {
        if oracle%(1<<20) == 0 {
            println("oracle", oracle>>20, "<< 20")
        }
        keys := guessKey(sIn, sOut, oracle)
        for _, k := range keys {
            fmt.Printf("trying key %x\n", k)
            var iz IZ
            iz.Init(k)
            for _, o := range output {
                dec := iz.Decrypt(o)
                fmt.Printf("%x => %x\n", o, dec)
            }
        }
    }
}

const ROUNDS = 8

func guessKey(in, out [8]uint16, oracle uint) [][8]uint16 {
    s1, s2, s3, s4 := unpack16(uint16(oracle))
    s5, s6, s7, _ := unpack16(uint16(oracle >> 16))
    sums := [7]uint16{s1, s2, s3, s4, s5, s6, s7}
    // Bitmask of possible values
    var keys [32]uint16
    for i := uint(0); i < 32; i++ {
        i1, i2 := i/4, i%4
        xin := (in[i1] >> (4 * i2)) & 0xf
        xout := (out[i1] >> (4 * i2)) & 0xf
        //println("looking for",xin, "=>",xout)
        var rk [9]uint16
        for j := range rk {
            rk[j] = PI[j][i]
        }
        for k := uint(0); k < 16; k++ {
            x := xin
            for round := 0; round < ROUNDS-1; round++ {
                x ^= rk[round] ^ uint16(k)
                x = S[x]
                x ^= sums[round]
            }
            x ^= rk[ROUNDS-1] ^ uint16(k)
            x = S[x]
            x ^= rk[ROUNDS] ^ uint16(k)
            if x == xout {
                keys[i] |= (1 << k)
            }
        }
        if keys[i] == 0 {
            return nil
        }
    }
    choices := uint64(1)
    for _, k := range keys {
        choices *= uint64(bits.OnesCount16(k))
    }
    fmt.Printf("sum %07x => try %d keys\n", oracle, choices)
    ks := tryKeys(in, out, keys)
    fmt.Println("done")
    return ks
}

func tryKeys(in, out [8]uint16, keys [32]uint16) (result [][8]uint16) {
    var key [8]uint16
    var do func(uint)
    do = func(idx uint) {
        if idx == 32 {
            var enc IZ
            enc.Init(key)
            if enc.Encrypt(in) == out {
                fmt.Printf("FOUND %x\n", key)
                result = append(result, key)
            }
        } else {
            k := keys[idx]
            for i := 0; i < 16; i++ {
                if (k>>i)&1 == 1 {
                    key[idx/4] &^= 0xf << (4 * (idx % 4))
                    key[idx/4] |= uint16(i) << (4 * (idx % 4))
                    do(idx + 1)
                }
            }
        }
    }
    do(0)
    return
}

var S = [16]uint16{12, 5, 6, 11, 9, 0, 10, 13, 3, 14, 15, 8, 4, 7, 1, 2}

var Sinv [16]uint16

func init() {
    for i, j := range S {
        Sinv[j] = uint16(i)
    }
}

var PI = [9][32]uint16{
    {0x2, 0x4, 0x3, 0xf, 0x6, 0xa, 0x8, 0x8, 0x8, 0x5, 0xa, 0x3, 0x0, 0x8, 0xd, 0x3,
        0x1, 0x3, 0x1, 0x9, 0x8, 0xa, 0x2, 0xe, 0x0, 0x3, 0x7, 0x0, 0x7, 0x3, 0x4, 0x4},
    {0xa, 0x4, 0x0, 0x9, 0x3, 0x8, 0x2, 0x2, 0x2, 0x9, 0x9, 0xf, 0x3, 0x1, 0xd, 0x0,
        0x0, 0x8, 0x2, 0xe, 0xf, 0xa, 0x9, 0x8, 0xe, 0xc, 0x4, 0xe, 0x6, 0xc, 0x8, 0x9},
    {0x4, 0x5, 0x2, 0x8, 0x2, 0x1, 0xe, 0x6, 0x3, 0x8, 0xd, 0x0, 0x1, 0x3, 0x7, 0x7,
        0xb, 0xe, 0x5, 0x4, 0x6, 0x6, 0xc, 0xf, 0x3, 0x4, 0xe, 0x9, 0x0, 0xc, 0x6, 0xc},
    {0xc, 0x0, 0xa, 0xc, 0x2, 0x9, 0xb, 0x7, 0xc, 0x9, 0x7, 0xc, 0x5, 0x0, 0xd, 0xd,
        0x3, 0xf, 0x8, 0x4, 0xd, 0x5, 0xb, 0x5, 0xb, 0x5, 0x4, 0x7, 0x0, 0x9, 0x1, 0x7},
    {0x9, 0x2, 0x1, 0x6, 0xd, 0x5, 0xd, 0x9, 0x8, 0x9, 0x7, 0x9, 0xf, 0xb, 0x1, 0xb,
        0xd, 0x1, 0x3, 0x1, 0x0, 0xb, 0xa, 0x6, 0x9, 0x8, 0xd, 0xf, 0xb, 0x5, 0xa, 0xc},
    {0x2, 0xf, 0xf, 0xd, 0x7, 0x2, 0xd, 0xb, 0xd, 0x0, 0x1, 0xa, 0xd, 0xf, 0xb, 0x7,
        0xb, 0x8, 0xe, 0x1, 0xa, 0xf, 0xe, 0xd, 0x6, 0xa, 0x2, 0x6, 0x7, 0xe, 0x9, 0x6},
    {0xb, 0xa, 0x7, 0xc, 0x9, 0x0, 0x4, 0x5, 0xf, 0x1, 0x2, 0xc, 0x7, 0xf, 0x9, 0x9,
        0x2, 0x4, 0xa, 0x1, 0x9, 0x9, 0x4, 0x7, 0xb, 0x3, 0x9, 0x1, 0x6, 0xc, 0xf, 0x7},
    {0x0, 0x8, 0x0, 0x1, 0xf, 0x2, 0xe, 0x2, 0x8, 0x5, 0x8, 0xe, 0xf, 0xc, 0x1, 0x6,
        0x6, 0x3, 0x6, 0x9, 0x2, 0x0, 0xd, 0x8, 0x7, 0x1, 0x5, 0x7, 0x4, 0xe, 0x6, 0x9},
    {0xa, 0x4, 0x5, 0x8, 0xf, 0xe, 0xa, 0x3, 0xf, 0x4, 0x9, 0x3, 0x3, 0xd, 0x7, 0xe,
        0x0, 0xd, 0x9, 0x5, 0x7, 0x4, 0x8, 0xf, 0x7, 0x2, 0x8, 0xe, 0xb, 0x6, 0x5, 0x8},
}

func unpack16(n uint16) (a, b, c, d uint16) {
    return n & 0xf, (n >> 4) & 0xf, (n >> 8) & 0xf, (n >> 12) & 0xf
}

func pack16(a, b, c, d uint16) uint16 {
    return a | b<<4 | c<<8 | d<<12
}

type IZ struct {
    rk [9][8]uint16
    s  [8]uint16
}

func (z *IZ) Init(key [8]uint16) {
    for i := range PI {
        for j := range key {
            z.rk[i][j] = key[j] ^ pack16(
                PI[i][4*j],
                PI[i][4*j+1],
                PI[i][4*j+2],
                PI[i][4*j+3])
        }
    }
}

func (z *IZ) Encrypt(m [8]uint16) [8]uint16 {
    z.s = m
    for i := uint(0); i < ROUNDS-1; i++ {
        z.XOR(i)
        z.S()
        z.P()
    }
    z.XOR(ROUNDS - 1)
    z.S()
    z.XOR(ROUNDS)
    return z.s
}

func (z *IZ) Decrypt(m [8]uint16) [8]uint16 {
    z.s = m
    z.XOR(ROUNDS)
    z.Sinv()
    z.XOR(ROUNDS - 1)
    for i := ROUNDS - 2; i >= 0; i-- {
        z.P()
        z.Sinv()
        z.XOR(uint(i))
    }
    return z.s
}

func (z *IZ) XOR(round uint) {
    for j := range z.s {
        z.s[j] ^= z.rk[round][j]
    }
}

func (z *IZ) S() {
    for i, x := range z.s {
        a, b, c, d := unpack16(x)
        z.s[i] = pack16(S[a], S[b], S[c], S[d])
    }
}

func (z *IZ) Sinv() {
    for i, x := range z.s {
        a, b, c, d := unpack16(x)
        z.s[i] = pack16(Sinv[a], Sinv[b], Sinv[c], Sinv[d])
    }
}

func (z *IZ) P() {
    s := z.s[0] ^ z.s[1] ^ z.s[2] ^ z.s[3]
    s ^= z.s[4] ^ z.s[5] ^ z.s[6] ^ z.s[7]

    sum := s ^ (s >> 8)
    sum = sum ^ (sum >> 4)
    sum &= 0xf
    //println(sum)
    z._P(sum)
}

func (z *IZ) _P(sum uint16) {
    mask := pack16(sum, sum, sum, sum)
    for i := range z.s {
        z.s[i] ^= mask
    }
}