Python配置文件加密
加密文件读取类
加密及配置文件读取类如下:
# -*- coding: utf-8 -*-
import StringIO
import binascii
import codecs
import locale
import re
import token
import tokenize
from base64 import b64decode
from base64 import b64encode
from hashlib import md5
from traceback import print_exc
from Crypto import Random
from Crypto.Cipher import AES
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
try:
from Crypto.Util.Padding import pad, unpad
except ImportError:
from Crypto.Util.py3compat import bchr, bord
def pad(data_to_pad, block_size):
padding_len = block_size - len(data_to_pad) % block_size
padding = bchr(padding_len) * padding_len
return data_to_pad + padding
def unpad(padded_data, block_size):
pdata_len = len(padded_data)
if pdata_len % block_size:
raise ValueError("Input data is not padded")
padding_len = bord(padded_data[-1])
if padding_len < 1 or padding_len > min(block_size, pdata_len):
raise ValueError("Padding is incorrect.")
if padded_data[-padding_len:] != bchr(padding_len) * padding_len:
raise ValueError("PKCS#7 padding is incorrect.")
return padded_data[:-padding_len]
__all__ = ['RsaCrypto', 'AESCrypto', 'Settings']
class RsaCrypto(object):
"""RSA 加解密
Usage:
RsaCrypto().create_rsa_key()
"""
def create_rsa_key(self):
"""生成RSA秘钥对"""
try:
key = RSA.generate(2048)
encrypted_key = key.exportKey(pkcs=8)
public_key = key.publickey().exportKey().decode('utf-8')
private_key = encrypted_key.decode('utf-8')
return {'state': 1, 'message': {'public_key': public_key, 'private_key': private_key}}
except Exception as err:
return {'state': 0, 'message': str(err)}
def encrypt(self, public_key, plaintext):
"""加密方法"""
try:
recipient_key = RSA.import_key(public_key)
cipher_rsa = PKCS1_v1_5.new(recipient_key)
en_data = cipher_rsa.encrypt(plaintext.encode('utf-8'))
hex_data = binascii.hexlify(en_data).decode('utf-8')
return {'state': 1, 'message': hex_data}
except Exception as err:
return {'state': 0, 'message': str(err)}
def decrypt(self, private_key, hex_data):
"""解密方法"""
try:
private_key = RSA.import_key(private_key)
cipher_rsa = PKCS1_v1_5.new(private_key)
en_data = binascii.unhexlify(hex_data.encode('utf-8'))
data = cipher_rsa.decrypt(en_data, None).decode('utf-8')
return {'state': 1, 'message': data}
except Exception as err:
return {'state': 0, 'message': str(err)}
class AESCrypto(object):
"""
Usage:
c = AESCrypto('password').encrypt('message')
m = AESCrypto('password').decrypt(c)
"""
def __init__(self, key):
self.key = md5(key.encode('utf8')).hexdigest()
def encrypt(self, raw):
raw = pad(raw, AES.block_size)
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return b64encode(iv + cipher.encrypt(raw))
def decrypt(self, enc):
enc = b64decode(enc)
iv = enc[:16]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(enc[16:]), AES.block_size).decode('utf8')
r_encoding = re.compile(r'\s*coding\s*[=:]\s*([-\w.]+)')
class Settings(object):
def __init__(self, filename, decrypt=None, commentchar='#'):
"""
对配置文件配置项进行解密处理
"""
self._commentchar = commentchar
self.__decrypt = decrypt or str
self.__data = {}
self.__read(filename)
def _get_data(self):
return self.__data
data = property(_get_data)
def __read(self, filename=''):
encoding = None
f = open(filename, 'rb')
text = f.read()
f.close()
text = text + '\n'
begin = 0
if text.startswith(codecs.BOM_UTF8):
begin = 3
encoding = 'UTF-8'
elif text.startswith(codecs.BOM_UTF16):
begin = 2
encoding = 'UTF-16'
if not encoding:
try:
unicode(text, 'UTF-8')
encoding = 'UTF-8'
except BaseException:
try:
encoding = locale.getdefaultlocale()[1]
except BaseException:
encoding = None
if not encoding:
encoding = 'UTF-8'
try:
codecs.lookup(encoding)
except BaseException:
encoding = 'UTF-8'
self._encoding = encoding
f = StringIO.StringIO(text)
f.seek(begin)
lineno = 0
comments = []
while True:
lastpos = f.tell()
line = f.readline()
lineno += 1
if not line:
break
line = line.strip()
if line:
if line.startswith(self._commentchar):
if lineno == 1: # first comment line
b = r_encoding.search(line[1:])
if b:
self._encoding = b.groups()[0]
continue
elif '=' in line:
pos = line.find('=')
begin, end = pos, pos + 1
keyname = line[:begin].strip()
rest = line[end:].strip()
# if key= then value will be set ''
if rest == '':
value = 'None'
else:
f.seek(lastpos + end)
try:
value, iden_existed, _lineno = self.__read_line(f)
_lineno -= 1
except Exception:
print_exc()
raise Exception("Parsing ini file error in %s:%d:%s" % (filename, lineno, line))
try:
txt = '#coding=%s\n%s' % (self._encoding, value)
# 解密函数被定义为'_'
v = eval(txt, {'_': self.__decrypt}, dict(self.data))
self.__data[keyname] = v
except Exception:
print_exc()
raise Exception("Converting value (%s) error in %s:%d:%s" % (value, filename, lineno, line))
lineno += _lineno
def __read_line(self, f):
g = tokenize.generate_tokens(f.readline)
buf = []
time = 0
iden_existed = False
while True:
v = g.next()
tokentype, t, (lineno, start), end, line = v
if tokentype == 54:
continue
if tokentype in (token.INDENT, token.DEDENT, tokenize.COMMENT):
continue
if tokentype == token.NAME:
iden_existed = True
if tokentype == token.NEWLINE:
return ''.join(buf), iden_existed, lineno
else:
if t == '=' and time == 0:
time += 1
continue
buf.append(t)
使用例子
以django为例,读取local_settings配置。
修改django的settings.py文件,读取加密配置。
# from config.local_settings import * # noqa
# 包含加密信息的配置文件
local_settings_file = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'config',
'local_settings.py'))
# 解密使用的函数
decrypt_func = AESCrypto(key='secret key').decrypt
local_setting = Settings(local_settings_file, decrypt=decrypt_func)
# 读取更新配置
locals().update(local_setting.data)
配置文件例子:
# 加密后的配置项内容,需要使用'_'来定义才能被正确解密。加密内容可使用如下方式获得
# encrypt_str = AESCrypto('secret key').encrypt('hello world')
TEST_CONFIG = _('v7onhJeOJjImxnXkjspcMetB/wYhOEnWTFgNAuSM4pc=')