Example #1
0
def check_affine_equivalence(f, g, A, a, B, b):
    """Checks whether f(x) = (B o g o A)(x + a) + b"""
    for x in xrange(0, 2**N):
        y = oplus(x, a)
        y = apply_bin_mat(y, A)
        y = g[y]
        y = apply_bin_mat(y, B)
        y = oplus(y, b)
        if y != f[x]:
            return False
    return True
Example #2
0
def affine_equivalence(f, g):
    """Returns, if it exists, the tuple A, a, B, b where A and B are
    matrices and where a and b are integers such that, for all x:

    f(x) = (B o g o A)(x + a) + b,

    where "o" denotes functional composition and "+" denotes XOR. If
    no such affine permutations exist, returns an empty list.

    Internally calls a function written in C++ for speed which returns
    the "Linear Representative" using an algorithm from

    Alex Biryukov, Christophe De Canniere, An Braeken, and Bart
    Preneel (2003).  "A Toolbox for Cryptanalysis: Linear and Affine
    Equivalence Algorithms", Advances in Cryptology -- EUROCRYPT 2003,
    Lecture Notes in Computer Science 2656, E. Biham (ed.),
    Springer-Verlag, pp. 33--50, 2003.

    """
    if len(f) != len(g):
        raise "f and g are of different dimensions!"
    table_f = defaultdict(list)
    table_c = defaultdict(int)
    for c in xrange(0, len(f)):
        f_c = le_class_representative([oplus(f[x], c) for x in xrange(0, len(f))])
        d = hash_sbox(f_c)
        table_f[d] = f_c
        table_c[d] = c
    rs = []
    a = -1
    b = -1    
    for c in xrange(0, len(f)):
        g_c = le_class_representative([g[oplus(x, c)] for x in xrange(0, len(f))])
        d = hash_sbox(g_c)
        if d in table_c.keys():
            a = c
            b = table_c[d]
            rs = g_c
            break
    if a == -1:
        return []
    l_f = linear_equivalence([oplus(f[x], b) for x in xrange(0, len(f))],
                             rs)
    A_f, B_f = l_f[0], l_f[1]
    l_g = linear_equivalence([g[oplus(x, a)] for x in xrange(0, len(f))],
                             rs)
    A_g, B_g = l_g[0], l_g[1]
    A = A_g.inverse() * A_f
    B = B_f * B_g.inverse()
    a = apply_bin_mat(a, A.inverse())
    return [A, a, B, b]
Example #3
0
def test_ae_equivalence(N, verbose=False):
    from timeit import default_timer
    false_negatives = 0
    false_positives = 0
    n_tested = 10
    for i in xrange(0, n_tested):
        # checking if linearly equivalent permutations are identified
        f = random_permutation(N)
        A = rand_linear_permutation(N)
        a = randint(0, 2**N-1)
        B = rand_linear_permutation(N)
        b = randint(0, 2**N-1)
        g = [oplus(apply_bin_mat(f[apply_bin_mat(oplus(x, a), A)], B), b)
             for x in xrange(0, 2**N)]
        computation_start = default_timer()
        result = affine_equivalence(f, g)
        computation_end = default_timer()
        elapsed = computation_end - computation_start
        if len(result) > 1:
            if not check_affine_equivalence(f, g, result[0], result[1], result[2], result[3]):
                false_negatives += 1
                if verbose:
                    print "[FAIL] wrong affine permutations"
            else:
                if verbose:
                    print "[success]     AE {:0.4f}".format(elapsed)

        else:
            false_negatives += 1
            if verbose:
                print "[FAIL]     AE {:0.4f}s (nothing found)".format(elapsed)
        # checking if non-affine equivalent functions are identified
        g = random_permutation(N)
        result = affine_equivalence(f, g)
        if len(result) == 0:
            if verbose:
                print "[success] non-AE {:0.4f}s".format(elapsed)
        else:
            if check_affine_equivalence(f, g, result[0], result[1], result[2], result[3]):
                if verbose:
                    print "[success] act.AE {:0.4f}".format(elapsed)
            else:
                false_positives += 1
                if verbose:
                    "[FAIL] matrices found for non-LE permutations"
    print "* testing if AE functions are identified correctly (with correct affine permutations)"
    print_result(n_tested-false_negatives, n_tested)
    print "* testing if NON-LE functions are identified correctly"
    print_result(n_tested-false_positives, n_tested)
Example #4
0
def xor_equivalence(f, g):
    """Returns a pair [k0, k1] of integers such that, for all x:

    f[x] = g[x + k0] + k1,

    where "+" denotes the bitwise XOR.
    """
    N = int(log(len(f), 2))
    for k0 in xrange(0, 2**N):
        k1 = oplus(f[0], g[k0])
        good = True
        for x in xrange(1, 2**N):
            if oplus(f[x], g[oplus(k0, x)]) != k1:
                good = False
                break
        if good:
            return [k0, k1]
    return []
Example #5
0
    def __call__(self, x):
        """Returns the result of applying L to the integer x, intepreting it
        as a binary vector.

        """
        result = 0
        for i in xrange(0, len(self.masks)):
            if (x >> i) & 1 == 1:
                result = oplus(result, self.masks[i])
        return result
Example #6
0
def partial_linear_permutation_to_full(v, n):
    """Returns a matrix corresponding to a linear permutation of n bits
    mapping any x < len(v) to v[x].

    """
    if len(v) > 2**n:
        raise "n should be at least as big as the dimension of the state"
    if v[0] != 0:
        raise "v should start with 0"
    u = int(log(len(v), 2))
    if len(v) != 2**u:
        raise "size of v should be a power of 2"
    basis = []
    rebuilt_space = [0]
    # finding a basis of v
    for new_vector in v:
        if new_vector not in rebuilt_space:
            basis.append(new_vector)
            new_rebuilt_space = list(rebuilt_space)
            for x in rebuilt_space:
                new_rebuilt_space.append(oplus(x, new_vector))
            rebuilt_space = new_rebuilt_space
        if len(basis) == u:
            break
    # completing the basis
    while len(rebuilt_space) < 2**n:
        new_vector = 0
        while new_vector in rebuilt_space:
            new_vector += 1
        basis.append(new_vector)
        new_rebuilt_space = list(rebuilt_space)
        for x in rebuilt_space:
            new_rebuilt_space.append(oplus(x, new_vector))
        rebuilt_space = new_rebuilt_space
    result = Matrix(GF(2), n, n, [[(b >> (n - 1 - i)) & 1
                                   for i in xrange(0, n)]
                                  for b in reversed(basis)]).transpose()
    check = linear_function_matrix_to_lut(result)
    if check[0:len(v)] == v:
        return result
    else:
        raise "no such matrix"
Example #7
0
def linear_span(basis, with_zero=True):
    result = []
    if with_zero:
        result.append(0)
    visited = defaultdict(int)
    visited[0] = 1
    for i in xrange(1, 2**len(basis)):
        x = 0
        for j in xrange(0, len(basis)):
            if (i >> j) & 1 == 1:
                x = oplus(x, basis[j])
        if visited[x] != 1:
            result.append(x)
            visited[x] = 1
    return result
Example #8
0
def extract_direct_sum(list_of_spaces, N):
    space_pairs = []
    for va, vb in itertools.combinations(list_of_spaces, 2):
        # basic tests
        good = True
        if len(va[0]) * len(vb[0]) < 2**N:
            good = False
        if good:
            if len(va[1]) * len(vb[1]) < 2**N:
                good = False
        if good:
            if rank_of_vector_set(va[0] + vb[0], N) != N:
                good = False
        if good:
            if rank_of_vector_set(va[1] + vb[1], N) != N:
                good = False
        if good:
            # basic tests passed
            # more complicated cases
            # -- the y coordinate is good but the x coordinate is too big
            if len(va[1]) * len(
                    vb[1]) == 2**N and len(va[0]) * len(vb[0]) > 2**N:
                if len(vb[0]) > len(va[0]):  # case where vb needs shringking
                    # print "1", va[0], vb[0]
                    new_vb0_indicator = defaultdict(int)
                    for x in vb[0]:
                        new_vb0_indicator[x] = 1
                    for v in va[0]:
                        if v != 0:
                            for x in vb[0]:
                                new_vb0_indicator[oplus(x, v)] = 0
                    new_b0 = [
                        x for x in new_vb0_indicator.keys()
                        if new_vb0_indicator[x] == 1
                    ]
                    vb[0] = new_b0
                elif len(va[0]) > len(vb[0]):  # case where va needs shringking
                    # print "2"
                    new_va0_indicator = defaultdict(int)
                    for x in va[0]:
                        new_va0_indicator[x] = 1
                    for v in vb[0]:
                        if v != 0:
                            for x in va[0]:
                                new_va0_indicator[oplus(x, v)] = 0
                    new_a0 = [
                        x for x in new_va0_indicator.keys()
                        if new_va0_indicator[x] == 1
                    ]
                    va[0] = new_a0
            # -- the x coordinate is good but the y coordinate is too big
            if len(va[0]) * len(
                    vb[0]) == 2**N and len(va[1]) * len(vb[1]) > 2**N:
                if len(vb[1]) > len(va[1]):  # case where vb needs shringking
                    # print "3"
                    new_vb1_indicator = defaultdict(int)
                    for x in vb[1]:
                        new_vb1_indicator[x] = 1
                    for v in va[1]:
                        if v != 0:
                            for x in vb[1]:
                                new_vb1_indicator[oplus(x, v)] = 0
                    new_b1 = [
                        x for x in new_vb1_indicator.keys()
                        if new_vb1_indicator[x] == 1
                    ]
                    vb[1] = new_b1
                elif len(va[1]) > len(vb[1]):  # case where va needs shringking
                    # print "4"
                    new_va1_indicator = defaultdict(int)
                    for x in va[1]:
                        new_va1_indicator[x] = 1
                    for v in vb[1]:
                        if v != 0:
                            for x in va[1]:
                                new_va1_indicator[oplus(x, v)] = 0
                    new_a1 = [
                        x for x in new_va1_indicator.keys()
                        if new_va1_indicator[x] == 1
                    ]
                    va[1] = new_a1
            # simple case: all spaces are just the right size
            if len(va[0]) * len(vb[0]) == 2**N and len(va[1]) * len(
                    vb[1]) == 2**N:
                space_pairs.append([va, vb])
    return space_pairs
Example #9
0
def extract_affine_bases(z,
                         dimension,
                         word_length,
                         n_threads=DEFAULT_N_THREADS,
                         number="fixed dimension"):
    """Returns a list containing the Gaussian Jacobi basis of each affine
    space of dimension `dimension` that is contained in the list `z` of
    integers intepreted as elements of $\F_2^n$ where $n$ is equal to
    `word_length`.

    The number of threads to use can be specified using the argument
    `n_threads`.

    It can have 3 different behaviours depending on the value of the
    argument `number`:

    - if it is "just one" then it will stop as soon as it has found an
      affine space of the desired dimension and return its basis;

    - if it is "fixed dimension" then it will return all affine spaces
      with exactly the given dimension, even if they are affine
      subspaces of a larger space contained in `z`; and

    - if it is "all dimensions" then it will return all vector spaces
      of dimensions at least `dimension`. If a larger vector space is
      found, its bases will be return and its subspaces will be
      ignored.

    """
    if number not in ["all dimensions", "fixed dimension", "just one"]:
        raise Exception(
            "Unknown value for parameter `number` in extract_affine_bases:" +
            number)
    result = extract_affine_bases_fast(z, int(dimension), int(word_length),
                                       int(n_threads), str(number))
    if number == "all dimensions":
        # in the case where we have obtained larger spaces, we remove
        # their subspaces from the list
        bigger_affine = [[oplus(b[0], x) for x in linear_span(b[1:])]
                         for b in result if len(b) > dimension + 1]
        if len(bigger_affine) == 0:
            # nothing to do as there is no bigger space
            return result
        else:
            new_result = list([b for b in result if len(b) > dimension + 1])
            for b in result:
                if len(b) == dimension + 1:
                    aff = [oplus(b[0], v) for v in linear_span(b[1:])]
                    is_included_in_bigger = False
                    for big_space in bigger_affine:
                        is_included = True
                        for v in aff:
                            if v not in big_space:
                                is_included = False
                                break
                        if is_included:
                            is_included_in_bigger = True
                            break
                    if not is_included_in_bigger:
                        new_result.append(b)
            return new_result
    else:
        return result