def renewCookie(self): # ensure the renewed cookie will be valid ie current cookie can actually decrypt the session vault if not CryW(cryptedObj=self.userDict['saes'], bytesPwrd=En(En(self.cookieIn)._by64()[16:])._sha256()).decrypt(): return False # create a new session vault with known, same private Ed25519 key but change the 'secret' and time hence AES key too cookieBytes = self.ioUUID(self.userDict['uuid']) + En()._rnd(32) + af.itb(int(time.time())) self.cookieOut = En(cookieBytes)._b64() self.sql.replaceRows('users', { **self.userDict, 'saes': CryW(bytesMessage = self.priv, bytesPwrd = En(cookieBytes[16:])._sha256()).encrypt(), }) return True
def parseCookie(self): # b64 → bytes, huuid (16 bytes) + AES 'secret' + time (UTC/*nix) cookieBytes = En(self.cookieIn)._by64() huuid, phKey, ts = cookieBytes[0:16], cookieBytes[16:48], cookieBytes[48:] # from bytes huuid get uuid len 36 which IDs user and get SQL dict (line) for this user self.userDict = self.sql.getOneDataDict('users', 'uuid', self.ioUUID(huuid)) # in case login from cookie if not self.email: if False if not self.userDict else self.userDict.get('email'): self.email = self.userDict.get('email') else: return False # check that cookie not expired if int(time.time()) - gv.COOKIE.MAX > af.ifb(cookieBytes[48:]): return False if not self.userDict: return False # concatenate AES 'secret' with time and SHA256 hash to get AES key to decrypt 'saes' session vault and get # Ed25519 private key self.priv = CryW(cryptedObj = self.userDict['saes'], bytesPwrd = En(phKey + ts)._sha256()).decrypt() # keyring if self.priv: self.keyring = Crypt25519(self.priv, self.userDict['pub']) return bool(self.priv)
def getChallenge(self): if not self.userDict: return False # none if never logged in + case where first login ever *or* reset email password privateKeys = { 'current': self.userDict['priv'], 'next': self.parseNextKeys(self.userDict['nextkeys'])[3] } self.response['challenge'] = {k: En(CryW.getChallenge(v))._b64() for k, v in privateKeys.items() if v} return self
def changePassword(self, newHash, pub, priv): testBytes = En()._rnd(32) assert( testBytes == Crypt25519(privateBytes=priv).decrypt(Crypt25519(publicBytes=pub).encrypt(testBytes)) ) self.userDict.update({ 'nextkeys': None, 'priv': CryW(bytesMessage = newHash + priv, bytesPwrd = newHash).encrypt(), 'pub': pub, 'pwrdchange': 0 }) self.sql.replaceRows('users', self.userDict) self.sql.reconnect() self.response['type'] = 'passwordchanged'
asymKC = Crypt25519() t = time.time() for plain in plains: asym = asymKC.encrypt(plain) dasym = Crypt25519(asymKC.privateBytes, asymKC.publicBytes).decrypt(asym) assert (dasym == plain) print(time.time() - t) aes = En().rnd(16)._sha256() t = time.time() for plain in plains: sym = CryW(bytesMessage=plain, bytesPwrd=aes, slow=1).encrypt() dsym = CryW(cryptedObj=sym, bytesPwrd=aes, slow=1).decrypt() assert (dsym == plain) print(time.time() - t) # speed test def makeNotes(n=1, z=1): generator = getattr(lipsum, { 0: 'generate_words', 1: 'generate_sentences' }.get(random.randint(0, 1))) return {
def bj(obj, aes = None): compressed = CryW(cryptedObj = obj, bytesPwrd = aes).decrypt() if aes else obj if not compressed: return False return json.loads(zlib.decompress(compressed))
def jb(obj, aes = None): compressed = zlib.compress(En(json.dumps(obj))._by()) return CryW(bytesMessage = compressed, bytesPwrd = aes).encrypt() if aes else compressed
def newKeypair(self): password = En()._rnd58(8) keys25519 = Crypt25519() return password, CryW(bytesMessage=En(password)._sha512u() + keys25519.privateBytes, bytesPwrd=En(password)._sha512u()).encrypt(), keys25519.publicBytes
def getCookie(self, login): self.response['cookie'] = '' self.getChallenge() # get encrypted vault for this user and try to decrypt decrypted = { k: CryW( cryptedObj = self.userDict['priv'] if k == 'current' else self.parseNextKeys(self.userDict['nextkeys'])[3], pbkd = En(v)._by64() ).decrypt() for k, v in login.pbkdf2b64.items() } decrypted = {k: v for k, v in decrypted.items() if v} # analyze content of dict to figure out what type is to be sent to front end -> standard, reset, change, first # (cookie and forgot are other 'types' but not relevant here) if self.userDict['nextkeys']: type = 'reset' if self.userDict['priv'] else 'first' if not decrypted: self.response['type'] = type return self if not login.newhash and (not decrypted.get('current') or type == 'first'): self.response['type'] = type return self else: type = 'change' if self.userDict['pwrdchange'] else 'standard' if not decrypted: self.response['type'] = type return self if not login.newhash and type == 'change': self.response['type'] = 'change' return self self.response['type'] = 'standard' # we can cancel the request as user knows password if type == 'reset': if decrypted.get('current'): self.sql.wesc(f"UPDATE users SET nextkeys* WHERE uuid*", v = [(None, self.userDict['uuid'])]) self.userDict['nextkeys'] = None else: if not login.newhash: self.response['type'] = 'reset' return self # case where 'nextkeys' needs to be moved to usual permanents vaults ie new user or pwrd reset (not change, reset) if set(decrypted.keys()) == {'next'}: _, _, pub, _ = self.parseNextKeys(self.userDict['nextkeys']) self.hashedpwrd, self.priv = af.En(login.newhash)._by64(), decrypted.get('next')[64:] self.changePassword(self.hashedpwrd, pub, self.priv) # enable lists where assigned if type == 'first': self.sql.wesc(f"UPDATE rights SET disp* WHERE uuid*", v = [(1, self.userDict['uuid'])]) else: # otherwise if reset then all keys are lost :( self.sql.wesc(f"UPDATE rights SET aes* WHERE uuid*", v = [(None, self.userDict['uuid'])]) else: # decrypt the user's vault which contains (1) the hashed pwrd and (2) the private Ed25519 key self.hashedpwrd, self.priv = decrypted.get('current')[0:64], decrypted.get('current')[64:] # case where we are just Δing password if type == 'change': self.hashedpwrd = af.En(login.newhash)._by64() self.changePassword(self.hashedpwrd, self.userDict['pub'], self.priv) # cookie content in bytes: 16 bytes for uuid, 32 bytes of session AES 'secret' (not key) and time UTC, make b64 cookie cookieBytes = self.ioUUID(self.userDict['uuid']) + En()._rnd(32) + af.itb(int(time.time())) self.cookieOut = En(cookieBytes)._b64() # then rotate user's vault (avoid replay attack if XSS attack getting client-generated PBKDF2) and generate session # AES vault which is a SHA256 hashed combination of AES 'secret' above with time UTC so even a corrupt browser # can't fake cookie time generation to server as the hash which decrypts the session vault would change self.sql.replaceRows('users', { **self.userDict, 'priv': CryW(bytesMessage = self.hashedpwrd + self.priv, bytesPwrd = self.hashedpwrd).encrypt(), 'saes': CryW(bytesMessage = self.priv, bytesPwrd = En(cookieBytes[16:])._sha256()).encrypt(), }) self.keyring = Crypt25519(self.priv, self.userDict['pub']) self.response['cookie'] = self.cookieOut return self
self.response['challenge'] = {k: En(CryW.getChallenge(v))._b64() for k, v in privateKeys.items() if v} return self def parseNextKeys(self, nextKeys): if not nextKeys: return b'', b'', b'', b'' return af.ifb(nextKeys[0:4]), nextKeys[4:36], nextKeys[36:68], nextKeys[68:] password = '******' msg= b'\x16C\xd8\xa6\xc1\xdb~\xcdY\xc3\xd77\xeb\xfe\xa1\x0e\x9c\xe4\xdc:m5\xe7\xf5|\xc1\xb2U\x15\xbb\xe9\xd6\x86\xc1\xd8\xdcM\xb2\xeez\xd3\xe5C\xb9o\x1eN\x040\x19_\xe6\xc8\x9b\xdee\x95\xe2t\xe9\x82\xdd\xf5\t\x80U\xeb/\x08\xab~\xe5\x86"\xdf\xf2\xf2\x04!P\xae\xc5\xe9tv\x1c\xb9|\xbd\x99\xd2\xf1V\x14\x04\xac' cipher = CryW(bytesMessage = msg, bytesPwrd = En(password)._sha512u()).encrypt() CryW(cryptedObj = cipher, bytesPwrd = En(password)._sha512u()).decrypt()