summaryrefslogtreecommitdiff
path: root/app.py
blob: efc45bfc9e130a8165d1e47202caf285c9925085 (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
import binascii, secrets

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 Crypto.Cipher import AES

from aesgcmanalysis import xor, gmac, gcm_encrypt, nonce_reuse_recover_secrets, gf128_to_bytes, nonce_truncation_recover_secrets

app = Flask(__name__)

@app.route('/')
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):
        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():
    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_nonce_reuse(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_nonce_reuse(k, nonce, m1, m2, mf):
    aad1 = aad2 = b""
    c1, mac1 = gcm_encrypt(k, nonce, aad1, m1)
    c2, mac2 = gcm_encrypt(k, nonce, aad2, m2)

    possible_secrets = nonce_reuse_recover_secrets(nonce, aad1, aad2, c1, c2, mac1, mac2)
    c_forged = xor(c1, xor(m1, mf))
    aad_forged = b""
    macs = []
    for h, s in possible_secrets:
        mac = gmac(h, s, aad_forged, c_forged)
        macs.append((gf128_to_bytes(h), s, mac))
    return c_forged, macs

class NonceTruncationForm(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])
    mf = StringField('forged message', validators=[DataRequired(), Length(min=1, max=64)])

@app.route('/nonce-truncation', methods=['GET', 'POST'])
def nonce_truncation():
    form = NonceTruncationForm(meta={'csrf': False})
    key = nonce = None
    mf = ''
    h = c_forged = mac = None
    if form.is_submitted():
        key, nonce, mf = form.key.data, form.nonce.data, form.mf.data
        if form.validate():
            skey = binascii.unhexlify(key)
            snonce = binascii.unhexlify(nonce)
            h, c_forged, mac = solve_nonce_truncation(skey, snonce, bytes(mf, 'utf-8'))
    return render_template('nonce-truncation.html', form=form, key=key, nonce=nonce,
                           mf=mf, h=h, c_forged=c_forged, mac=mac)

def solve_nonce_truncation(k, nonce, mf):
    aad = b""
    m = secrets.token_bytes(512)
    c, mac = gcm_encrypt(k, nonce, aad, m, mac_bytes=1)

    def oracle(base, aad, mac, nonce):
        cipher = AES.new(k, mode=AES.MODE_GCM, nonce=nonce, mac_len=1)
        cipher.update(aad)
        cipher.decrypt_and_verify(base, mac)
    h, s = nonce_truncation_recover_secrets(c, mac, nonce, 1, aad, oracle)
    c_forged, aad_forged = xor(c, xor(m, mf)), b""
    mac = gmac(h, s, aad_forged, c_forged)
    return gf128_to_bytes(h), c_forged, mac[:1]