Как не продолбать пароли в Python скриптах

Хранение паролей всегда было головной болью. В классическом варианте у вас есть пользователь, который очень старается не забыть жутко секретный «qwerty123» и информационная система, которая хранит хеш от этого пароля. Хорошая система еще и заботливо солит хеши, чтобы отравить жизнь нехорошим людям, которые могут украсть базу с хешированными паролями. Тут все понятно. Какие-то пароли храним в голове, а какие-то засовываем в зашифрованном виде в keepass.
Все меняется, когда мы убираем из схемы человека, который старательно вводит ключ с бумажки. При взаимодействии двух информационных систем, на клиентской стороне в любом случае должен храниться пароль в открытом для системы виде, чтобы его можно было передать и сравнить с эталонным хешем. И вот на этом этапе админы обычно открывают местный филиал велосипедостроительного завода и начинают старательно прятать, обфусцировать и закапывать секретный ключ в коде скриптов. Многие из этих вариантов не просто бесполезны, но и опасны. Я попробую предложить удобное и безопасное решение этой проблемы для python. И чуть затронем powershell.
Как делать не надо
Всем знакома концепция «временного скриптика». Вот буквально только данные по-быстрому распарсить из базы и удалить. А потом внезапно выясняется, что скрипт уже из dev-зоны мигрировал куда-то в продакшен. И тут начинают всплывать неприятные сюрпризы от изначальной «одноразовости».
Чаще всего встречается вариант в стиле:
db_login = 'john.doe' password = 'password!'
Проблема в том, что здесь пароль светится в открытом виде и достаточно просто обнаруживается среди залежей старых скриптов автоматическим поиском. Чуть более сложный вариант идет по пути security through obscurity, с хранением пароля в зашифрованном виде прямо в коде. При этом расшифровка обратно должна выполняться тут же, иначе клиент не сможет предъявить этот пароль серверной стороне. Такой способ спасет максимум от случайного взгляда, но любой серьезный разбор кода вручную позволит без проблем вытащить секретный ключ. Код ниже спасет только от таких «shoulder surfers»:
- KDE4 & KDE5 KWallet (требуется dbus)
- Freedesktop Secret Service — множество DE, включая GNOME (требуется secretstorage)
- Windows Credential Locker
- macOS Keychain
- Похитить сам код (легко)
- Деобфусцировать при необходимости (легко)
- Похитить сам код (легко)
- Деобфусцировать при необходимости (легко)
- Скомпрометировать локальную машину, залогинившись под атакуемым пользователем (сложно)
Безопасный ввод пароля
Еще один частый вариант утечки секретных паролей — история командной строки. Использование стандартного input здесь недопустимо:
age = input("What is your age? ") print "Your age is: ", age type(age) >>output What is your age? 100 Your age is: 100 type 'int'>
В примере выше я уже упоминал библиотеку getpass:
# Безопасно запрашиваем ввод пароля в CLI password = getpass.getpass(prompt="Enter secret password:")
Ввод данных при ее использовании аналогичен классическому *nix подходу при входе в систему. Ни в какие системные логи данные не пишутся и не отображаются на экране.
Немного о Powershell
Для Powershell правильным вариантом является использование штатного Windows Credential Locker.
Реализуется это модулем CredentialManager.
Install-Module CredentialManager -force New-StoredCredential -Target $url -Username $ENV:Username -Pass . Get-StoredCredential -Target .
- Информационная безопасность
- Python
- PowerShell
Как работать с файлами в Python

Программа на Python — это не только функционал, но и оптимальная система работы с файлами. Например, если вы пишете чат-бот, вам нужно загрузить в него готовые ответы в файле txt. Если вы сделали программу для обработки фото — здесь ваш код на Python должен быть готов обрабатывать файлы, которые загрузит в сервис пользователь. В этой статье разберемся, как работать с файлами на Python, какие для этого есть библиотеки и методы.

Освойте профессию
«Python-разработчик»
Что такое файл
На любом компьютере и в любой операционной системе есть файлы — область данных со своим именем, хранящаяся на носителе. Их принято считать базовыми объектами, из которых складываются директории. В Python с помощью файлов можно сохранять результат работы программы или получать из них данные для обработки в коде.
Python-разработчик
Освойте Python, самый популярный язык программирования
3 383 ₽/мес 6 150 ₽/мес

- открытие;
- операции чтения из файла и записи в файл;
- закрытие.
Разберемся со всеми действиями по порядку.
Открытие файла на чтение и запись в Python
На компьютере файл надо открыть перед тем, как вносить в него изменения. Такое же правила действует и для Python: нужно открыть файл на чтение и запись. Без этапа открытия нельзя читать содержимое или изменять его. Открыть файл можно с помощью функции open() :
open(file, mode='rt')
В функцию в качестве аргументов требуется передать путь файлу (file) и выбрать режим работы (mode). По умолчанию Python выбирает значение rt , но доступны и другие режимы:
Аргумент mode
Как работает
Чтение из файла
Открыть как текстовый файл
Запись в файл и создание файла, если его не существует
Запись в файл и вызовы исключения, если файла не существует
Открыть как двоичный файл
Запись в файл путем добавления новых значений в конец
Работа в режиме чтения и записи
Можно комбинировать режимы и использовать сразу несколько. К примеру, по умолчанию функция open() принимает аргумент rt . Это значит, что файл будет открыт в виде текстового документа для чтения. С помощью r+ и w+ можно открыть файл сразу для чтения и записи на Python. В первом случае несуществующие файлы будут создаваться, а во втором такое действие спровоцирует вызов исключения.
Если в Python надо открыть файл data.txt в виде текстового документа с правами для чтения, то для этого следует использовать следующий код:
f = open('data.txt')
При этом Python установит режим открытия файла по умолчанию. Если нужен другой режим, то следует передать в функцию аргументы mode:
f = open('data.txt', 'w+')
После окончания работы файлы надо закрывать — так же, как мы это делаем на компьютере. Для этого используется функция close() после кода взаимодействия с файлом:
f = open('data.txt', 'w+') # действия с файлом f.close()
Также файлы можно открывать с помощью менеджера контекста with . В этом случае файл автоматически закроется, когда работа с ним завершится:
with open('data.txt', 'w+') as f: # действия с файлом
При открытии файла можно дополнительно указать кодировку, если нужна специфическая или требуется больше контроля над работой кода:
with open('data.txt', 'w+', encoding='utf-8') as f: # действия с файлом
Кодировку можно указать третьим параметром и при обычном способе открытия:
f = open('data.txt', 'w+', encoding='utf-8') # действия с файлом f.close()
Чтение файла
Чтение из файла Python можно реализовать с помощью функции read(), если он открыт в режиме чтения r . Синтаксис функции выглядит следующим образом:
file.read(size)
В строке выше file обозначает объект, с которым работаем, а size — количество символов для чтения. Если ничего не указать в качестве аргумента функции read() , то получится прочитать сразу все содержимое документа.
Для примера: создадим в проекте file.txt и запишем в него строку «Привет, Python!». Теперь прочитаем первые шесть символов из файла:
with open('file.txt', 'r', encoding='utf-8') as f: data = f.read(6) print(data)
Если запустить код, то Python выведет в консоль фразу «Привет» — это и есть первые шесть символов строки «Привет, Python!» в file.txt:
>>> Привет
Теперь отредактируем код. В этом примере не будем передавать никакие аргументы в функцию read() :
with open('file.txt', 'r', encoding='utf-8') as f: data = f.read() print(data) >>> Привет, Python!
В этом случае система выводит сразу все содержимое файла.
Содержимое файла можно считывать построчно. Убедимся в этом, отредактировав file.txt. Добавим в него список продуктов:
Морковь Сметана Мука Яблоки
Теперь прочитаем этот список с помощью функции readline() :
with open('file.txt', 'r', encoding='utf-8') as f: print(f.readline()) >>> Морковь
Python прочитал первую строку списка продуктов. Если вызвать функцию readline() несколько раз, то получится прочитать последующие строки файла:
with open('file.txt', 'r', encoding='utf-8') as f: print(f.readline()) print(f.readline()) print(f.readline()) print(f.readline()) >>> Морковь >>> Сметана >>> Мука >>> Яблоки
Более удобно последовательное чтение с помощью readline() можно реализовать в цикле:
with open('file.txt', 'r', encoding='utf-8') as f: for line in f: print(line) >>> Морковь >>> Сметана >>> Мука >>> Яблоки
Все строки файла можно прочитать с помощью метода readlines() , возвращающего содержимое в виде списка вместе со специальными символами:
with open('file.txt', 'r', encoding='utf-8') as f: data = f.readlines() print(data) >>> ['Морковь\n', 'Сметана\n', 'Мука\n', 'Яблоки']
Такое чтение можно реализовать и без метода readlines() , воспользовавшись конструктором списков list() :
with open('file.txt', 'r', encoding='utf-8') as f: data = list(f) print(data) >>> ['Морковь\n', 'Сметана\n', 'Мука\n', 'Яблоки']
При чтении всего файла в Python стоит помнить, что он может быть слишком большим. Если разместить его полностью в оперативной памяти компьютера не получается, следует считывать его частями.
Запись в файл на Python
Для записи файла в Python используется функция write() . В качестве аргумента ей следует передать строку, содержимое которой будет записано:
file.write(string)
Удалим все содержимое file.txt и запишем в него строку «Привет, Python!»:
with open('file.txt', 'a', encoding='utf-8') as f: data = 'Привет, Python!' f.write(data)
Обратите внимание, что в этом примере мы открыли файл в режиме a, что позволяет записывать новые строки в конец. Выполняем код, открываем файл — теперь в нем содержится строка «Привет, Python!».
В файл можно записать сразу список строк. Для этого применяется метод writelines() , которому в качестве аргумента надо передать сам список. В качестве примера создадим переменную со списком продуктов и запишем ее в file.txt:
with open('file.txt', 'a', encoding='utf-8') as f: grocery = ['Морковь', 'Яблоки', 'Мука', 'Молоко'] f.writelines(grocery)
Если выполнить код и проверить file.txt, то мы увидим единую строку без пробелов. Все дело в том, что метод writelines() не добавляет разделители строк автоматически. Нужно сделать это вручную, прописав \n каждому элементу списка:
with open('file.txt', 'a', encoding='utf-8') as f: grocery = ['Морковь\n', 'Яблоки\n', 'Мука\n', 'Молоко\n'] f.writelines(grocery)
Теперь при выполнении кода каждый элемент списка будет записываться на новую строку.

Станьте разработчиком на Python и решайте самые разные задачи: от написания кода до автоматизации процессов
Управление указателем
В Python есть возможность явно задать позицию указателя в файле — для этого используется метод seek(). Узнать текущую позицию можно с помощью метода tell() .
Рассмотрим принципы работы с методами на примере. Откроем file.txt в режиме доступа r+ и запишем в него строку «0123456789ABCDEF»:
with open('file.txt', 'r+', encoding='utf-8') as f: f.write('0123456789ABCDEF')
Теперь прочитаем седьмой символ в строке. Для этого воспользуемся методом seek() для перемещения указателя. В качестве аргумента надо передать функции аргумент смещения в байтах. Вспомним, что нумерация строк начинается с нуля, а нам надо прочитать седьмой символ строки. Значит, укажем смещение в шесть байтов:
with open('file.txt', 'r+', encoding='utf-8') as f: f.write('123456789ABCDEF') f.seek(5) data = f.read(1) print(data) >>> 6
Можно осуществить смещение с конца, тогда в качестве второго элемента надо передать отправную точку для формирования смещения. Всего доступны три начальные позиции:
- 0 — от начала файла;
- 1 — от текущей позиции;
- 2 — от конца файла.
Теперь прочитаем четвертый символ с конца строки:
with open('file.txt', 'r+b') as f: f.seek(-4, 2) data = f.read(1) print(data) >>> b'C'
Узнать байт позиции указателя можно с помощью метода tell() :
with open('file.txt', 'r+', encoding='utf-8') as f: f.write('123456789ABCDEF') f.read(5) print(f.tell())
Работа с файловой системой
В Python можно работать не только с конкретным файлом, но и со всей системой. Перемещаться между каталогами, создавать новые файлы и переименовывать существующие.
Для работы с файловой системой на Python используют встроенная библиотека OS. Ее необходимо отдельно импортировать в код проекта, чтобы получить доступа к ее методам:
import os
Теперь узнаем с помощью Python текущий каталог. Делаем это с помощью метода getcwd() . В выводе метода получим путь к папке, в которой сейчас находимся:
import os print(os.getcwd()) >>> /Users/daniilshat/PycharmProjects/pythonProject
С помощью метода listdir() можно получить список с содержимым каталога. В качестве аргумента можно передать путь к директории; если этого не сделать, то система покажет содержимое текущего каталога:
import os # содержимое текущего каталога print(os.listdir()) # содержимое директории PycharmProjects print(os.listdir('/Users/daniilshat/PycharmProjects')) >>> ['file.txt', 'main.py', '.idea'] >>> ['.DS_Store', 'pythonProject', 'bot', 'love-couples']
Для создания новых директорий файлов в Python используют метод mkdir() . В качестве аргумента ему надо передать полный путь, включая название новой директории. Для примера создадим директорию Example в PycharmProjects:
import os # содержимое директории PycharmProjects до создания print(os.listdir('/Users/daniilshat/PycharmProjects')) os.mkdir('/Users/daniilshat/PycharmProjects/Example') # содержимое директории PycharmProjects после создания print(os.listdir('/Users/daniilshat/PycharmProjects')) >>> ['.DS_Store', 'pythonProject', 'bot', 'love-couples'] >>> ['.DS_Store', 'pythonProject', 'Example', 'bot', 'love-couples']
С помощью Python можно переименовывать файлы и директории. Для этого используется метода rename() , которому необходимо передать в качестве аргументов путь к старому и новым файлам. Переименуем директорию Example в New Example:
import os dir = '/Users/daniilshat/PycharmProjects' old_file = os.path.join(dir, 'Example') new_file = os.path.join(dir, 'New Example') # содержимое директории PycharmProjects до переименования print(os.listdir(dir)) os.rename(old_file, new_file) # содержимое директории PycharmProjects после переименования создания print(os.listdir(dir)) >>> ['.DS_Store', 'pythonProject', 'Example', 'bot', 'love-couples'] >>> ['.DS_Store', 'pythonProject', 'bot', 'New Example', 'love-couples']
Для удаления пустых директорий файлов в Python можно использовать метод rmdir() , а для удаления директории со всем содержимым — shutil.rmtree() . В качестве аргумента требуется передать путь к директории. Для примера удалим директорию New Example, которую недавно создали:
import os # содержимое директории PycharmProjects до удаления print(os.listdir('/Users/daniilshat/PycharmProjects')) os.rmdir('/Users/daniilshat/PycharmProjects/New Example') # содержимое директории PycharmProjects после удаления print(os.listdir('/Users/daniilshat/PycharmProjects')) >>> ['.DS_Store', 'pythonProject', 'bot', 'New Example', 'love-couples'] >>> ['.DS_Store', 'pythonProject', 'bot', 'love-couples']
Итог
В Python можно работать с файлами и получать доступ к файловой системе для изменения каталогов и директорий. Все это можно делать с помощью кода. В Python для этого предусмотрены следующие методы.
| Метод | Что делает |
| os.getcwd() | Узнать текущий каталог |
| os.listdir(path) | Узнать список содержимого каталога |
| os.mkdir(path) | Создать директорию |
| os.rename(old_file, new_file) | Переименовать файл или директорию |
| os.rmdir(path) | Удалить пустую директорию |
| shutil.rmtree(path) | Удалить директорию со всем содержимым |
| file.read(size) | Прочитать определенное количество символов из файла |
| file.readline() | Прочитать строку из файла |
| file.readlines() | Прочитать все строки из файла |
| file.write(string) | Записать строку в файл |
| file.writelines(list) | Записать список строк в файл |
| file.seek(int) | Переместить указатель |
| file.tell() | Узнать текущее положение указателя |
Python-разработчик
Освойте Python с нуля. Подготовим к трудоустройству: дадим много практики, реальные проекты для портфолио, поможем с резюме. Лучшие студенты пройдут стажировки в проектах компаний-партнеров.
Не записывает в файл .txt но ошибок нет
Почему то, спустя два года он перестал работать. Ошибок не выдает, но и не записывает ничего в .txt. Подскажите, что не так?
Отслеживать
51.6k 201 201 золотой знак 63 63 серебряных знака 245 245 бронзовых знаков
задан 22 апр 2020 в 10:34
386 1 1 серебряный знак 13 13 бронзовых знаков
У меня всё работает на Python 3.8 .
22 апр 2020 в 10:37
А у меня почему-то не хочет после двух лет работы ): Python 3.6.4
22 апр 2020 в 10:43
@Denzel, а вы файл закрыли после записи? PS безопаснее использовать context manager — with open(. ) as f: json.dum(js1, f)
22 апр 2020 в 10:51
@MaxU Нет, не закрывал. Ваш способ тоже не записывает.
22 апр 2020 в 10:59
попробуйте прочитать записанный файл при помощи Python скрипта — похоже файл записывается, но не в ту директорию, где вы его ожидаете
22 апр 2020 в 11:01
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
- Во-первых укажите полный путь к файлу дабы избежать сюрпризов (файл может записываться не в ту директорию, где вы его ожидаете увидеть):
filename = r"c:\temp\Paper.txt"
при работе с неполными путями убедитесь, что интерпретатор запускается / работает в нужной директории:
print(os.path.abspath(os.curdir)) # показать текущую директорию os.chdir(r"c:\some\target\directory") # сменить текущую директорию
- Во-вторых используйте контекстный менеджер при работе с файлами — он сам позаботится о своевременном закрытии файла:
with open(filename, "w") as f: json.dump(js1, f)
Выкладка python-проектов с помощью pip и wheel
Термин «бинарные яйца» воодит в заблуждение. В понимании автора, «бинарный» — это отличный от текстового. Конкретно «binary eggs» — это те же исходники, но запакованные zip’ом.
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
Возможно я напутал с терминологией; я имел ввиду:
Конкретно «binary eggs» — это те же исходники, но запакованные zip’ом.
+ архив может содержать бинарные файлы для определенной платформы.
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
Я таких яиц не встречал. В качестве «ресурса» можно в любую систему дистрибуции положить любой бинарник — как он по платформе выбираться будет?
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
В имени файла есть информация о версии питона и платформе.
Всего голосов 1: ↑1 и ↓0 +1
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
Да, действительно — там внутри настоящий линуксовский .so файл :). Спасибо за наводку.
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
Спасибо за pypm, раньше не слышал.
Кстати они тоже говорят, что не факт что будет работать:
We recommend ActivePython users to install packages using PyPM, and only if that fails (usually it doesn’t), attempt the same using pip/easy_install.
Можно пытаться комбинировать: сначала ставить из pypm, а нерабочие пакеты ставить из своего индекса. Но тут есть опасность, что нерабочий модуль обнаружится не сразу.
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Интересно. Пара вопросов:
1. В pip что-ли уже поддержку wheel влили? Или нужно ветку использовать, или wheel сам поддержку добавляет?
2. А без localshop нельзя? pip умеет ставить пакеты просто из папки («pip install —no-index —find-links:file:///local/dir/SomePackage»); можно еще просто путь до архива указывать, хоть локальный, хоть нет («pip install ./downloads/SomePackage-1.0.4.tar.gz»), да и листинг файлов вроде можно хоть nginx’ом, хоть «python -m http.server» отдавать (и ключиком каким-нибудь сказть pip-у там пакеты искать, pip же умеет ссылки в свободной форме скрейпить). Я сильно в вопросе не разбирался, но всегда было непонятно, почему люди хотят поднять локальный pypi сервер — может, подскажите, какие именно преимущества дает свой pypi сервер?
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
У pip есть ветка, в которой добавлена поддержка wheel.
Можно и без localshop — зависит от потребностей. По началу у нас была общая папка под NFS; сборочная машина кладет, а сервер оттуда ставит. Можно использовать и для разработки в офисе, чтобы не тянуть с интернета. С nginx тоже получится, хотя часть API pip’а не будет работать (с поиском кажись). Если требуется аутентификация, то можно настроить basic_auth на nginx.
Плюсы использования localshop:
1. Самое главное — возможность загрузки пакетов: своего проекта, сторонние дописанные модули. Например, надо как-то допилить чей-то модуль, и не хочется ждать пока автор примет pull request. Добавим, например, свои инициалы в версию в файле setup.py и зальем в свой индекс. В requirements.txt своего проекта указываем нашу версию, так во время развертки избежим вытягивание из git’a и мороки с доступом к приватным проектам.
2. Аутентификация: каждому сотруднику выдать по пользователю, при нужде можно отключить без возни с htaccess файлами.
3. В фоновом режиме подгружает используемые модули — выступает как зеркало.
Вот так загружаем свой проект в индекс(загрузит .tar.gz и .whl пакеты):
python setup.py sdist bdist_wheel upload -r mypypi
Если ваш проект написан только на питоне, то bdist_wheel можно исключить.
А эта команда скачает все зависимости, построит из них wheel пакеты, и загрузит их в индекс:
pip wheel -v —build-option upload —build-option -r —build-option mypypi -r requirements.txt
Могу в деталях описать в отдельном посте, будет ли полезно?
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
Ага, т.е. все-таки ветка. С ней, кстати, нужно иметь в виду, что там (в отличие от pip 1.3.1) не проверяются ssl-сертификаты при скачивании, ее обновить бы.
При установке с pypi-сервера еще, вроде бы, есть одна особенность — pip лезет еще по разным ссылкам (не разбирался каким, но вроде из long_description (часто это README) и по всем со странички Home Page, которая как url в setup.py указывается) и ищет новые версии еще и там (что замедляет установку — иногда очень сильно, а при отсутствии проверки ssl — это еще и небезопасно). Т.е., вроде бы, без pypi сервера может все работать быстрее и безопаснее. Ну так, предостережение — если используйте ветку, то лучше форкнуть и до мастера обновить ее.
С загрузкой пакетов — сделать «python setup.py sdist bdist_wheel», без «upload» — .tar.gz-шки (и, видимо, .whl-ки) появятся в папке dist рядом с setup.py (а дальше уже эти файлы куда угодно) — точно так же можно избежать выкачивания из git и прочей мороки.
Зеркало для архивов делается установкой переменной окружения PIP_DOWNLOAD_CACHE (хотя оно обычно не очень спасает, т.к. основное время все равно чаще тратится на скрейпинг ссылок c домашней странички проекта).
Но вцелом ясно, спасибо — с pypi-сервером, видимо, и правда удобнее права раздавать, + pip search заработает.
Пост про wheel — думаю да, полезно.