summaryrefslogtreecommitdiff
path: root/templates/key-commitment.html
blob: ab72df2397eb440ed137a91e381c2d5313000b6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
<!DOCTYPE html>
<html>
  <head>
    <title>Forbidden Salamanders &middot; Key Commitment</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="/static/styles.css">
    <link rel="stylesheet" type="text/css" href="/forbidden-salamanders/static/styles.css">
    <link rel="shortcut icon" type="image/x-icon" href="/forbidden-salamanders/static/favicon.ico">
  </head>
  <body>
	<div class="container">
        <div>
            <div class="home">
                <a href="/forbidden-salamanders" class="home-title">Forbidden Salamanders</a>
                <span> at </span><a href="/">cyfraeviolae.org</a>
            </div>
            <div class="crumbs">
                <a href="/git/forbidden-salamanders">source code</a>
                <span class="sep"> · </span>
				<a href="/forbidden-salamanders/key-commitment"><strong>key commitment</strong></a>
                <span class="sep"> · </span>
                <a href="/forbidden-salamanders/nonce-reuse">nonce reuse</a>
                <span class="sep"> · </span>
                <a href="/forbidden-salamanders/mac-truncation">mac truncation</a>
            </div>
        </div>
        <p>
            <strong>Key commitment.</strong> One of
            our agents has infiltrated Roseacrucis&rsquo; inner circle, but all
            secret keys are required to be surrendered to the
            counterintelligence authority. Help her send ciphertexts back to
            the Library that decrypt to confidential information under one key,
            but innocuous banter under another.
        </p>
        <br>
		{% if form.errors %}
		<div class="errors">
			Errors:
			<ul>
				{% for name, errors in form.errors.items() %}
				{% for error in errors %}
				<li> {{name}}: {{ error }} </li>
				{% endfor %}
				{% endfor %}
			</ul>
		</div>
		{% endif %}
        <form action="/forbidden-salamanders/key-commitment" method="post" enctype="multipart/form-data">
			<div><em>
				The Library&rsquo;s agent chooses two images: a JPEG file containing
				confidential information, and a BMP file that looks innocuous.
			</em></div><br>

			<input type="radio" id="sample" name="mode" value="sample" required checked>
			<label for="sample">Use sample JPEG and BMP files:</label><br>
			<br>
			<img class="responsive-img" src="/forbidden-salamanders/static/axolotl.jpg">
			<img class="responsive-img" src="/forbidden-salamanders/static/kitten.bmp">
			<br>
			<br>
			<input type="radio" id="custom" name="mode" value="custom" required>
			<label for="custom">Select custom files:</label><br>

            <div>
            <label for="jpeg">JPEG file (&lt;150KB)</label>
			<input type="file" id="jpeg" name="jpeg" accept="image/jpeg">
            </div>

            <div>
            <label for="bmp">BMP file (&lt;50KB)</label>
			<input type="file" id="bmp" name="bmp" accept="image/bmp">
            </div>

			<br><div><em>
				The agent now computes two keys, a nonce and constructs a single
				ciphertext. When decrypted under the first key, it will look
				identical to the JPEG file; when decrypted under the second
				key, it will look identical to the BMP file.
			</em></div>

		<p>
		Key 1: <code>5c3cb198432b0903e58de9c9647bd241</code>
		<br>
		Key 2: <code>df923ae8976230008a081d23205d7a4f</code>
		<br>
		Nonce: <code>4a4f5247454c424f52474553</code>
		</p>
		<br>

            <div>
				<button type="submit">Download polyglot ciphertext</button>
            </div>
        </form>
		<form action="/forbidden-salamanders/key-commitment" method="get">
		<div>
			<button type="submit">Reset</button>
		</div>
		</form>
		<p>
            You can test your ciphertext with Go. Run the following in a shell,
            then try opening <code>first.jpg</code> and <code>second.bmp</code>
            in an image viewer.
		</p>
		<details>
			<summary>
                Test script.
			</summary>
<pre style="font-size: small">
TEMP="$(mktemp).go"
cat &gt; "$TEMP" &lt;&lt;EOF
package main
import ("crypto/aes"; "crypto/cipher"; "encoding/hex"; "os")
func main() {
  var key, nonce, ciphertext, plaintext []byte; var block cipher.Block; var aesgcm cipher.AEAD; var err error
  if len(os.Args) &lt; 4 { panic("usage: go run salamander.go <key> <nonce> <ciphertext-filename>") }
  if key, err = hex.DecodeString(os.Args[1]); err != nil { panic(err.Error()) }
  if nonce, err = hex.DecodeString(os.Args[2]); err != nil { panic(err.Error()) }
  if ciphertext, err = os.ReadFile(os.Args[3]); err != nil { panic(err.Error()) }
  if block, err = aes.NewCipher(key); err != nil { panic(err.Error()) }
  if aesgcm, err = cipher.NewGCM(block); err != nil { panic(err.Error()) }
  if plaintext, err = aesgcm.Open(nil, nonce, ciphertext, nil); err != nil { panic(err.Error()) }
  if _, err = os.Stdout.Write(plaintext); err != nil { panic(err.Error()) }
}
EOF
go run "$TEMP" 5c3cb198432b0903e58de9c9647bd241 4a4f5247454c424f52474553 polyglot.enc &gt; first.jpg
go run "$TEMP" df923ae8976230008a081d23205d7a4f 4a4f5247454c424f52474553 polyglot.enc &gt; second.bmp
</pre>
		</details>
		<details>
			<summary>
                Attack outline.
			</summary>
        <p>
            This attack was shown by Yevgeniy Dodis, Paul Grubbs, Thomas Ristenpart, and Joanne Woodage
            in <a href="https://eprint.iacr.org/2019/016">Fast Message Franking: From Invisible Salamanders to Encryptment</a>.
        </p>
        <p>
            First, we will describe a general strategy to create a ciphertext that yields the same MAC
            with two different keys. Then we will show how to construct a ciphertext that yields
            meaningful results when decrypted with those two keys.
        </p>
        <p>
            Consider arbitrary keys \(k_1, k_2\), nonce \(n\), and ciphertext
            \(c\) (additional associated data can be accounted for in a
            straightforward manner). \(k_1, k_2\) are associated
            with authentication keys \(h_1, h_2\) and blinds \(s_1, s_2\), respectively.
        </p>
        <p>
            Given a ciphertext of three blocks, we will attempt to add a fourth ciphertext block \(c_3\), set the MACs equal to each other,
            and solve for \(c_3\). Remember that in \(\mathbb{F}_{2^{128}}\), addition is the same as subtraction.
        </p>
        <p>
            For the resulting ciphertext of four blocks, the MACs for each key are computed as
            \[
                mac_{1} = s_1 + \vert c\vert{}h_1 + c_3h_1^2 + c_2h_1^3 + c_1h_1^4 + c_0h_1^5
            \]
            \[
                mac_{2} = s_2 + \vert c\vert{}h_2 + c_3h_2^2 + c_2h_2^3 + c_1h_2^4 + c_0h_2^5
            \]
            where \(s\) is a constant depending on the AES-GCM key and the nonce, and \(h\)
            is the authentication key depending only on the AES-GCM key. We can now set the MACs equal to each other and solve for \(c_3\).
            \[
                s_1 + \vert c\vert{}h_1 + c_3h_1^2 + c_2h_1^3 + c_1h_1^4 + c_0h_1^5 = s_2 + \vert c\vert{}h_2 + c_3h_2^2 + c_2h_2^3 + c_1h_2^4 + c_0h_2^5
            \]
            \[
                c_3(h_1^2 + h_2^2) = (s_1 + s_2) + \vert c \vert (h_1 + h_2) + c_2(h_1^3+h_2^3) + c_1(h_1^4 + h_2^4) + c_0(h_1^5 + h_2^5)
            \]
            \[
                c_3 = \frac{(s_1 + s_2) + \vert c \vert (h_1 + h_2) + c_2(h_1^3+h_2^3) + c_1(h_1^4 + h_2^4) + c_0(h_1^5 + h_2^5)}{h_1^2 + h_2^2}
            \]
        </p>
        <p>
            Note that the choice to place the extra block in the final position was arbitrary. For the attack below we will instead need
            to change the penultimate block rather than adding a block; the computation is similar.
        </p>
        <p>
            For the next phase, we construct a ciphertext that decrypts to a valid JPEG under one key and a valid BMP under another.
            The basic strategy is to place the JPEG bytes and BMP bytes at different locations, carefully arranging it so
            each parser will ignore the other data for the file. JPEG files can include comments, in which we will include the
            BMP data. The BMP parser will stop reading as soon as the indicated length of the BMP has been read, after which
            we will include the JPEG data. In each decrypted file, the data for the other image will be scrambled as we are using
            a different key, but it will not matter as the garbage data will be in a location that is ignored by the image parser.
        </p>
        <p>
            All JPEG files start with the magic bytes <code>ffd8</code> and end
            with <code>ffd9</code>. We will place a JPEG comment immediately
            after the initial magic bytes, which is indicated by <code>fffe</code> and is followed by a 2-byte big-endian encoding of the comment length.
            All BMP files start with the magic bytes <code>424d</code> followed by a 4-byte little-endian encoding of the file length.
        </p>
        <p>
            Because the JPEG comment has a maximum length, our BMP file will need to be less than <code>ffff=65536</code> bytes.
            Thus, we desire the JPEG file to start with <code>ffd8 fffe ffff</code> and the BMP file to start with <code>424d wxyz 0000</code>,
            where <code>wxyz</code> is the actual length of our BMP file.
        </p>
        <p>
            To make it easier, we will only require the first five bytes to match; we can modify ciphertext afterwards to satisfy the final
            byte of the BMP header. The final byte of the JPEG header will be arbitrary, but this is the least significant part of the
            comment length, so we will restrict our BMP file length to less than <code>ff00=65280</code> bytes.
        </p>
        <p>
            Since these must be in the same location at the start of the file,
            we will need to brute-force search for two keys \(k_1, k_2\) and a nonce that
            encrypt to the same ciphertext. The easiest way to do this is via a
            birthday attack: fix an arbitrary nonce, then generate random keys
            for both the JPEG header and the BMP header. Encrypt each and store
            them in a lookup table. Repeat until two keys are found that
            encrypt their respective headers to the same ciphertext bytes.
        </p>
        <p>
            To the ciphertext header, add the encryption of the BMP file
            (without the header) under \(k_2\), then pad with arbitrary data to reach the end of the JPEG comment (this will be ignored by the BMP
            parser, which has already finished reading the file). After the
            JPEG comment is over, add the encryption of the JPEG file (without the header and final magic bytes) under
            \(k_1\).
        </p>
        <p>
            Finally, add two more bytes of ciphertext that will make the final two
            bytes of the JPEG file into the appropriate final magic bytes
            <code>ffd9</code>.
        </p>
        <p>
            These ciphertexts do not have the same MAC yet. If we tried to use
            the strategy outlined at the beginning where we add an extra block
            at the end, the JPEG file would no longer end in <code>ffd9</code>
            and thus would be invalid. Instead, we need to modify it to change
            the penultimate block.
        </p>
        <p>
            However, we don't want any data from the penultimate block to show
            up in our JPEG image. Thus, after the JPEG file data ends, we start
            another comment that will extend until the penultimate block,
            hiding it from the parser. Care must be taken to ensure the
            penultimate block is really on a block boundary (the comment can be
            padded to increase the length if necessary). After this second
            comment will appear the final magic bytes <code>ffd9</code> as
            desired.
        </p>
        </details>
		<details>
			<summary>
                Show me the code.
			</summary>
        <pre>
from <a href="/git/forbidden-salamanders">aesgcmanalysis</a> import att_merge_jpg_bmp

with open('first.jpg', 'rb') as h:
    jpg = h.read()
with open('second.bmp', 'rb') as h:
    bmp = h.read()
c, mac = att_merge_jpg_bmp(jpg, bmp, aad=b"")</pre></details>
<script>
MathJax = {
  tex: {
		extensions: ["AMSmath.js", "AMSsymbols.js"]
  }
};
</script>
<script id="MathJax-script" async
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js">
</script>
  </body>
</html>