summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--aesgcmanalysis.py2
-rw-r--r--app.py46
-rw-r--r--static/styles.css32
-rw-r--r--templates/nonce-reuse.html160
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)
diff --git a/app.py b/app.py
index 2177e9c..d573efc 100644
--- a/app.py
+++ b/app.py
@@ -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 &ldquo;greater&rdquo; 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>