diff options
-rw-r--r-- | aesgcmanalysis.py | 2 | ||||
-rw-r--r-- | app.py | 46 | ||||
-rw-r--r-- | static/styles.css | 32 | ||||
-rw-r--r-- | templates/nonce-reuse.html | 160 |
4 files changed, 156 insertions, 84 deletions
diff --git a/aesgcmanalysis.py b/aesgcmanalysis.py index 0b81522..5123c63 100644 --- a/aesgcmanalysis.py +++ b/aesgcmanalysis.py @@ -517,7 +517,7 @@ def compute_forbidden_polynomial(aad1, aad2, c1, c2, mac1, mac2): b2 = bytes_to_gf128(bs2[i*16:(i+1)*16]) f.append(gf128_add(b1, b2)) f.append(gf128_add(bytes_to_gf128(mac1), bytes_to_gf128(mac2))) - return list(reversed(f)) + return collapse(list(reversed(f))) def nonce_reuse_recover_secrets(nonce, aad1, aad2, c1, c2, mac1, mac2): f = compute_forbidden_polynomial(aad1, aad2, c1, c2, mac1, mac2) @@ -1,6 +1,9 @@ import binascii from flask import Flask, render_template, request, redirect, url_for +from flask_wtf import FlaskForm +from wtforms import StringField +from wtforms.validators import DataRequired, Length, ValidationError from aesgcmanalysis import xor, gmac, gcm_encrypt, nonce_reuse_recover_secrets, gf128_to_bytes @@ -10,18 +13,41 @@ app = Flask(__name__) def index(): return render_template('index.html') +def hex_check(form, field): + if len(field.data) % 2 != 0: + raise ValidationError(f'not valid hex; must have even length') + if not all(c in '1234567890abcdef' for c in field.data): + raise ValidationError(f'not valid hex; contains non-hex character') + +def not_equal_to(other): + def helper(form, field): + print(other, form['m1'], field) + if other not in form: + return + if form[other].data == field.data: + raise ValidationError(f'must not be equal to {other}') + return helper + +class NonceReuseForm(FlaskForm): + key = StringField('key', validators=[DataRequired(), Length(min=32, max=32), hex_check]) + nonce = StringField('nonce', validators=[DataRequired(), Length(min=24, max=24), hex_check]) + m1 = StringField('first message', validators=[DataRequired(), Length(min=1, max=64)]) + m2 = StringField('second message', validators=[DataRequired(), Length(min=1, max=64), not_equal_to('m1')]) + mf = StringField('forged message', validators=[DataRequired(), Length(min=1, max=64)]) + @app.route('/nonce-reuse', methods=['GET', 'POST']) def nonce_reuse(): - key = nonce = c_forged = macs = None - m1 = m2 = mf = '' - if request.method == 'POST': - key = binascii.unhexlify(request.form['key']) - nonce = binascii.unhexlify(request.form['nonce']) - m1 = request.form['m1'] - m2 = request.form['m2'] - mf = request.form['mf'] - c_forged, macs = solve(key, nonce, bytes(m1, 'ascii'), bytes(m2, 'ascii'), bytes(mf, 'ascii')) - return render_template('nonce-reuse.html', key=key, nonce=nonce, m1=m1, m2=m2, mf=mf, c_forged=c_forged, macs=macs) + form = NonceReuseForm(meta={'csrf': False}) + key = nonce = None + m1 = m2 = mf = c_forged = '' + macs = None + if form.is_submitted(): + key, nonce, m1, m2, mf = form.key.data, form.nonce.data, form.m1.data, form.m2.data, form.mf.data + if form.validate(): + skey = binascii.unhexlify(key) + snonce = binascii.unhexlify(nonce) + c_forged, macs = solve(skey, snonce, bytes(m1, 'utf-8'), bytes(m2, 'utf-8'), bytes(mf, 'utf-8')) + return render_template('nonce-reuse.html', form=form, key=key, nonce=nonce, m1=m1, m2=m2, mf=mf, c_forged=c_forged, macs=macs) def solve(k, nonce, m1, m2, mf): aad1 = aad2 = b"" diff --git a/static/styles.css b/static/styles.css index 6360fc1..9688050 100644 --- a/static/styles.css +++ b/static/styles.css @@ -14,3 +14,35 @@ ul { margin-top: 5px; } + +.inner-ul { + margin-top: initial; +} + +pre { + white-space: pre-wrap; +} + +details[open=""] { + border: 1px dotted darkslategrey; + padding: 1em; +} + +input[type="text"] { + width: 50%; + min-width: 350px; +} + +.errors { + border-left: 2px crimson solid; + padding-left: 1em; +} + +.solution { + border-left: 2px #289528 solid; + padding-left: 1em; +} + +code { + word-wrap: anywhere; +} diff --git a/templates/nonce-reuse.html b/templates/nonce-reuse.html index 9761955..2637d50 100644 --- a/templates/nonce-reuse.html +++ b/templates/nonce-reuse.html @@ -32,6 +32,88 @@ forge arbitrary ciphertext. </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/nonce-reuse" method="post"> + <div><em> + Roseacrucis chooses a key, a nonce, and two messages. He encrypts both messages under the same nonce. + </em></div><br> + + <div> + <label for="key">Key (16 bytes in hex)</label> + <input name="key" id="key" type="text" value="{{ key if key else '746c6f6e6f7262697374657274697573' }}" minlength=32 maxlength=32 required> + </div> + + <div> + <label for="nonce">Nonce (12 bytes in hex)</label> + <input name="nonce" id="nonce" type="text" value="{{ nonce if nonce else '4a4f5247454c424f52474553' }}" minlength=24 maxlength=24 required> + </div> + + <div> + <label for="m1">First intercepted message</label> + <input name="m1" id="m1" type="text" required maxlength=64 value="{{m1 if m1 else 'The universe (which others call the Library)'}}"> + </div> + + <div> + <label for="m2">Second intercepted message</label> + <input name="m2" id="m2" type="text" required maxlength=64 value="{{m2 if m2 else 'From any of the hexagons one can see, interminably'}}"> + </div> + + <br><div><em> + After intercepting the ciphertexts, you choose a new message to forge under the same key and nonce. + </em></div><br> + + <div> + <label for="mf">Forged message; shorter than the first message</label> + <input name="mf" id="mf" type="text" required maxlength=64 value="{{mf}}"> + </div> + + <div> + <button type="submit">Recover authentication key and forge MAC</button> + </div> + </form> + <form action="/forbidden-salamanders/nonce-reuse" method="get"> + <div> + <button type="submit">Reset</button> + </div> + </form> + {% if macs %} + <div class="solution"> + <p> + Forged ciphertext: <code>{{ c_forged.hex() }}</code> + {% if macs|length == 1 %} + <br> + Forged MAC: <code>{{macs[0][2].hex()}}</code> + <br> + Authentication key: <code>{{macs[0][0].hex()}}</code></li> + {% endif %} + </p> + {% if macs|length != 1 %} + Forged MAC candidates: + <ul> + {% for h, _, mac in macs %} + <li> + MAC: <code>{{mac.hex()}}</code> + <ul class="inner-ul"> + <li>Authentication key: <code>{{h.hex()}}</code></li> + </ul> + </li> + {% endfor %} + </ul> + {% endif %} + </div> + {% endif %} + <br> <details> <summary> Attack outline. @@ -84,61 +166,6 @@ in this case, one can check each possibility online. </p> </details> - <br> - <form action="/forbidden-salamanders/nonce-reuse" method="post"> - <div> - <label for="key">Key (16 bytes in hex)</label> - <input name="key" id="key" type="text" value="{{ key.hex() if key else '59454c4c4f575f5355424d4152494e45' }}" minlength=32 maxlength=32> - </div> - - <div> - <label for="nonce">Nonce (12 bytes in hex)</label> - <input name="nonce" id="nonce" type="text" value="{{ nonce.hex() if nonce else '4a4f5247454c424f52474553' }}" minlength=24 maxlength=24> - </div> - - <div> - <label for="m1">First intercepted message (in ASCII)</label> - <input name="m1" id="m1" type="text" required maxlength=100 value="{{m1}}"> - </div> - - <div> - <label for="m2">Second intercepted message (in ASCII)</label> - <input name="m2" id="m2" type="text" required maxlength=100 value="{{m2}}"> - </div> - - <div> - <label for="mf">Forged message; shorter than the first message (in ASCII)</label> - <input name="mf" id="mf" type="text" required maxlength=100 value="{{mf}}"> - </div> - - <div> - <button type="submit">Recover authentication key and forge MAC</button> - </div> - </form> - {% if macs %} - <div> - <p> - Forged ciphertext: <code>{{ c_forged.hex() }}</code> - </p> - Forged MAC candidates: - <ul> - {% for h, _, mac in macs %} - <li> - MAC: <code>{{mac.hex()}}</code> - <ul> - <li>Authentication key: <code>{{h.hex()}}</code></li> - </ul> - </li> - {% endfor %} - </ul> - <form action="/forbidden-salamanders/nonce-reuse" method="get"> - <div> - <button type="submit">Reset</button> - </div> - </form> - </div> - {% endif %} - <br> <details> <summary> Show me the code. @@ -148,10 +175,8 @@ from <a href="/git/forbidden-salamanders">aesgcmanalysis</a> import xor, gmac, g k = b"tlonorbistertius" nonce = b"jorgelborges" -m1 = b"The universe (which others call the Library)" -aad1 = b"The Anatomy of Melancholy" -m2 = b"From any of the hexagons one can see, interminably" -aad2 = b"Letizia Alvarez de Toledo" +m1, aad1 = b"The universe (which others call the Library)", b"" +m2, aad2 = b"From any of the hexagons one can see, interminably", b"" c1, mac1 = gcm_encrypt(k, nonce, aad1, m1) c2, mac2 = gcm_encrypt(k, nonce, aad2, m2) @@ -161,21 +186,10 @@ possible_secrets = nonce_reuse_recover_secrets(nonce, aad1, aad2, c1, c2, mac1, # Forge the ciphertext m_forged = b"As was natural, this inordinate hope" -assert len(m_forged) <= len(m1) -c_forged = xor(c1, xor(m1, m_forged)) -aad_forged = b"You who read me, are You sure of understanding my language?" +c_forged, aad_forged = xor(c1, xor(m1, m_forged)), b"" -# Check possible candidates for authentication key -succeeded = False for h, s in possible_secrets: - mac_forged = gmac(h, s, aad_forged, c_forged) - try: - assert gcm_decrypt(k, nonce, aad_forged, c_forged, mac_forged) == m_forged - succeeded = True - print(c_forged.hex(), mac_forged.hex()) - except AssertionError: - pass -assert succeeded</pre></details> + print("MAC candidate": gmac(h, s, aad_forged, c_forged))</pre></details> <details> <summary> Show me the math. @@ -219,7 +233,7 @@ for factor, _ in p.factor(): </p> <ul> <li>The gcd of two polynomials is unique only up to multiplication by a non-zero constant because “greater” is defined for polynomials in terms of degree. When used in algorithms, gcd refers to the <em>monic</em> gcd, which is unique.</li> - <li>The <a href="https://math.stackexchange.com/a/943626/1084004">inverse Frobenius automorphism</a> (i.e., square root) in \(\mathbb{F}_{2^{128}}\) is given by \(\sqrt{x} = x^{2^{127}})\).</li> + <li>The <a href="https://math.stackexchange.com/a/943626/1084004">inverse Frobenius automorphism</a> (i.e., square root) in \(\mathbb{F}_{2^{128}}\) is given by \(\sqrt{x} = x^{2^{127}}\).</li> </ul> </details> <script> |