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]
|