加密不仅仅是简单的数据转发。我建议编写自己的日志格式化程序并将其设置为根格式化程序——这样,无论您在应用程序中从哪里记录日志,即使是由您的代码不控制的部分,它也将始终通过一层加密。因此,可以尝试以下类似的内容:
import base64
import logging
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto import Random
class EncryptedLogFormatter(logging.Formatter):
def __init__(self, key, fmt=None, datefmt=None):
self._key = SHA256.new(key).digest()
super(EncryptedLogFormatter, self).__init__(fmt=fmt, datefmt=datefmt)
def format(self, record):
message = record.msg
if message:
iv = Random.new().read(AES.block_size)
cipher = AES.new(self._key, AES.MODE_CBC, iv)
padding = AES.block_size - len(message) % AES.block_size
message += chr(padding) * padding
message_enc = iv + cipher.encrypt(message)
record.msg = base64.b64encode(message_enc).decode("latin-1")
return super(EncryptedLogFormatter, self).format(record)
然后您可以在任何可以更改日志格式的地方使用它, 即:
import sys
import logging
# lets get the root logger
root = logging.getLogger()
root.handlers = [] # blank out the existing handlers
# create a new handler, file handler instead of stdout is perfectly fine
handler = logging.StreamHandler(stream=sys.stdout)
# now lets get to business
handler.setFormatter(EncryptedLogFormatter("Whatever key/pass you'd like to use",
"[%(levelname)s] %(message)s"))
# lets add it to the root logger so it gets called by the rest of the app automatically
root.addHandler(handler)
# And lets see what happens:
logging.warn("Sensitive stuff, hide me!")
# [WARNING] NDKeIav5G5DtbaSPB4Y/DR3+GZ9IwmXKzVTua1tTuDZ7uMwxBAKTXgIi0lam2dOQ
# YMMV, the IV is random so every block will be different every time
当然,您可以对日志记录(logging.LogRecord)中的级别、时间戳以及几乎任何内容进行加密,并输出您喜欢的任何格式。当需要读取日志时,只需进行相反操作-在这个答案中有一个示例。
更新:根据要求,以下是如何执行“反向”操作(即解密已加密的日志)的说明。首先,让我们创建一些用于测试的日志条目(继续之前的内容):
root.setLevel(logging.DEBUG) # let's make sure we support all levels
logging.warn("Lorem ipsum dolor sit amet.")
logging.info("Consectetur adipiscing elit.")
logging.debug("Sed do eiusmod tempor.")
假设格式保持不变([%(levelname)s] %(message)s
),这将生成一个类似于以下日志的内容(当然,由于随机IV的存在,它将始终是不同的):
[WARNING] LQMLkbx3YF7ra3e5ZLRj3p1mi2dwCOJe/GMfo2Xg8BBSZMDmZO75rrgoiy/6kqjf
[INFO] D+ehnsq1kWQi61AsLOBkqglXla7jgc2myPFaCGcfCRe6drk9ZmNl+M3UkKPWkDiU
[DEBUG] +rHEHkM2YHJCkIL+YwWI4FNqg6AOXfaBLRyhZpk8/fQxrXLWxcGoGxh9A2vO+7bq
要为这样的日志(文件)创建一个读取器,我们需要了解格式,以便区分加密和非加密数据。在这种情况下,分离各个部分很容易 - 每个日志条目都在新行上,级别未加密,实际加密数据总是与实际日志级别之间用空格隔开。因此,要将所有这些组合起来,我们可以构建一些类似于以下的东西:
import base64
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
def log_decryptor(key, stream):
key = SHA256.new(key).digest()
for line in stream:
if not line.strip():
continue
level, stream = line.split(None, 1)
message_enc = base64.b64decode(stream.encode("latin-1"))
iv = message_enc[:AES.block_size]
message = AES.new(key, AES.MODE_CBC, iv).decrypt(message_enc[AES.block_size:])
padding = ord(message[-1])
if message[-padding:] != chr(padding) * padding:
raise ValueError("Invalid padding encountered.")
yield "{} {}".format(level, message[:-padding])
然后您可以像使用常规生成器一样使用它来解密日志,例如:
logs = ["[WARNING] LQMLkbx3YF7ra3e5ZLRj3p1mi2dwCOJe/GMfo2Xg8BBSZMDmZO75rrgoiy/6kqjf",
"[INFO] D+ehnsq1kWQi61AsLOBkqglXla7jgc2myPFaCGcfCRe6drk9ZmNl+M3UkKPWkDiU",
"[DEBUG] +rHEHkM2YHJCkIL+YwWI4FNqg6AOXfaBLRyhZpk8/fQxrXLWxcGoGxh9A2vO+7bq"]
for line in log_decryptor("Whatever key/pass you'd like to use", logs):
print(line)
如果你已将日志设置为流式记录到文件中,那么你可以直接解密该文件,如下所示:
with open("path/to/encrypted.log", "r") as f:
for line in log_decryptor("Whatever key/pass you'd like to use", f):
print(line)
\n
也可能出现在单个日志消息的密文中间。 - Artjom B.