Browse Source

Stealth mnemonic entry

Different keyboard keys make different clicking sounds when pressed.  In
particular, the ENTER and SPACE keys are noticeably louder on most keyboards.
This makes possible an acoustic side-channel attack whereby an eavesdropper
with a microphone could gain knowledge about the lengths of the words in a
user's mnemonic seed phrase as they're being typed.  This information could
also be obtained by analyzing the pauses between words.

Since the words in the Electrum wordlist used by MMGen vary in length from three
to twelve letters, the keyspace required to be searched in a brute-force attack
on the seed could thus be drastically reduced.

Stealth mnemonic entry mitigates this vulnerability by treating all characters
that are not lowercase letters in the range 'a-z' as “pad characters”.  Pad
characters are simply discarded and can be typed either before, after or in the
middle of words.  For each word, once a sufficient number of characters,
including pad characters, have been typed (currently 12), any pad character
can be used to enter the word.  Since each word can now be entered using the
same number of keystrokes and padded with random characters at random
positions, acoustic analysis of the user's keyboard clicks will yield little
useful information for an attacker.

This feature is enabled by default as it doesn't impact existing functionality
and can simply be ignored if one chooses not to use it.
MMGen 7 years ago
parent
commit
90ebc94170
1 changed files with 18 additions and 6 deletions
  1. 18 6
      mmgen/seed.py

+ 18 - 6
mmgen/seed.py

@@ -380,22 +380,34 @@ class Mnemonic (SeedSourceUnenc):
 			prompt = 'Mnemonic length of {} words chosen. OK?'.format(mn_len)
 			if keypress_confirm(prompt,default_yes=True,no_nl=True): break
 
-		m = 'Enter your {}-word mnemonic, hitting RETURN or SPACE after each word:'
-		msg(m.format(mn_len))
+		wl = baseconv.digits[self.wl_id]
+		longest_word = max(len(w) for w in wl)
+		from string import ascii_lowercase
+
+		m  = 'Enter your {}-word mnemonic, hitting ENTER or SPACE after each word.\n'
+		m += "Optionally, you may use pad characters.  Anything you type that's not a\n"
+		m += 'lowercase letter will be treated as a “pad character”, i.e. it will simply\n'
+		m += 'be discarded.  Pad characters may be typed before, after, or in the middle\n'
+		m += "of words.  For each word, once you've typed {} characters total (including\n"
+		m += 'pad characters) a pad character will enter the word.'
+		msg(m.decode('utf8').format(mn_len,longest_word))
 
 		def get_word():
-			s = ''
+			s,pad = '',0
 			while True:
 				ch = get_char_raw('')
 				if ch in '\b\x7f':
 					if s: s = s[:-1]
 				elif ch in '\n ':
 					if s: break
-				else: s += ch
+				elif ch not in ascii_lowercase:
+					pad += 1
+					if s and pad + len(s) > longest_word:
+						break
+				else:
+					s += ch
 			return s
 
-		wl = baseconv.digits[self.wl_id]
-
 		def in_list(w):
 			from bisect import bisect_left
 			idx = bisect_left(wl,w)