arxpy.differential.characteristic module

Manage characteristics.

class arxpy.differential.characteristic.ChSignatureType[source]

Bases: enum.Enum

Represent the different types of signatures available for a characteristic.

Full

the signature includes the input and all output differences of each non-linear operation.

InputOutput

the signature only includes the input and output differences

class arxpy.differential.characteristic.BvCharacteristic(func, diff_type, input_diff_names, prefix='d', initial_var2diff=None)[source]

Bases: object

Represent characteristics of bit-vector functions.

Given a bit-vector function BvFunction \(f\), a characteristic is a trail of differences obtained by propagating an input difference over \(f\).

In particular, a characteristic is composed of the input difference and the output difference of each non-linear operation.

This class manages symbolic characteristics, where the input difference is given symbolically and the intermediate differences are Term that depend on the input difference.

>>> from arxpy.bitvector.core import Variable
>>> from arxpy.differential.difference import XorDiff, RXDiff
>>> from arxpy.differential.characteristic import BvCharacteristic
>>> from arxpy.primitives.primitives import BvFunction
>>> from arxpy.primitives.chaskey import ChaskeyPi
>>> issubclass(ChaskeyPi, BvFunction)
True
>>> ChaskeyPi.set_rounds(1)
>>> ch = BvCharacteristic(ChaskeyPi, XorDiff, ["dv0", "dv1", "dv2", "dv3"])
>>> ch.ssa  
{'input_vars': (dv0, dv1, dv2, dv3),
'output_vars': (d7, d12, d13, d9),
'assignments': ((d0, dv0 + dv1), (d1, dv1 <<< 5), (d2, d0 ^ d1), (d3, d0 <<< 16), (d4, dv2 + dv3),
(d5, dv3 <<< 8), (d6, d4 ^ d5), (d7, d3 + d6), (d8, d6 <<< 13), (d9, d7 ^ d8), (d10, d2 + d4),
(d11, d2 <<< 7), (d12, d10 ^ d11), (d13, d10 <<< 16))}
>>> ch.input_diff
(XorDiff(dv0), XorDiff(dv1), XorDiff(dv2), XorDiff(dv3))
>>> ch.nonlinear_diffs 
OrderedDict([(XorDiff(d0), XDA(XorDiff(dv0), XorDiff(dv1))),
(XorDiff(d4), XDA(XorDiff(dv2), XorDiff(dv3))),
(XorDiff(d7), XDA(XorDiff(d0 <<< 16), XorDiff(d4 ^ (dv3 <<< 8)))),
(XorDiff(d10), XDA(XorDiff(d0 ^ (dv1 <<< 5)), XorDiff(d4)))])
>>> ch.output_diff 
[[XorDiff(d7), XorDiff(d7)],
[XorDiff(d12), XorDiff(d10 ^ ((d0 ^ (dv1 <<< 5)) <<< 7))],
[XorDiff(d13), XorDiff(d10 <<< 16)],
[XorDiff(d9), XorDiff(d7 ^ ((d4 ^ (dv3 <<< 8)) <<< 13))]]
>>> ch = BvCharacteristic(ChaskeyPi, RXDiff, ["dv0", "dv1", "dv2", "dv3"])
>>> ch.input_diff
(RXDiff(dv0), RXDiff(dv1), RXDiff(dv2), RXDiff(dv3))
>>> ch.nonlinear_diffs 
OrderedDict([(RXDiff(d0), RXDA(RXDiff(dv0), RXDiff(dv1))),
(RXDiff(d4), RXDA(RXDiff(dv2), RXDiff(dv3))),
(RXDiff(d7), RXDA(RXDiff(d0 <<< 16), RXDiff(d4 ^ (dv3 <<< 8)))),
(RXDiff(d10), RXDA(RXDiff(d0 ^ (dv1 <<< 5)), RXDiff(d4)))])
>>> ch.output_diff 
[[RXDiff(d7), RXDiff(d7)],
[RXDiff(d12), RXDiff(d10 ^ ((d0 ^ (dv1 <<< 5)) <<< 7))],
[RXDiff(d13), RXDiff(d10 <<< 16)],
[RXDiff(d9), RXDiff(d7 ^ ((d4 ^ (dv3 <<< 8)) <<< 13))]]
func

the BvFunction

diff_type

the Difference of the characteristic

input_diff

a list containing the input symbolic differences

nonlinear_diffs

an collections.OrderedDict mapping non-linear symbolic differences to their corresponding Derivative

output_diff

a list, where the i-th element is a pair containing the i-th output symbolic difference and its value

empirical_weight(input_diff, output_diff, pair_samples)[source]

Return the empirical weight of a given differential.

Given a differential (a pair of input and output differences), the differential probability is the fraction of input pairs with the given input difference leading to output pairs with the given output difference.

This method returns an approximation of the weight of the differential probability by sampling a given number of input pairs.

If no correct output pairs are found, math.inf is returned.

>>> from arxpy.bitvector.core import Constant
>>> from arxpy.differential.difference import XorDiff, RXDiff
>>> from arxpy.differential.characteristic import BvCharacteristic
>>> from arxpy.primitives.chaskey import ChaskeyPi
>>> ChaskeyPi.set_rounds(1)
>>> ch = BvCharacteristic(ChaskeyPi, XorDiff, ["dv" + str(i) for i in range(4)])
>>> zero, one = XorDiff(Constant(0, 32)), XorDiff(Constant(1, 32))
>>> ch.empirical_weight([zero, zero, zero, zero], [zero, zero, zero, zero], 100)
0.0
>>> ch.empirical_weight([zero, zero, zero, zero], [one, one, one, one], 100)
inf
>>> ch = BvCharacteristic(ChaskeyPi, RXDiff, ["dv" + str(i) for i in range(4)])
>>> zero, one = RXDiff(Constant(0, 32)), RXDiff(Constant(1, 32))
>>> 4 - 1 <= ch.empirical_weight([zero]*4, [zero]*4, 3 * 2**6) <= 8
True
>>> ch.empirical_weight([zero]*4, [one]*4, 3 * 2**6)
inf
signature(ch_signature_type)[source]

Return the signature of the characteristic.

The signature is a “hash” of the characteristic used for comparing.

For the type of the signature, see ChSignatureType.

>>> from arxpy.bitvector.core import Variable
>>> from arxpy.differential.difference import XorDiff
>>> from arxpy.differential.characteristic import BvCharacteristic, ChSignatureType
>>> from arxpy.primitives.primitives import BvFunction
>>> from arxpy.primitives.chaskey import ChaskeyPi
>>> issubclass(ChaskeyPi, BvFunction)
True
>>> ChaskeyPi.set_rounds(1)
>>> ch = BvCharacteristic(ChaskeyPi, XorDiff, ["dv0", "dv1", "dv2", "dv3"])
>>> ch.signature(ChSignatureType.Full)
[dv0, dv1, dv2, dv3, d0, d4, d7, d10]
>>> ch.signature(ChSignatureType.InputOutput)
[dv0, dv1, dv2, dv3, d7, d12, d13, d9]
class arxpy.differential.characteristic.SingleKeyCh(bv_cipher, diff_type)[source]

Bases: arxpy.differential.characteristic.BvCharacteristic

Represent single-key characteristics of block ciphers.

A single-key characteristic of a Cipher is a BvCharacteristic over the Encryption function of the cipher.

The plaintext differences start with the prefix "dp" and the non-linear differences start with the prefix "dx".

>>> from arxpy.bitvector.core import Variable
>>> from arxpy.differential.difference import XorDiff
>>> from arxpy.differential.characteristic import SingleKeyCh
>>> from arxpy.primitives.primitives import Cipher
>>> from arxpy.primitives import speck
>>> Speck32 = speck.get_Speck_instance(speck.SpeckInstance.speck_32_64)
>>> issubclass(Speck32, Cipher)
True
>>> Speck32.set_rounds(1)
>>> ch = SingleKeyCh(Speck32, XorDiff)
>>> ch .ssa  
{'input_vars': (dp0, dp1), 'output_vars': (dx2, dx4),
'assignments': ((dx0, dp0 >>> 7), (dx1, dp1 + dx0), (dx2, dx1 ^ k0), (dx3, dp1 <<< 2), (dx4, dx2 ^ dx3))}
>>> ch.input_diff
(XorDiff(dp0), XorDiff(dp1))
>>> ch.nonlinear_diffs
OrderedDict([(XorDiff(dx1), XDA(XorDiff(dp1), XorDiff(dp0 >>> 7)))])
>>> ch.output_diff
[[XorDiff(dx2), XorDiff(dx1)], [XorDiff(dx4), XorDiff(dx1 ^ (dp1 <<< 2))]]
empirical_weight(input_diff, output_diff, pair_samples, key_samples, precision=1, rk_diffs=None)[source]

Return the empirical weight distribution of a given differential.

This method returns a collections.Counter storing the distribution of differential probability weights over the given number of keys.

The weights are rounded to the given number of precision digits after the decimal point.

See also BvCharacteristic.empirical_weight.

>>> from arxpy.bitvector.core import Constant
>>> from arxpy.differential.difference import XorDiff
>>> from arxpy.differential.characteristic import SingleKeyCh
>>> from arxpy.primitives import speck
>>> Speck32 = speck.get_Speck_instance(speck.SpeckInstance.speck_32_64)
>>> Speck32.set_rounds(1)
>>> ch = SingleKeyCh(Speck32, XorDiff)
>>> zero, one = XorDiff(Constant(0, 16)), XorDiff(Constant(1, 16))
>>> ch.empirical_weight([zero, zero], [zero, zero], 100, 10)
Counter({0.0: 10})
>>> ch.empirical_weight([zero, zero], [one, one], 100, 10)
Counter({inf: 10})
class arxpy.differential.characteristic.RelatedKeyCh(bv_cipher, diff_type)[source]

Bases: object

Represent related-key characteristics of block ciphers.

A related-key characteristic of a Cipher is a pair BvCharacteristic, one over the KeySchedule of the cipher, and another one over the the Encryption function of the cipher, where the output differences of the key schedule characteristic are used as round key differences in the encryption characteristic.

The master key differences start with the prefix "dmk", the round key differences start with the prefix "dk", the plaintext differences start with the prefix "dp" and the non-linear differences start with the prefix "dx".

>>> from arxpy.bitvector.core import Variable
>>> from arxpy.differential.difference import XorDiff
>>> from arxpy.differential.characteristic import RelatedKeyCh
>>> from arxpy.primitives.primitives import Cipher
>>> from arxpy.primitives.lea import LeaCipher
>>> issubclass(LeaCipher, Cipher)
True
>>> LeaCipher.set_rounds(1)
>>> rkch = RelatedKeyCh(LeaCipher, XorDiff)
>>> rkch .key_schedule_ch.ssa  
{'input_vars': (dmk0, dmk1, dmk2, dmk3),
'output_vars': (dk1, dk3, dk5, dk3, dk7, dk3),
'assignments': ((dk0, 0xc3efe9db + dmk0), (dk1, dk0 <<< 1), (dk2, 0x87dfd3b7 + dmk1), (dk3, dk2 <<< 3),
(dk4, 0x0fbfa76f + dmk2), (dk5, dk4 <<< 6), (dk6, 0x1f7f4ede + dmk3), (dk7, dk6 <<< 11))}
>>> rkch.key_schedule_ch.input_diff
(XorDiff(dmk0), XorDiff(dmk1), XorDiff(dmk2), XorDiff(dmk3))
>>> rkch.key_schedule_ch.output_diff  
[[XorDiff(dk1), XorDiff(dk0 <<< 1)], [XorDiff(dk3), XorDiff(dk2 <<< 3)],
[XorDiff(dk5), XorDiff(dk4 <<< 6)], [XorDiff(dk3), XorDiff(dk2 <<< 3)],
[XorDiff(dk7), XorDiff(dk6 <<< 11)], [XorDiff(dk3), XorDiff(dk2 <<< 3)]]
>>> rkch.key_schedule_ch.nonlinear_diffs  
OrderedDict([(XorDiff(dk0), XDCA_0xc3efe9db(XorDiff(dmk0))),
(XorDiff(dk2), XDCA_0x87dfd3b7(XorDiff(dmk1))),
(XorDiff(dk4), XDCA_0x0fbfa76f(XorDiff(dmk2))),
(XorDiff(dk6), XDCA_0x1f7f4ede(XorDiff(dmk3)))])
>>> rkch.encryption_ch.ssa  
{'input_vars': (dp0, dp1, dp2, dp3),
'output_vars': (dx3, dx7, dx11, dp0),
'assignments': ((dx0, dk1 ^ dp0), (dx1, dk3 ^ dp1), (dx2, dx0 + dx1), (dx3, dx2 <<< 9),
(dx4, dk5 ^ dp1), (dx5, dk3 ^ dp2), (dx6, dx4 + dx5), (dx7, dx6 >>> 5), (dx8, dk7 ^ dp2),
(dx9, dk3 ^ dp3), (dx10, dx8 + dx9), (dx11, dx10 >>> 3))}
>>> rkch.encryption_ch.input_diff
(XorDiff(dp0), XorDiff(dp1), XorDiff(dp2), XorDiff(dp3))
>>> rkch.encryption_ch.output_diff 
[[XorDiff(dx3), XorDiff(dx2 <<< 9)], [XorDiff(dx7), XorDiff(dx6 >>> 5)],
[XorDiff(dx11), XorDiff(dx10 >>> 3)], [XorDiff(dp0), XorDiff(dp0)]]
>>> rkch.encryption_ch.nonlinear_diffs  
OrderedDict([(XorDiff(dx2), XDA(XorDiff(dp0 ^ (dk0 <<< 1)), XorDiff(dp1 ^ (dk2 <<< 3)))),
(XorDiff(dx6), XDA(XorDiff(dp1 ^ (dk4 <<< 6)), XorDiff(dp2 ^ (dk2 <<< 3)))),
(XorDiff(dx10), XDA(XorDiff(dp2 ^ (dk6 <<< 11)), XorDiff(dp3 ^ (dk2 <<< 3))))])
key_schedule_ch

the BvCharacteristic over the key schedule

encryption_ch

the BvCharacteristic over the encryption function

empirical_weight(key_input_diff, key_output_diff, key_samples, enc_input_diff, enc_output_diff, enc_samples, precision=1)[source]

Return the empirical weight of a given differential for multiple keys.

This method returns the differential probability weight for the key schedule characteristic (see BvCharacteristic.empirical_weight) and the collections.Counter storing the distribution of weights for the encryption characteristic (see SingleKeyCh.empirical_weight).

>>> from arxpy.bitvector.core import Variable, Constant
>>> from arxpy.differential.difference import XorDiff
>>> from arxpy.differential.characteristic import RelatedKeyCh
>>> from arxpy.primitives.lea import LeaCipher
>>> LeaCipher.set_rounds(1)
>>> rkch = RelatedKeyCh(LeaCipher, XorDiff)
>>> zero, one = XorDiff(Constant(0, 32)), XorDiff(Constant(1, 32))
>>> kid, kod = [zero]*4, [zero]*6
>>> eid, eod = [zero]*4, [zero]*4
>>> rkch.empirical_weight(kid, kod, 10, eid, eod, 100)
(0.0, Counter({0.0: 10}))
>>> kid, kod = [zero]*4, [one]*6
>>> eid, eod = [zero]*4, [one]*4
>>> rkch.empirical_weight(kid, kod, 10, eid, eod, 100)
(inf, Counter({inf: 10}))
signature(ch_signature_type)[source]

Return the signature of the related-key characteristic.

The signature of a related-key characteristic is the concatenation of the key schedule and encryption signatures.

See also BvCharacteristic.signature.

>>> from arxpy.bitvector.core import Variable
>>> from arxpy.differential.difference import XorDiff
>>> from arxpy.differential.characteristic import RelatedKeyCh, ChSignatureType
>>> from arxpy.primitives.primitives import Cipher
>>> from arxpy.primitives.lea import LeaCipher
>>> LeaCipher.set_rounds(1)
>>> rkch = RelatedKeyCh(LeaCipher, XorDiff)
>>> rkch.signature(ChSignatureType.Full)  
[dmk0, dmk1, dmk2, dmk3, dk0, dk2, dk4, dk6, dp0, dp1, dp2, dp3, dx2, dx6, dx10]
>>> rkch.signature(ChSignatureType.InputOutput)  
[dmk0, dmk1, dmk2, dmk3, dk1, dk3, dk5, dk3, dk7, dk3, dp0, dp1, dp2, dp3, dx3, dx7, dx11]