3

ログに機密情報を書き込む必要があるWebアプリ(geventを使用していますが、それは重要ではありません)を開発しています。明らかなアイデアは、アプリケーションにハードコードされた公開鍵を使用して機密情報を暗号化することです。それを読むには、秘密鍵が必要で、2048 ビットの RSA で十分安全なようです。私はpycryptoを選択し(M2Cryptoも試しましたが、私の目的にはほとんど違いがありませんでした)、ログ暗号化をlogging.Formatterサブクラスとして実装しました。ただし、私は pycrypto と cryptoraphy に慣れていないため、データの暗号化方法の選択が適切かどうかわかりません。PKCS1_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")

更新:以下の受け入れられた回答のおかげで、今では次のことがわかります:

  1. 私のソリューションは今のところかなり有効なようです (ログエントリがほとんどなく、パフォーマンスに関する考慮事項がなく、信頼できるストレージが多かれ少なかれあります)。セキュリティに関して、私が今できる最善のことは、私のデーモンを実行するユーザーがプログラムの.pyおよび.pycファイルに書き込むことを禁止することを忘れないことです。:-) ただし、ユーザーが侵害された場合でも、デーモン プロセスにデバッガーをアタッチしようとする可能性があるため、ログインも無効にする必要があります。かなり明白な瞬間ですが、非常に重要な瞬間です。

  2. 確かに、はるかにスケーラブルなソリューションがあります。非常に一般的な手法は、低速だが信頼性の高い RSA を使用して AES キーを暗号化し、かなり高速な AES を使用してデータを暗号化することです。この場合のデータ暗号化は対称的ですが、AES キーを取得するには、RSA を解読するか、プログラムの実行中にメモリから取得する必要があります。高レベル ライブラリとバイナリ ログ ファイル形式を使用したスト​​リーム暗号化も有効な方法ですが、ストリームとして暗号化されたバイナリ ログ形式は、ログの破損に対して非常に脆弱であるはずです。低レベルのいくつかのもの(少なくとも各デーモンの開始時のログローテーション)。

  3. に変更.encode('hex')しました.encode('base64').replace('\n').replace('\r')。幸いなことに、base64 コーデックは行末がなくても問題なく動作します。スペースを節約します。

  4. 信頼されていないストレージを使用するには、記録に署名する必要があるかもしれませんが、それは別の話のようです.

  5. 例外のキャッチに基づいて文字列が暗号化されているかどうかを確認することは問題ありません。ログが悪意のあるユーザーによって改ざんされない限り、例外を発生させるのは RSA 復号化ではなく base64 コーデックであるためです。

4

1 に答える 1

3

RSAで直接データを暗号化しているようです。これは比較的遅く、データの小さな部分しか暗号化できないという問題があります。「復号化が機能しない」に基づいて暗号化されたデータと平文データを区別することも、おそらく機能するものの、あまりクリーンなソリューションではありません。OAEP を使用していますが、これは優れています。スペースを節約するために、16 進数の代わりに base64 を使用することをお勧めします。

ただし、クリプトは間違いやすいです。このため、可能な限り高レベルの暗号化ライブラリを常に使用する必要があります。パディングスキームを自分で指定する必要があるものは、「高レベル」ではありません。ただし、かなり低レベルのライブラリに頼ることなく、効率的な行ベースのログ暗号化システムを作成できるかどうかはわかりません.

ログの個々の部分だけを暗号化する理由がない場合は、全体を暗号化することを検討してください。

行ベースの暗号化がどうしても必要な場合は、次のようにすることができます: 安全なランダム ソースからランダムな対称 AES キーを作成し、短いが一意の ID を付与します。このキーを RSA で暗号化し、その結果を ID とともに "KEY" などのタグを前に付けた行にログ ファイルに書き込みます。ログ行ごとに、ランダムな IV を生成し、その IV を使用して CBC モードで AES256 でメッセージを暗号化し (現在、行ごとの長さ制限はありません!)、キー ID、IV、および暗号化されたメッセージをログに書き込みます。 「ENC」などのタグが前に付けられます。一定時間後、対称キーを破棄して繰り返します (新しいものを生成し、ログに書き込みます)。このアプローチの欠点は、メモリから対称キーを回復できる攻撃者が、そのキーで暗号化されたメッセージを読み取ることができることです。利点は、より高いレベルのビルディング ブロックを使用できることと、はるかに高速であることです (私の CPU では、AES-128 で 1 秒あたり 1 KB のログ行を 70,000 行暗号化できますが、最大 256 バイトの約 3,500 チャンクしか暗号化できません)。 RSA2048 を使用)。ちなみに、RSA 復号化は非常に遅いです (1 秒あたり約 100 チャンク)。

認証がないことに注意してください。つまり、ログの変更に気付かないということです。このため、ログ ストレージを信頼していると仮定します。それ以外の場合は、 RFC 5848を参照してください。

于 2013-02-02T07:38:53.750 に答える