У нас имеется утилита, которая управляет билдами и деплоями.
Помимо всего прочего — в ней прописаны несколько паролей — для авторизации в базе данных (Apache Cassandra) и для отправки почтовых уведомлений.
Хранились они в plaintext виде, прямо в коде утилиты.
Для решения этой задачи — был написан отдельный класс, который умеет шифровать и дешифровать файл, в котором хранятся логины и пароли (при этом его нешифрованная версия не хранится в репозитории) и который возвращает необходимые данные другим методам утилиты.
Данные из расшированного файла считываются с помощью ConfigParser, а что бы по умолчанию избежать создания файла на диске после его расшифровки — он передаётся модулю ConfigParser-у в виде «memory file«, с помощью модуля StringIO.
Если же файл требуется расшифровать и сохранить на диск для редактирования — в основном скрипте утилиты предусморена отдельная опция.
Отличная статья по PyCrypto есть тут>>>.
Сам скрипт:
#!/usr/bin/env python
import os
import StringIO
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
from lib.shared import ConfigParser
class RDSCryptor(object):
"""Can be done with https://pypi.python.org/pypi/keyring#using-keyring module"""
def __init__(self, rdsmanager_local_path):
"""Used to define key for symmetric encryption.
key : byte string
The secret key to use in the symmetric cipher.
It must be 16 (*AES-128*), 24 (*AES-192*), or 32 (*AES-256*) bytes long.
See pydoc Crypto.Cipher.AES.new for more details.
passfile_enc - encrypted file;
passfile_clear - decrypted file, to view, edit etc."""
self.key = hashlib.sha256('somepass').digest()
# encrypted file, MUST be stored in repository
self.passfile_enc = os.path.join(rdsmanager_local_path, 'conf', 'credentials.txt.enc')
# decrypted file, to view, edit etc
# usually must NOT be stored on disc and/or in repository
self.passfile_clear = os.path.join(rdsmanager_local_path, 'conf', 'credentials.txt')
def pad(self, s):
"""Pad message blocks before encrypt.
Electronic codebook and cipher-block chaining (CBC) mode are examples of block cipher mode of operation.
Block cipher modes for symmetric-key encryption algorithms require plain text input
that is a multiple of the block size, so messages may have to be padded to bring them to this length."""
return s + b"" * (AES.block_size - len(s) % AES.block_size)
def encrypt(self, message):
"""Encrypt with AES in CBC mode:
https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
using randomly generated IV
https://en.wikipedia.org/wiki/Initialization_vector"""
message = self.pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def decrypt(self, ciphertext):
iv = ciphertext[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext[AES.block_size:])
return plaintext.rstrip(b"")
def encrypt_file(self):
with open(self.passfile_clear, 'rb') as fo:
plaintext = fo.read()
enc = self.encrypt(plaintext)
with open(self.passfile_enc, 'wb') as fo:
fo.write(enc)
def decrypt_file(self, return_type=None):
"""By default decrypt_file() must be called from get_credentials()
with return_type = 'mem'. If so - file will NOT be created on the disk - data will be passed to ConfigParser()
using StringIO() as "memory file".
If decrypt_file() called from RDSmanager with rds --decrypt option - it will create new file conf/credentials.txt
with decrypted data from credentials.txt.enc."""
with open(self.passfile_enc, 'rb') as fo:
ciphertext = fo.read()
__dec = self.decrypt(ciphertext)
if return_type == 'mem':
return __dec
else:
with open(self.passfile_clear, 'wb') as fo:
fo.write(__dec)
def get_credentials(self, section, option):
return_type = 'mem'
buf = StringIO.StringIO(self.decrypt_file(return_type))
config = ConfigParser.ConfigParser()
config.readfp(buf)
return config.get(section, option)
Сам файл с данными является простым ini-документом, который выглядит так:
d:RDSrdsmanager>type confcredentials.txt [cloudlibrary] clc_user = user clc_password = pass [kantar_smtp] smtpconnect_user = [email protected] smtpconnect_password = pass
Использование класса для авторизации другими методами:
...
# Cloudlibrary access data
crypto = RDSCryptor(rdsmanager_local_path)
clc_user = crypto.get_credentials('cloudlibrary', 'clc_user')
clc_password = crypto.get_credentials('cloudlibrary', 'clc_password')
# Sendmail credentials
smtpconnect_user = crypto.get_credentials('kantar_smtp', 'smtpconnect_user')
smtpconnect_password = crypto.get_credentials('kantar_smtp', 'smtpconnect_password')
...
...
# set of defaults, if variables not found
defaults = {'base_url': 'https://www.dev.domain.com/cloudlibrary/',
'user': self.clc_user,
'password': self.clc_password, }
...
user = defaults.get('user')
password = defaults.get('password')
...
Ссылки по теме




