def decryptPassword(self, encryptedPassword, groupName): """ Decrypt a password. First block is IV. After decryption strips PKCS#5 padding. @param groupName key that will be shown to user on Trezor and was used to encrypt the password. A string in utf-8. @returns: string in unicode """ ugroup = tobytes(groupName) iv, encryptedPassword = encryptedPassword[: BLOCKSIZE], encryptedPassword[ BLOCKSIZE:] # we junk the input, decrypt and reassemble the plaintext passwordBytes = b"" first = not self.settings.NArg splits = [ encryptedPassword[x:x + self.MAX_PADDED_TREZOR_ENCRYPT_SIZE] for x in range(0, len(encryptedPassword), self.MAX_PADDED_TREZOR_ENCRYPT_SIZE) ] for junk in splits: plain = self.trezor.decrypt_keyvalue(basics.Magic.groupNode, ugroup, junk, ask_on_encrypt=False, ask_on_decrypt=first, iv=iv) first = False passwordBytes += Padding(BLOCKSIZE).unpad(plain) return normalize_nfc(passwordBytes)
def obfuscateFilename(self, plaintextFileName): """ Plaintext filename -> pad16 -> AES encrypt on Trezor --> base64 encode --> homegrown padding == obfuscated filename input: string output: string """ namebytes = encoding.tobytes(plaintextFileName) pad16 = Padding(BLOCKSIZE).pad(namebytes) # self.settings.mlogger.log("Press confirm on Trezor device to encrypt file " # "name %s (if necessary).", plaintextFileName) # we do not use an IV here so that we can quickly deobfuscate # filenames without having to read the file encFn = self.trezor.encrypt_keyvalue( basics.Magic.fileNameNode, basics.Magic.fileNameKey, pad16, ask_on_encrypt=False, ask_on_decrypt=not self.settings.QArg, iv=self.baddressIv) bs64 = base64.urlsafe_b64encode(encFn) # mod 4 bs64Str = encoding.normalize_nfc(bs64) ret = PaddingHomegrown().pad(bs64Str) # mod 16 self.settings.mlogger.log( "The obfuscated filename for \"%s\" is \"%s\"." % (plaintextFileName, ret), logging.DEBUG, "Encryption") # self.settings.mlogger.log("\n\tplaintext is %s (%d), \n\tpad16 is %s (%d), " # "\n\tencFn is %s (%d), \n\tbs64 is %s (%d), \n\thgP is %s (%d)", # plaintextFileName, len(plaintextFileName), pad16, len(pad16), # binascii.hexlify(encFn), len(encFn), bs64, len(bs64), ret, len(ret)) return ret
def encrypt(self, plaintext, iv, key): """ Pad plaintext with PKCS#5 and encrypt it. """ cipher = AES.new(key, AES.MODE_CBC, iv) padded = Padding(BLOCKSIZE).pad(plaintext) return cipher.encrypt(padded)
def encryptOnTrezorDevice(self, blob, keystring, innerIv=None): """ Encrypt data. Does PKCS#5 padding before encryption. Store IV as first block. @param keystring: key that will be shown to user on Trezor and used to encrypt the data. A string in utf-8 @returns: bytes """ self.settings.mlogger.log( 'Time entering encryptOnTrezorDevice: %s' % (datetime.datetime.now()), logging.DEBUG, "Encryption") rnd = Random.new() rndBlock = rnd.read(BLOCKSIZE) if innerIv is not None: rndBlock = innerIv ukeystring = encoding.normalize_nfc( keystring) # keystring.decode("utf-8") # minimum size of unpadded plaintext as input to trezor.encrypt_keyvalue() is 0 ==> padded that is 16 bytes # maximum size of unpadded plaintext as input to trezor.encrypt_keyvalue() is 1023 ==> padded that is 1024 bytes # plaintext input to trezor.encrypt_keyvalue() must be a multiple of 16 # trezor.encrypt_keyvalue() throws error on anythin larger than 1024 # In order to handle blobs larger than 1023 we junk the blobs encrypted = b'' first = not self.settings.QArg splits = [ blob[x:x + self.MAXUNPADDEDTREZORENCRYPTSIZE] for x in range(0, len(blob), self.MAXUNPADDEDTREZORENCRYPTSIZE) ] curr, max = 0, len(splits) for junk in splits: padded = Padding(BLOCKSIZE).pad(junk) try: encrypted += self.trezor.encrypt_keyvalue( basics.Magic.levelTwoNode, ukeystring, padded, ask_on_encrypt=False, ask_on_decrypt=first, iv=rndBlock) except Exception as e: self.settings.mlogger.log('Trezor failed. (%s)' % (e), logging.CRITICAL, "Encryption") raise first = False curr += 1 if self.logger.getEffectiveLevel() == logging.DEBUG: sys.stderr.write("\rencrypting block %d of %d" % (curr, max), ) if self.logger.getEffectiveLevel() == logging.DEBUG: sys.stderr.write(" --> done\n") ret = rndBlock + encrypted self.settings.mlogger.log( "Trezor encryption: plain-size = %d, encrypted-size = %d" % (len(blob), len(ret)), logging.DEBUG, "Encryption") self.settings.mlogger.log( 'Time leaving encryptOnTrezorDevice: %s' % (datetime.datetime.now()), logging.DEBUG, "Encryption") return ret
def decrypt(self, ciphertext, iv, key): """ Decrypt ciphertext, unpad it and return """ cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = cipher.decrypt(ciphertext) unpadded = Padding(BLOCKSIZE).unpad(plaintext) return unpadded
def encrypt(self, plaintext, iv, key): """ Pad plaintext with PKCS#5 and encrypt it. """ self.settings.mlogger.log( "AES CBC encryption with key of size %d bits." % (len(key) * 8), logging.DEBUG, "Encryption") cipher = AES.new(key, AES.MODE_CBC, iv) padded = Padding(BLOCKSIZE).pad(plaintext) return cipher.encrypt(padded)
def decrypt(self, ciphertext, iv, key): """ Decrypt ciphertext, unpad it and return """ self.settings.mlogger.log( "AES CBC decryption with key of size %d bits." % (len(key) * 8), logging.DEBUG, "Encryption") cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = cipher.decrypt(ciphertext) unpadded = Padding(BLOCKSIZE).unpad(plaintext) return unpadded
def unwrapPrivateKey(self): """ Decrypt private RSA key using self.encryptedEphemeral from self.encryptedPrivate. Encrypted ephemeral key will be decrypted with Trezor. @returns RSA private key as Crypto.RSA._RSAobj """ ephemeral = self.trezor.decrypt_keyvalue(Magic.backupNode, Magic.backupKey, self.encryptedEphemeral, ask_on_encrypt=False, ask_on_decrypt=True) cipher = AES.new(ephemeral, AES.MODE_CBC, self.ephemeralIv) padded = cipher.decrypt(self.encryptedPrivate) privateDer = Padding(self.BLOCKSIZE).unpad(padded) privateKey = RSA.importKey(privateDer) return privateKey
def wrapPrivateKey(self, privateKey): """ Wrap serialized private key by encrypting it with trezor. """ # Trezor client won't allow to encrypt whole serialized RSA # key in one go - it's too big. We need an ephemeral symmetric # key and encrypt the small ephemeral with Trezor. rng = Random.new() ephemeral = rng.read(self.SYMMETRIC_KEYSIZE) self.ephemeralIv = rng.read(self.BLOCKSIZE) cipher = AES.new(ephemeral, AES.MODE_CBC, self.ephemeralIv) padded = Padding(self.BLOCKSIZE).pad(privateKey) self.encryptedPrivate = cipher.encrypt(padded) self.encryptedEphemeral = self.trezor.encrypt_keyvalue( Magic.backupNode, Magic.backupKey, ephemeral, ask_on_encrypt=False, ask_on_decrypt=True)
def deobfuscateFilename(self, obfuscatedFileName): """ obfuscated filename --> homegrown unpadding --> base64 decode --> AES decrypt on Trezor --> unpad16 == Plaintext filename input: string output: string """ hgUp = PaddingHomegrown().unpad(obfuscatedFileName) # mod 4 hgUpBytes = encoding.tobytes(hgUp) bs64 = base64.urlsafe_b64decode(hgUpBytes) # mod anything self.settings.mlogger.log( "Press confirm on Trezor device to decrypt " "file name %s (if necessary)." % (obfuscatedFileName), logging.DEBUG, "Encryption") if len(bs64) % BLOCKSIZE != 0: raise ValueError("Critical error. File name " + obfuscatedFileName + " could not be deobfuscated. Skipping it.") # we do not use an IV here so that we can quickly deobfuscate filenames # without reading the file, even for files that do not exist decFn = self.trezor.decrypt_keyvalue( basics.Magic.fileNameNode, basics.Magic.fileNameKey, bs64, ask_on_encrypt=False, ask_on_decrypt=not self.settings.QArg, iv=self.baddressIv) ret = Padding(BLOCKSIZE).unpad(decFn) retStr = encoding.normalize_nfc(ret) # self.settings.mlogger.log("\n\tobfuscatedFileName is %s (%d), " # "\n\thgUp is %s (%d), \n\tbs64 is %s (%d), \n\tdecFn is %s (%d), " # "\n\tret is %s (%d)", obfuscatedFileName, len(obfuscatedFileName), # hgUp, len(hgUp), binascii.hexlify(bs64), len(bs64), # decFn, len(decFn), ret, len(ret)) if len(retStr) == 0: # try the old version, maybe it is an old file return (self.deobfuscateFilenameOld(obfuscatedFileName)) self.settings.mlogger.log( "The plaintext filename for %s is %s." % (obfuscatedFileName, retStr), logging.DEBUG, "Encryption") return retStr
def encryptPassword(self, password, groupName): """ Encrypt a password. Does PKCS#5 padding before encryption. Store IV as first block. @param password: text to encrypt (combined password+comments) @type password: string @param groupName: key that will be shown to user on Trezor and used to encrypt the password. A string in utf-8 @type groupName: string @returns: bytes """ rnd = Random.new() rndBlock = rnd.read(BLOCKSIZE) ugroup = tobytes(groupName) password = tobytes(password) # minimum size of unpadded plaintext as input to trezor.encrypt_keyvalue() is 0 ==> padded that is 16 bytes # maximum size of unpadded plaintext as input to trezor.encrypt_keyvalue() is 1023 ==> padded that is 1024 bytes # plaintext input to trezor.encrypt_keyvalue() must be a multiple of 16 # trezor.encrypt_keyvalue() throws error on anythin larger than 1024 # In order to handle passwords+comments larger than 1023 we junk the passwords+comments encrypted = b"" first = not self.settings.NArg splits = [ password[x:x + self.MAX_UNPADDED_TREZOR_ENCRYPT_SIZE] for x in range(0, len(password), self.MAX_UNPADDED_TREZOR_ENCRYPT_SIZE) ] for junk in splits: padded = Padding(BLOCKSIZE).pad(junk) encrypted += self.trezor.encrypt_keyvalue(basics.Magic.groupNode, ugroup, padded, ask_on_encrypt=False, ask_on_decrypt=first, iv=rndBlock) first = False ret = rndBlock + encrypted # print "Trezor encryption: plain-size =", len(password), ", encrypted-size =", len(encrypted) return ret
def decryptOnTrezorDevice(self, encryptedblob, keystring): """ Decrypt a blob. First block is IV. After decryption strips PKCS#5 padding. @param keystring: key that will be shown to user on Trezor and was used to encrypt the blob. A string in utf-8. @returns bytes """ if (len(encryptedblob) > 8388608): # 8M+ and -2 option self.settings.mlogger.log( "This will take more than 10 minutes. Be ready to wait! " "Decrypting each Megabyte on the Trezor (model 1) takes about 75 seconds, " "or 0.8MB/min. This file will take about %d minutes. If you want to " "en/decrypt fast the next time around, remove the `-2` or `--twice` " "option when you encrypt a file." % (len(encryptedblob) // 819200), logging.WARNING, "Encryption") self.settings.mlogger.log( 'Time entering decryptOnTrezorDevice: %s' % (datetime.datetime.now()), logging.DEBUG, "Encryption") ukeystring = encoding.normalize_nfc( keystring) # keystring.decode("utf-8") iv, encryptedblob = encryptedblob[:BLOCKSIZE], encryptedblob[ BLOCKSIZE:] # we junk the input, decrypt and reassemble the plaintext blob = b'' first = not self.settings.QArg self.settings.mlogger.log( "Press confirm on Trezor device for second level " "file decryption on Trezor device itself (if necessary).", logging.DEBUG, "Encryption") self.settings.mlogger.log( "Trezor decryption: encrypted-size = %d" % (len(encryptedblob)), logging.DEBUG, "Encryption") splits = [ encryptedblob[x:x + self.MAXPADDEDTREZORENCRYPTSIZE] for x in range(0, len(encryptedblob), self.MAXPADDEDTREZORENCRYPTSIZE) ] curr, max = 0, len(splits) for junk in splits: try: plain = self.trezor.decrypt_keyvalue(basics.Magic.levelTwoNode, ukeystring, junk, ask_on_encrypt=False, ask_on_decrypt=first, iv=iv) except Exception as e: self.settings.mlogger.log('Trezor failed. (%s)' % (e), logging.CRITICAL, "Encryption") raise first = False blob += Padding(BLOCKSIZE).unpad(plain) curr += 1 if self.logger.getEffectiveLevel() == logging.DEBUG: sys.stderr.write("\rdecrypting block %d of %d" % (curr, max), ) if self.logger.getEffectiveLevel() == logging.DEBUG: sys.stderr.write(" --> done\n") self.settings.mlogger.log( "Trezor decryption: encrypted-size = %d, plain-size = %d" % (len(encryptedblob), len(blob)), logging.DEBUG, "Encryption") if len(blob) == 0: raise ValueError("Decrypting data failed. Wrong Trezor device?") self.settings.mlogger.log( 'Time leaving decryptOnTrezorDevice: %s' % (datetime.datetime.now()), logging.DEBUG, "Encryption") self.innerIv = iv return blob