Esempio n. 1
0
    def exportCsv(self, fname):
        """
		See also the method with the same name in Dialog class in dialogs.py

		@param fname: name of CSV file
		@type fname: string
		@returns nothing
		"""
        backupKey = self.backupKey
        try:
            privateKey = backupKey.unwrapPrivateKey()
        except CallException:
            return

        with open(fname, "w") as f:
            csv.register_dialect("escaped", doublequote=False, escapechar='\\')
            writer = csv.writer(f, dialect="escaped")
            sortedGroupNames = sorted(self.groups.keys())
            for groupName in sortedGroupNames:
                group = self.groups[groupName]
                for entry in group.entries:
                    key, _, bkupPw = entry
                    decryptedPwComments = backupKey.decryptPassword(
                        bkupPw, privateKey)
                    lngth = int(decryptedPwComments[0:4])
                    password = decryptedPwComments[4:4 + lngth]
                    comments = decryptedPwComments[4 + lngth:]
                    # if we don't escape than the 2-letter string '"\' will
                    # lead to an exception on import
                    csvEntry = (escape(groupName), escape(key),
                                escape(password), escape(comments))
                    # all 4 elements in the 4-tuple are of type string
                    # Py2-vs-Py3: writerow() in Py2 implements on 7-bit unicode
                    # In py3 it implements full unicode.
                    # That means if there is a foreign character, writerow()
                    # in Py2 reports the exception:
                    # "UnicodeEncodeError: 'ascii' codec can't encode character u'\xxx' in position xx
                    # In Py2 we need to convert it back to bytes!
                    if sys.version_info[0] < 3:  # Py2-vs-Py3:
                        # the byte-conversion un-escapes, so we have to escape again!
                        csvEntry = (escape(tobytes(groupName)),
                                    escape(tobytes(key)),
                                    escape(tobytes(password)),
                                    escape(tobytes(comments)))
                    writer.writerow(csvEntry)

        self.settings.mlogger.log(
            "TrezorPass has finished exporting CSV file "
            "from \"%s\" to \"%s\"." % (self.settings.dbFilename, fname),
            logging.INFO, "CSV export")
Esempio n. 2
0
    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
Esempio n. 4
0
    def encryptPassword(self, password):
        """
		Encrypt password with RSA under OAEP padding and return it.
		Password must be shorter than modulus length minus padding
		length.

		With a 4096 bit RSA key that results in a maximum length of
		470 bytes for the plaintext for this implementation (4096//8-2*hashsize-2).

		@param password: password entry, key/value pair
		@type pasword: string (unicode)
		@returns: encrypted passwords of type bytes
		"""
        cipher = PKCS1_OAEP.new(self.publicKey)
        # With a 4096 bit RSA key PKCS1_OAEP.cipher.encrypt() has as limit a maximum
        # length of 470 bytes for the plaintext in this implementation
        # (4096//8-2*hashsize-2).
        # The resulting encrypted block is of size 512 bytes.
        # To allow larger plain text we junk the plaintext into 446 byte pieces
        # (so that it would work also in the future with larger hashsizes).
        # RSA-only is not ideal, but should be acceptable.
        # See https://security.stackexchange.com/questions/33434
        encrypted = b''
        passwordBytes = encoding.tobytes(password)
        splits = [
            passwordBytes[x:x + self.SAFE_RSA_BLOCKSIZE_WITHOUTBUFFER] for x in
            range(0, len(passwordBytes), self.SAFE_RSA_BLOCKSIZE_WITHOUTBUFFER)
        ]
        for junk in splits:
            encrypted += cipher.encrypt(junk)
        # print "RSA PKCS encryption: plain-size =", len(passwordBytes), ", encrypted-size =", len(encrypted)
        return encrypted
Esempio n. 5
0
    def chosenDeviceStr(self):
        """
		Returns device string of chosen Trezor
		in Py3: must return str, i.e. bytes; not unicode!
		"""
        itemData = self.trezorList.currentItem().data(Qt.UserRole)
        deviceStr = encoding.tobytes(itemData)
        return deviceStr
Esempio n. 6
0
    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 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 saveBlobToEncFile(self,
                          fname,
                          obfuscate,
                          twice,
                          outerKey=None,
                          outerIv=None,
                          innerIv=None):
        """
		Take blob, encrypt blob, and write encrypted data to disk.
		Requires Trezor connected.

		@param fname: the name of the plaintext file,
			it will be used to derive the name of the encrypted file
		@param obfuscate: bool to indicate if an obfuscated filename (True)
			is desired or a plaintext filename (False) for the encrypted file
		@param twice: True if data should be encrypted twice
		@param outerKey: usually None,
			if the same file is encrypted twice
			it is different be default, by design, because the outerKey and outerIv are random.
			If one wants to produce
			an identical encrypted file multiple time (e.g. for a safetyCheckDec())
			then one needs to fix the outerKey and outerIv.
		@param outerIv: see outerKey

		@throws IOError: if writing file failed
		"""
        assert len(self.outerKey) == KEYSIZE
        rnd = Random.new()
        self.outerIv = rnd.read(BLOCKSIZE)
        if outerIv is not None:
            self.outerIv = outerIv
        wrappedKey = self.wrapKey(self.outerKey, self.outerIv)

        if obfuscate:
            head, tail = os.path.split(fname)
            fname = os.path.join(head, self.obfuscateFilename(tail))
        else:
            fname += basics.FILEEXT

        if os.path.isfile(fname):
            self.settings.mlogger.log(
                "File %s exists and encryption will overwrite it." % (fname),
                logging.WARNING, "Encryption")
            if not os.access(fname, os.W_OK):
                os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR)
                # self.settings.mlogger.log("File %s cannot be written. "
                # 	"No write permissions. Skipping it.", fname)
                # raise IOError("File " + fname + " cannot be written. "
                # 	"No write permissions. Skipping it.")

        with open(fname, "wb") as f:
            version = basics.FILEFORMAT_VERSION
            f.write(basics.Magic.headerStr)
            f.write(pack("!I", version))
            versionSw = self.versionSw.ljust(16)  # add padding
            bversionStr = encoding.tobytes(versionSw)
            if len(bversionStr) != 16:
                raise IOError(
                    "Version string cannot be made 16 bytes. Aborting. (\"%s\")"
                    % bversionStr)
            f.write(bversionStr)
            if twice:
                self.noOfEncryptions = 2
            else:
                self.noOfEncryptions = 1
            f.write(pack("!I", self.noOfEncryptions))
            futureUse = b'\x00' * 32  # 32 empty bytes
            f.write(futureUse)
            f.write(wrappedKey)
            f.write(self.outerIv)
            encrypted = self.encryptOuter(self.blob, self.outerIv)

            if self.noOfEncryptions == 2:
                encrypted = self.encryptOnTrezorDevice(
                    encrypted, basics.Magic.levelTwoKey, innerIv)

            hmacDigest = hmac.new(self.outerKey, encrypted,
                                  hashlib.sha256).digest()
            f.write(b'\x00\x00\x00\x00')  # unused, fill 4 bytes with 0
            ll = pack("!I", len(encrypted))
            f.write(ll)
            f.write(encrypted)
            f.write(hmacDigest)
            ww = f.tell()
            self.settings.mlogger.log(
                "Wrote %d bytes to file %s." % (ww, fname), logging.DEBUG,
                "Encryption")
            return fname