Title : A Dynamic Polyalphabetic Substitution Cipher
Author : veins
==Phrack Inc.==
Volume 0x0b, Issue 0x3e, Phile #0x0e of 0x10
|=---------=[ A Dynamic Polyalphabetic Substitution Cipher ]=------------=|
|=-----------------------------------------------------------------------=|
|=----------------=[ Veins <veins at tristeza.org> ]=--------------------=|
1 - Introduction
1.1 - First of all, a reminder. What is polyalphabetic substitution ?
1.2 - Weaknesses in polyalphabetic substitution
2 - IMPLEMENTATION OF DPA-128
2.1 - DPA-128 used as a one way hash function
2.2 - DPA-128 used as PRNG
3 - Acknowledgment
4 - References
5 - Source Code
--[ 1 - Introduction
In Phrack #51, mythrandir discussed the cryptanalysis of monoalphabetic
ciphers and basic substitutions and transpositions. This paper discusses a
different substitution known as 'polyalphabetic substitution' and how some
mechanisms can be implanted to take advantage of its characteristics. This
document will then focus on 'dynamic polyalphabetic substitution' which is
an evolution of polyalphabetic substitution with key-dependant s-tables.
A "functional-but-still-work-in-progress" cipher will be presented. It is
a 128-bits secret-key block cipher that uses polyalphabetic s-tables which
are highly dependant of the key. The cipher, DPA-128, consists in a simple
function that makes 3 operations on the block. It is not a Feistel network
but still respects Shannon's principles of diffusion and confusion. It has
only been reviewed by a few people, so I strongly discourage its use as it
has not proven anything yet. However, if you use it and have any comments,
I'd be glad to hear from you, but remember, do not encrypt sensitive stuff
cause someone will probably come, break the cipher and go spreading all of
your secrets on IRC ;)
Finally, just to clarify a few things. I use the acronym DPA (for "dynamic
polyalphabetic algorithms") in this document to refer to key dependancy in
polyalphabetic substitution. I've seen people using the term "dynamic" for
ciphers that used polyalphabetic substitution in a mode that uses a pseudo
random vector (CBC for example). While I'll keep using the acronym, assume
that key-dependant substitution works in total abstraction of the mode and
DPA-128 has an implementation of both ECB and CBC modes as I'm writing.
Also, while I have not seen a dynamic polyalphabetic cipher implementation
it does not mean that all of the ideas in this paper are new. DES had some
variants that performed key-dependant substitutions by exchanging lines of
s-tables, and several ciphers use one-way hash functions for subkeys.
----[ 1.1 - First of all, a reminder. What is polyalphabetic substitution ?
Polyalphabetic substitution is an hybrid between transposition and
substitution.
Transposition consists in reordering the characters in the plaintext to
produce the cipher:
Assume my secret message is:
THIS IS MY SECRET MESSAGE DONT READ IT, ARAM SUCKS
After transposing it in a 8 columns table, it becomes:
T H I S I S M Y
S E C R E T M E
S S A G E D O N
T R E A D I T A
R A M S U C K S
The cipher is produced by reading the columns instead of the lines:
TSSTR HESRA ICAEM SRGAS IEEDU STDIC MMOTK YENAS
While substitution consists in interchanging the characters in the
plaintext to produce the cipher:
Assume my secret message is:
THIS IS ANOTHER ATTEMPT TO PRESERVE MY PRIVACY
Substitution alphabet is a simple rearrangement:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Y Z W X U V S T Q R O P K L M N I J G H E F C D A B
The cipher is produced by replacing the letter in plaintext by the new
letter in the rearranged alphabet:
HTQG QG YLMHTUJ YHHUKNH HM NJUGUJFU KA NJQFYWA
Note that both these methods do not even require a key, the parties that
wish to share the secret, have to share the "protocol" which is the
number of columns for the transposition, or the rearranged alphabet for
the substitution. In practice, there are methods to use keys with both
substitution and transposition but in the end, both are insecure with or
without a key. I won't go through the description of how you can break
these, the methods were described in phrack #51 if I recall correctly
and they are so simple that some tv magazines use these on their game
pages.
Now let's get back to polyalphabetic substitution.
A transposed substitution table looks like this more or less:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
B C D E F G H I J K L M N O P Q R S T U V W X Y Z A
C D E F G H I J K L M N O P Q R S T U V W X Y Z A B
D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
E F G H I J K L M N O P Q R S T U V W X Y Z A B C D
F G H I J K L M N O P Q R S T U V W X Y Z A B C D E
G H I J K L M N O P Q R S T U V W X Y Z A B C D E F
H I J K L M N O P Q R S T U V W X Y Z A B C D E F G
I J K L M N O P Q R S T U V W X Y Z A B C D E F G H
J K L M N O P Q R S T U V W X Y Z A B C D E F G H I
K L M N O P Q R S T U V W X Y Z A B C D E F G H I J
L M N O P Q R S T U V W X Y Z A B C D E F G H I J K
M N O P Q R S T U V W X Y Z A B C D E F G H I J K L
N O P Q R S T U V W X Y Z A B C D E F G H I J K L M
O P Q R S T U V W X Y Z A B C D E F G H I J K L M N
P Q R S T U V W X Y Z A B C D E F G H I J K L M N O
Q R S T U V W X Y Z A B C D E F G H I J K L M N O P
R S T U V W X Y Z A B C D E F G H I J K L M N O P Q
S T U V W X Y Z A B C D E F G H I J K L M N O P Q R
T U V W X Y Z A B C D E F G H I J K L M N O P Q R S
U V W X Y Z A B C D E F G H I J K L M N O P Q R S T
V W X Y Z A B C D E F G H I J K L M N O P Q R S T U
W X Y Z A B C D E F G H I J K L M N O P Q R S T U V
X Y Z A B C D E F G H I J K L M N O P Q R S T U V W
Y Z A B C D E F G H I J K L M N O P Q R S T U V W X
Z A B C D E F G H I J K L M N O P Q R S T U V W X Y
This is known as the "Vigenere Table", because it was invented by Blaise
Vigenere (a French mathematician, if you care). Unlike transposition and
substitution, a key is required.
Assume my secret key is:
BLEH
Assume my secret message is:
LAST ATTEMPT
The ciphertext is the intersection of each character of the secret key with
each character of the secret message. Key characters are seeked on the very
first line, and message characters are seeked on the very first column.
Since the key is shorter than the message, it is padded with itself so that
it becomes:
BLEHBLEHBLE
If it was longer, then either the message would be padded with random crap
or the key would be truncated (this is more common).
The cipher is obtained by replacing a letter in the message by the
intersection of current message character and current key character in the
table. The secret message becomes:
MLWABUXLNAX
As you may notice, even though a character may appear two or more times in
the plaintext, it is not encrypted the same way in the ciphertext depending
on which character from the key is used for the substitution. This is what
"polyalphabetic" means in "polyalphabetic substitution". It was known for a
while as the "unbreakable cipher" and a variant was used successfuly during
Second World War by the Resistance against the Germans.
While this sounds stronger than transposition and substitution, it is still
very weak and unless a RNG is used to generate a key that is as long as the
data to encrypt (one-time pad), it is possible to recover the key size, the
key itself and/or the message with enough data to analyze. The methods used
to break this kind of cipher is out of the scope of this paper but a search
on google will give you enough hints ;)
Polyalphabetic tables have three interesting properties:
a) f(a, b) == c
f(a, b) == f(b, a)
but... f(a, c) != b
and assuming c = f(a, b), then there is a f1 such as f1(a, c) == b
b) using the ASCII layout, there are 256^2 combinations which will
produce 256 possible results (including the original character).
c) if we assume that the key is truly random AND has the same size
as the message to encrypt, then all results are equiprobable.
and equiprobability means that:
- if you only take one character in the ciphertext, then you have as
many chances for it to be any cleartext character. They all appear
the exact same number of times in the table and are the result of
as many combinations each.
- there is no "useless" substitution. If substitution of character 'A'
results in character 'A', then it is not considered as a useless
substitution as it had as many chances to be out than any other.
----[ 1.2 - Weaknesses in polyalphabetic substitution
As I previously said, the above cipher is weak. The weaknesses are
numerous and mostly related to the cipher leaking informations about the
cleartext, the key and/or the substitution table. If one can encrypt data
using the same key, he can determine the size of the key with one message
and determine the structure of the substitution table with another, giving
him all the necessary information to understand the ciphertext and any
other ciphertext encrypted using the same key. But don't get this wrong,
he doesn't HAVE to be able to encrypt data, this is just a convenience ;)
The fact that the key is concatenated to itself does not make a change,
and actually an implementation on computer would work on data using a
modulo on the size of the key to save memory.
The reasons why it is so easy are described here:
- if one chooses a key A and a key B such as they only differ by
one bit, then the ciphertext will only differ by one byte.
- if one chooses a message A and a message B such as they also
only differ by one bit, then the ciphertext will differ by one
byte.
- if one changes one bit in ciphertext and decrypts it, the
resulting message will only differ by one byte.
- if one has partial knowledge of the key, or of the message,
then he can determine which substitutions are not probable and
therefore reduce drastically the complexity of an attack. Also
partial knowledge of the key or the message gives statistical
analysis a chance to break the ciphertext when polyalphabetic
substitution had all the characteristics needed to prevent that
from happening.
So... let's sum things up. Polyalphabetic substitution as described above
is vulnerable to chosen texts attack, known texts attack, key correlation
attack and eventually statistical attacks. Oh... almost forgot... any
partial information reveals information about other unrelated data. If I
partially know the plaintext, then with access to the ciphertext I am
able to recover partially the key, with partial knowledge of the key and
access to the ciphertext, i am able to recover partially the plaintext.
There is not one point of failure, there are only points of failures...
----[ 1.3 - Theory of information
Shannon described two properties that a block cipher should have
in order to be strong. Not that all ciphers respecting these are strong,
but those that do not respect it are most likeley weak.
These properties are 'confusion' and 'diffusion'. The first one is what
we achieve with polyalphabetic substitution, incapacity to deduce from a
single encrypted byte, with no other information, the result of which
substitution it is. This is because of the equiprobabiliy polyalphabetic
tables gives us. The second is diffusion, which is lacking from the
above cipher, and one of the reason why it is so vulnerable.
Diffusion is a characteristic where a minor cause produces a major
result. It is sometimes called 'cascading effect'.
Basically, diffusion should ensure that a one-bit change in the key
alters the whole ciphertext, that a one-bit change in the plaintext
also alters the whole ciphertext and that a one-bit change in the
ciphertext alters the whole plaintext. This complete alteration is
only in appearance, and a better look at the complete ciphertext
would reveal an average of half bits modified as you'll notice in the
output of `bitdiff` later in this paper.
While it is difficult to decide wether or not a cipher has a
correct confusion and diffusion, they both produce an entropic result that
can be measured using several methods. These methods will be used in this
paper but explained further in the references. A cipher not producing true
entropy is weak, true entropy (== white noise).
One way to add confusion is to ensure that the ciphertext is not dependant
of the key on a character basis. Changing one bit of the key should change
the whole ciphertext. This can be achieved by the use of a one-way hash
function for key generation. Some one-way hash functions have properties
that make them suitable for use, these are:
h = f(M)
- no matter the length of M, h has a fixed size
- assuming M, it should be easy to compute h
- assuming h, it should be difficult to compute M
- a one-bit change in M alters half the bits in h (in average).
- it should be hard to find a M' to a fixed M such as f(M) = f(M')
- it should be hard to find any M and M' such as f(M) = f(M')
The two last properties seem to be identical but in practice it is "easier"
to produce a random collision, than to produce a collision for an expected
output. Assuming h is 128-bits long, finding a particular collision takes
at most 2^128 tries, while finding a collision takes at most 2^64 tries.
This is known as the anniversary attack.
The use of such a function will make key correlation hardly practicable as
choosing two keys that have a relation will result in subkeys that have no
relation at all, even if the relation is a single bit difference. I am not
even mentionning attacks based on partial knowledge of the key ;)
Also, this will prevent users from choosing weak keys, purposedly or not,
as it will be difficult to find a key that will produce a weak key
(assuming that there are weak keys ;) once passed throught the one-way hash
function. By weak key, I do not mean keys like "aaaa" or "foobar", but keys
that will produce a subkey that introduces a weakness in the encryption
process (such as DES's four weak keys).
The function not being reversible, partial knowledge of plaintext and
access to ciphertext does not reveal the key but the subkey from which you
cannot obtain information about the key. If the algorithm iterates for
several rounds, it is possible to generate subkeys by calling f on previous
subkey:
round1: f(k)
round2: f(f(k))
round3: f(f(f(k)))
and so on...
Note that there is nothing that prevents an implementation from precomputing
the subkeys for better performances (this is what my implementation does)
instead of computing them for each block.
The characteristics remains, knowing the subkey for round3 does not give
information about the subkey used for round2 or round1. That is one of the
failure points plugged ;)
Finally, this will increase confusion by creating a relation between each
single bit of the user input key and each byte of the ciphertext.
Unfortunately, this is not enough. We added confusion but even though it
is theoritically not possible to retrieve the key, even by having access
to the full message and the full ciphertext, it is still possible with a
partial knowledge to retrieve the subkey and to decrypt any data that is
encrypted with the original key. This is where diffusion comes into play
with a method called 'byte chaining'. Byte chaining is to a block what
block chaining is to a ciphertext, a propagation of changes which will
affect all subsequent data. This is done with a simple Xor, where each
byte of the block is xor-ed with the next one (modulo size of the block
to have the last one be xor-ed with the first one). That way, a single
bit change in a byte will have repercussion on every byte after that one.
If the function used to encrypt data is called for more than one round,
then all bytes are guaranteed to have been altered by at least one byte.
This operation is done before encryption so that the result of an
encrypted byte is dependant not only of the current byte but of all the
ones that were used for the byte chaining. As rounds occur, cascading
effect takes place and the change propagates through the block.
It is possible to increase complexity by using a key-related permutation
before encryption. DPA-128 uses a key-related shifting instead but this
can be considered as a permutation in some way. Some functions known as
'string hash functions' can compute an integer value out of a string.
They are commonly used by C developpers to create hash tables and they
are pretty simple to write. It is possible to use these functions on a
subkey to create a key-related circular shifting within the block:
- we have a subkey for the round that we computed using f, this subkey
is hashed to produce an integer. the hash function does not have to
respect any constraints because of f properties. the paranoiac could
implement a function that has low collisions and a nice repartition
but since it is applied on the result of f, it inherits some of its
characteristics.
- assuming the block size is 128, we reduce that integer to 128
(7 bits) there is no magic stuff here, just a modulo.
- the result is used to shift the block circularly >>>
Note that the key-relation for the shifting has no more security than
a simple byte shifting - at least on Vigenere table - but only adds
more confusion. It was initially introduced as a security measure for
substitution tables that had not equiprobable results.
It prevents elimination of some substitution combinations by analyzing
which bits are set in an encrypted byte when you know its plaintext
equivalent. From the ciphertext, it is not possible to determine wether
a block was shifted (the hash value of the key could have been 0 or some
product of 128, who knows ;) and if it was shifted, it is not possible to
know where the bits come from (which byte they were on originally and
what was their position) which makes it difficult to determine if the
bit of sign on a particular byte is really a bit of sign or not and if it
was part of that byte or not. Also, the shifting is dependant from the
subkey used for a round so it will be different at each round and help
diffusion through the byte chaining phase.
Finally, it is possible, using the same method, to create a relation
between a subkey and the substitution table. This is where dynamic
polyalphabetic substitution comes into play !
As we've seen, a polyalphabetic substitution has 256^2 substitutions
with 256 outcomes. This means that if an attacker would want to try
all combinations possible, he would have to try 256 combinations
for a character to be sure the right couple was used (if he knew the
structure of the substitution table, or 256^2 otherwise). It is
possible to increase that value by creating a relation between the key
and the substitution table. There are 256 characters, so it is possible
to create 256 different tables by shifting ONE byte on each line:
instead of:
0 1 2 3 4 5 6 7 8 9 ...
1 2 3 4 5 6 7 8 9 ...
2 3 4 5 6 7 8
3 4 5 6 7 8
4 5 6 7 8
5 ....
...
we end with (n being the shift):
n%256 (n+1)%256 (n+2)%256 (n+3)%256 (n+4)%256 (n+5)%256 ...
(n+1)%256 (n+2)%256 (n+3)%256 (n+4)%256 (n+5)%256 ...
(n+2)%256 (n+3)%256 (n+4)%256 (n+5)%256 (n+6)%256
(n+3)%256 (n+4)%256 (n+5)%256 (n+6)%256 (n+7)%256
(n+4)%256 (n+5)%256 (n+6)%256 (n+7)%256 (n+8)%256
(n+5)%256 (n+6)%256 (n+7)%256 (n+8)%256 (n+9)%256
(n+6)%256 ...
...
This means that an attacker would need to try 256^2 combinations
before he knows for sure the right combination was used. he needs to
try the same combinations as before but with every variation of 'n'.
'n' can be computed using the same method as for the block shifting
but since there are 256 possible shifts for the substitution table,
then the result will be reduced modulo 256 (8 bits).
The tables being structured in a logical way, they can be represented
by arithmetics which removes the need to store the 256 possible tables
and saves quite a bit of memory. It is also possible with more work to
create polyalphabetic s-tables that are shuffled instead of shifted,
such tables would still share the characteristics of polyalphabetism
but prevent partial knowledge of the table from deducing the full
internal structure. I did not have enough time to keep on working on
this so I am unable to give an example of these, however, simple
tables such as the one above is sufficient in my opinion.
k being the character from the key, d being the character from the
message and s being the shifting.
encryption can be represented using this equation:
(k + d + s) mod 256
while decryption is:
((256 + d) - k - s) mod 256
The amusing part is that when you play with statistics, you get a very
different view if you are in the position of the attacker or of the
nice guy trying to keep his secret. Assuming there are 'n' rounds, then
you have (256^2) * m substitutions useable where 1 <= m <= n and n <= 256.
This is because some subkeys might produce identical substitution tables.
In another hand, and im not doing the maths for this ;), the attacker has
not only to figure out which substitutions were done, but also the tables
in which they were done... in the exact same order... out of data that
does not inform him on the subkeys used to generate the tables he is
trying to determine the structure of ;)
The result is NOT equiprobable, because it would require exactly 256
rounds with different tables which is hardly doable (just determining
if it is doable requires trying 2^128^256 keys if im correct), but
from the attacker point of view, even an exhaustive search might
create an indecision because many keys will probably result in the
same cipher if applied to different messages (many will produce the
same cipher if applied to garbage too ;).
--[ 2 - IMPLEMENTATION OF DPA-128
As I said, DPA-128 is a secret-key block cipher. Its block and key size
are 128-bits. This is not a limitation imposed by the algorithm which
is easily adaptable to different key and block sizes. It consists of
16 rounds, each performing:
- a byte chaining;
- a subkey-related shifting;
- a subkey-related polyalphabetic substitution;
All of the rounds have their own subkey.
The implementation uses all of the ideas explained in this paper and
before I provide the code, here are a few tests performed on it.
----[ 2.1 - DPA-128 used as a one way hash function
Bruce Schneier explained in "Applied Cryptography" that some ciphers can
be turned into one way hash functions by using them in BC modes (CBC for
that matter) using a fixed key and initialization vector with more or
less efficiency. It is hard to determine if DPA-128 is efficient because
it was not been analyzed by many people and I consider it as efficient
to produce checksums as to encrypt. If there is a weakness in the cipher
then the checksum will not be secure. The same applies to DPA-128 used
as a PRNG. So... I did some testing ;)
I used three tools, the first one 'bitdiff' is a little utility that goes
through two files and compares them bit per bit. It then outputs the
number of bits that have changed and the repartition of zero's and one's.
A sample output looks like this:
% ./bitdiff file1 file2
64 bits have changed.
ratio for file1:
0's: 55
1's: 73
ratio for file2:
0's: 71
1's: 57
I also used a tool 'segments', which counts segments of identical bits in
a file. A sample output looks like this:
% ./segments file1
0's segments:
1 => 19
2 => 6
3 => 4
4 => 0
1's segments:
1 => 13
2 => 7
3 => 3
4 => 3
Finally, I used an existing tool called 'ent' which is available at
http://www.fourmilab.ch/random/
which performs several entropy tests, helping determine:
- if DPA-128 passes deterministic tests and how does it compare to a
PRNG (I used NetBSD's /dev/urandom).
- what is the impact to a checksum when a bit changes in a file.
Theoritically, an equiprobable cipher would not be a nice idea for a
one-way hash function as it would be easily subject to collisions, but
as I explained, the result seems to be equiprobable while there is a
limited range of possible substitution for a fixed key.
I checksum-ed /etc/passwd three times, the first one was the real file,
the second one was the file with a one bit change and the third one was
the file with a 6 bytes addition. All bytes where affected, tests with
bitdiff showed that a one bit change produced an average of 60 bits
modified in the 128 bits checksum.
% ./dpa sum passwd |hexdump
0000000 be85 3b72 1a76 48e6 5d08 939b 104f 3f23
0000010
% ./dpa sum passwd.1 | hexdump
0000000 f9d3 c5fe d146 2170 144d 900d 0e99 c64b
0000010
% ./dpa sum passwd.2 | hexdump
0000000 fa19 4869 3f61 798a 2e81 91e9 bc92 78ee
0000010
After i redirected the checksums to files, i call bitdiff on them. The
files do not contain the hexadecimal representation, but the real
128 bits outputs:
% ./bitdiff passwd.chk passwd.1.chk
63 bits have changed.
ratio for passwd.chk:
0's: 65
1's: 63
ratio for passwd.1.chk:
0's: 68
1's: 60
% ./bitdiff passwd.chk passwd.2.chk
61 bits have changed.
ratio for passwd.chk:
0's: 65
1's: 63
ratio for passwd.2.chk:
0's: 64
1's: 64
You'll notice a nice repartition of zero's and one's, lets' see what
segments has to say about this:
% ./segments passwd.chk
0's segments:
1 => 13
2 => 10
3 => 3
4 => 2
1's segments:
1 => 15
2 => 4
3 => 5
4 => 0
% ./segments passwd.1.chk
0's segments:
1 => 11
2 => 8
3 => 5
4 => 3
5 => 0
1's segments:
1 => 13
2 => 9
3 => 2
4 => 0
5 => 1
% ./segments passwd.2.chk
0's segments:
1 => 12
2 => 10
3 => 3
4 => 1
5 => 0
1's segments:
1 => 16
2 => 3
3 => 4
4 => 3
5 => 1
Well all we can notice is that there are mostly small segments and that
they are well reparted. I'm skipping the entropy test since it will
illustrate the use of DPA-128 as a PRNG ;)
----[ 2.2 - DPA-128 used as PRNG
For the following tests concerning segments and entropy:
- the file 'urandom.seed' consists in 1024 bytes read from NetBSD 1.6.1's
/dev/urandom
- the file 'dpa.seed' consists in the result of an ECB encryption on dpa's
main.c and a reduction of the output to 1024 bytes.
This means that while tests on urandom.seed apply to the result of a PRNG,
the tests on dpa.seed can be reproduced. It shows good entropy on
encrypting a fixed value and the results should be quite the same if used
as a PRNG. The tests that are performed by 'ent' are described on their
website, I'm not going to describe them here because it is out of the
scope of this paper and I would do it far less better than their
page does.
% ./segments urandom.seed
0's segments:
1 => 1019
2 => 418
3 => 212
4 => 88
5 => 35
6 => 18
1's segments:
1 => 1043
2 => 448
3 => 179
4 => 74
5 => 32
6 => 13
% ./segments dpa.seed
0's segments:
1 => 1087
2 => 443
3 => 175
4 => 72
5 => 29
6 => 18
1's segments:
1 => 1039
2 => 453
3 => 195
4 => 67
5 => 34
6 => 15
% ./ent -b urandom.seed
Entropy = 0.999928 bits per bit.
Optimum compression would reduce the size
of this 8192 bit file by 0 percent.
Chi square distribution for 8192 samples is 0.82, and randomly
would exceed this value 50.00 percent of the times.
Arithmetic mean value of data bits is 0.4950 (0.5 = random).
Monte Carlo value for Pi is 3.058823529 (error 2.63 percent).
Serial correlation coefficient is -0.002542 (totally uncorrelated = 0.0).
% ./ent -b dpa.seed
Entropy = 1.000000 bits per bit.
Optimum compression would reduce the size
of this 8192 bit file by 0 percent.
Chi square distribution for 8192 samples is 0.00, and randomly
would exceed this value 75.00 percent of the times.
Arithmetic mean value of data bits is 0.5000 (0.5 = random).
Monte Carlo value for Pi is 3.200000000 (error 1.86 percent).
Serial correlation coefficient is -0.003906 (totally uncorrelated = 0.0).
% ./ent -bc urandom.seed
Value Char Occurrences Fraction
0 4137 0.505005
1 4055 0.494995
Total: 8192 1.000000
Entropy = 0.999928 bits per bit.
Optimum compression would reduce the size
of this 8192 bit file by 0 percent.
Chi square distribution for 8192 samples is 0.82, and randomly
would exceed this value 50.00 percent of the times.
Arithmetic mean value of data bits is 0.4950 (0.5 = random).
Monte Carlo value for Pi is 3.058823529 (error 2.63 percent).
Serial correlation coefficient is -0.002542 (totally uncorrelated = 0.0).
% ./ent -bc dpa.seed
Value Char Occurrences Fraction
0 4096 0.500000
1 4096 0.500000
Total: 8192 1.000000
Entropy = 1.000000 bits per bit.
Optimum compression would reduce the size
of this 8192 bit file by 0 percent.
Chi square distribution for 8192 samples is 0.00, and randomly
would exceed this value 75.00 percent of the times.
Arithmetic mean value of data bits is 0.5000 (0.5 = random).
Monte Carlo value for Pi is 3.200000000 (error 1.86 percent).
Serial correlation coefficient is -0.003906 (totally uncorrelated = 0.0).
The last tests must have given you an idea of the confusion, diffusion and
entropy present in a DPA-128 encrypted ciphertext. More results are
available online on my webpage, I just did not want to put too much in
here since they all look the same ;)
--[ 3 - Acknowledgment
I would like to thank a few people:
k` who helped me with previous versions and some parts of dpa-128,
acid, who supported my endless harassement (hey try this please !),
pitufo for being the first dpa-128 tester and benchmarker,
hypno for reading this and spot bad sentences :)
br1an for reading this also and giving advices,
a ph.d whose name will remain private who audited dpa-128
and mayhem who both suggested to write a paper about dpa.
--[ 4 - REFERENCES
. http://www.tristeza.org/projects/dpa/
my page for the dpa project with examples and a lot of testing
. http://www.csua.berkeley.edu/cypherpunks/
cypherpunks
. http://www.fourmilab.ch/random/
entropy tests and their description
. http://www.schneier.com/paper-blowfish-fse.html
a paper on blowfish and what features a cipher should provide
. "applied cryptography", Bruce Schneier
THE book ;)
--[ 5 - Source Code
All of the code is provided under the ISC license, do whatever you want
with it, but please please don't use it to encrypt sensitive data unless
you know what you are doing (that means you could not break it and have
confidence in your skills). The code is NOT optimized for speed, it is
a work in progress and many parts can be improved, i'm just a bit in a
hurry and by the time you read this, it will probably be a lot cleaner ;)
If you plan on using dpa-128 even though I'm still warning you not to,
here are a few recommandations:
- the following code accepts keys both as parameter or as file. It
is preferable for many reasons to use a file, but the best reason
(aside from someone issueing a `ps` at the wrong moment...) is that
you can have your key be the result of a PRNG:
% dd if=/dev/urandom of=/home/veins/.dpa/secret.key bs=1024 count=1
The odds of someone guessing your key become pretty low :)
- use CBC mode. the impact of using CBC mode on performances is too
low to be an excuse for not using it.
To encrypt:
% dpa -a enc -m cbc -k file:secret.key -d file:/etc/passwd -o p.enc
To decrypt:
% dpa -a dec -m cbc -k file:secret.key -d file:p.enc -o p.dec
/*
* Copyright (c) 2004 Chehade Veins <veins at tristeza.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* bitdiff.c */
/*
* This is a small utility to compare the bits in two files. It is ugly
* and could be rewritten in a sexier way but it does its job so no
* need to waste time on it ;)
*
*/
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sysexits.h>
int main(int argc, char *argv[])
{
int i;
int size1, size2; /* size counters */
char *s1, *s2;
int s1_0, s1_1; /* in s1: 0s and 1s counter */
int s2_0, s2_1; /* in s2: 0s and 1s counter */
int fd1, fd2;
unsigned int cnt;
unsigned int diff;
unsigned int total;
struct stat sa;
struct stat sb;
if (argc < 3)
return (EX_USAGE);
fd1 = open(argv[1], O_RDONLY);
fd2 = open(argv[2], O_RDONLY);
if (fd1 < 0 || fd2 < 0)
return (EX_SOFTWARE);
fstat(fd1, &sa);
fstat(fd2, &sb);
size1 = sa.st_size;
size2 = sb.st_size;
s1 = mmap(NULL, sa.st_size, PROT_READ, MAP_PRIVATE, fd1, 0);
s2 = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd2, 0);
if (s1 == (void *)MAP_FAILED || s2 == (void *)MAP_FAILED)
return (EX_SOFTWARE);
s1_1 = s2_1 = s1_0 = s2_0 = diff = total = 0;
while (size1 && size2)
{
for (i = 7, cnt = 0; i >= 0; --i, ++cnt)
{
if (((*s1 >> i) & 0x1) != ((*s2 >> i) & 0x1))
++diff;
if ((*s1 >> i) & 0x1)
++s1_1;
else if (((*s1 >> i) & 0x1) == 0)
++s1_0;
if ((*s2 >> i) & 0x1)
++s2_1;
else if (((*s2 >> i) & 0x1) == 0)
++s2_0;
++total;
}
++s1; ++s2; size1--; size2--;
}
if (diff == 0)
printf("bit strings are identical\n");
else
{
printf("%d bits have changed.\n", diff, total);
printf("ratio for %s:\n", argv[1]);
printf("\t0's: %d\n", s1_0);
printf("\t1's: %d\n", s1_1);
printf("\n");
printf("ratio for %s:\n", argv[2]);
printf("\t0's: %d\n", s2_0);
printf("\t1's: %d\n", s2_1);
}
munmap(s1, sa.st_size);
munmap(s2, sb.st_size);
return (EX_OK);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* segments.c */
/*
* This is a small utility to count the segments of identical bits in a
* file. It could also be rewritten in a sexier way but...
*
*/
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sysexits.h>
int main(int argc, char *argv[])
{
int i;
int fd;
int cnt;
int last;
int biggest;
int size;
char *map;
struct stat sb;
unsigned int STATS[2][32];
if (argc < 2)
return (EX_USAGE);
/* Initialize the segments counters */
for (cnt = 0; cnt < 2; ++cnt)
for (i = 0; i < 32; ++i)
STATS[cnt][i] = 0;
/* Open and map the file in memory */
fd = open(argv[1], O_RDONLY);
if (fd < 0)
return (EX_SOFTWARE);
fstat(fd, &sb);
map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == (void *)MAP_FAILED)
return (EX_SOFTWARE);
last = -1;
biggest = 0;
size = sb.st_size;
while (size--)
{
for (i = 7, cnt = 0; i >= 0; --i, ++cnt)
{
if ((*map >> i) & 0x1)
{
if (last == 0)
{
if (cnt > biggest)
biggest = cnt;
if (cnt >= 32)
errx(EX_SOFTWARE, "This cannot be an entropy source ;)");
STATS[last][cnt] += 1;
cnt = 0;
}
last = 1;
}
else
{
if (last == 1)
{
if (cnt > biggest)
biggest = cnt;
if (cnt >= 32)
errx(EX_SOFTWARE, "This cannot be an entropy source ;)");
STATS[last][cnt] += 1;
cnt = 0;
}
last = 0;
}
}
++map;
}
munmap(map, sb.st_size);
printf("0's segments:\n");
for (i = 1; i < biggest; i++)
printf("\t%d => %d\n", i, STATS[0][i]);
printf("\n1's segments:\n");
for (i = 1; i < biggest; i++)
printf("\t%d => %d\n", i, STATS[1][i]);
return (EX_OK);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
Again, the source code that follows is a work in progress, and some parts
deserve a cleaner rewrite. data.c is truly ugly ;)
It was tested on Linux & BSD/i386, SunOS/sparc and OSF1/alpha, if it does
not run on your unix box, porting it should be trivial.
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
# Makefile
NAME = dpa
SRCS = main.c\
bitshift.c\
bytechain.c\
blockchain.c\
E.c\
D.c\
S_E.c\
S_D.c\
iv.c\
ecb.c\
cbc.c\
checksum128.c\
hash32.c\
key.c\
data.c\
sum.c\
usage.c
OBJS = $(SRCS:.c=.o)
CFLAGS =
LDFLAGS =
$(NAME) : $(OBJS)
cc -o $(NAME) $(OBJS) $(LDFLAGS)
clean :
rm -f *.o *~
fclean : clean
rm -f $(NAME)
re : fclean $(NAME)
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* include/dpa.h */
#ifndef _DPA_H_
#define _DPA_H_
#define DPA_KEY_SIZE 16
#define DPA_BLOCK_SIZE 16
#define DPA_ENCRYPT 0
#define DPA_DECRYPT 1
#define DPA_MODE_ECB 0
#define DPA_MODE_CBC 1
struct s_dpa_sub_key {
unsigned char key[DPA_KEY_SIZE];
unsigned char shift;
};
typedef struct s_dpa_sub_key DPA_SUB_KEY;
struct s_dpa_key {
struct s_dpa_sub_key subkey[16];
};
typedef struct s_dpa_key DPA_KEY;
struct s_dpa_data {
unsigned char *data;
unsigned long length;
};
typedef struct s_dpa_data DPA_DATA;
void checksum128(unsigned char *, unsigned char *, unsigned int);
unsigned long hash32(unsigned char *, unsigned int);
unsigned char dpa_encrypt(unsigned int, unsigned int, unsigned int);
unsigned char dpa_decrypt(unsigned int, unsigned int, unsigned int);
void DPA_ecb_encrypt(DPA_KEY *, DPA_DATA *, DPA_DATA *);
void DPA_ecb_decrypt(DPA_KEY *, DPA_DATA *, DPA_DATA *);
void DPA_cbc_encrypt(DPA_KEY *, DPA_DATA *, DPA_DATA *);
void DPA_cbc_decrypt(DPA_KEY *, DPA_DATA *, DPA_DATA *);
void DPA_sum(DPA_KEY *, DPA_DATA *, DPA_DATA *);
void DPA_set_key(DPA_KEY *, unsigned char *, unsigned int);
void DPA_set_keyfile(DPA_KEY *, char *);
void DPA_set_data(DPA_DATA *, unsigned char *, unsigned int);
void DPA_set_datafile(DPA_DATA *, char *);
void DPA_set_ciphertext(DPA_DATA *, DPA_DATA *, int, int);
void DPA_write_to_file(DPA_DATA *, char *);
void DPA_sum_write_to_file(DPA_DATA *, char *);
void rbytechain(unsigned char *);
void lbytechain(unsigned char *);
void rbitshift(unsigned char *, unsigned int);
void lbitshift(unsigned char *, unsigned int);
void blockchain(unsigned char *, unsigned char *);
void IV(unsigned char *);
void E(unsigned char *, unsigned char *, unsigned int);
void D(unsigned char *, unsigned char *, unsigned int);
void S_E(unsigned char *, unsigned char *, unsigned int);
void S_D(unsigned char *, unsigned char *, unsigned int);
void usage(void);
#endif
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* checksum128.c */
/* NEEDS_FIX */
/*
* This function creates a 128 bits (16 bytes) checksum out of a variable
* length input. It has NOT been verified so it is most likely broken and
* subject to collisions even though I was not able to find any myself.
*
* The following constraints need to be respected:
* - the function has to return a 128 bits value no matter what;
* - it should be difficult to determine the result by knowing the input;
* - it should be difficult to determine the input by knowing the result;
* - it should be difficult to find an input that will produce an identic
* result as a known input;
* - it should be difficult to find two inputs that will produce the same
* result;
* - it should be easy to compute the result of an input;
*
* If checksum128() happens to be broken, DPA-128 could be fixed by
* replacing it with any one-way hash function that produces a 128 bits
* output (MD5 comes to mind first ;).
*/
#define __NBROUNDS 32
void checksum128(unsigned char *key, unsigned char *skey, unsigned int size)
{
unsigned int cnt;
unsigned int length;
unsigned long a;
unsigned long b;
unsigned long c;
unsigned long d;
unsigned char *save;
/* Initialization of contexts */
a = 0xdeadbeef;
b = 0xadbeefde;
c = 0xbeefdead;
d = 0xefdeadbe;
for (cnt = 0; cnt < __NBROUNDS; ++cnt)
{
for (length = 0, save = key; length < size; ++save, ++length)
{
/* each context is first summed up with the cumplement of
* the current ascii character.
*/
a = (a + ~(*save));
b = (b + ~(*save));
c = (c + ~(*save));
d = (d + ~(*save));
/* Confusion */
/*
* Context A is summed with the product of:
* - cumplement of B, C and cumplement of D;
*
* Context B is summed with the product of:
* - cumplement of C, D and cumplement of A;
*
* Context C is summed with the product of:
* - cumplement of D, A and cumplement of B;
*
* Context D is summed with the product of:
* - cumplement of A, B and cumplement of C;
*
* Every context has a repercussion on all others
* including itself, and multiplication makes it
* hard to determine the previous values of each
* contexts after a few rounds.
*/
a += ~b * c * ~d;
b += ~c * d * ~a;
c += ~d * a * ~b;
d += ~a * b * ~c;
}
/* Diffusion */
/*
* The bytes of each contexts are shuffled within the
* same context, the first byte of A becomes the last
* which becomes the first. the second becomes the
* third which becomes the second. This permutation
* is also applied to B, C and D, just before they go
* through another round.
*/
a = (((a & 0x000000ff) << 24) +
((a & 0x0000ff00) << 8) +
((a & 0x00ff0000) >> 8) +
((a & 0xff000000) >> 24));
b = (((b & 0x000000ff) << 24) +
((b & 0x0000ff00) << 8) +
((b & 0x00ff0000) >> 8) +
((b & 0xff000000) >> 24));
c = (((c & 0x000000ff) << 24) +
((c & 0x0000ff00) << 8) +
((c & 0x00ff0000) >> 8) +
((c & 0xff000000) >> 24));
d = (((d & 0x000000ff) << 24) +
((d & 0x0000ff00) << 8) +
((d & 0x00ff0000) >> 8) +
((d & 0xff000000) >> 24));
}
/* Diffusion */
/*
* The Checksum is constructed by taking respectively
* the first byte of A, B, C and D, then the second,
* the third and the fourth.
*/
skey[0] = (a & 0xff000000) >> 24;
skey[1] = (b & 0xff000000) >> 24;
skey[2] = (c & 0xff000000) >> 24;
skey[3] = (d & 0xff000000) >> 24;
skey[4] = (a & 0x00ff0000) >> 16;
skey[5] = (b & 0x00ff0000) >> 16;
skey[6] = (c & 0x00ff0000) >> 16;
skey[7] = (d & 0x00ff0000) >> 16;
skey[8] = (a & 0x0000ff00) >> 8;
skey[9] = (b & 0x0000ff00) >> 8;
skey[10] = (c & 0x0000ff00) >> 8;
skey[11] = (d & 0x0000ff00) >> 8;
skey[12] = (a & 0x000000ff);
skey[13] = (b & 0x000000ff);
skey[14] = (c & 0x000000ff);
skey[15] = (d & 0x000000ff);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* hash32.c */
/*
* This function computes a 32 bits output out a variable length input. It is
* not important to have a nice distribution and low collisions as it is used
* on the output of checksum128() (see checksum128.c). There is a requirement
* though, the function should not consider \0 as a key terminator.
*/
unsigned long hash32(unsigned char *k, unsigned int length)
{
unsigned long h;
for (h = 0; *k && length; ++k, --length)
h = 13 * h + *k;
return (h);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* bytechain.c */
#include "include/dpa.h"
void rbytechain(unsigned char *block)
{
int i;
for (i = 0; i < DPA_BLOCK_SIZE; ++i)
block[i] ^= block[(i + 1) % DPA_BLOCK_SIZE];
return;
}
void lbytechain(unsigned char *block)
{
int i;
for (i = DPA_BLOCK_SIZE - 1; i >= 0; --i)
block[i] ^= block[(i + 1) % DPA_BLOCK_SIZE];
return;
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* bitshift.c */
#include <string.h>
#include "include/dpa.h"
void rbitshift(unsigned char *block, unsigned int shift)
{
unsigned int i;
unsigned int div;
unsigned int mod;
unsigned int rel;
unsigned char mask;
unsigned char remainder;
unsigned char sblock[DPA_BLOCK_SIZE];
if (shift)
{
mask = 0;
shift %= 128;
div = shift / 8;
mod = shift % 8;
rel = DPA_BLOCK_SIZE - div;
for (i = 0; i < mod; ++i)
mask |= (1 << i);
for (i = 0; i < DPA_BLOCK_SIZE; ++i)
{
remainder =
((block[(rel + i - 1) % DPA_BLOCK_SIZE]) & mask) << (8 - mod);
sblock[i] = ((block[(rel + i) % DPA_BLOCK_SIZE]) >> mod) | remainder;
}
}
memcpy(block, sblock, DPA_BLOCK_SIZE);
}
void lbitshift(unsigned char *block, unsigned int shift)
{
int i;
unsigned int div;
unsigned int mod;
unsigned int rel;
unsigned char mask;
unsigned char remainder;
unsigned char sblock[DPA_BLOCK_SIZE];
if (shift)
{
mask = 0;
shift %= 128;
div = shift / 8;
mod = shift % 8;
rel = DPA_BLOCK_SIZE + div;
for (i = 0; i < (8 - mod); ++i)
mask |= (1 << i);
mask = ~mask;
for (i = 0; i < DPA_BLOCK_SIZE; ++i)
{
remainder =
(block[(rel + i + 1) % DPA_BLOCK_SIZE] & mask) >> (8 - mod);
sblock[i] =
((block[(rel + i) % DPA_BLOCK_SIZE]) << mod) | remainder;
}
}
memcpy(block, sblock, DPA_BLOCK_SIZE);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* S_E.c */
#include "include/dpa.h"
/*
* The substitution table looks like this:
*
* (s+0)%256 (s+1)%256 (s+2)%256 (s+3)%256 (s+4)%256 (s+5)%256 (s+6)%256 ...
* (s+1)%256 (s+2)%256 (s+3)%256 (s+4)%256 (s+5)%256 (s+6)%256 (s+7)%256 ...
* (s+2)%256 (s+3)%256 (s+4)%256 (s+5)%256 (s+6)%256 (s+7)%256 (s+8)%256 ...
* (s+3)%256 (s+4)%256 (s+5)%256 (s+6)%256 (s+7)%256 (s+8)%256 (s+9)%256 ...
* (s+4)%256 (s+5)%256 (s+6)%256 (s+7)%256 ...
* (s+5)%256 (s+6)%256 (s+7)%256 (s+8)%256 ...
* (s+6)%256 (s+7)%256 (s+8)%256 (s+9)%256 ...
* ...
*/
void S_E(unsigned char *key, unsigned char *block, unsigned int s)
{
int i;
for (i = 0; i < DPA_BLOCK_SIZE; ++i)
block[i] = (key[i] + block[i] + s) % 256;
return;
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* S_D.c */
#include "include/dpa.h"
void S_D(unsigned char *key, unsigned char *block, unsigned int s)
{
int i;
for (i = 0; i < DPA_BLOCK_SIZE; ++i)
block[i] = ((256 + block[i]) - key[i] - s) % 256;
return;
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* E.c */
#include "include/dpa.h"
/* This is the function that is iterated at each round to encrypt */
void E(unsigned char *key, unsigned char *block, unsigned int shift)
{
rbytechain(block);
rbitshift(block, shift);
S_E(key, block, shift);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* D.c */
#include "include/dpa.h"
/* This is the function used to decrypt */
void D(unsigned char *key, unsigned char *block, unsigned int shift)
{
S_D(key, block, shift);
lbitshift(block, shift);
lbytechain(block);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* blockchain.c */
#include "include/dpa.h"
/* Block chaining for BC modes */
void blockchain(unsigned char *dst, unsigned char *src)
{
int i;
for (i = 0; i < DPA_BLOCK_SIZE; ++i)
dst[i] = dst[i] ^ src[i];
return;
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* iv.c */
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "include/dpa.h"
/* Initialization vector */
void IV(unsigned char *block)
{
int i;
srandom(time(NULL) % getpid());
for (i = 0; i < DPA_BLOCK_SIZE; ++i)
block[i] = random();
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* key.c */
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include "include/dpa.h"
/* This is the function used to precompute the subkeys */
void DPA_set_key(DPA_KEY *k, unsigned char *key, unsigned int len)
{
/* Compute subkey #0 */
checksum128(key, k->subkey[0].key, len);
/* Compute subkey #1 -> #15: k.n = H(k.(n-1)%16), where 0 <= n <= 15 */
checksum128(k->subkey[0].key, k->subkey[1].key, DPA_KEY_SIZE);
checksum128(k->subkey[1].key, k->subkey[2].key, DPA_KEY_SIZE);
checksum128(k->subkey[2].key, k->subkey[3].key, DPA_KEY_SIZE);
checksum128(k->subkey[3].key, k->subkey[4].key, DPA_KEY_SIZE);
checksum128(k->subkey[4].key, k->subkey[5].key, DPA_KEY_SIZE);
checksum128(k->subkey[5].key, k->subkey[6].key, DPA_KEY_SIZE);
checksum128(k->subkey[6].key, k->subkey[7].key, DPA_KEY_SIZE);
checksum128(k->subkey[7].key, k->subkey[8].key, DPA_KEY_SIZE);
checksum128(k->subkey[8].key, k->subkey[9].key, DPA_KEY_SIZE);
checksum128(k->subkey[9].key, k->subkey[10].key, DPA_KEY_SIZE);
checksum128(k->subkey[10].key, k->subkey[11].key, DPA_KEY_SIZE);
checksum128(k->subkey[11].key, k->subkey[12].key, DPA_KEY_SIZE);
checksum128(k->subkey[12].key, k->subkey[13].key, DPA_KEY_SIZE);
checksum128(k->subkey[13].key, k->subkey[14].key, DPA_KEY_SIZE);
checksum128(k->subkey[14].key, k->subkey[15].key, DPA_KEY_SIZE);
/* Paranoia: overwrite subkey #0 to prevent a possible biais in H
* from revealing informations about the initial key.
*/
checksum128(k->subkey[15].key, k->subkey[0].key, DPA_KEY_SIZE);
/* Compute shifts. Shifts are inverted to break a possible relation
* between shiftings and subkeys. The last subkey is used to compute
* the first shift, and so on...
*/
k->subkey[0].shift = hash32(k->subkey[15].key, DPA_KEY_SIZE);
k->subkey[1].shift = hash32(k->subkey[14].key, DPA_KEY_SIZE);
k->subkey[2].shift = hash32(k->subkey[13].key, DPA_KEY_SIZE);
k->subkey[3].shift = hash32(k->subkey[12].key, DPA_KEY_SIZE);
k->subkey[4].shift = hash32(k->subkey[11].key, DPA_KEY_SIZE);
k->subkey[5].shift = hash32(k->subkey[10].key, DPA_KEY_SIZE);
k->subkey[6].shift = hash32(k->subkey[9].key, DPA_KEY_SIZE);
k->subkey[7].shift = hash32(k->subkey[8].key, DPA_KEY_SIZE);
k->subkey[8].shift = hash32(k->subkey[7].key, DPA_KEY_SIZE);
k->subkey[9].shift = hash32(k->subkey[6].key, DPA_KEY_SIZE);
k->subkey[10].shift = hash32(k->subkey[5].key, DPA_KEY_SIZE);
k->subkey[11].shift = hash32(k->subkey[4].key, DPA_KEY_SIZE);
k->subkey[12].shift = hash32(k->subkey[3].key, DPA_KEY_SIZE);
k->subkey[13].shift = hash32(k->subkey[2].key, DPA_KEY_SIZE);
k->subkey[14].shift = hash32(k->subkey[1].key, DPA_KEY_SIZE);
k->subkey[15].shift = hash32(k->subkey[0].key, DPA_KEY_SIZE);
}
/* And this one for using a file as a secret key */
void DPA_set_keyfile(DPA_KEY *k, char *filename)
{
int fd;
void *key;
struct stat sb;
fd = open(filename, O_RDONLY);
if (fd < 0)
{
fprintf(stderr, "failed to open %s as a secret key.\n", filename);
exit(1);
}
fstat(fd, &sb);
key =
(unsigned char *)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (key == (void *)MAP_FAILED)
{
fprintf(stderr, "mmap() call failure.\n");
exit(1);
}
DPA_set_key(k, key, sb.st_size);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* data.c */
/*
* Warning: ugliest file ;)
*/
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "include/dpa.h"
void DPA_set_data(DPA_DATA *d, unsigned char *data, unsigned int len)
{
d->data = data;
d->length = len;
}
void DPA_set_datafile(DPA_DATA *d, char *filename)
{
int fd;
struct stat sb;
fd = open(filename, O_RDONLY);
if (fd < 0)
{
fprintf(stderr, "failed to open data file %s.\n", filename);
exit(1);
}
fstat(fd, &sb);
d->data =
(unsigned char *)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (d->data == (void *)MAP_FAILED)
{
fprintf(stderr, "mmap() call failure.\n");
exit(1);
}
d->length = sb.st_size;
}
/* Allocate enough memory to hold the result of encryption/decryption */
void DPA_set_ciphertext(DPA_DATA *d, DPA_DATA *c, int mode, int action)
{
int sz;
sz = 0;
if (action == DPA_ENCRYPT)
{
if (mode == DPA_MODE_ECB)
{
if ((d->length % DPA_BLOCK_SIZE) == 0)
sz = d->length + DPA_BLOCK_SIZE;
else
sz = d->length + (DPA_BLOCK_SIZE - (d->length % DPA_BLOCK_SIZE)) +
DPA_BLOCK_SIZE;
}
else if (mode == DPA_MODE_CBC)
{
if ((d->length % DPA_BLOCK_SIZE) == 0)
sz = d->length + (DPA_BLOCK_SIZE * 2);
else
sz = d->length + (DPA_BLOCK_SIZE - (d->length % DPA_BLOCK_SIZE)) +
(DPA_BLOCK_SIZE * 2);
}
}
else if (action == DPA_DECRYPT)
{
if (mode == DPA_MODE_ECB)
sz = d->length - DPA_BLOCK_SIZE;
else if (mode == DPA_MODE_CBC)
sz = d->length - (DPA_BLOCK_SIZE * 2);
}
c->data =
(unsigned char *)mmap(NULL, sz,
PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
if (c->data == (void *)MAP_FAILED)
{
fprintf(stderr, "mmap() call failure.\n");
exit(1);
}
c->length = sz;
}
/* Write the result of encryption/decryption to filename */
void DPA_write_to_file(DPA_DATA *data, char *filename)
{
int fd;
int cnt;
int wasfile;
wasfile = 0;
if (!strcmp(filename, "-"))
fd = 1;
else
{
fd = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
if (fd < 0)
{
fprintf(stderr, "failed to open result file %s.\n", filename);
exit(1);
}
wasfile = 1;
}
for (cnt = 0; cnt < data->length;)
if ((data->length - cnt) < DPA_BLOCK_SIZE)
cnt += write(fd, data->data + cnt, data->length - cnt);
else
cnt += write(fd, data->data + cnt, DPA_BLOCK_SIZE);
if (wasfile)
close(fd);
}
/* Write the result of checksum to filename in base 16 */
void DPA_sum_write_to_file(DPA_DATA *data, char *filename)
{
int fd;
int cnt;
int cnt2;
int wasfile;
unsigned char base[] = "0123456789abcdef";
unsigned char buffer[DPA_BLOCK_SIZE * 2 + 2];
wasfile = 0;
if (!strcmp(filename, "-"))
fd = 1;
else
{
fd = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
if (fd < 0)
{
fprintf(stderr, "failed to open result file %s.\n", filename);
exit(1);
}
wasfile = 1;
}
for (cnt = cnt2 = 0; cnt < DPA_BLOCK_SIZE; ++cnt, (cnt2 += 2))
{
buffer[cnt2] =
base[*(data->data + data->length - DPA_BLOCK_SIZE + cnt) / 16];
buffer[cnt2 + 1] =
base[*(data->data + data->length - DPA_BLOCK_SIZE + cnt) % 16];
}
buffer[DPA_BLOCK_SIZE * 2] = '\n';
buffer[DPA_BLOCK_SIZE * 2 + 1] = '\0';
write(fd, buffer, DPA_BLOCK_SIZE * 2 + 2);
if (wasfile)
close(fd);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* ecb.c */
/*
* Encryption/Decryption in ECB mode.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "include/dpa.h"
/* XXX - for better performances, unroll the loops ;) */
void DPA_ecb_encrypt(DPA_KEY *key, DPA_DATA *data, DPA_DATA *cipher)
{
int j;
int cnt;
unsigned char *cptr;
unsigned char block[DPA_BLOCK_SIZE];
cnt = data->length;
cptr = cipher->data;
memset(block, 0, 16);
for (; cnt > 0; data->data += DPA_BLOCK_SIZE, cptr += DPA_BLOCK_SIZE)
{
if (cnt < DPA_BLOCK_SIZE)
{
memcpy(block, data->data, cnt);
memset(block + cnt, 0, DPA_BLOCK_SIZE - cnt);
}
else
memcpy(block, data->data, DPA_BLOCK_SIZE);
for (j = 0; j < 16; ++j)
E(key->subkey[j].key, block, key->subkey[j].shift);
memcpy(cptr, block, DPA_BLOCK_SIZE);
cnt -= DPA_BLOCK_SIZE;
}
/* Padding block */
memset(block, 0, DPA_BLOCK_SIZE);
if (data->length % DPA_BLOCK_SIZE)
block[DPA_BLOCK_SIZE - 1] = DPA_BLOCK_SIZE - data->length % DPA_BLOCK_SIZE;
for (j = 0; j < 16; ++j)
E(key->subkey[j].key, block, key->subkey[j].shift);
memcpy(cptr, block, DPA_BLOCK_SIZE);
}
void DPA_ecb_decrypt(DPA_KEY *key, DPA_DATA *data, DPA_DATA *cipher)
{
int j;
int cnt;
unsigned char padding;
unsigned char *cptr;
unsigned char block[DPA_BLOCK_SIZE];
/* Data is padded so... we got at least 2 * DPA_BLOCK_SIZE bytes and
* data->length / DPA_BLOCK_SIZE should be even
*/
if ((data->length % DPA_BLOCK_SIZE) || data->length < (2 * DPA_BLOCK_SIZE))
exit(1);
/* Extract padding information */
memcpy(block, data->data + data->length - DPA_BLOCK_SIZE, DPA_BLOCK_SIZE);
for (j = 15; j >= 0; --j)
D(key->subkey[j].key, block, key->subkey[j].shift);
padding = block[DPA_BLOCK_SIZE - 1];
cipher->length -= padding;
cptr = cipher->data;
cnt = data->length - DPA_BLOCK_SIZE;
memset(block, 0, DPA_BLOCK_SIZE);
for (;
cnt > 0;
cnt -= DPA_BLOCK_SIZE, data->data += DPA_BLOCK_SIZE,
cptr += DPA_BLOCK_SIZE)
{
memcpy(block, data->data, DPA_BLOCK_SIZE);
for (j = 15; j >= 0; --j)
D(key->subkey[j].key, block, key->subkey[j].shift);
if (cnt >= DPA_BLOCK_SIZE)
memcpy(cptr, block, DPA_BLOCK_SIZE);
else
memcpy(cptr, block, DPA_BLOCK_SIZE - (padding % DPA_BLOCK_SIZE));
}
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* cbc.c */
/*
* Encryption/Decryption in CBC mode.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "include/dpa.h"
/* XXX - for better performances, unroll the loops ;) */
void DPA_cbc_encrypt(DPA_KEY *key, DPA_DATA *data, DPA_DATA *cipher)
{
int j;
int cnt;
unsigned char *cptr;
unsigned char block[DPA_BLOCK_SIZE];
unsigned char iv[DPA_BLOCK_SIZE];
unsigned char xblock[DPA_BLOCK_SIZE];
/* IV */
cptr = cipher->data;
IV(iv);
memcpy(xblock, iv, DPA_BLOCK_SIZE);
for (j = 0; j < 16; ++j)
E(key->subkey[j].key, iv, key->subkey[j].shift);
memcpy(cptr, iv, DPA_BLOCK_SIZE);
cptr += DPA_BLOCK_SIZE;
cnt = data->length;
memset(block, 0, 16);
for (; cnt > 0; data->data += DPA_BLOCK_SIZE, cptr += DPA_BLOCK_SIZE)
{
if (cnt < DPA_BLOCK_SIZE)
{
memcpy(block, data->data, cnt);
memset(block + cnt, 0, DPA_BLOCK_SIZE - cnt);
}
else
memcpy(block, data->data, DPA_BLOCK_SIZE);
blockchain(block, xblock);
for (j = 0; j < 16; ++j)
E(key->subkey[j].key, block, key->subkey[j].shift);
memcpy(xblock, block, DPA_BLOCK_SIZE);
memcpy(cptr, block, DPA_BLOCK_SIZE);
cnt -= DPA_BLOCK_SIZE;
}
/* Padding */
memset(block, 0, DPA_BLOCK_SIZE);
if (data->length % DPA_BLOCK_SIZE)
block[DPA_BLOCK_SIZE - 1] = DPA_BLOCK_SIZE - data->length % DPA_BLOCK_SIZE;
blockchain(block, xblock);
for (j = 0; j < 16; ++j)
E(key->subkey[j].key, block, key->subkey[j].shift);
memcpy(cptr, block, DPA_BLOCK_SIZE);
}
void DPA_cbc_decrypt(DPA_KEY *key, DPA_DATA *data, DPA_DATA *cipher)
{
int j;
int cnt;
unsigned char padding;
unsigned char *cptr;
unsigned char block[DPA_BLOCK_SIZE];
unsigned char xblock[DPA_BLOCK_SIZE];
unsigned char xblockprev[DPA_BLOCK_SIZE];
unsigned char *xorptr;
/*
* CBC mode uses padding, data->length / DPA_BLOCK_SIZE _MUST_ be even.
* Also, we got a block for the IV, at least a block for the data and
* a block for the padding information, this makes the size of cryptogram
* at least 3 * DPA_BLOCK_SIZE.
*/
if ((data->length % DPA_BLOCK_SIZE) || data->length < (3 * DPA_BLOCK_SIZE))
exit(1);
/* Extract padding information by undoing block chaining on last block */
memcpy(block, data->data + data->length - DPA_BLOCK_SIZE, DPA_BLOCK_SIZE);
for (j = 15; j >= 0; --j)
D(key->subkey[j].key, block, key->subkey[j].shift);
xorptr = data->data + data->length - (DPA_BLOCK_SIZE * 2);
blockchain(block, xorptr);
padding = block[DPA_BLOCK_SIZE - 1];
cipher->length -= padding;
/* Extract Initialization vector */
memcpy(xblock, data->data, DPA_BLOCK_SIZE);
for (j = 15; j >= 0; --j)
D(key->subkey[j].key, xblock, key->subkey[j].shift);
cptr = cipher->data;
cnt = data->length - (DPA_BLOCK_SIZE * 2);
memset(block, 0, DPA_BLOCK_SIZE);
for (data->data += DPA_BLOCK_SIZE;
cnt >= DPA_BLOCK_SIZE;
cnt -= DPA_BLOCK_SIZE, data->data += DPA_BLOCK_SIZE,
cptr += DPA_BLOCK_SIZE)
{
memcpy(block, data->data, DPA_BLOCK_SIZE);
memcpy(xblockprev, block, DPA_BLOCK_SIZE);
for (j = 15; j >= 0; --j)
D(key->subkey[j].key, block, key->subkey[j].shift);
blockchain(block, xblock);
if (cnt >= DPA_BLOCK_SIZE)
memcpy(cptr, block, DPA_BLOCK_SIZE);
else
memcpy(cptr, block, DPA_BLOCK_SIZE - (padding % DPA_BLOCK_SIZE));
memcpy(xblock, xblockprev, DPA_BLOCK_SIZE);
}
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* sum.c */
/* NEEDS_FIX */
/*
* This is basically a CBC encryption with a fixed IV and fixed key, the
* last block being the checksum. This needs a rewrite because there is
* no need to allocate memory for the whole ciphertext as only two blocks
* are needed.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "include/dpa.h"
/* XXX - for better performances, unroll the loops ;) */
void DPA_sum(DPA_KEY *key, DPA_DATA *data, DPA_DATA *cipher)
{
int j;
int cnt;
unsigned char *cptr;
unsigned char block[DPA_BLOCK_SIZE];
unsigned char iv[DPA_BLOCK_SIZE];
unsigned char xblock[DPA_BLOCK_SIZE];
/* Fixed key */
DPA_set_key(key, (unsigned char *)"deadbeef", 8);
/* Fixed IV */
memcpy(iv, "0123456789abcdef", DPA_BLOCK_SIZE);
memcpy(xblock, iv, DPA_BLOCK_SIZE);
cptr = cipher->data;
memcpy(xblock, iv, DPA_BLOCK_SIZE);
for (j = 0; j < 16; ++j)
E(key->subkey[j].key, iv, key->subkey[j].shift);
memcpy(cptr, iv, DPA_BLOCK_SIZE);
cptr += DPA_BLOCK_SIZE;
cnt = data->length;
memset(block, 0, 16);
for (; cnt > 0; data->data += DPA_BLOCK_SIZE, cptr += DPA_BLOCK_SIZE)
{
if (cnt < DPA_BLOCK_SIZE)
{
memcpy(block, data->data, cnt);
memset(block + cnt, 0, DPA_BLOCK_SIZE - cnt);
}
else
memcpy(block, data->data, DPA_BLOCK_SIZE);
blockchain(block, xblock);
for (j = 0; j < 16; ++j)
E(key->subkey[j].key, block, key->subkey[j].shift);
memcpy(xblock, block, DPA_BLOCK_SIZE);
memcpy(cptr, block, DPA_BLOCK_SIZE);
cnt -= DPA_BLOCK_SIZE;
}
memset(block, 0, DPA_BLOCK_SIZE);
if (data->length % DPA_BLOCK_SIZE)
block[DPA_BLOCK_SIZE - 1] = DPA_BLOCK_SIZE - data->length % DPA_BLOCK_SIZE;
blockchain(block, xblock);
for (j = 0; j < 16; ++j)
E(key->subkey[j].key, block, key->subkey[j].shift);
memcpy(cptr, block, DPA_BLOCK_SIZE);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* usage.c */
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>
void usage(void)
{
fprintf(stderr, "usage: dpa -a action -m mode -k key -d data -o outfile\n");
fprintf(stderr, " dpa -s filename\n");
fprintf(stderr, "\taction can be : encrypt, decrypt\n");
fprintf(stderr, "\tmode can be : ecb, cbc\n");
fprintf(stderr, "\tkey can be : \"key\" or file:/path/to/keyfile\n");
fprintf(stderr, "\tdata can be : \"data\" or file:/path/to/datafile\n");
fprintf(stderr, "\toutfile can be: \"-\" (stdout) or a filename\n");
fprintf(stderr, "\twhen -s is used, a checksum of filename is computed\n");
exit (EX_USAGE);
}
8<- - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - - 8< - - - - -
/* main.c */
#include <stdio.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include "include/dpa.h"
int main(int argc, char *argv[])
{
int kflag;
int dflag;
int sflag;
int mflag;
int aflag;
int oflag;
int opt;
int mode;
int action;
char *output;
DPA_KEY key;
DPA_DATA data;
DPA_DATA cipher;
mode = DPA_MODE_ECB;
action = DPA_ENCRYPT;
output = "-";
mflag = aflag = kflag = dflag = sflag = oflag = 0;
while ((opt = getopt(argc, argv, "a:m:k:d:o:s:")) != -1)
{
switch (opt)
{
case 'a':
if (!strcmp(optarg, "enc") || !strcmp(optarg, "encrypt"))
action = DPA_ENCRYPT;
else if (!strcmp(optarg, "dec") || !strcmp(optarg, "decrypt"))
action = DPA_DECRYPT;
else
{
fprintf(stderr, "unknown action, expected encrypt or decrypt\n");
return (EX_USAGE);
}
aflag = 1;
break;
case 'm':
if (!strcmp(optarg, "ecb"))
mode = DPA_MODE_ECB;
else if (!strcmp(optarg, "cbc"))
mode = DPA_MODE_CBC;
else
{
fprintf(stderr, "unknown mode, expected ecb or cbc\n");
return (EX_USAGE);
}
mflag = 1;
break;
case 'k':
if (strncmp(optarg, "file:", 5) || strlen(optarg) == 5)
DPA_set_key(&key, (unsigned char *)optarg, strlen(optarg));
else
DPA_set_keyfile(&key, optarg + 5);
kflag = 1;
break;
case 'd':
if (strncmp(optarg, "file:", 5) || strlen(optarg) == 5)
DPA_set_data(&data, (unsigned char *)optarg, strlen(optarg));
else
DPA_set_datafile(&data, optarg + 5);
dflag = 1;
break;
case 'o':
output = optarg;
oflag = 1;
break;
case 's':
DPA_set_datafile(&data, optarg);
sflag = 1;
break;
default:
usage();
}
}
if ((!aflag || !mflag || !kflag || !dflag) && !sflag)
usage();
if (sflag)
{
DPA_set_ciphertext(&data, &cipher, DPA_MODE_CBC, DPA_ENCRYPT);
DPA_sum(&key, &data, &cipher);
DPA_sum_write_to_file(&cipher, output);
}
else
{
DPA_set_ciphertext(&data, &cipher, mode, action);
if (action == DPA_ENCRYPT)
{
if (mode == DPA_MODE_ECB)
DPA_ecb_encrypt(&key, &data, &cipher);
else if (mode == DPA_MODE_CBC)
DPA_cbc_encrypt(&key, &data, &cipher);
}
else if (action == DPA_DECRYPT)
{
if (mode == DPA_MODE_ECB)
DPA_ecb_decrypt(&key, &data, &cipher);
else if (mode == DPA_MODE_CBC)
DPA_cbc_decrypt(&key, &data, &cipher);
}
DPA_write_to_file(&cipher, output);
}
return (EX_OK);
}
|=[ EOF ]=---------------------------------------------------------------=|