使用pycrypto使用公钥进行Python日志加密

使用pycrypto使用公钥进行Python日志加密,python,logging,pycrypto,Python,Logging,Pycrypto,我正在开发一个web应用程序(使用gevent,但这并不重要),它必须在日志中写入一些机密信息。显而易见的想法是使用硬编码到我的应用程序中的公钥加密机密信息。要阅读它,需要一个私钥,2048位RSA似乎足够安全。我选择了pycrypto(也尝试了M2Crypto,但我发现几乎没有什么不同),并将日志加密作为logging.Formatter子类实现。然而,我对pycrypto和Cryptography还不熟悉,我不确定我对数据加密方式的选择是否合理。PKCS1\u OAEP模块是我需要的吗?或者

我正在开发一个web应用程序(使用gevent,但这并不重要),它必须在日志中写入一些机密信息。显而易见的想法是使用硬编码到我的应用程序中的公钥加密机密信息。要阅读它,需要一个私钥,2048位RSA似乎足够安全。我选择了pycrypto(也尝试了M2Crypto,但我发现几乎没有什么不同),并将日志加密作为
logging.Formatter
子类实现。然而,我对pycrypto和Cryptography还不熟悉,我不确定我对数据加密方式的选择是否合理。
PKCS1\u OAEP
模块是我需要的吗?或者有更友好的加密方法,而不将数据分成小块

所以,我所做的是:

import logging
import sys

from Crypto.Cipher import PKCS1_OAEP as pkcs1
from Crypto.PublicKey import RSA

PUBLIC_KEY = """ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDe2mtK03UhymB+SrIbJJUwCPhWNMl8/gA9d7jex0ciSuFfShDaqJ4wYWG4OOl\
VqKMxPrPcZ/PMSwtc021yI8TXfgewb65H/YQw4JzzGANq2+mFT8jWRDn+xUc6vcWnXIG3OPg5DvIipGQvIPNIUUP3qE7yDHnS5xdVdFrVe2bUUXmZJ9\
0xJpyqlTuRtIgfIfEQC9cggrdr1G50tXdXZjS0M1WXl5P6599oH/ykjpDFrCnh5fz9WDwUc0mNJ+11Qh+yfDp3k7AhzhRaROKLVWnfkklFaFm7LsdVX\
KPjp7dPRcTb84c2OnlIjU0ykL74Fy0K3eaPvM6TLe/K1XuD3933 pupkin@pupkin"""

PUBLIC_KEY = RSA.importKey(PUBLIC_KEY)

LOG_FORMAT = '[%(asctime)-15s - %(levelname)s: %(message)s]'

# May be more, but there is a limit.
# I suppose, the algorithm requires enough padding,
# and size of padding depends on key length.
MAX_MSG_LEN = 128

# Size of a block encoded with padding. For a 2048-bit key seems to be OK.
ENCODED_CHUNK_LEN = 256


def encode_msg(msg):
    res = []
    k = pkcs1.new(PUBLIC_KEY)
    for i in xrange(0, len(msg), MAX_MSG_LEN):
        v = k.encrypt(msg[i : i+MAX_MSG_LEN])
        # There are nicer ways to make a readable line from data than using hex. However, using
        # hex representation requires no extra code, so let it be hex.
        res.append(v.encode('hex'))
        assert len(v) == ENCODED_CHUNK_LEN
    return ''.join(res)


def decode_msg(msg, private_key):
    msg = msg.decode('hex')
    res = []
    k = pkcs1.new(private_key)
    for i in xrange(0, len(msg), ENCODED_CHUNK_LEN):
        res.append(k.decrypt(msg[i : i+ENCODED_CHUNK_LEN]))
    return ''.join(res)


class CryptoFormatter(logging.Formatter):
    NOT_SECRET = ('CRITICAL',)
    def format(self, record):
        """
        If needed, I may encode only certain types of messages.
        """
        try:
            msg = logging.Formatter.format(self, record)
            if not record.levelname in self.NOT_SECRET:
                msg = encode_msg(logging.Formatter.format(self, record))
            return msg
        except:
            import traceback
            return traceback.format_exc()


def decrypt_file(key_fname, data_fname):
    """
    The function decrypts logs and never runs on server. In fact,
    server does not have a private key at all. The only key owner
    is server admin.
    """
    res = ''
    with open(key_fname, 'r') as kf:
        pkey = RSA.importKey(kf.read())
    with open(data_fname, 'r') as f:
        for l in f:
            l = l.strip()
            if l:
                try:
                    res += decode_msg(l, pkey) + '\n'
                except Exception: # A line may be unencrypted
                    res += l + '\n'
    return res

# Unfortunately dictConfig() does not support altering formatter class.
# Anyway, in demo code I am not going to use dictConfig().


logger = logging.getLogger()
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(CryptoFormatter(LOG_FORMAT))
logger.handlers = []
logger.addHandler(handler)

logging.warning("This is secret")
logging.critical("This is not secret")
更新:感谢下面接受的答案,现在我明白了:

  • 我的解决方案目前似乎非常有效(日志条目很少,没有性能考虑,或多或少的可信存储)。关于安全性,我现在能做的最好的事情就是不要忘记禁止运行我的守护程序的用户写入程序的
    .py
    .pyc
    文件。:-)但是,如果用户受到威胁,他仍然可能尝试将调试器附加到我的守护进程,因此我也应该为他禁用登录。非常明显的时刻,但非常重要的时刻

  • 当然,有些解决方案的可扩展性要高得多。一种非常常见的技术是使用缓慢但可靠的RSA加密AES密钥,并使用相当快的AES加密数据。本例中的数据加密是对称的,但检索AES密钥需要断开RSA或在程序运行时从内存中获取它。使用高级库和二进制日志文件格式的流加密也是一种方法,尽管作为流加密的二进制日志格式应该非常容易受到日志损坏的影响,即使由于断电而突然重新启动也可能是一个问题,除非我在较低的级别上做一些事情(至少在每次启动守护程序时循环日志)

  • 我将
    .encode('hex')
    更改为
    .encode('base64')。替换('\n')。替换('\r')
    。幸运的是,base64编解码器工作正常,没有行尾。它节省了一些空间

  • 使用不受信任的存储可能需要对记录进行签名,但这似乎是另一回事

  • 基于捕获异常检查字符串是否加密是可以的,因为除非日志被恶意用户篡改,否则引发异常的是base64编解码器,而不是RSA解密


  • 您似乎直接使用RSA加密数据。这相对较慢,并且存在一个问题,即您只能加密数据的一小部分。基于“解密不起作用”区分加密数据和明文数据也不是一个非常干净的解决方案,尽管它可能会起作用。您确实使用OAEP,这很好。您可能希望使用base64而不是十六进制来节省空间

    然而,加密很容易出错。因此,您应该尽可能始终使用高级加密库。您必须自己指定填充方案的任何内容都不是“高级”。不过,我不确定您是否能够创建一个高效的、基于行的日志加密系统,而不必求助于较低级别的库

    <强>如果您没有理由只对日志的各个部分进行加密,请考虑只加密整个内容。>/P> 如果您确实非常需要基于行的加密,您可以做以下几点:从安全的随机性源创建一个随机对称AES密钥,并为其提供一个简短但唯一的ID。使用RSA加密此密钥,并将结果写入日志文件中,在一行中加上前缀标记,例如“key”,以及ID。对于每个日志行,生成随机IV,使用所述IV在CBC模式下使用AES256对消息进行加密(您现在没有任何每行的长度限制!),并将密钥ID、IV和加密消息写入日志,前缀为标记,例如“ENC”。一段时间后,销毁对称密钥并重复(生成新密钥,写入日志)。这种方法的缺点是,能够从内存中恢复对称密钥的攻击者可以读取使用该密钥加密的消息。其优点是,您可以使用更高级别的构建块,而且速度要快得多(在我的CPU上,您可以使用AES-128加密70000条每秒1KB的日志行,但使用RSA2048只能加密3500块最大256字节的日志行)。顺便说一下,RSA解密速度非常慢(大约每秒100块)


    请注意,您没有身份验证,即您不会注意到对日志的修改。因此,我假设您信任日志存储。否则,请参见。

    谢谢。使用用RSA加密的AES密钥是一个很酷的主意,尽管速度不是我真正关心的。通常我每分钟不会有超过两个秘密日志条目。关于存储,我信任它,虽然不太信任,但是,在存储受损的情况下,很难保护我的程序和数据。我得稍微消化一下你的想法。谢谢。理解并接受。更新我的问题以反映结论。