def encode(s, compact=False): """From a byte string, produce a list of words that durably encodes the string. s: the byte string to be encoded compact: instead of using the length encoding scheme, pad by prepending a 1 bit The words in the encoding dictionary were chosen to be common and unambiguous. The encoding also includes a checksum. The encoding is constructed so that common errors are extremely unlikely to produce a valid encoding. """ if not isinstance(s, bytes): raise TypeError("mnemonic.encode can only encode byte strings") k = Keccak() k.absorb(s) checksum_length = max(1, (len(s)-1).bit_length()) checksum = k.squeeze(checksum_length) length = chr(checksum_length) if compact else encode_varint(len(s), endian='little') s += checksum s += length word_index = 0 i = bytes2int(s) retval = [None] * int(floor(log(i, len(words)) + 1)) for j in xrange(len(retval)): assert i > 0 word_index += i % len(words) word_index %= len(words) retval[j] = words[word_index] i //= len(words) assert i == 0 return tuple(retval)
def keccak_fun(msg, nbits_in, nbits_out): """ a wrapper for Keccak's reference code using the default values r = 1024, c = 576 """ kclass = Keccak() assert len(msg) * 8 >= nbits_in > 0 # the ref code of Keccak doesn't accept # output lengths that are not multiples of 8 nbytes_out = ceil(nbits_out, 8) nbits_extra = nbytes_out * 8 - nbits_out assert 0 <= nbits_extra <= 7 if nbits_in % 8: # to fix the bug (?) in Keccak's offical python code last_byte = ord(msg[-1]) # last_byte = int('{:08b}'.format(last_byte)[::-1], 2) last_byte <<= 8 - (nbits_in % 8) msg = msg[:-1] + chr(last_byte) hex_msg = hexlify(msg) hashed = kclass.Keccak( (nbits_in, hex_msg), n=nbytes_out * 8 ) hashed = unhexlify(hashed) # truncate the output byte_mask = (1 << (8 - nbits_extra)) - 1 hashed = hashed[:-1] + chr(ord(hashed[-1]) & byte_mask) assert len(hashed) * 8 - 7 <= nbits_out <= len(hashed) * 8 return hashed
def randomart(s, height=9, width=17, length=64, border=True, tag=''): """Produce a easy to compare visual representation of a string. Follows the algorithm laid out here http://www.dirk-loss.de/sshvis/drunken_bishop.pdf with the substitution of Keccak for MD5. s: the string to create a representation of height: (optional) the height of the representation to generate, default 9 width: (optional) the width of the representation to generate, default 17 length: (optional) the length of the random walk, essentially how many points are plotted in the representation, default 64 border: (optional) whether to put a border around the representation, default True tag: (optional) a short string to be incorporated into the border, does nothing if border is False, defaults to the empty string """ k = Keccak() k.absorb(s) # we reverse the endianness so that increasing length produces a radically # different randomart i = bytes2int(reversed(k.squeeze(int(ceil(length / 4.0))))) field = [ [0 for _ in xrange(width)] for __ in xrange(height) ] start = (height // 2, width // 2) position = start directions = ((-1, -1), (-1, 1), (1, -1), (1, 1)) for j in xrange(length): row_off, col_off = directions[(i>>(j*2)) % 4] position = (min(max(position[0] + row_off, 0), height - 1), min(max(position[1] + col_off, 0), width - 1)) field[position[0]][position[1]] += 1 field[start[0]][start[1]] = 15 field[position[0]][position[1]] = 16 chars = ' .o+=*BOX@%&#/^SE' if border: if len(tag) > width - 2: tag = tag[:width-2] if tag: tag_pad_len = (width - len(tag) - 2) / 2.0 first_row = '+' + ('-'*int(floor(tag_pad_len))) \ + '['+tag+']' \ + ('-'*int(ceil(tag_pad_len))) + '+\n' else: first_row = '+' + ('-'*width) + '+\n' last_row = '\n+' + ('-'*width) + '+' return first_row \ + '\n'.join('|'+''.join(chars[cell] for cell in row)+'|' for row in field) \ + last_row else: return '\n'.join(''.join(chars[cell] for cell in row) for row in field)
def encode(s, compact=False): """From a byte string, produce a list of words that durably encodes the string. s: the byte string to be encoded compact: instead of using the length encoding scheme, pad by prepending a 1 bit The words in the encoding dictionary were chosen to be common and unambiguous. The encoding also includes a checksum. The encoding is constructed so that common errors are extremely unlikely to produce a valid encoding. """ if not isinstance(s, bytes): raise TypeError("mnemonic.encode can only encode byte strings") k = Keccak() k.absorb(s) checksum_length = max(1, (len(s) - 1).bit_length()) checksum = k.squeeze(checksum_length) length = chr(checksum_length) if compact else encode_varint( len(s), endian='little') s += checksum s += length word_index = 0 i = bytes2int(s) retval = [None] * int(floor(log(i, len(words)) + 1)) for j in xrange(len(retval)): assert i > 0 word_index += i % len(words) word_index %= len(words) retval[j] = words[word_index] i //= len(words) assert i == 0 return tuple(retval)
def unpad_and_checksum(s, compact, checksum): """Check length padding and checksum for a string return the string without length padding or checksum raise ValueError if either are wrong""" assert isinstance(s, bytes) if checksum: if compact: checksum_length = ord(s[-1]) consumed = 1 length = len(s) - checksum_length - consumed else: (length, consumed) = decode_varint(s, endian='little') checksum_length = max(1, (length - 1).bit_length()) s = s[:-consumed] s, checksum = s[:-checksum_length], s[-checksum_length:] if len(s) != length: raise ValueError("Invalid length") k = Keccak() k.absorb(s) if k.squeeze(checksum_length) != checksum: raise ValueError("Invalid checksum") return s else: if compact: return s[:-1] else: (length, consumed) = decode_varint(s, endian='little') s = s[:-consumed] if len(s) != length: raise ValueError("Invalid length") return s
def unpad_and_checksum(s, compact, checksum): """Check length padding and checksum for a string return the string without length padding or checksum raise ValueError if either are wrong""" assert isinstance(s, bytes) if checksum: if compact: checksum_length = ord(s[-1]) consumed = 1 length = len(s) - checksum_length - consumed else: (length, consumed) = decode_varint(s, endian='little') checksum_length = max(1, (length-1).bit_length()) s = s[:-consumed] s, checksum = s[:-checksum_length], s[-checksum_length:] if len(s) != length: raise ValueError("Invalid length") k = Keccak() k.absorb(s) if k.squeeze(checksum_length) != checksum: raise ValueError("Invalid checksum") return s else: if compact: return s[:-1] else: (length, consumed) = decode_varint(s, endian='little') s = s[:-consumed] if len(s) != length: raise ValueError("Invalid length") return s
def decode(w, compact=False, permissive=False): """From a list of words, or a whitespace-separated string of words, produce the original string that was encoded. w: the list of words, or whitespace delimited words to be decoded compact: compact encoding was used instead of length encoding permissive: if there are spelling errors, correct them instead of throwing an error (will still throw ValueError if spelling can't be corrected) Raises ValueError if the encoding is invalid. """ if isinstance(w, bytes): w = w.split() indexes = [None] * len(w) for i, word in enumerate(w): if word in rwords: indexes[i] = rwords[word] elif permissive: for nearby in dldist(word, 1): if nearby in rwords: indexes[i] = rwords[nearby] break if indexes[i] is None: raise ValueError('Unrecognized word %s' % repr(word)) # because we don't directly encode the mantissas, we have to extract them values = reduce( lambda (last_index, accum), index: (index, accum + [(index - last_index) % len(words)]), indexes, (0, []))[1] i = sum(mantissa * len(words)**radix for radix, mantissa in enumerate(values)) # we don't need to worry about truncating null bytes because of the encoded length on the end s = int2bytes(i) if compact: checksum_length = ord(s[-1]) consumed = 1 length = len(s) - checksum_length - consumed else: (length, consumed) = decode_varint(s, endian='little') checksum_length = max(1, (length - 1).bit_length()) s = s[:-consumed] s, checksum = s[:-checksum_length], s[-checksum_length:] if len(s) != length: raise ValueError("Invalid length") k = Keccak() k.absorb(s) if k.squeeze(checksum_length) != checksum: raise ValueError("Invalid checksum") return s
def decode(w, compact=False, permissive=False): """From a list of words, or a whitespace-separated string of words, produce the original string that was encoded. w: the list of words, or whitespace delimited words to be decoded compact: compact encoding was used instead of length encoding permissive: if there are spelling errors, correct them instead of throwing an error (will still throw ValueError if spelling can't be corrected) Raises ValueError if the encoding is invalid. """ if isinstance(w, bytes): w = w.split() indexes = [None]*len(w) for i,word in enumerate(w): if word in rwords: indexes[i] = rwords[word] elif permissive: for nearby in dldist(word, 1): if nearby in rwords: indexes[i] = rwords[nearby] break if indexes[i] is None: raise ValueError('Unrecognized word %s' % repr(word)) # because we don't directly encode the mantissas, we have to extract them values = reduce(lambda (last_index, accum), index: (index, accum + [(index - last_index) % len(words)]), indexes, (0, []))[1] i = sum(mantissa * len(words)**radix for radix, mantissa in enumerate(values)) # we don't need to worry about truncating null bytes because of the encoded length on the end s = int2bytes(i) if compact: checksum_length = ord(s[-1]) consumed = 1 length = len(s) - checksum_length - consumed else: (length, consumed) = decode_varint(s, endian='little') checksum_length = max(1, (length-1).bit_length()) s = s[:-consumed] s, checksum = s[:-checksum_length], s[-checksum_length:] if len(s) != length: raise ValueError("Invalid length") k = Keccak() k.absorb(s) if k.squeeze(checksum_length) != checksum: raise ValueError("Invalid checksum") return s
def randomart(s, height=9, width=17, length=64, border=True, tag=''): """Produce a easy to compare visual representation of a string. Follows the algorithm laid out here http://www.dirk-loss.de/sshvis/drunken_bishop.pdf with the substitution of Keccak for MD5. s: the string to create a representation of height: (optional) the height of the representation to generate, default 9 width: (optional) the width of the representation to generate, default 17 length: (optional) the length of the random walk, essentially how many points are plotted in the representation, default 64 border: (optional) whether to put a border around the representation, default True tag: (optional) a short string to be incorporated into the border, does nothing if border is False, defaults to the empty string """ k = Keccak() k.absorb(s) # we reverse the endianness so that increasing length produces a radically # different randomart i = bytes2int(reversed(k.squeeze(int(ceil(length / 4.0)))), endian='little') field = [[0 for _ in xrange(width)] for __ in xrange(height)] start = (height // 2, width // 2) position = start directions = ((-1, -1), (-1, 1), (1, -1), (1, 1)) for j in xrange(length): row_off, col_off = directions[(i >> (j * 2)) % 4] position = (min(max(position[0] + row_off, 0), height - 1), min(max(position[1] + col_off, 0), width - 1)) field[position[0]][position[1]] += 1 field[start[0]][start[1]] = 15 field[position[0]][position[1]] = 16 chars = ' .o+=*BOX@%&#/^SE' if border: if len(tag) > width - 2: tag = tag[:width - 2] if tag: tag_pad_len = (width - len(tag) - 2) / 2.0 first_row = '+' + ('-'*int(floor(tag_pad_len))) \ + '['+tag+']' \ + ('-'*int(ceil(tag_pad_len))) + '+\n' else: first_row = '+' + ('-' * width) + '+\n' last_row = '\n+' + ('-' * width) + '+' return first_row \ + '\n'.join('|'+''.join(chars[cell] for cell in row)+'|' for row in field) \ + last_row else: return '\n'.join(''.join(chars[cell] for cell in row) for row in field)
def pad_and_checksum(s, compact, checksum): """Apply length padding and checksum to a string""" assert isinstance(s, bytes) if checksum: k = Keccak() k.absorb(s) checksum_length = max(1, (len(s)-1).bit_length()) checksum = k.squeeze(checksum_length) length = chr(checksum_length) if compact else encode_varint(len(s), endian='little') return s + checksum + length else: length = '\x01' if compact else encode_varint(len(s), endian='little') return s + length
def pad_and_checksum(s, compact, checksum): """Apply length padding and checksum to a string""" assert isinstance(s, bytes) if checksum: k = Keccak() k.absorb(s) checksum_length = max(1, (len(s) - 1).bit_length()) checksum = k.squeeze(checksum_length) length = chr(checksum_length) if compact else encode_varint( len(s), endian='little') return s + checksum + length else: length = '\x01' if compact else encode_varint(len(s), endian='little') return s + length
def oaep_keccak(m, label='', out_len=None, hash_len=32, random=random, keccak_args=dict()): """Perform OAEP (as specified by PKCS#1v2.1) with Keccak as the one-way function and mask-generating function All lengths specified in *bytes* m: message to be padded label: (optional) to be associated with the message, the default is the empty string out_len: (optional) the length of the message after padding, the default is len(m) + 2*hash_len + 2 hash_len: (optional) the length of the output of the hash algorithm, the default is 32 random: (optional) source of entropy for the random seed generation, the default is python's random module keccak_args: (optional) parameters for the Keccak sponge function, the defaults are the Keccak defaults """ if out_len is not None and len(m) > out_len - 2*hash_len - 2: raise ValueError("Message too long to specified output and hash lengths") # hash the label k = Keccak(**keccak_args) k.absorb(label) lhash = k.squeeze(hash_len) if out_len is not None: pad_string = '\x00' * (out_len - len(m) - 2*hash_len - 2) else: pad_string = '' # pad m padded = lhash + pad_string + '\x01' + m # generate rand_seed, a hash_len-byte random string rand_seed = random.getrandbits(hash_len*8) rand_seed = int2bytes(rand_seed) # expand rand_seed to the length of padded k = Keccak(**keccak_args) k.absorb(rand_seed) mask = k.squeeze(len(padded)) # XOR the message with the expanded r masked = ''.join(imap(chr, imap(xor, imap(ord, padded), imap(ord, mask)))) # hash masked to generate the seed mask k = Keccak(**keccak_args) k.absorb(masked) seed_mask = k.squeeze(len(rand_seed)) # mask the seed masked_seed = ''.join(imap(chr, imap(xor, imap(ord, rand_seed), imap(ord, seed_mask)))) # concatenate the two together return '\x00' + masked_seed + masked
def unoaep_keccak(m, label='', hash_len=32, keccak_args=dict()): """Recover a message padded with OAEP (as specified by PKCS#1v2.1) with Keccak as the one-way function and mask-generating function All lengths specified in *bytes* m: message to be decoded label: (optional) the label expected on the message, the default is the empty string hash_len: (optional) the length of the output of the hash algorithm, the default is 32 keccak_args: (optional) parameters for the Keccak sponge function, the defaults are the Keccak defaults """ # hash the label k = Keccak(**keccak_args) k.absorb(label) lhash = k.squeeze(hash_len) # split the three parts of the OAEP'd message leading_null, masked_seed, masked = m[0], m[1:hash_len+1], m[hash_len+1:] # recover rand_seed k = Keccak(**keccak_args) k.absorb(masked) seed_mask = k.squeeze(len(masked_seed)) rand_seed = ''.join(imap(chr, imap(xor, imap(ord, masked_seed), imap(ord, seed_mask)))) # recover the original message k = Keccak(**keccak_args) k.absorb(rand_seed) mask = k.squeeze(len(masked)) padded = ''.join(imap(chr, imap(xor, imap(ord, masked), imap(ord, mask)))) # find the '\x01' separator byte without leaking timing info ## these need to be longs because of Python's small int caching separator_index = 0L separator = 0L for i in xrange(hash_len,len(padded)): # the result of ord is unavoidably a small int char = ord(padded[i]) nonnull = int(char != 0) # set separator and separator_index, but only if char is nonnull and # they haven't already been set separator |= -long(separator == 0) & -nonnull & char separator_index |= -long(separator_index == 0) & -nonnull & i # check that lhash matches, the separator is correct, and the leading NUL is # preserved without leaking which one failed if sum([ 0L, # force the sum to be a long secure_compare(lhash, padded[:hash_len]), separator == 1L, leading_null == chr(0L) ]) != 3L: raise ValueError("Decryption failed")
def oaep_keccak(m, label='', out_len=None, hash_len=32, random=random, keccak_args=dict()): """Perform OAEP (as specified by PKCS#1v2.1) with Keccak as the one-way function and mask-generating function All lengths specified in *bytes* m: message to be padded label: (optional) to be associated with the message, the default is the empty string out_len: (optional) the length of the message after padding, the default is len(m) + 2*hash_len + 2 hash_len: (optional) the length of the output of the hash algorithm, the default is 32 random: (optional) source of entropy for the random seed generation, the default is python's random module keccak_args: (optional) parameters for the Keccak sponge function, the defaults are the Keccak defaults """ if out_len is not None and len(m) > out_len - 2 * hash_len - 2: raise ValueError( "Message too long to specified output and hash lengths") # hash the label k = Keccak(**keccak_args) k.absorb(label) lhash = k.squeeze(hash_len) if out_len is not None: pad_string = '\x00' * (out_len - len(m) - 2 * hash_len - 2) else: pad_string = '' # pad m padded = lhash + pad_string + '\x01' + m # generate rand_seed, a hash_len-byte random string rand_seed = random.getrandbits(hash_len * 8) rand_seed = int2bytes(rand_seed) # expand rand_seed to the length of padded k = Keccak(**keccak_args) k.absorb(rand_seed) mask = k.squeeze(len(padded)) # XOR the message with the expanded r masked = ''.join(imap(chr, imap(xor, imap(ord, padded), imap(ord, mask)))) # hash masked to generate the seed mask k = Keccak(**keccak_args) k.absorb(masked) seed_mask = k.squeeze(len(rand_seed)) # mask the seed masked_seed = ''.join( imap(chr, imap(xor, imap(ord, rand_seed), imap(ord, seed_mask)))) # concatenate the two together return '\x00' + masked_seed + masked
def unoaep_keccak(m, label='', hash_len=32, keccak_args=dict()): """Recover a message padded with OAEP (as specified by PKCS#1v2.1) with Keccak as the one-way function and mask-generating function All lengths specified in *bytes* m: message to be decoded label: (optional) the label expected on the message, the default is the empty string hash_len: (optional) the length of the output of the hash algorithm, the default is 32 keccak_args: (optional) parameters for the Keccak sponge function, the defaults are the Keccak defaults """ # hash the label k = Keccak(**keccak_args) k.absorb(label) lhash = k.squeeze(hash_len) # split the three parts of the OAEP'd message leading_null, masked_seed, masked = m[0], m[1:hash_len + 1], m[hash_len + 1:] # recover rand_seed k = Keccak(**keccak_args) k.absorb(masked) seed_mask = k.squeeze(len(masked_seed)) rand_seed = ''.join( imap(chr, imap(xor, imap(ord, masked_seed), imap(ord, seed_mask)))) # recover the original message k = Keccak(**keccak_args) k.absorb(rand_seed) mask = k.squeeze(len(masked)) padded = ''.join(imap(chr, imap(xor, imap(ord, masked), imap(ord, mask)))) # find the '\x01' separator byte without leaking timing info ## these need to be longs because of Python's small int caching separator_index = 0L separator = 0L for i in xrange(hash_len, len(padded)): # the result of ord is unavoidably a small int char = ord(padded[i]) nonnull = int(char != 0) # set separator and separator_index, but only if char is nonnull and # they haven't already been set separator |= -long(separator == 0) & -nonnull & char separator_index |= -long(separator_index == 0) & -nonnull & i # check that lhash matches, the separator is correct, and the leading NUL is # preserved without leaking which one failed if sum([ 0L, # force the sum to be a long secure_compare(lhash, padded[:hash_len]), separator == 1L, leading_null == chr(0L) ]) != 3L: raise ValueError("Decryption failed")