keys-on-cli
quadrismegistus 4 years ago
parent c15c3afeb9
commit 832c82953e

@ -23,4 +23,3 @@ class Caller(Operator):
# @hack: repurposing this for now as a narrator # @hack: repurposing this for now as a narrator
e

@ -117,19 +117,19 @@ class KomradeAsymmetricPrivateKey(KomradeAsymmetricKey):
def key(self): return self.privkey def key(self): return self.privkey
def __repr__(self): return f'''[Asymmetric Private Key] ({self.discreet})''' def __repr__(self): return f'''[Asymmetric Private Key] ({self.discreet})'''
def make_key_discreet(data,chance_bowdlerize=0.5): def make_key_discreet(data,chance_redacted=0.5):
import random import random
if not data: return '?' if not data: return '?'
if not isBase64(data): data=b64encode(data) if not isBase64(data): data=b64encode(data)
key=data.decode() key=data.decode()
return ''.join((k if random.random()<chance_bowdlerize else '-') for k in key) return ''.join((k if random.random()<chance_redacted else '-') for k in key)
def make_key_discreet_str(string,chance_bowdlerize=0.5): def make_key_discreet_str(string,chance_redacted=0.5):
import random import random
if not string: return '?' if not string: return '?'
return ''.join((k if random.random()<chance_bowdlerize else '-') for k in string) return ''.join((k if random.random()<chance_redacted else '-') for k in string)
def make_key_discreet1(data,len_start=10,len_end=10,ellipsis='.',show_len=True): def make_key_discreet1(data,len_start=10,len_end=10,ellipsis='.',show_len=True):

@ -58,25 +58,31 @@ class Persona(Caller):
# forge public/private keys # 1) forge public/private keys
keypair = KomradeAsymmetricKey() keypair = KomradeAsymmetricKey()
pubkey,privkey = keypair.pubkey_obj,keypair.privkey_obj pubkey,privkey = keypair.pubkey_obj,keypair.privkey_obj
# make sure we have passphrase # 2) make sure we have passphrase
if SHOW_STATUS: passphrase=self.crypt_keys.hash(b'boogywoogy')
if SHOW_STATUS and not passphrase:
passphrase = self.cli.status_keymaker_body( passphrase = self.cli.status_keymaker_body(
name, name,
passphrase, passphrase,
pubkey, pubkey,
privkey, privkey,
self.crypt_keys.hash self.crypt_keys.hash,
self
) )
else: else:
if not passphrase: passphrase=getpass('Enter a memorable password to encrypt your private key with: ') if not passphrase: passphrase=getpass('Enter a memorable password to encrypt your private key with: ')
# encrypt private key # 3) form an encryption key
exit() privkey_decr = KomradeSymmetricKeyWithPassphrase(passphrase)
privkey_encr = privkey_decr.encrypt(privkey.data)
self.cli.status_keymaker_part3(privkey,privkey_decr,privkey_encr,passphrase)
exit()
# save the ones we should on server # save the ones we should on server
data = { data = {

@ -282,6 +282,59 @@ ART_KEY_PAIR2B = """
""" """
ART_KEY_PAIR_SPLITTING1 = """
_
/o
\
<
<
<
(2A)
symmetric
encryption
key, from
hashed
passphrase
"""
ART_KEY_PAIR_SPLITTING2 = """
_ __
/o / o\\
\ \_ /
< --(encrypts)--> <|
< <|
< <|
(2A) (2)
symmetric asymmetric
encryption private
key, from key
hashed
passphrase
"""
ART_KEY_PAIR_SPLITTING3 = """
_ __ _
/o / o\\ \\
\ \_ / /
< --(encrypts)--> <| --(into)--> |
< <| |
< <| |
(2A) (2) (2B)
symmetric asymmetric encrypted form
encryption private of asymmetric
key, from key private key (2)
hashed
passphrase
"""
@ -319,6 +372,14 @@ ART_KEY_PAIR3A2 = """
""" """
ART_KEY_KEY2A = """ _
/o
\
<
<
<
"""
ART_KEY_PAIR3B2 = """ ART_KEY_PAIR3B2 = """
@ -496,6 +557,24 @@ ART_KEY_PAIR4Z1 = """
form of (2) form of (2)
""" """
ART_KEY_PAIR4ZZ = """
__
/o \\_____
\__/-="="`
? _
?? \\
? _ /
? |
? |
? |
"""
ART_KEY_PAIR4Z2 = """ ART_KEY_PAIR4Z2 = """
__ __

@ -20,8 +20,8 @@ class CLI(Logger):
def run(self,inp='',name=''): def run(self,inp='',name=''):
self.name=name self.name=name
clear_screen() clear_screen()
self.boot() # self.boot()
self.help() # self.help()
if inp: self.route(inp) if inp: self.route(inp)
@ -45,7 +45,7 @@ class CLI(Logger):
def boot(self,indent=5): def boot(self,indent=5):
logo=art.text2art(CLI_TITLE,font=CLI_FONT) logo=art.text2art(CLI_TITLE,font=CLI_FONT)
# logo=make_key_discreet_str(logo,chance_bowdlerize=0.1) #.decode() # logo=make_key_discreet_str(logo,chance_redacted=0.1) #.decode()
logo=tw.indent(logo, ' '*indent) logo=tw.indent(logo, ' '*indent)
scan_print(logo,max_pause=0.005) scan_print(logo,max_pause=0.005)
@ -69,7 +69,7 @@ class CLI(Logger):
### DIALOGUES ### DIALOGUES
# hello, op? # hello, op?
def status_keymaker_intro(self,name): def status_keymaker_part1(self,name):
self.status(None,{ART_OLDPHONE4+'\n',True},3) #,scan=False,width=None,pause=None,clear=None) self.status(None,{ART_OLDPHONE4+'\n',True},3) #,scan=False,width=None,pause=None,clear=None)
nm=name if name else '?' nm=name if name else '?'
@ -110,131 +110,216 @@ class CLI(Logger):
def status_keymaker_body(self,name,passphrase,pubkey,privkey,hasher): def status_keymaker_part2(self,name,passphrase,pubkey,privkey,hasher,persona):
from getpass import getpass
# gen what we need # gen what we need
uri_id = pubkey.data_b64 uri_id = pubkey.data_b64
qr_str = get_qr_str(uri_id) qr_str = get_qr_str(uri_id)
qr_path = os.path.join(PATH_QRCODES,name+'.png') qr_path = os.path.join(PATH_QRCODES,name+'.png')
# # what are pub/priv? # what are pub/priv?
# self.status( self.status(
# 'I will forge for you two matching keys, part of an "asymmetric" pair.', 'I will forge for you two matching keys, part of an "asymmetric" pair.',
# 'Please, watch me work.', 'Please, watch me work.',
# None,{tw.indent(ART_KEY,' '*5)+'\n'}, None,{tw.indent(ART_KEY,' '*5)+'\n'},
# 'I use a high-level cryptographic function from Themis, a well-respected open-source cryptography library.', 'I use a high-level cryptographic function from Themis, a well-respected open-source cryptography library.',
# 'I use the iron-clad Elliptic Curve algorthm to generate the asymmetric keypair.', 'I use the iron-clad Elliptic Curve algorthm to generate the asymmetric keypair.',
# '> GenerateKeyPair(KEY_PAIR_TYPE.EC)', '> GenerateKeyPair(KEY_PAIR_TYPE.EC)',
# 3 3
# ) )
# self.status( self.status(
# None, None,
# {ART_KEY_PAIR,True} {ART_KEY_PAIR,True}
# ) #,clear=False,indent=10,pause=False) ) #,clear=False,indent=10,pause=False)
# self.status( self.status(
# None,{ART_KEY_PAIR}, None,{ART_KEY_PAIR},
# 'A matching set of keys have been generated.', 'A matching set of keys have been generated.',
# None,{ART_KEY_PAIR2A+'\n\nA matching set of keys have been generated.'}, None,{ART_KEY_PAIR2A+'\n\nA matching set of keys have been generated.'},
# '1) First, I have made a "public key" which you can share with anyone:', '1) First, I have made a "public key" which you can share with anyone:',
# f'{repr(pubkey)}', f'{repr(pubkey)}',
# 'This key is a randomly-generated binary string, which acts as your "address" on Komrade.', 'This key is a randomly-generated binary string, which acts as your "address" on Komrade.',
# 'By sharing this key with someone, you enable them to write you an encrypted message which only you can read.' 'By sharing this key with someone, you enable them to write you an encrypted message which only you can read.'
# ) )
# self.status( self.status(
# None,{ART_KEY_PAIR2A}, None,{ART_KEY_PAIR2A},
# f'You can share your public key by copy/pasting it to them over a secure channel (e.g. Signal).', f'You can share your public key by copy/pasting it to them over a secure channel (e.g. Signal).',
# 'Or, you can share it as a QR code, especially phone to phone:', 'Or, you can share it as a QR code, especially phone to phone:',
# {qr_str+'\n\n',True,5}, {qr_str+'\n\n',True,5},
# f'\n\n(If registration is successful, this QR code be saved as an image to your device at: {qr_path}.)' f'\n\n(If registration is successful, this QR code be saved as an image to your device at: {qr_path}.)'
# ) )
# private keys # private keys
# self.status(None, self.status(None,
# {ART_KEY_PAIR2B}, {ART_KEY_PAIR2B},
# 'Second, I have forged a matching "private key":', 'Second, I have forged a matching "private key":',
# f'{repr(privkey)}', f'{repr(privkey)}',
# 'With it, you can decrypt any message sent to you via your public key.', 'With it, you can decrypt any message sent to you via your public key.',
# 'You you should never, ever give this key to anyone.', 'You you should never, ever give this key to anyone.',
# 'In fact, this key is so dangerous that I will immediately destroy it by splitting it into two half-keys:' 'In fact, this key is so dangerous that I will immediately destroy it by splitting it into two half-keys:'
# ) )
# self.status(None, self.status(None,
# {ART_KEY_PAIR31A}, {ART_KEY_PAIR31A},
# {ART_KEY_PAIR3B+'\n',True}, {ART_KEY_PAIR3B+'\n',True},
# 3,'Allow me to explain.', 3,'Allow me to explain.',
# '(2A) is a separate encryption key generated by your password.', '(2A) is a separate encryption key generated by your password.',
# '(2B) is a version of (2) which has been encrypted by (2A).', '(2B) is a version of (2) which has been encrypted by (2A).',
# "Because (2) will be destroyed, to rebuild it requires decrypting (2B) with (2A).", "Because (2) will be destroyed, to rebuild it requires decrypting (2B) with (2A).",
# ) )
# self.status( self.status(
# None,{ART_KEY_PAIR5+'\n'}, None,{ART_KEY_PAIR5+'\n'},
# "However, in a final move, I will now destroy (2A), too.", "However, in a final move, I will now destroy (2A), too.",
# None,{ART_KEY_PAIR4Z1+'\n'}, None,{ART_KEY_PAIR4Z1+'\n'},
# 'Why? Because now only you can regenerate it, by remembering the password which created it.', 'Why? Because now only you can regenerate it, by remembering the password which created it.',
# # None,{ART_KEY_PAIR4Z1+'\n'}, # None,{ART_KEY_PAIR4Z1+'\n'},
# 'However, this also means that if you lose or forget your password, you\'re screwed.', 'However, this also means that if you lose or forget your password, you\'re screwed.',
# None,{ART_KEY_PAIR4Z2+'\n'}, None,{ART_KEY_PAIR4Z2+'\n'},
# "Because without key (2A),you couldn never unlock (2B).", "Because without key (2A),you couldn never unlock (2B).",
# None,{ART_KEY_PAIR4Z3+'\n'}, None,{ART_KEY_PAIR4Z3+'\n'},
# "And without (2B) and (2A) together, you could never re-assemble the private key of (2).", "And without (2B) and (2A) together, you could never re-assemble the private key of (2).",
# None,{ART_KEY_PAIR4Z42+'\n'}, None,{ART_KEY_PAIR4Z42+'\n'},
# "And without (2), you couldn't read messages sent to your public key.", "And without (2), you couldn't read messages sent to your public key.",
) )
self.status( self.status(
None,{ART_KEY_PAIR4Z1}, None,{ART_KEY_PAIR4ZZ},
'So choosing a password is an important thing!' 'So choosing a password is an important thing!'
) )
if not passphrase: if not passphrase:
from getpass import getpass
self.status( self.status(
'And it looks like you haven\'t yet chosen a password.', 'And it looks like you haven\'t yet chosen a password.',
3,"Don't tell it to me! Never tell it to anyone.", 3,"Don't tell it to me! Never tell it to anyone.",
"Ideally, don't even save it on your computer; just remember it, or write it down on paper.", "Ideally, don't even save it on your computer; just remember it, or write it down on paper.",
"Instead, whisper it to Komrade @Hasher, who scrambles information '1-way', like a blender.", "Instead, whisper it to Komrade @Hasher, who scrambles information '1-way', like a blender.",
) )
res = self.status(None, res = self.status(None,
{ART_FROG_BLENDER,True}, {indent_str(ART_FROG_BLENDER,10),True},
"@Keymaker: Go ahead, try it. Type anything to @Hasher.", "@Keymaker: Go ahead, try it. Type anything to @Hasher.",
('str_to_hash',f'@{name}: ',input) ('str_to_hash',f'@{name}: ',input)
) )
str_to_hash = res.get('vals').get('str_to_hash') str_to_hash = res.get('vals').get('str_to_hash')
hashed_str = hasher(str_to_hash.encode()) hashed_str1 = hasher(str_to_hash.encode())
res = self.status( res = self.status(
'@Hasher: '+hashed_str '@Hasher: '+hashed_str1
) )
res = self.status( res = self.status(
'@Keymaker: See? Ok, now type in a password.' '@Keymaker: Whatever you typed, there\'s no way to reconstruct it from that garbled mess.',
('str_to_hash',f'@{name}: ',getpass) 'But whatever you typed will always produce the *same* garbled mess.',
('str_to_hash',f'Try typing the exact same thing over again:\n@{name}: ',input)
) )
str_to_hash = res.get('vals').get('str_to_hash') str_to_hash = res.get('vals').get('str_to_hash')
hashed_pass1 = hasher(str_to_hash.encode()) hashed_str2 = hasher(str_to_hash.encode())
res = self.status( res = self.status(
'@Hasher: '+hashed_pass1 '@Hasher: '+hashed_str2
) )
if hashed_str1==hashed_str2:
self.status('See how the hashed values are also exactly the same?')
else:
self.status('See how the hashed values have also changed?')
res = self.status( res = self.status(
'@Keymaker: Whatever you entered, it\'s already forgotten. That hashed mess is all that remains.', ('str_to_hash',f'Now try typing something just a little bit different:\n@{name}: ',input)
'Now type in the same password one more time to verify it:',
('str_to_hash',f'@{name}: ',getpass)
) )
str_to_hash = res.get('vals').get('str_to_hash') str_to_hash = res.get('vals').get('str_to_hash')
hashed_pass2 = hasher(str_to_hash.encode()) hashed_str3 = hasher(str_to_hash.encode())
res = self.status( res = self.status(
'@Hasher: '+hashed_pass2 '@Hasher: '+hashed_str3
) )
if hashed_str2==hashed_str3:
if hashed_pass1==hashed_pass2: self.status('See how the hashed values are also the same?')
self.status('The passwords matched.')
else: else:
self.status('The passwords did not match.') self.status('See how the hashed values have also changed?')
self.status(
None,{indent_str(ART_FROG_BLENDER,10)},
'@Keymaker: Behind the scenes, @Hasher is using the SHA-256 hashing function, which was designed by the NSA.',
'But @Hasher also adds a secret "salt" to the recipe, as it\'s called.',
'To whatever you type in, @Hasher adds a secret phrase: another random string of characters which never changes.',
"By doing so, the hash output is \"salted\": made even more idiosyncratic to outside observers.",
)
self.status(
None,{indent_str(ART_FROG_BLENDER,10)},
f"I've taken the liberty of generating a random secret for your device, which I show here mostly redacted:",
make_key_discreet_str(persona.crypt_keys.secret.decode(),0.25),
'The full version of this secret is silently added to every input you type into @Hasher.',
"I've saved this secret phrase to a hidden location on your device hardware.",
)
self.status(
None,{indent_str(ART_FROG_BLENDER,10)},
'However, this means that you will be unable to log in to your account from any other device.',
'This limitation provides yet another level of hardware protection against network attacks.',
'However, you can always choose (not recommended) to the secret file with another device by a secure channel.',
3,f'But, please excuse me Komrade @{name} -- I digress.'
)
while not passphrase:
res = self.status(None,
{indent_str(ART_FROG_BLENDER,10)},
"@Keymaker: Please type your chosen password into @Hasher.",
('str_to_hash',f'\n@{name}: ',getpass),
pause=False
)
str_to_hash = res.get('vals').get('str_to_hash')
hashed_pass1 = hasher(str_to_hash.encode())
res = self.status(
'\n@Hasher: '+hashed_pass1,
pause=False
)
res = self.status(
'\nNow type in the same password one more time to verify it:',
('str_to_hash',f'\n@{name}: ',getpass),
pause=False
)
str_to_hash = res.get('vals').get('str_to_hash')
hashed_pass2 = hasher(str_to_hash.encode())
res = self.status(
'\n@Hasher: '+hashed_pass2,
pause=False
)
if hashed_pass1==hashed_pass2:
self.status('','@Keymaker: Excellent. The passwords clearly matched, because the hashed values matched.',pause=False)
passphrase = hashed_pass1
else:
self.status('@Keymaker: A pity. It looks like the passwords didn\'t match, since the hashed values didn\'t match either. Try again?')
return passphrase
def status_keymaker_part3(self,privkey,privkey_decr,privkey_encr,passphrase):
# self.status(
# None,{tw.indent(ART_KEY,' '*5)+'\n',True},
# # None,{ART_+'\n',True},
# 'Now that we have a hashed passphrase, we can generate the (2A) encryption key.',
# {ART_KEY_KEY2A,True,0.1},
# '''This key (2A) is formed using Themis's high-level symmetric encryption key library, SecureCell using Seal mode.''',
# '''It uses the AES-256 encryption algorithm, which was developed by the U.S. National Institute of Standards and Technology (NIST) in 2001.'''
# )
s1=self.print('Now that we have (2A), we can use it to encrypt the super-sensitive private key (2):',ret=True)
s2a = self.print(f"(2A) {make_key_discreet_str(passphrase)}",ret=True)
screen1 = f'''{s1}\n\n{ART_KEY_PAIR_SPLITTING1}\n{s2a}'''
s2 = self.print(f"(2) {make_key_discreet(privkey.data_b64)}",ret=True)
screen2 = f'''{s1}\n\n{ART_KEY_PAIR_SPLITTING2}\n{s2a}\n\n\n{s2}'''
repr_privkey = repr(privkey).replace('] ',']\n')
s2b = self.print(f"(2B) {make_key_discreet(b64encode(privkey_encr))}",ret=True)
screen3 = f'''{s1}\n\n{ART_KEY_PAIR_SPLITTING3}\n{s2a}\n\n\n{s2}\n\n\n{s2b}'''
self.status(None,s1)
self.status(None,{screen1})
do_pause()
self.status(None,{screen2})
do_pause()
self.status(None,{screen3})
do_pause()

@ -188,4 +188,4 @@ PAUSE_LOGGER = 1
CLI_TITLE = 'KOMRADE' CLI_TITLE = 'KOMRADE'
CLI_FONT = 'clr5x6'#'colossal' CLI_FONT = 'clr5x6'#'colossal'
STATUS_LINE_WIDTH = 50 CLI_WIDTH = STATUS_LINE_WIDTH = 50

@ -71,7 +71,7 @@ class Logger(object):
# except KeyboardInterrupt: # except KeyboardInterrupt:
# exit() # exit()
def print(*x,width=STATUS_LINE_WIDTH,end='\n',indent=1,scan=False,**y): def print(*x,width=STATUS_LINE_WIDTH,end='\n',indent=1,ret=False,scan=False,**y):
if not scan and not width: if not scan and not width:
print(*x,end=end,**y) print(*x,end=end,**y)
else: else:
@ -82,6 +82,7 @@ class Logger(object):
# xw = [_ for _ in tw.wrap(xs,width=width)] # xw = [_ for _ in tw.wrap(xs,width=width)]
xs=end.join(xw) xs=end.join(xw)
xs = tw.indent(xs,' '*indent) xs = tw.indent(xs,' '*indent)
if ret: return xs
print(xs) if scan==False else scan_print(xs) print(xs) if scan==False else scan_print(xs)
def status(self,*msg,pause=True,clear=False,ticks=[],tab=2,speed=10,end=None,indent=0,width=80,scan=False): def status(self,*msg,pause=True,clear=False,ticks=[],tab=2,speed=10,end=None,indent=0,width=80,scan=False):
@ -114,7 +115,7 @@ class Logger(object):
elif type(para) is set: # logo/image elif type(para) is set: # logo/image
pl = [x for x in para if type(x)==str] pl = [x for x in para if type(x)==str]
txt=pl[0] txt=pl[0]
speed =[x for x in para if type(x)==int] speed =[x for x in para if type(x) in {int,float}]
speed = speed[0] if speed else 1 speed = speed[0] if speed else 1
if True in para: if True in para:
scan_print(txt,speed=speed) scan_print(txt,speed=speed)
@ -260,13 +261,13 @@ def capture_stdout(func):
def scan_print(xstr,min_pause=0,max_pause=0.015,speed=100000): def scan_print(xstr,min_pause=0,max_pause=.001,speed=1):
import random,time import random,time
for c in xstr: for c in xstr:
print(c,end='',flush=True) print(c,end='',flush=True)
# naptime=random.uniform(min_pause, max_pause / speed) naptime=random.uniform(min_pause, max_pause / speed)
# time.sleep(naptime) time.sleep(naptime)
time.sleep(.001) # time.sleep()
@ -276,4 +277,10 @@ def get_qr_str(data):
qr.add_data(data) qr.add_data(data)
ascii = capture_stdout(qr.print_ascii) ascii = capture_stdout(qr.print_ascii)
ascii = ascii[:-1] # removing last line break ascii = ascii[:-1] # removing last line break
return '\n ' + ascii.strip() return '\n ' + ascii.strip()
def indent_str(x,n):
import textwrap as tw
return tw.indent(x,' '*n)
Loading…
Cancel
Save