Cryptanalysis and its applications to password hashing

Cryptanalysis and its applications to password hashing Speeding up preimage search for password hashes by cryptanalysis J ENS CH RISTIAN H I LLERU P...
Author: Julian Rose
9 downloads 0 Views 744KB Size
Cryptanalysis and its applications to password hashing

Speeding up preimage search for password hashes by cryptanalysis

J ENS CH RISTIAN H I LLERU P

KTH Information and Communication Technology

Master of Science Thesis Stockholm, Sweden 2013 TRITA-ICT-EX-2013:200

C R Y P TA N A LY S I S A N D I T S A P P L I C AT I O N S T O PA S S W O R D HASHING Speeding up preimage search for password hashes by cryptanalysis

jens christian hillerup

Technical University of Denmark Royal Institute of Technology June 2013

Jens Christian Hillerup: Cryptanalysis and its applications to password hashing, Speeding up preimage search for password hashes by cryptc June 2013 analysis,

P R E FA C E

This thesis was prepared at the Department of Informatics and Mathematical Modelling at the Technical University of Denmark in fulfillment of the requirements for acquiring an M.Sc. in Computer Science and Engineering. Likewise, it fulfills the requirements for acquiring an M.Sc. in Information Technology at The Royal Institute of Technology in Sweden, and as such constitutes the thesis work of a NordSecMob dual degree with 30 ECTS obtained in Stockholm and 90 ECTS in Copenhagen. The work was supervised by associate professor Christian Rechberger at DTU and co-supervised by associate professor Markus Hidell at KTH, and assigned a workload of 30 ECTS credits. The thesis deals with optimizations to brute-force cracking of hashed passwords. Its main contribution is an attack on plain MD5 hashes of passwords of 12 or fewer characters, approximately 27% faster than brute force in theory, and 50% faster in practice. It works by trading increased memory usage for computation time by caching parts of the MD5 calculation that are the same across many runs of MD5.

iii

ABSTRACT

Hash functions are a type of mathematical function that takes an input of arbitrary length and gives an output of fixed length, called a hash value. Many consider hash values to be sort of a “fingerprint” of some data, since they can be assumed to have unique outputs for any possible input. This assumption does not make a lot of sense, because the size of the input space is infinite while the size of the output space remains finite! It is, nevertheless, defensible because of the infeasibility of finding two inputs that yield the same hash value. Hash functions are often used to store passwords in databases since it is not feasible either to go from a hash value to a preimage. By saving the hash value rather than the password and checking the hash value of the user’s input it is possible to check passwords without the need to store them, which is an advantage if one wants to control the damage of a possible data leakage. This work researches different cryptanalytic techniques for searching for preimages to hash values in a password-cracking context. A 27% increase in performance is gained using a time/memory tradeoff instead of naively iterating through password candidates. The attack is also demonstrated in practice, where it attains a 50% improvement. The data is then analyzed and discussed for the purpose of assessing the implementability of the attacks in already existing cracking implementations.

v

S A M M A N FAT T N I N G

Hashfunktioner är en typ av matematisk funktion som tar input av slumpmässig längd och ger output av fast längd, ett så kallat hashvärde. Många betecknar hashvärden som en typ av “fingeravtryck” av data, eftersom de kan antas ha unika utdata för alla möjliga indata. Detta antagande ger dock ingen mening eftersom storleken på inputmängden är oändlig medan storleken på outputmängden är ändlig! Det kan dock ändå försvaras eftersom det inte är beräkningsmässigt överkomligt att hitta två indata som ger samma hashvärde. Det är heller inte överkomligt att gå ifrån hashvärde tillbaka till en urbild, varför hashfunktioner ibland används för att spara lösenord i databaser. Genom att spara hashvärden i stället för lösenord och kontrollera dem emot hashvärden av användarens input är det möjligt att kontrollera lösenord utan att behöva spara dem, vilket är en fördel om man vill begränsa skadan vid en dataläcka. I detta verket utforskas olika kryptoanalytiska tekniker för att söka efter urbilder till hashvärden i kontext av lösenordsknäckning. Med hjälp av ett time/memory-tradeoff uppnås en teoretisk förbättring på 27% jämfört med ett “naivt förlopp”. Attacken blir även bevisad i praktiken, där det påvisas en 50% förbättring. Resultaten analyseras härefter och diskuteras med hänseende till att värdera implementerbarheten i redan existerande knäckningsimplementationer.

vii

S A M M E N FAT N I N G

Hashfunktioner er en type af matematisk funktion, der tager et input af vilkårlig længde og giver et output af fast længde, kaldet en hashværdi. Mange betegner hashværdier som en slags “fingeraftryk” af noget data, idet de kan antages at have unikke outputs for alle mulige inputs. Denne antagelse giver dog ikke meget mening, fordi størrelsen på inputrummet er uendelig, hvorimod størrelsen på outputrummet er endelig! Den kan alligevel forsvares, fordi det ikke er beregningsmæssigt overkommeligt at finde to inputs, der giver den samme hashværdi. Det er heller ikke overkommeligt at gå fra hashværdi tilbage til et urbillede, hvorfor hashfunktioner undertiden bruges til at gemme kodeord i databaser. Ved at gemme hashværdier i stedet for kodeord, og tjekke dem imod hashværdier af brugeres inputs er det muligt at tjekke kodeord uden at behøve at gemme dem, hvilket er en fordel hvis man vil begrænse skaden ved et muligt datalæk. I dette værk udforskes forskellige kryptoanalytiske teknikker til at søge efter urbilleder til hashværdier i en kodeords-crackingskontekst. Ved hjælp af et time/memory-tradeoff opnås en teoretisk forbedring på 27% i forhold til et “naivt gennemløb”. Angrebet bliver også eftervist i praksis, hvor der opnås en 50% forbedring. Dataen bliver efterfølgende analyseret og diskuteret med henblik på at vurdere implementerbarheden i allerede eksisterende crackingimplementeringer.

ix

ACKNOWLEDGEMENTS

I want to thank my main supervisor Christian Rechberger, who provided indispensable guidance and patience throughout the period of writing. Though I haven’t always been the best in terms of arranging meetings, he has consistently provided new insights and good inputs as to how to make this research relevant for the community. Also I’d like to thank my supervisor from KTH, Markus Hidell. Thanks to CCC for sparking my interest for IT security in the first place. Thanks to Sparvnästet for comfort in the long winter nights in Stockholm, and thanks to my classmates in my Swedish-for-Danes class. Thanks also to the people of Forskningsavdelningen and Labitat for teaching the public to be curious as to how systems work. Thanks to Fabrizio Becci for warning me that this whole ordeal would be impossible—and be (partly) right! To Hans Peter Jensen for carreer discussions, to my neighbor Sarah and the rest of GL6 for putting up with my obnoxiousness, and to ML6 for adopting me when it was too much. To Symphogen for hosting me during the production of this thesis, to DUSIKA for giving me a creative outlet when all of this geekery became too dull, to Marianne Zibrandtsen for watching over me when I was unwell, and to the Amok society for all the fun. And for my friend, Joe Connor, who likes to keep in the background, but has inspired me more often than he realizes. I also want to thank Solar Designer and Jens Steube (atom) for discussions on the attacks, and for providing me with otherwise closedsource code for investigation. Moreover, I’d like to thank my colleagues Martin Mehl Lauridsen, Tanja Søndergaard and Peter Christian Koch for the many fruitful discussions on the thesis matter. And my family but especially my father for inspiring me to go the academic route.

xi

CONTENTS

i introduction 1 introduction 1.1 Motivation . . . . . . . . . . . . . . . . . . 1.2 Method . . . . . . . . . . . . . . . . . . . . 1.3 Scope . . . . . . . . . . . . . . . . . . . . . 1.4 Limitations . . . . . . . . . . . . . . . . . . 1.5 Contribution . . . . . . . . . . . . . . . . . 1.6 Outline of thesis . . . . . . . . . . . . . . . 1.7 Nomenclature and notation . . . . . . . . 2 background 2.1 Hash functions . . . . . . . . . . . . . . . 2.1.1 The Merkle-Damgård construction 2.2 MD5 . . . . . . . . . . . . . . . . . . . . . . 2.2.1 Structure . . . . . . . . . . . . . . . 2.2.2 Compression function . . . . . . . 2.3 Cryptographic attacks . . . . . . . . . . . 2.3.1 Meet-in-the-middle attacks . . . . 2.4 Local collisions . . . . . . . . . . . . . . . 2.5 Related work . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

ii current state of password hashing 3 hash functions for secure password storage 3.1 Salting . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Speed issues . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Applied key stretching: PBKDF2, PHPass and MD5crypt . . . . . . . . . . . . . . . . . . . . . . 3.3 Password Hashing Competition . . . . . . . . . . . . . . 3.4 Properties of passwords as inputs to general-purpose hash functions . . . . . . . . . . . . . . . . . . . . . . . . 3.5 Cracking implementations . . . . . . . . . . . . . . . . . 3.5.1 Brute force . . . . . . . . . . . . . . . . . . . . . . 3.5.2 Time-memory tradeoffs . . . . . . . . . . . . . . 3.6 Survey of cracking implementations . . . . . . . . . . . 3.6.1 John the Ripper . . . . . . . . . . . . . . . . . . . 3.6.2 HashCat . . . . . . . . . . . . . . . . . . . . . . . 4 cryptanalytic optimizations to password cracking 4.1 Speeding up dictionary attacks using local collisions . 4.2 Precaching the last 15 rounds . . . . . . . . . . . . . . . 4.2.1 Complexity analysis . . . . . . . . . . . . . . . . 4.3 Meet-in-the-middle attack . . . . . . . . . . . . . . . . .

1 3 4 5 5 6 6 7 7 9 9 10 10 11 12 13 13 15 15 17 19 20 20 21 21 22 23 23 24 25 25 26 27 27 28 29 30

xiii

xiv

contents

4.3.1 Parallelizability . . . . . . . . . . . . . . . . . . . 4.3.2 Memory complexity . . . . . . . . . . . . . . . . 4.4 Applicability on SHA-1 . . . . . . . . . . . . . . . . . . 5 benchmarking password crackers 5.1 On the (in)comparability of cracking implementations 5.1.1 Towards comparability . . . . . . . . . . . . . . . 5.2 Benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . 5.2.1 Applicability of attacks on other implementations 5.3 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.1 Profiler findings . . . . . . . . . . . . . . . . . . . 5.3.2 Data quality . . . . . . . . . . . . . . . . . . . . . 5.4 Sources of error . . . . . . . . . . . . . . . . . . . . . . .

31 32 33 35 35 35 36 36 37 37 39 39

iii conclusion and discussion 6 discussion 6.1 Tunable table lengths . . . . . . . . . . . . . . . . . . . . 6.2 Future work . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.1 Ivan Golubev’s results . . . . . . . . . . . . . . . 7 conclusion

43 45 45 45 46 49

bibliography

51

iv appendices a listings a.1 md5_attack . . . . . . . a.1.1 main.c . . . . . a.1.2 common.c . . . . a.1.3 md5.c . . . . . . a.1.4 md5.h . . . . . . a.1.5 naivesearch.c a.1.6 cacheattack.c a.1.7 mitmattack.c . a.2 benchmark.py . . . . . a.3 analyze.py . . . . . . .

55 57 57 57 59 60 71 72 74 75 78 80

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

LIST OF FIGURES

Figure 1 Figure 2

Figure 3

Figure 4

Figure 5

Figure 6 Figure 7 Figure 8

Putting passwords through a one-way function. The Merkle-Damgård construction. The message comes in, padding is applied and the message chunks are put into the compression functions that serves as chaining values for each other. The result is the hash value. . . . . . . . The MD5 compression function. The chaining variables a, b, c, d are put into the MD5 operation function along with a 512-bit message chunk. Then 64 operations are run before adding the chaining values back onto the output. This structure keeps repeating itself until there are no more message chunks left. The ⊕ symbol denotes 32-bit modular addition. . . . . . . . . One MD5 operation ϕ. The  symbol denotes 32-bit modular addition. Drawing by Wikipedia user Surachit, released under a CC BY-SA license. An example of bad entropy in a dictionaryderived password. The boxes in grey denote bits of entropy. Comic courtesy of xkcd.com, released under a Creative Commons BY-NC license. . . . . . . . . . . . . . . . . . . . . . . . . The MD5 message expansion. . . . . . . . . . Output from the Visual Studio 2010 profiler. . Histograms of search times in an alphabet/attack matrix. . . . . . . . . . . . . . . . . . . . . .

3

10

12

14

24 29 39 40

xv

L I S T O F TA B L E S

Table 1 Table 2

Table 3 Table 4

xvi

Overview of the F function in MD5. . . . . . . The attacks and their theoretical performance. Note that the cache and MITM attacks converge for passwords of less than eight characters. The MITM attack is about 3% better than the cache attack in terms of speed-up compared to a naive search. . . . . . . . . . . . . . . . . . Mean search times in seconds, for different alphabet sizes |A|. . . . . . . . . . . . . . . . . . . Median search times in seconds, for different alphabet sizes |A|. . . . . . . . . . . . . . . . . .

13

32 38 38

LISTINGS

../projects/md5_attack/md5_attack/main.c . . . . . . . . . . .

57

../projects/md5_attack/md5_attack/common.c . . . . . . . . .

59

../projects/md5_attack/md5_attack/md5.c . . . . . . . . . . . .

60

../projects/md5_attack/md5_attack/md5.h . . . . . . . . . . . .

72

../projects/md5_attack/md5_attack/naivesearch.c . . . . . . .

72

../projects/md5_attack/md5_attack/cacheattack.c . . . . . . . .

74

../projects/md5_attack/md5_attack/mitmattack.c . . . . . . . .

75

../projects/benchmarks/benchmark.py . . . . . . . . . . . . . .

78

../projects/benchmarks/analyze.py . . . . . . . . . . . . . . . .

80

xvii

LIST OF ACRONYMS

MitM

Meet-in-the-Middle

RFC

Request For Comments

TMTO

Time-Memory Trade-Off

xviii

Part I INTRODUCTION This part goes through some required background knowledge needed for this thesis, as well as a discussion about scope, method and contribution.

1

INTRODUCTION

Storing user passwords poses a challenge to administrators of software systems and services. Storing the passwords in plain text comes with a certain risk because the administrators might not always be on the forefront of new vulnerabilities in the underlying software on the database servers, and are as such potentially vulnerable to database leaks. To mitigate against such attacks one would usually employ oneway functions to store the passwords. One-way functions compute a value vout on the basis on another value vin , see Figure 1, all while having the property that it is difficult to go from vout back to vin , as well as some other properties discussed in Section 2.1. Note how a small change in the passwords yields a big change in hash value; this is known as the avalance effect. When a user inputs a password for control, the input is hashed and compared with the stored hash value. One goal of this thesis is to investigate the possibilities for the special case of password hashing rather than digesting large files. Password hashing is a special case for two reasons: firstly, the inputs to the hash function (i.e. passwords) can be expected to be generally much shorter than those experienced in file digestion, as well as the property that the input is a secret parameter in the case of password hashing. The thesis also seeks to explore the possibilities in optimizing brute-force searches for passwords by exploiting some of these properties of hash functions. vin

H

vout

hunter123

H

7d90bbcd63d2cff0c7967b4f5c73385a

punter123

H

d185438549222a39719502e8250eba14

Figure 1: Putting passwords through a one-way function.

3

4

introduction

1.1

motivation

In the past years there has been quite a number of embarrassing breaches of user database integrity from large companies. These leaks are carried out by IT criminals that have gained access to them in an unlawful manner and released them privately or publically, sometimes after trying to blackmail the original holder of the data. The companies that have had parts of or entire user databases leaked include LinkedIn, Linode and Twitter. Instead of storing passwords in plain text, system designers moved on to saving the passwords using hash functions that translate a password into another value, but without offering the possibility to go backwards. In that way, passwords can be stored in a unified way that does not reveal the passwords, even if an attacker gets access to the database. Currently, the best way to crack hashed passwords with guarranteed success is to create an exhaustive list of all possible candidates and run each of these candidates through the hash function. If the password is in the candidate list, it will be recovered for certain. There is another type of attack discussed in Chapter 3, called time-memory trade-offs; these provide drastic improvements in computation time when cracking, at the cost of memory and an exhaustive search in a necessary precomputation phase. Speeding up password recovery with cryptanalytic techniques is uncharted territory since all advances so far have concerned optimizations on the implementation-level. Using knowledge of the innards of a hash function to trigger local collisions might allow us to raise the bar for password cracking a bit further at the cost of reduced generality— i.e. an optimization for one function might not be applicable on other functions. Some time-memory trade-offs like rainbow tables (to be discussed later in Section 3.5.2) constitute a very parallelizable way to attack password hashes, and brute-force cracking is fully parallelizable, i.e. that each sub-problem does not depend on any of the other problems. The optimizations decribed in this thesis will not be fully parallelizable, since they depend on caching internal states of the hash function during cracking. However, it is likely that it will still be parallelizable to a degree that will yield significant speed advantages. Finally it would make sense to introduce the meet-in-the-middle framework, which is the technique of attacking a problem from both sides at the same time and saving progress for use across several parts of the same computation. This will be discussed further in Section 2.3.1.

1.2 method

1.2

method

By inspecting the MD5 and SHA1 innards it should be possible to construct models for various attacks, taking into account the special properties of human-chosen passwords. In the context of an exhaustive search of some space, it might be beneficial to order the list of candidates in a certain way, or trade memory for computation time. To start off, a reference implementation of MD5 will be written. Its performance is not of particular importance, since the efficiency of the proposed attacks will be measured in ratio compared to this control implementation. It is assumed that this performance ratio does not change if the attacks were to be carried out on another implementation, because the proposed attacks make use of internal states that need to be calculated anyway in order to get the final hash value. Storing these interim values does not make any further requirements to an implementation than just storing them. The attacks should then be implemented, documented and tested against each other and unmodified cracking implementations. The results should be assessed and discussed. Finally, there should be a discussion on the applicability of the attacks in current cracking implementations.

1.3

scope

This thesis deals with attacks on algorithmic shortcomings of the MD5 algorithm primarily, and SHA1 in the cases where an attack can be tweaked to work on that. Attacks will be made to deal with the case of hashes being calculated as h = H(p), h, p, H being the hash value, password and hash function, respectively, i.e. the case of a plain hashing (see Section 1.7). The attacks work for password candidates (possibly including salts) that use the first three message words of MD5, i.e. the combined length of the password candidate and a possible salt should not exceed 12 characters. Since it is largely impossible to iterate over all passwords having the entire printable alphabet of the ASCII printable characters, experiments are carried out on smaller alphabets. As an alternative strategy, one could construct the passwords to be cracked in such a way that would make the search fast, even though the alphabet is relatively large, by picking a password that is checked in an early iteration, but since one cannot govern how the competing crackers iterate; that is

5

6

introduction

not a fair tactic. Thus, it is impossible to do a fair study over the entire printable alphabet, but it is conjectured that the attack will retain the same proportional speed-up even for larger alphabets.

1.4

limitations

Although attacks will be presented in this work, they have not been implemented in industry-standard cracking utilities like John the Ripper. This is in part due to time constraints, but also because the codebase is somewhat opaque with not a lot of documentation about how to modify the source code. Hashcat, its competition, is not open source. The attacks are not written for multithreaded applications, although they could have been. Neither are they compiled for or tested on GPUs because of lacking hardware to test on. Neither do the implementations support the user providing lists of hashes to be cracked, although the attacks do support it.

1.5

contribution

This thesis presents the first known work that uses hash function cryptanalysis to aid in password cracking. Current cracking implementations have made implementation-level optimizations in order to hash individual password candidates individually rather than doing optimizations that made use of the developers’ knowledge of the internal state(s) of the hash functions. The attacks trade computational complexity for memory by storing parts of the computation that would be the same across a large number of password candidates, and uses the memory as a look-up table to aid in these searches. It is further argued that the attacks, suitable for passwords of 12 characters or fewer, can be implemented on any MD5-based preimage search because they constitute nothing more than systematic storage of internal states that would be present during the computation anyway. The work also contributes a proof-of-concept implementation of the attacks, called md5_attack, listed in Appendix A.1.

1.6 outline of thesis

1.6

outline of thesis

• This chapter presents an introduction to the thesis matter; it briefly describes how hash functions are used for password storage before discussing some meta-matter about scope and limitations. • Chapter 2 goes into some background knowledge needed for the thesis. Hash functions are presented in a more general way, and some of their properties and shortcomings are described. The meet-in-the-middle framework is also introduced. • Chapter 3 describes how hash functions are used for password storage, and what kinds of attacks there have been previously. It also touches upon another type of time/memory tradeoff called rainbow tables, and discusses some defensive precautions systems designers can take to defend against hash function attacks. Section 3.5 surveys different cracking strategies and implementations. • Chapter 4 presents the attacks that constitute the main contribution of this thesis, along with some considerations on passwordspecific properties of preimages (as opposed to binary data). • Chapter 5 benchmarks the proposed attacks against a control implementation as well as other password crackers. After the benchmark the results are briefly analyzed and surveyed for quality. • Chapters 6 and 7 discusses and concludes the findings.

1.7

nomenclature and notation

In this work some technical jargon is used: • The term hash function is used in interchangeably for cryptographic hash function. • A password candidate refers to a word that is tested as a potential password for some hash value. • Plain hashing is the operation of feeding a word into a hash function, and nothing else. No iterated hashing/stretching, salting or anything else in that regard. • The mathematical expression |x| refers to the complexity of some variable x, that is how many different values x can take.

7

2

BACKGROUND

This section goes through some required background knowledge that the reader will need to have in order to get a full understanding of this work. Readers knowledgeable in hash functions and common attacks on them can skip this section, but acquaintance with meet-inthe-middle framework is crucial to understanding the primary attack proposed, see Section 2.3.1.

2.1

hash functions

Hash functions are one-way functions that maps the {0, 1}x x ∈ N space into the {0, 1}n space with each hash function dictating a fixed n for that particular function. Let H(x) denote the hash value of the message x. x is also called the preimage to H(x). Hash functions generally satisfy the following properties: 1. Collision resistance: It should be difficult to find two messages x, x0 such that H(x) = H(x0 ). 2. Preimage resistance: Given a hash value y it should be difficult to find x such that y = H(x). 3. Second preimage resistance: Given a message x it should be difficult to find another message x0 6= x such that H(x) = H(x0 ). In this context difficult denotes computational infeasibility, i.e. that carring out the attack is no easier than random guessing. As so, the function constitutes an untamperable means to ensure the accurateness of file transfers, even in the case of malicious hops in the network. A hash function would be considered “broken” when either of these properties are defeated for some hash function. The one-way property of hash functions is also used for passwords, since it is more safe to store the output of a one-way function in a database than passwords in plain text. Upon login, the same function can be applied to the given credentials and the result can be compared with the database value.

9

As long as the hash value itself can be transferred over a trusted link, in which case the hash value itself could be tampered with.

10

background

message apply padding

IV

m0

m1

f

f

mn ...

f

h

Figure 2: The Merkle-Damgård construction. The message comes in, padding is applied and the message chunks are put into the compression functions that serves as chaining values for each other. The result is the hash value.

2.1.1

The Merkle-Damgård construction

There is a challenge to the construction of general-purpose hash functions, namely that the input may be of any length, while the output length must be fixed. This obstacle is overcome by chopping up the message to be hashed into smaller pieces that are then processed in turn by a one-way function that requires a fixed-length input. Combining these subruns of the one-way function such that the output of the first serves as the initial vector of the other yields a structure that allows for a variable-length input and a fixed-length output, see Figure 2. The Merkle-Damgård construction, described by Ralph Merkle in his 1979 Ph.D. thesis and independently proved sound by himself [8] and Damgård [2], is a technique to use secure one-way functions in a chain as long as the input to be hashed. It comes with a proof that if the compression function satisfies the properties described in Section 2.1, then the resulting hash function does, too.

2.2

md5

MD5 is a general-purpose hash function, designed by Ron Rivest and presented at a rump session at CRYPTO’91. It was presented to replace an earlier variant called MD4 on the grounds that, in quote, “MD4 was perhaps being adopted for use more quickly than justified by the existing critical review”[11]. MD4 and MD5 use the same basic design, however.

2.2 md5

The MD5 algorithm was designed to be fast in software on 32-bit processors; it attains that goal by using 32-bit words internally, and by using functions that are cheap on CPUs, including exclusve-or and modular addition. The RFC does not discuss the algorithm’s aptness for password storage, only that it is designed to be fast[11]; namely its speed has been the area of some criticism, which will be further covered in Section 3.2. Since it is so fast, it has enjoyed wide adoption since its inception in the early 1990s for the purposes of checksumming files but—unrelated to its speed—also as a one-way function for password storage. At the time of writing, MD5 is robust against preimage attacks; the best-known public attack for full preimages has a computational complexity of 2123.4 [12], although currently unpublished work from DTU presents a preimage attack at MD5 with complexity 2121 . MD5 is, however, completely broken with regards to collisions, which can be computed in seconds on a modern PC [14]. Cryptographers have advised systems designers to move on to more mature hash functions because of this fact. Despite these urges for the algorithm’s resignation, some systems still employ it for use in password storage. The remainder of this section describes how MD5 works.

2.2.1

Structure

The MD5 compression function (discussed in the next section) has an input block size of 512 bits, which means the input data has to be padded to a multiple of 512 bits. It does so by adding a 1 and then adding zeros until the length of the message is 448 modulo 512. Then the length of the message without padding is appended, zero-padded to be 64 bits wide, to fill the rest of the last 512-bit block. The message length is now divisible by 512. After the padding, state is prepared by allocating four chaining values A, B, C, D which are initially set to four constants h0 . . . h4 . Then, for each of the 512-bit chunks, the compression function is run with the current chaining values and message chunk as input. The output of the compression function serves as the chaining values for the next message chunk. When there are no more message chunks left, h0 . . . h4 are added once more. The final hash value is then the content of the four chaining variables. This is a Merkle-Damgård construction. This structure, also shown in Figure 3 gives rise to two distinct terms: an inner state which describes what happens in the 64 operations, and an outer state (the Merkle-Damgård construction which was just de-

11

12

background

a b

message chunk

c d

64 × ϕ



a 0 b 0 c0 d 0 Figure 3: The MD5 compression function. The chaining variables a, b, c, d are put into the MD5 operation function along with a 512-bit message chunk. Then 64 operations are run before adding the chaining values back onto the output. This structure keeps repeating itself until there are no more message chunks left. The ⊕ symbol denotes 32-bit modular addition.

scribed) that takes the result of the inner state and adds it to the chaining variables which end up being the input to the next compression function.

2.2.2

Compression function

The MD5 compression function is a one-way function that takes two inputs: The chaining variables a, b, c, d, and a 512-bit message chunk. Moreover, it has two arrays of constants: the addition constants and the shift constants. Each compression function call consists of 4 rounds that in turn consist of 16 MD5 operations, each with a different profile of addition and shift constants. Each of the rounds moreover feature a different permutation of the ordering of the sixteen 32-bit words in the message chunk. One such operation is shown in Figure 4. So having the chaining variables as a, b, c, d, the function F is calculated from the current values of b, c and d, and the result is added to a. F changes for each of the four rounds, and an overview is provided in Table ??. Then also the message word m and the addition constant k is added, before a is bitwise left-rotated by s steps, s being the shift constant. Finally, b is added to a before substituting (a, b, c, d) 7→ (b, c, d, a). One message chunk consists of sixteen message words, all of them used four times each to add up to 64 operations for one compression

2.3 cryptographic attacks

Round

Steps

F(b, c, d)

1

0 − 15

(b ∧ c) ∨ (¬b ∧ d)

2

16 − 31

(b ∧ d) ∨ (c ∧ ¬d)

3

32 − 47

b⊕c⊕d

4

48 − 63

c ⊕ (b ∨ ¬d)

Table 1: Overview of the F function in MD5.

function call. The technique of using message words more than once is called message expansion.

2.3

cryptographic attacks

Cryptology comprises the sciences of crafting new ciphers (cryptography), as well as trying to break them (cryptanalysis). Hash functions, being a subtopic in cryptology naturally falls under these terms as well. When attacking hash functions, one seeks to find ways in which the three hash function properties outlined in Section 2.1 can be broken in more efficient ways than by brute force. If that is possible, even if it is still computationally infeasible, the technique will constitute an attack. And further, as will be shown, an attack can have a loop complexity equivalent to brute force, but if the calculations done are provably simpler, the attack would still hold.

2.3.1

Meet-in-the-middle attacks

The general meet-in-the-middle framework depends on forwards calculation of some function, caching the results and finally backwards calculation of the same function. It is applicable whenever an operation has a f ◦ g construction, for reversible suboperations f, g. As a trivial example, consider a cryptosystem built upon a symmetric cipher using E for encryption and D for decryption being called twice using a tuple of keys hk1 , k2 i. To encrypt a message m, calculate c = Ek2 (Ek1 (m)). To decrypt a ciphertext c, calculate m = Dk2 (Dk1 (c)). At first one might be led to believe that the complexity of such a system depends on the complexity of |k1 | × |k2 |, although it is not actually the case.

13

14

background

A

B

C

D

B

C

D

F Mi Ki 24) & 255;

23

return ((uint32_t)c1 c = byteswap(strtol(tmp, strncpy(tmp, string+24, 8); target->d = byteswap(strtol(tmp,

30 31 32 33 34 35 36 37

NULL, 16)); NULL, 16)); NULL, 16)); NULL, 16));

38 }

a.1.3

md5.c

1 #include 2 #include 3 #include 4 #include 5 #include "md5.h" 6 7 #ifdef _LIBC 8 # include 9 # if __BYTE_ORDER == __BIG_ENDIAN 10 #

define WORDS_BIGENDIAN 1

11 # endif 12 #endif 13 14 #ifdef WORDS_BIGENDIAN 15 # define SWAP(n)

\ (((n) 8) & 0xff00) | ((n) >> 24)) 17 #else 18 # define SWAP(n) (n) 19 #endif 16

20 21 22 #define F(b, c, d) (((b) & (c)) | ((~(b)) & (d))) 23 #define G(b, c, d) (((b) & (d)) | ((c) & (~(d)))) 24 #define H(b, c, d) ((b) ^ (c) ^ (d))

bibliography

25 #define I(b, c, d) ((c) ^ ((b) | (~(d)))) 26 27 #define ROTATE_LEFT(w, s)

(w = (w > (32 - s))) _ 28 #define ROTATE RIGHT(w, s) (w = (w >> s) | (w d); else if (r < 32) f_val state_ptr->d); else if (r < 48) f_val state_ptr->d); else f_val _ state ptr->d); if

65

66

67

68

= F(state_ptr->b, state_ptr->c, = G(state_ptr->b, state_ptr->c, = H(state_ptr->b, state_ptr->c, = I(state_ptr->b, state_ptr->c,

69

new_b

70

= state_ptr->a;

71

new_b += f_val; new_b += k[r]; new_b += m[m_idx[r]];

72 73 74 75

ROTATE_LEFT(new_b, s[r]);

76 77

old_a = state_ptr->a; state_ptr->a = state_ptr->d; state_ptr->d = state_ptr->c; state_ptr->c = state_ptr->b; state_ptr->b = old_a;

78 79 80 81 82 83

state_ptr->b = new_b + (state_ptr->c);

84 85 }

86 void md5_round_backwards(md5_state *state_ptr, uint32_t* m, int r

) { 87 88 89

uint32_t f_val; uint32_t new_a; uint32_t old_b;

90 91 92 93 94 95

old_b = state_ptr->b; state_ptr->b = state_ptr->c; state_ptr->c = state_ptr->d; state_ptr->d = state_ptr->a; state_ptr->a = old_b;

96 97

98

99

100

101

(r < 16) f_val state_ptr->d); else if (r < 32) f_val state_ptr->d); else if (r < 48) f_val state_ptr->d); else f_val state_ptr->d); if

= F(state_ptr->b, state_ptr->c, = G(state_ptr->b, state_ptr->c, = H(state_ptr->b, state_ptr->c, = I(state_ptr->b, state_ptr->c,

bibliography

102 103

new_a = state_ptr->a - state_ptr->b; ROTATE_RIGHT(new_a, s[r]);

104 105 106 107

new_a -= f_val; new_a -= k[r]; new_a -= m[m_idx[r]];

108 109

state_ptr->a = new_a;

110 } 111 112 md5_state md5(char * input) 113 { 114

md5_state ret;

115 116 117

uint32_t *m = (uint32_t *) calloc(16, sizeof(uint32_t)); int input_length = strlen(input);

118 119

120

121 122 123 124

// We don’t want inputs larger than what fits in the first three message words // (we can use strlen because there’s no chance of 0x00s just yet. if (input_length > 3 * sizeof(uint32_t)) { fprintf(stderr, "Password candidate too long."); exit(255); }

125 126

127

128

129

/* Do the padding. We need to consider m as a char pointer in order to properly * point out the right address for the padding to start. */ memcpy(m, input, input_length); memcpy((char *) m + input_length, fillbuf, 64 input_length);

130 131 132

// Add the length of the plaintext in bits m[14] = input_length * 8;

133 134 135

// Calculate the hash value md5_truncated(&ret, m, 63);

136 137 138 139 140

ret.a ret.b ret.c ret.d

+= += += +=

h0; h1; h2; h3;

141 142

free(m);

143 144 145 } 146

return ret;

63

64

bibliography

147 void md5_truncated(md5_state* state_ptr, uint32_t * m, int

stop_after_round) 148 { 149 150

int i; uint32_t tmp, tmp2;

151 152 153 154 155

state_ptr->a state_ptr->b state_ptr->c state_ptr->d

= = = =

h0; h1; h2; h3;

156 157 158 159 160

// Calculate the hash value for (i = 0; i a s->d s->c s->b

= = = =

d; c; b; a;

256 } 257 258 void md5_64to48_fast(md5_state* s, uint32_t * m) { 259 register uint32_t a, b, c, d; 260 261 262 263 264

a b c d

= = = =

s->a; s->b; s->c; s->d;

265 266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/

281 282 283

s->a = d; s->b = a;

b, c, d, m, 63); /* c, d, a, m, 62); /* d, a, b, m, 61); /* a, b, c, m, 60); /* b, c, d, m, 59); /* c, d, a, m, 58); /* d, a, b, m, 57); /* a, b, c, m, 56); /* b, c, d, m, 55); /* c, d, a, m, 54); /* d, a, b, m, 53); /* a, b, c, m, 52); /* b, c, d, m, 51); /* c, d, a, m, 50); /* d, a, b, m, 49); /*

bibliography

284 285

s->c = b; s->d = c;

286 } 287 288 void md5_48to1_fast(md5_state* s, uint32_t * m) { 289 register uint32_t a, b, c, d; 290 291 292 293

a b c d

= = = =

s->b; s->c; s->d; s->a;

294 295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/

a, b, c, m, 48); /* b, c, d, m, 47); /* c, d, a, m, 46); /* d, a, b, m, 45); /* a, b, c, m, 44); /* b, c, d, m, 43); /* c, d, a, m, 42); /* d, a, b, m, 41); /* a, b, c, m, 40); /* b, c, d, m, 39); /* c, d, a, m, 38); /* d, a, b, m, 37); /* a, b, c, m, 36); /* b, c, d, m, 35); /* c, d, a, m, 34); /* d, a, b, m, 33); /* a, b, c, m, 32); /* b, c, d, m, 31); /* c, d, a, m, 30); /* d, a, b, m, 29); /*

67

68

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

bibliography

a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/ c = md5_round_backwards_noswap(b, state: c d a b*/ d = md5_round_backwards_noswap(c, state: d a b c*/ a = md5_round_backwards_noswap(d, state: a b c d*/ b = md5_round_backwards_noswap(a, state: b c d a*/

a, b, c, m, 28); /* b, c, d, m, 27); /* c, d, a, m, 26); /* d, a, b, m, 25); /* a, b, c, m, 24); /* b, c, d, m, 23); /* c, d, a, m, 22); /* d, a, b, m, 21); /* a, b, c, m, 20); /* b, c, d, m, 19); /* c, d, a, m, 18); /* d, a, b, m, 17); /* a, b, c, m, 16); /* b, c, d, m, 15); /* c, d, a, m, 14); /* d, a, b, m, 13); /* a, b, c, m, 12); /* b, c, d, m, 11); /* c, d, a, m, 10); /* d, a, b, m, 9); /* a, b, c, m, 8); /* b, c, d, m, 7); /* c, d, a, m, 6); /* d, a, b, m, 5); /* a, b, c, m, 4); /* b, c, d, m, 3); /*

bibliography

341

c = md5_round_backwards_noswap(b, c, d, a, m, 2); /* state: c d a b*/

342 343 344 345 346

s->a s->b s->c s->d

= = = =

c; d; a; b;

347 } 348 349 void md5_0to64_fast(md5_state* s, uint32_t * m) { 350 register uint32_t a, b, c, d; 351 352 353 354 355

a b c d

= = = =

h0; h1; h2; h3;

356 357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

a = md5_round_noswap(a, */ d = md5_round_noswap(d, */ c = md5_round_noswap(c, */ b = md5_round_noswap(b, */ a = md5_round_noswap(a, */ d = md5_round_noswap(d, */ c = md5_round_noswap(c, */ b = md5_round_noswap(b, */ a = md5_round_noswap(a, */ d = md5_round_noswap(d, */ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/

b, c, d, m, 0); /* state: d a b c a, b, c, m, 1); /* state: c d a b d, a, b, m, 2); /* state: b c d a c, d, a, m, 3); /* state: a b c d b, c, d, m, 4); /* state: d a b c a, b, c, m, 5); /* state: c d a b d, a, b, m, 6); /* state: b c d a c, d, a, m, 7); /* state: a b c d b, c, d, m, 8); /* state: d a b c a, b, c, m, 9); /* state: c d a b d, a, b, m, 10); /* state: b c d c, d, a, m, 11); /* state: a b c b, c, d, m, 12); /* state: d a b a, b, c, m, 13); /* state: c d a d, a, b, m, 14); /* state: b c d c, d, a, m, 15); /* state: a b c b, c, d, m, 16); /* state: d a b

69

70

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

bibliography

d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/

a, b, c, m, 17); /* state: c d a d, a, b, m, 18); /* state: b c d c, d, a, m, 19); /* state: a b c b, c, d, m, 20); /* state: d a b a, b, c, m, 21); /* state: c d a d, a, b, m, 22); /* state: b c d c, d, a, m, 23); /* state: a b c b, c, d, m, 24); /* state: d a b a, b, c, m, 25); /* state: c d a d, a, b, m, 26); /* state: b c d c, d, a, m, 27); /* state: a b c b, c, d, m, 28); /* state: d a b a, b, c, m, 29); /* state: c d a d, a, b, m, 30); /* state: b c d c, d, a, m, 31); /* state: a b c b, c, d, m, 32); /* state: d a b a, b, c, m, 33); /* state: c d a d, a, b, m, 34); /* state: b c d c, d, a, m, 35); /* state: a b c b, c, d, m, 36); /* state: d a b a, b, c, m, 37); /* state: c d a d, a, b, m, 38); /* state: b c d c, d, a, m, 39); /* state: a b c b, c, d, m, 40); /* state: d a b a, b, c, m, 41); /* state: c d a d, a, b, m, 42); /* state: b c d

bibliography

b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/ a = md5_round_noswap(a, c*/ d = md5_round_noswap(d, b*/ c = md5_round_noswap(c, a*/ b = md5_round_noswap(b, d*/

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

s->a s->b s->c s->d

422 423 424 425 426 }

a.1.4

md5.h

= = = =

a; b; c; d;

c, d, a, m, 43); /* state: a b c b, c, d, m, 44); /* state: d a b a, b, c, m, 45); /* state: c d a d, a, b, m, 46); /* state: b c d c, d, a, m, 47); /* state: a b c b, c, d, m, 48); /* state: d a b a, b, c, m, 49); /* state: c d a d, a, b, m, 50); /* state: b c d c, d, a, m, 51); /* state: a b c b, c, d, m, 52); /* state: d a b a, b, c, m, 53); /* state: c d a d, a, b, m, 54); /* state: b c d c, d, a, m, 55); /* state: a b c b, c, d, m, 56); /* state: d a b a, b, c, m, 57); /* state: c d a d, a, b, m, 58); /* state: b c d c, d, a, m, 59); /* state: a b c b, c, d, m, 60); /* state: d a b a, b, c, m, 61); /* state: c d a d, a, b, m, 62); /* state: b c d c, d, a, m, 63); /* state: a b c

71

72

bibliography

1 #include 2 3 #define TRUE 1 4 #define FALSE 0 5 6 extern const uint32_t h0; 7 extern const uint32_t h1; 8 extern const uint32_t h2; 9 extern const uint32_t h3; 10 extern const int m_idx[]; 11 12 #ifndef MD5_STATE_STRUCT 13 typedef struct 14 15 16 17 18 19 20

{ uint32_t a; uint32_t b; uint32_t c; uint32_t d; _ } md5 state; #define MD5_STATE_STRUCT #endif

21 22 #define MAYBE_INLINE 23

md5_round (md5_state *state_ptr, uint32_t* m, int r); 25 void md5_round_backwards(md5_state *state_ptr, uint32_t* m, int r); 26 md5_state md5 (char * input); 27 void md5_truncated (md5_state* state_ptr, uint32_t * m, int stop_after_round); 24 void

a.1.5

naivesearch.c

1 #include 2 #include 3 #include 4 #include "md5.h" 5 6 extern int get_candidate_word(char bytes_begin, int bytes_base,

int strength); 7 8 MAYBE_INLINE int 9 10 11 12 13 14 15

naive_search(char bytes_begin, char bytes_end, uint32_t a, uint32_t b, uint32_t c, uint32_t d, int length) { md5_state target, tmp; uint32_t *m; int m1cnt, m1num ; uint8_t b1, b2, b3, b4; uint8_t b9; int bytes_base;

bibliography

16 17

i = 0; bytes_base = bytes_end - bytes_begin + 1;

18 19 20 21 22

target.a target.b target.c target.d

= = = =

a b c d

-

h0; h1; h2; h3;

23 24 25 26

/* Initialize the message data structure. */ m = (uint32_t *) calloc(16, sizeof(uint32_t)); m[14] = length*8;

27 28 29 30

m1num = (int) pow(bytes_base, 4); for (m1cnt = 0; m1cnt < m1num; m1cnt++) { m[1] = get_candidate_word(bytes_begin, bytes_base , m1cnt);

31 32 33 34

for (b9 = bytes_begin; b9

Suggest Documents