WEBVTT

00:01.350 --> 00:03.390
Hello, my name is Mike Stay.

00:03.390 --> 00:11.430
I'm the CTO of Pyrefx Corporation, and today I'm going to tell you about how we recovered several hundred thousand dollars worth of Bitcoin from an encrypted zip file.

00:12.950 --> 00:20.170
Around 20 years ago, I was working as a reverse engineer and cryptanalyst for AccessData while getting my physics degree at BYU.

00:20.590 --> 00:23.250
It was the late 90s, early 2000s.

00:23.250 --> 00:32.970
The US government had been gradually easing off export restrictions on software containing cryptography, but most software still contained pretty worthless password protection.

00:33.130 --> 00:40.410
We'd buy desktop office software, I'd reverse engineer it to figure out what algorithm it was using for access control, and then break the crypto.

00:40.450 --> 00:44.970
It was a never-ending stream of interesting but not impossible math puzzles.

00:44.970 --> 00:48.710
I wrote about 40 password crackers during my time there.

00:48.870 --> 00:54.230
We would sell them to home users, system administrators, local and federal law enforcement agencies.

00:54.490 --> 01:04.370
I got to go down to the Federal Law Enforcement Training Center in Glynco a few times to teach secret service agents, FBI agents, ATF, about cryptography and the use of our products.

01:05.250 --> 01:07.710
Two of the projects really stand out in my memory.

01:07.710 --> 01:10.370
The first was Microsoft Word 97.

01:10.950 --> 01:18.210
Before Word 97, the files were encrypted by XORing the bytes with a repeating 16-byte string derived from the password.

01:18.490 --> 01:24.470
The most common bytes in a Word file were either 0, 255, or 32, which is space.

01:24.470 --> 01:34.570
So we'd just look at the most common character in each of the 16 columns, try the 3 to the 16th variations on those, and recovering the key was usually instantaneous.

01:34.570 --> 01:47.090
But to help people feel like they'd gotten their money's worth, we'd put on a little show like the scene in War Games, where it would be animated, showing random characters that would gradually reveal the right password.

01:47.990 --> 01:50.610
Microsoft 97 changed that.

01:50.910 --> 01:58.710
It might have been possible to find out the encryption format through Microsoft Developer Network, but we were a small company and couldn't afford the subscription.

01:58.850 --> 02:03.650
It also wasn't clear we'd be allowed to write the cracker if we did get the info from them.

02:03.650 --> 02:07.370
So to figure out how it worked, I used softdice.

02:07.810 --> 02:12.150
There was a button on a cable that would go to a card in the computer.

02:12.170 --> 02:23.430
I'd type the password, and then hit enter and the button on the cable as fast as I could, and hope that it would break somewhere in the crypto stuff.

02:23.650 --> 02:28.830
I'd walk up the stack, trying to figure out where the algorithm was.

02:28.830 --> 02:45.250
This was in the days before IDA Pro, so I printed out a few dozen pages of assembly code and taped it to the wall, and then drew all over it with a red crayon, updating the function names and so on as I figured stuff out.

02:45.250 --> 02:47.790
I was very pleased when I finally worked it all out.

02:47.990 --> 02:54.550
At the time, Microsoft was only allowed to export 40-bit cryptography, so they did as much as they were legally permitted to do.

02:54.550 --> 03:08.710
They'd repeatedly MD5 the password with some salt, which is randomly chosen bytes that is stored in the file, and then they'd get a 40-bit key, then they'd add salt to that and repeatedly hash it again.

03:08.710 --> 03:13.090
So it took about half a second to test a password on the computers at the time.

03:13.090 --> 03:18.290
So despite being a 40-bit key space, it was a fairly hard problem to attack.

03:18.770 --> 03:23.550
We had to resort to a dictionary attack, because breaking it outright was pretty much impossible.

03:24.550 --> 03:42.770
We did eventually write a cracker for larger companies that had their own computer labs and resources, other large agencies, to brute force the 40-bit key space using the fancy MMX instructions on the Pentium.

03:42.810 --> 03:47.590
This was long before graphics processors were available.

03:47.590 --> 03:52.670
I knew of one place that ran the software for nine months before finally getting in.

03:52.670 --> 03:57.090
The other really fun one I did was Zip Archives.

03:57.090 --> 04:06.150
The developer of PKZip, Phil Katz, made the decision, which was unusual at the time, to document his file format and include it with his software.

04:06.670 --> 04:09.090
That made it a favorite of developers.

04:09.090 --> 04:12.730
Roger Schlafly designed the stream cipher used for encrypting the archives.

04:12.730 --> 04:17.790
The Zip standard quickly became by far the most popular compression format on Windows.

04:17.790 --> 04:27.010
And many other formats, like Java's JAR format, OpenOffice's document formats, are really just Zip files with a particular directory structure inside.

04:29.370 --> 04:33.490
InfoZip is an open source implementation of the software.

04:33.490 --> 04:38.250
It was used as the basis for nearly all other branded Zip software, like WinZip.

04:38.250 --> 04:45.030
And at the time I was trying to crack it, WinZip had 95% of the market, according to CNET downloads.

04:47.210 --> 04:53.150
Eli Biem and Paul Kosher had published a known plaintext attack on the cipher.

04:53.150 --> 04:56.490
But the known plaintext was compressed plaintext.

04:56.490 --> 05:03.390
To get the Huffman codes at the start of a deflated file, you basically need 32 kilobytes of the file.

05:03.870 --> 05:08.210
The attack was practically useless to law enforcement agencies.

05:08.250 --> 05:14.690
This diagram is an illustration of the internal workings of the Zip cipher.

05:14.690 --> 05:18.570
Each of these boxes represents a byte, 8 bits.

05:18.670 --> 05:24.210
The cipher has 96 bits of internal state, split into three 32-bit chunks.

05:24.210 --> 05:28.610
One is called Key0, Key1, and Key2.

05:28.610 --> 05:39.150
This subscript indicates the state of that chunk after having processed that many bytes of text.

05:39.250 --> 05:41.470
So here, this is the initial state.

05:41.470 --> 05:47.710
We feed in the first byte of text, and we get the subsequent state of Key0.

05:47.710 --> 05:52.990
Then we take part of that and feed it in here, and we get the subsequent state of Key1.

05:52.990 --> 05:57.750
Then we take one of these bytes and feed it in here, and get the subsequent state of Key2.

05:57.750 --> 06:03.870
Then we do one more operation on these two low bytes to get the stream byte.

06:04.870 --> 06:10.570
Going into a little more detail, this first section is CRC32.

06:10.570 --> 06:13.790
CRC stands for Cyclical Redundancy Check.

06:13.790 --> 06:21.090
It was designed for detecting errors on the circular tracks that you would find on floppy disks.

06:21.370 --> 06:25.230
So here we can see the shift, it's being shifted right 8 bits.

06:25.230 --> 06:33.210
We take these 8 bits that got shifted off the bottom, and use them to look up a 32-bit word in this table.

06:33.370 --> 06:39.430
Then we take the byte that's coming in, and we also look up a 32-bit word for that one in the table.

06:39.430 --> 06:49.350
Then we XOR the high 24 bits here, that were left after shifting it down, with these two words, and we get the new state of Key0.

06:50.150 --> 07:03.130
We take the low byte of the new value of Key0, add it to the current state of Key1, multiply by this constant, add 1, and get the new state of Key1.

07:03.770 --> 07:07.590
So this is called a truncated linear congruential generator.

07:07.590 --> 07:13.070
Truncated because we're only taking the high byte as output.

07:13.070 --> 07:15.850
Linear because it's addition.

07:16.290 --> 07:19.710
Congruential because it's modulo 2 to the 32.

07:19.710 --> 07:26.290
And generator because it's spitting out pseudo-random bytes.

07:26.290 --> 07:31.450
So it takes this byte that it's spitting out and feeds it into CRC32 again.

07:31.450 --> 07:42.610
Now, this is a linear feedback shift register, but this one is linear with respect to XOR, while this one is linear with respect to addition, with carries.

07:42.610 --> 07:48.430
So they don't work well together, and that's what really gives it whatever strength it does have.

07:49.730 --> 07:58.510
So, CRC32, truncated linear congruential generator, another CRC32, and then this pseudo-squaring operation.

07:58.510 --> 08:03.250
If you look in the source code, the pseudo-squaring operation is like this.

08:03.250 --> 08:12.390
You take Key2 and the low 16 bits of that, and you OR it with 2, so you set bit 1.

08:12.790 --> 08:16.910
And then you multiply that by itself XOR1.

08:16.910 --> 08:20.050
So one of the values is going to be even and the other one's odd.

08:20.050 --> 08:23.670
You shift it down by 8 and take 8 bits.

08:23.670 --> 08:29.670
So it's the middle of this pseudo-squaring operation.

08:29.710 --> 08:34.390
And the result is a stream byte.

08:34.390 --> 08:45.650
Now, the way this stream cipher works is that you initialize it first with a password, and then you encrypt the plaintext that you want to encrypt.

08:45.690 --> 08:50.610
To make it harder, they encrypt some salt first.

08:53.530 --> 09:02.170
PKZip used bytes that were in memory, so it would allocate 10 bytes and use whatever bytes were there.

09:02.170 --> 09:04.010
That's how it got its entropy.

09:05.850 --> 09:16.130
InfoZip, because it was built for use on Linux as well as Windows, couldn't do that because when the memory was allocated, it was also initialized.

09:16.130 --> 09:20.110
So they got their entropy from the process ID and the timestamp.

09:20.110 --> 09:26.470
You'd XOR those two together and call srand on it to seed the random number generator.

09:26.470 --> 09:32.570
Then it would use rand to generate 10 bytes as salt.

09:32.730 --> 09:38.090
In the file, then you'd follow it by CRC32 and the plaintext.

09:38.290 --> 09:44.450
Now, BM and Kosher had their attack, and it was published when InfoZip was implemented.

09:44.450 --> 09:55.750
So they knew that if they just used rand, then if somebody had the timestamp and the process ID, they could compute these bytes.

09:55.850 --> 10:00.230
The CRC was stored in the file to check that the password was entered correctly.

10:00.230 --> 10:07.650
So they would know these bytes, and that would give them 12 bytes, which was sufficient to mount a known plaintext attack.

10:07.650 --> 10:10.550
So InfoZip tried to avoid that.

10:11.450 --> 10:14.430
And they made it harder, but not quite hard enough.

10:17.520 --> 10:25.200
So renaming these bytes individually, R0 through R9 are the bytes generated by rand.

10:25.200 --> 10:28.260
C0, C1 are the CRC32 bytes.

10:28.420 --> 10:33.860
And P0 through P3 and so on are the plaintext bytes.

10:33.860 --> 10:35.920
And I'll call this array X.

10:36.940 --> 10:44.220
Now, when you feed the password into the cipher, it treats it as though it's plaintext.

10:44.220 --> 10:50.660
You feed in the first password byte, it updates key 0, key 1, key 2, spits out a stream byte.

10:51.160 --> 10:58.680
And then it throws the stream byte away until it gets to the last character of the password.

10:58.760 --> 11:04.700
And that final stream byte is used to encrypt the first salt byte, R0.

11:05.540 --> 11:20.140
So I get this set of 10 bytes that I call Y, which is just these first 10 bytes generated by rand, exclusive word with the 10 bytes generated by the stream cipher.

11:20.440 --> 11:25.600
Now you'll notice here I call this S0, but this one is S1X.

11:25.600 --> 11:37.600
These ones that end in X depend on the values in X, but S0 only depends on the password itself.

11:38.180 --> 11:57.200
That turned out to be their tragic flaw, that when they encrypted this a second time, so they were using Y as the salt bytes in the file, they would generate S0 again because it was the same password.

11:58.300 --> 12:09.120
So they would XOR with S0 once to get the salt byte, XOR with S0 again to get the byte that was stored in the encryption header.

12:09.120 --> 12:25.280
Now all of these are hard to figure out because they depend both on the randomly generated bytes from rand, as well as the stream cipher bytes, twice over.

12:25.280 --> 12:27.480
So this is doubly encrypted stuff.

12:27.480 --> 12:50.920
But this first byte leaked, and given five files in the archive, all encrypted with the same password, this gave me eight bits of entropy per file to find the 32-bit internal state of the rand pseudorandom number generator.

12:51.920 --> 13:12.880
So given five files, I could simply take the first byte of each archive, run through all 32 bits of possible internal state, and verify that every tenth byte gave me the byte I was expecting.

13:12.880 --> 13:21.440
So I could uniquely identify the internal state, could uniquely find these ten bytes.

13:21.440 --> 13:24.980
And so then I had some place to stand to mount the attack.

13:24.980 --> 13:33.960
So the next thing I did was notice that I didn't need all 96 bits to produce the next stream byte.

13:34.460 --> 13:37.140
I only needed 40 bits.

13:37.140 --> 13:48.560
So here I didn't need to know both key 0 and... or rather the second and the first byte of key 0.

13:48.560 --> 13:51.900
I only needed to know the XOR of this byte with that one.

13:51.900 --> 13:53.680
So that's an 8-bit guess.

13:53.680 --> 13:59.680
I know the value from rand that's going in, so I can predict this thing.

14:02.080 --> 14:05.080
Here I'm multiplying by this constant.

14:06.840 --> 14:17.960
If I take the... I don't need to know the entire value of key 1, I just need to know the high byte of key 1 times the constant.

14:17.960 --> 14:21.820
So I've distributed this multiplication across the addition we had earlier.

14:21.820 --> 14:24.040
So here is the high byte of that.

14:26.860 --> 14:31.500
And when I add 1, it might cause a carry bit, so I have to guess a carry bit.

14:31.640 --> 14:33.540
Then it comes in here.

14:34.880 --> 14:37.580
I know what the high byte of this is.

14:38.280 --> 14:39.520
I feed it in.

14:39.520 --> 14:42.140
I just need to know these values.

14:42.140 --> 14:51.300
And given the stream byte, I can then figure out what the low 6 bits are here that are needed.

14:51.820 --> 14:59.540
This is how to figure out, given a stream byte, well, a plain text and a cipher text pair.

14:59.540 --> 15:07.040
These are the bits that you have to guess in order to get this stream byte.

15:07.040 --> 15:22.460
So what I would do is I would guess 40 bits, which would allow me to process this byte here.

15:22.760 --> 15:29.920
And so then I could check for each of the five files, does this value match what I expect it to?

15:29.920 --> 15:41.980
And since I had 40 bits that I was guessing and 40 bits of entropy to compare it to from the file, I could filter out all but one guess.

15:42.160 --> 15:51.760
And then for the next byte, I would guess 26 more bits and then just a few more bits to get the rest of the state after that point.

15:51.760 --> 15:58.740
Usually, once I found this 40-bit one, the next one I could get within a few minutes.

15:58.740 --> 16:09.780
The whole attack ran in about two hours on a Pentium at the time, cipher text only attack.

16:09.780 --> 16:13.260
I was able to break into these zip files.

16:13.400 --> 16:17.820
We caught several child pornographers that way.

16:18.900 --> 16:20.960
And I got a paper out of it.

16:20.960 --> 16:36.380
So in 2001, fast software encryption, I got this paper and that led 20 years later to a surprising message.

16:36.380 --> 16:49.200
So in October of last year, a guy contacts me out of the blue and says, I read your paper on known plaintext attacks and I've got this password that I've forgotten.

16:49.200 --> 16:51.160
Is there anything you can do to help?

16:51.160 --> 16:54.300
Can you tell me how things are looking these days?

16:54.300 --> 17:02.880
And so I looked it over, turns out he only had two files in the archive.

17:02.880 --> 17:17.100
With only two files, I didn't have enough information to derive the internal state of RAND and I didn't have enough information to filter the guesses I was making.

17:17.100 --> 17:23.080
So far more guesses would pass each filtering stage.

17:23.080 --> 17:31.740
And it worked out to be something like, on my back of the envelope calculations, something like 2 to the 70.

17:31.740 --> 17:38.020
So I told him, you know, compare the work done this year to find hash collisions in SHA-1.

17:38.160 --> 17:45.080
SHA-1 has 128-bit output, so to find a collision, you've got to look at 2 to the 64th hashes.

17:45.080 --> 17:47.360
That cost around $100,000.

17:47.360 --> 17:52.200
This is not something that you can use off-the-shelf software for.

17:52.620 --> 17:57.440
And then he blew my mind and said, I could spend so much on this archive.

17:58.200 --> 18:05.700
At that point I knew that he probably had several hundred thousand dollars of Bitcoin in this thing.

18:07.260 --> 18:11.080
At this point I started doing a more careful analysis.

18:11.080 --> 18:16.280
There are 96 bits of key material that need to be guessed in various stages.

18:16.440 --> 18:19.540
But I can't guess the bits of key material directly.

18:19.540 --> 18:29.300
Instead I have to guess bits of a function of the key material and use those constraints to limit what the key material could be.

18:30.260 --> 18:37.120
This first part is CRC32 of the initial key 0 value with the 0 byte.

18:37.120 --> 18:49.580
Since CRC32 itself is invertible, once I guess these 4 bytes, there's a very simple linear operation I do to recover the initial key 0 value itself.

18:49.920 --> 18:52.920
With key 1 it's a little more complicated.

18:52.920 --> 19:02.340
We take the initial key 1 value and multiply it by this constant from the truncated linear congruential generator to the nth power.

19:02.780 --> 19:06.440
And I'm guessing the high byte of that value.

19:06.440 --> 19:16.720
Given 4 of these, that'll put enough constraints on key 1 that I can brute force it and figure out what key 1 is given those 4 bytes.

19:16.760 --> 19:20.560
The initial value of key 2 I can guess directly.

19:21.440 --> 19:28.840
So in the first stage I guess these 5 bytes, that's 40 bits, and I guess 4 carry bits.

19:28.840 --> 19:52.460
These carry bits come from the possibility that when I add 1 in the truncated linear congruential generator, it carries over all the way to the 24th bit, which would change the value of this most significant byte.

19:52.460 --> 19:54.580
So I have to guess these carry bits.

19:54.580 --> 20:03.700
I need to guess 2 for each file, there are 2 files, because I have 2 passes of encryption that I do to each byte.

20:03.740 --> 20:09.160
So 2 passes of encryption, 2 files, makes 4 carry bits that need to be guessed.

20:09.160 --> 20:20.800
So the total work for this stage is 2 to the 44th, and after filtering the 16 bits from the file, I get 2 to the 28th key guesses passing.

20:20.800 --> 20:28.720
In stage 2 I guess these 3 bytes, that's 24 bits, 4 more carry bits makes 28.

20:28.900 --> 20:35.380
Added to the previous 2 to the 28th key guesses, I have 2 to the 56th keys to filter.

20:35.420 --> 20:38.960
And after filtering, 2 to the 40th remain.

20:39.900 --> 20:45.640
In stage 3 I guess 16 bits plus 4 carry bits makes 20.

20:46.100 --> 20:50.580
Previous 40 carried forward gives us 2 to the 60th keys to test.

20:50.580 --> 20:54.200
And after filtering, 2 to the 44th pass the third stage.

20:55.740 --> 21:02.440
In the fourth stage, I guess 16, as before, 4 carry bits gives us 20.

21:02.520 --> 21:06.020
44 from the previous stage, 20 now makes 64.

21:06.180 --> 21:09.700
This was the most complicated stage, 2 to the 64th.

21:09.760 --> 21:12.440
After filtering, 2 to the 48th remain.

21:12.440 --> 21:15.820
Now in my original attack, this is where I would stop.

21:15.820 --> 21:18.120
I have all the key material.

21:18.120 --> 21:28.380
With these 4 bytes, I can brute force key 1, run through the 2 to the 32 possibilities, and get the value itself.

21:28.380 --> 21:31.420
I can invert this and that would work.

21:31.420 --> 21:45.600
But at this point it struck me, if I have to do 2 to the 32 work for each of these 2 to the 48 keys, that will give a total cost of 2 to the 80th.

21:45.600 --> 21:49.560
There is no way we could do a 2 to the 80th attack.

21:49.560 --> 22:01.950
So I had to come up with a special approach, a new idea to make this step much less expensive.

22:03.000 --> 22:10.740
I remembered having read somewhere about an attack on truncated linear congruential generators that uses lattice reduction.

22:10.740 --> 22:25.660
Lattice reduction is the idea that if you have a lattice, which is like a vector space but only integer coordinates, and a basis for that lattice, then you can find a nicer basis.

22:25.820 --> 22:30.540
Where nicer means that the vectors are shorter and closer to orthogonal.

22:30.540 --> 22:33.100
So here I have a two-dimensional lattice.

22:33.100 --> 22:38.640
Here's a basis for that lattice, where one of these goes here and another goes there.

22:38.640 --> 22:47.580
Every point on this grid has a coordinate in terms of how many steps of this kind and how many steps of that kind it takes to get there.

22:48.000 --> 22:50.640
But lattice reduction gives you a much nicer basis.

22:50.640 --> 22:53.680
These are both shorter and closer to orthogonal.

22:56.340 --> 23:00.560
Lattice reduction is in general a hard problem.

23:00.560 --> 23:09.880
About half of the candidates for the post-quantum cryptography ciphers that NIST is considering right now use lattice reduction, for example, NTRU.

23:09.880 --> 23:12.280
But in small cases, it's tractable.

23:12.280 --> 23:17.780
And I had a very small case, only four or five dimensional.

23:17.780 --> 23:25.160
So I went and poked around on the cryptography stack exchange and found exactly what I needed.

23:25.640 --> 23:35.200
The vectors, in this case, the basis vectors were powers of the constant from the truncated linear congruential generator.

23:35.200 --> 23:40.880
In our case, it was 08088405 in hex.

23:42.580 --> 23:52.720
So you take the, if we call that C, then C and C squared and C cubed and C to the fourth and so on become basis vectors for this lattice.

23:52.720 --> 23:59.420
And then you add in one more basis vector for the modulus, which in our case was two to the 32, here called M.

23:59.420 --> 24:27.880
So the idea was that given the four most significant bytes of the key one value times this consecutive powers of C, I would do a linear transformation, get a value that was close to a basis vector in this small vector basis, then round off to get it exactly to a basis vector and then transform back.

24:27.880 --> 24:33.160
That would give me the exact value of key one zero.

24:33.340 --> 24:38.520
So I wrote up a Sage program to figure out how exactly that would work.

24:39.320 --> 24:45.000
Sage is a library for Python that lets you do linear algebra, among many other things.

24:46.160 --> 24:49.860
So here we have the modulus, two to the 32.

24:50.120 --> 24:54.060
We have the constant from the truncated linear congruential generator.

24:54.720 --> 25:00.040
We have the matrix that defines the basis for our four dimensional vector space.

25:00.400 --> 25:10.300
Now the vectors themselves are the modulus and the powers of the constant with minus ones on the diagonal.

25:10.580 --> 25:13.260
This does the lattice reduction step.

25:13.260 --> 25:32.120
Now to test how B worked, I would generate a random initial k1 value, then compute the actual powers of C times k1, modulo m, that I was going to try to recover.

25:32.120 --> 25:33.600
So I'd print these out.

25:34.100 --> 25:37.120
The most significant bytes were the things that I had guessed.

25:37.120 --> 25:41.040
I wanted to recover k's from the MSBs.

25:41.040 --> 25:50.700
So the secret stuff that I needed to know were the low 24 bits, the part that I had masked off up here.

25:50.700 --> 25:55.700
So this is the secret 24 bit values that I need to guess.

25:55.920 --> 26:06.360
Given the vector that is the most significant bytes, I multiplied it by B, which switched from one lattice to the other, did a basis change.

26:06.360 --> 26:14.380
Then I rounded to the nearest vector in this nicer basis.

26:14.520 --> 26:23.800
And that meant that I would get an actual value of k10 that would produce those same most significant bytes.

26:24.380 --> 26:30.200
Then I reversed that, transformed back, and got the guess.

26:30.200 --> 26:31.960
And I'd print it out.

26:31.960 --> 26:35.520
And when I started doing this, the guess was never right.

26:36.360 --> 26:38.720
Or rarely right.

26:39.220 --> 26:45.180
And that was because of the limited number of guesses I had here.

26:47.900 --> 26:57.500
If I could do one more stage, then I would be able to get the right answer every time.

26:57.500 --> 27:04.600
But here I had too few powers of C to find out what it was exactly.

27:04.600 --> 27:09.890
But then I started checking the difference between the guess and the actual value.

27:10.540 --> 27:17.660
So I take the keys, subtract off the MSBs, and the difference between the guess and the actual one

27:22.250 --> 27:28.290
turned out to fall into one of 36 possibilities every time.

27:28.290 --> 27:38.030
So as I thought about it, I realized that in this four dimensional space, by truncating it and just using the most significant bytes, I was adding a bunch of noise.

27:38.350 --> 27:46.350
And so the point in the four dimensional space I wanted was at the center of a cell.

27:46.350 --> 27:55.710
And what I was actually getting was a point near the hull of this cell that tiled this four dimensional space.

27:55.710 --> 28:07.550
And so because it was always within this cell, there were only a finite number of other possible lattice points that it could be.

28:08.350 --> 28:23.750
And so I computed for all of the two to the 32 possible K10 values, what the set of guesses were, and found that they fit into this one of 36 classes.

28:23.750 --> 28:31.330
And so instead of having to try out all two to the 32 possible key ones each time, I would only have to do 36.

28:31.330 --> 28:34.870
So instead of four billion, 36 values.

28:34.870 --> 28:37.090
And that made the attack feasible again.

28:38.270 --> 28:47.190
Once we'd guessed all the key material, we could filter the two to the 48 keys at stage four by using the remaining bytes in the encryption header.

28:47.770 --> 28:54.770
My business partner, Nash Foster, then started working on adapting my CPU based attack to run on GPUs.

28:54.770 --> 29:02.310
He wrote the code harness for getting code and data onto the GPUs, did all the CUDA stuff, advised me on how to structure the attack.

29:02.730 --> 29:16.210
We discovered very quickly, though, that getting the petabytes of possible keys onto the GPU would take too long, that the GPUs would just be sitting idle for most of the time we were paying for them, waiting for the data to arrive to process.

29:16.210 --> 29:18.330
So I went back to the drawing board.

29:18.470 --> 29:26.110
In each stage, I was guessing a whole bunch of bits and then filtering those results using the two bytes from the two archives in the file.

29:26.350 --> 29:30.170
I'd only keep about one out of 65,000.

29:30.250 --> 29:39.930
If I had some way of using that information to derive bits, rather than just guessing and checking, it'd save a lot of work, and more importantly, a lot of network traffic.

29:40.090 --> 29:44.410
The problem with that idea was that the math is too complicated.

29:44.410 --> 29:50.990
It involves mixing finite fields with integer rings, and those simply don't play well together.

29:51.130 --> 29:57.690
So I thought about some other cryptanalytic attacks I knew, and one that seemed promising was a meet-in-the-middle attack.

29:58.330 --> 30:11.810
A meet-in-the-middle attack usually applies to block ciphers, when it uses one part of the key material to do the first half of the encryption, and the second part of the key material to do the second half of the encryption.

30:12.830 --> 30:22.170
The reason we have triple DES instead of double DES is precisely because of this.

30:22.170 --> 30:28.650
Triple DES is a 112-bit cipher, even though there are three keys involved.

30:29.690 --> 30:32.950
You can do encrypt, and then decrypt, and then encrypt.

30:32.950 --> 30:35.190
So 112 bits of strength.

30:35.190 --> 30:42.290
If it's 112 bits, why not just do DES with one key, and then DES with another key, encrypting it twice?

30:42.290 --> 30:44.210
Well, the answer is right here.

30:44.210 --> 30:54.910
What you do is you take your plaintext-ciphertext pair, and then encrypt the plaintext under all 256 possible first DES keys.

30:55.430 --> 31:01.390
And then you take the ciphertext and decrypt it under all possible 256 DES keys.

31:01.390 --> 31:06.510
And right here in the middle, chances are that one of them will match.

31:06.510 --> 31:27.130
And so you sort all of these intermediate plaintexts, and whenever you find two intermediate texts that are the same, one of them came from the plaintext and one came from the ciphertext, and that uniquely identifies the key that should go between these two.

31:27.130 --> 31:39.210
So a meet-in-the-middle attack works when you've got key material that is not all used at every stage of the cipher.

31:39.210 --> 31:42.190
And in this stream cipher, that seemed to be the case, right?

31:42.190 --> 31:54.310
I had only key 0 at the beginning and only key 2 at the end, and it seemed like we could do something in the middle, but it wasn't quite right.

31:54.310 --> 32:00.310
But then I realized I could use the XOR of certain bytes in the middle of the cipher to mount the attack.

32:01.470 --> 32:04.550
Here's a diagram of the cipher again.

32:04.550 --> 32:15.630
And the cipher gets called four times in each stage for each byte, two for each file, the double encryption of the byte from rand.

32:16.110 --> 32:28.030
So in the center of the cipher, there are four different most significant bytes for key 1 after each of these encryptions.

32:28.090 --> 32:39.350
So what I did was take the first of those four and exclusivore it with each of the other three to get a 24-bit key into a table.

32:39.350 --> 32:43.690
And under that key, I would store my guess for these bits.

32:44.910 --> 32:53.990
Then from the other side, given a stream byte, there are 64 possible 14-bit values here.

32:53.990 --> 33:06.850
So I would guess some intermediate stream bytes, six bits of this, I guess it's boxed here, these six bits to figure out what the other eight are.

33:06.850 --> 33:15.750
And that would allow me to derive linearly from this stuff what this most significant byte of key 1 was again.

33:15.750 --> 33:25.190
And so given these bits that I was guessing, I would derive a key and store these bits under that key.

33:25.230 --> 33:45.130
And then anytime I had a collision, I would know that the set of bits here that I guessed were consistent with the set of bits here that I guessed to give this plaintext going to that stream byte.

33:46.330 --> 33:56.340
Now that is far smaller than running through all of the possibilities and then filtering.

33:57.010 --> 34:03.590
The amount of memory it required was on the order of a few megabytes.

34:03.590 --> 34:06.430
So here is the code itself.

34:06.730 --> 34:10.990
This is stage 1a moving forward through the cipher.

34:10.990 --> 34:20.470
We guess the initial stream byte, because it was XORed with itself twice, we have no information about it, so we would guess that one.

34:20.470 --> 34:25.250
Then we'd guess chunk 2, which came from CRC 32 of key 0.

34:25.250 --> 34:29.150
Chunk 3 is the most significant byte of key 1.

34:30.210 --> 34:33.390
Here are the carry bits.

34:33.570 --> 34:40.850
There are two bits for each of the two files, so 2 to the 4th is 16 possible carry bits.

34:43.790 --> 34:50.050
This is debugging code for checking against files that we created ourselves.

34:51.410 --> 35:06.850
This is the first half of the cipher, and it updates an upper and a lower bound on the possible low 24 bits of key 1, so that if it ever goes out of bounds, we can throw it away.

35:08.610 --> 35:13.850
We compute various bytes, get through here.

35:13.850 --> 35:29.270
If it has passed all of these upper and lower bound checks, then this converts those four most significant bytes to a map key and stores the candidate under that map key.

35:29.830 --> 35:39.970
In the second half, we're guessing the stream1x byte for file 0.

35:40.290 --> 35:51.710
We're guessing the 64-bit prefix, because each stream byte has 64 possible pre-images under the pseudo-squaring operation.

35:52.270 --> 35:59.530
So these are all of the indexed pre-images, and this gets the specific pre-image that we're caring about.

36:00.090 --> 36:02.030
There's more debugging information.

36:02.030 --> 36:04.070
Again, we do the second half step.

36:04.070 --> 36:11.990
And if there are no consistent results, then we just go to the next guess.

36:12.010 --> 36:17.190
Here we're guessing the stream1x pass for file 1.

36:17.630 --> 36:19.870
We do that second half step.

36:19.870 --> 36:23.990
And again, if there are no consistent ones, we go back to the next guess.

36:24.830 --> 36:31.310
Finally, we compute the stream1y bytes.

36:33.010 --> 36:37.950
And if there are no consistent ones with that prefix, we continue.

36:37.950 --> 36:52.370
So now given each of the possibilities for the first and second and third bytes, we put them together into a map key, and then meet in the middle.

36:54.020 --> 36:57.320
Here is the CRC20.

36:58.480 --> 37:05.860
If you'd like to look at the code, you can see how these exclusive work together, and we can derive some certain things.

37:05.860 --> 37:19.180
So starting at the bottom, we derive bits 15 to 2 of K and L from 15 to 2 of S and T, and these bytes OP that we've computed.

37:19.180 --> 37:28.130
So in the center, we find those groups that...

37:29.130 --> 37:38.410
those guesses that work, and we push back that guess whenever the two match up with each other.

37:38.410 --> 37:40.290
And here's some more debugging information.

37:40.650 --> 38:03.790
So that's the idea of the differential meet in the middle attack, is computing forwards and backwards, exclusive oring one of the bytes with the other three, and then whenever the two guesses match, we have a set of candidates that's a product of the set from the first half of the attack with the set from the second half of the attack.

38:03.790 --> 38:15.030
Using the differential meet in the middle attack on stage 1 and stage 2 allowed us to reduce the complexity of the attack in stage 1 from 2 to the 40th down to 2 to the 22nd.

38:15.030 --> 38:18.730
That's from trillions down to mere millions.

38:18.730 --> 38:24.290
And then in stage 2 from 2 to the 56th down to 2 to the 40th.

38:24.290 --> 38:31.210
And that let us run stages 1 and 2 on each of the machines that had GPUs attached to it.

38:31.210 --> 38:36.650
We completely eliminated the need to ship batches of keys over the network.

38:36.650 --> 38:45.110
And so we could generate the keys in place and then run what we called the GPU stage 3 kernel that did all the rest of the attack.

38:45.550 --> 38:47.750
Now we ran it for 10 days.

38:48.310 --> 38:53.630
We had tried it on all of our test archives that we'd created.

38:53.630 --> 38:54.910
It worked fine.

38:56.190 --> 39:00.870
10 days passed and it didn't find a key.

39:00.870 --> 39:03.010
We were distraught, pulling our hair out.

39:03.010 --> 39:04.230
What have we done wrong?

39:04.230 --> 39:10.690
We went back and discovered that there were a few other process IDs that could have worked.

39:10.990 --> 39:14.930
And we were wondering, oh no, are we going to have to do this four more times?

39:16.290 --> 39:29.830
But then our client, who was a programmer himself, discovered that there was a bug between the GPU stage and the CPU stage.

39:29.830 --> 39:37.250
So when I had run the tests, I had done it locally on my computer using the CPU version.

39:37.270 --> 39:45.010
And the test, we knew exactly where the key had to be, so it was a very small piece of key space that we had to check.

39:45.310 --> 40:00.810
But when my client went back and tried it again using the GPU stage 3, he discovered that when the key candidate was the first one in a list, it succeeded, but when it was the second one, it failed.

40:01.250 --> 40:04.210
And that led me to this line right here.

40:04.230 --> 40:08.810
We had swapped the thread index with the block index.

40:08.810 --> 40:22.420
So instead of incrementing by one into the current block, it would increment the block number and go off into space that was uninitialized, and so it would never work.

40:23.010 --> 40:28.910
So by switching the block index with the thread index, we started the search over again.

40:28.910 --> 40:40.230
Within a day and a half, we had found the three keys that decrypted the archive, and our client was able to get his Bitcoin keys out.

40:41.350 --> 41:00.990
In the end, the improvements that I made to my old attack took it from something that we estimated at approximately $100,000 and a year of processing, down to something that took about $10,000 of GPU time and a little under two weeks.

41:01.710 --> 41:08.110
Our client was very pleased and gave us a big bonus, and that's how we recovered his Bitcoin core.

41:08.230 --> 41:09.250
Thank you very much.

