Python在日志中隐藏明文密码的方法

 更新时间:2023年10月14日 09:12:58   作者:frankming  
logging日志模块是python的一个内置模块,该模块定义了一些函数和类,为上层应用程序或库实现了一个强大而又灵活的日志记录系统,这篇文章主要介绍了Python如何在日志中隐藏明文密码 ,需要的朋友可以参考下

Python如何在日志中隐藏明文密码

前言

在项目开发中,有的时候会遇到一些安全需求,用以提升程序整体的安全性,提高外来非法攻击的门槛,而在日志中隐藏明文密码打印便是最典型的安全需求之一。

在Python中,明文密码往往发生于命令执行参数、debug日志、依赖库打印等场景中。对于程序自身的明文密码打印,很轻易地就能通过修改相应代码行的方式修复,而对于非程序自身打印,比如依赖库、外部命令等,则比较棘手,无法通过直接修改代码的方式解决。其实,在Python中,logging日志模块提供了一些自定义方法以过滤特定字符串,绝大多数的Python程序均使用logging模块作为其日志记录系统,如果开发者已经得知相关明文密码打印的规则,且使用logging模块记录日志,那么使用在logging模块中过滤特定字符串的方法不失为一个很好的选择。

概念

logging日志模块是python的一个内置模块,该模块定义了一些函数和类,为上层应用程序或库实现了一个强大而又灵活的日志记录系统。

logging模块将日志的处理分为四个层次,分别是:

  • logger:logger向上层应用程序暴露接口,程序通过调用logger打印日志,比如logger.info,logger.error等等;
  • handler:handler用于将logger创建的日志记录输出至适合的目的地,比如标准输出、错误、文件等;
  • filter:filter对如何将日志记录输出提供了更细粒度的控制;
  • formatter:formatter指定了最终日志记录输出的格式。

如上,filter以及formatter层次均提供了对日志行为扩展的手段,针对明文密码打印问题,我们可以通过自定义filter或者formatter,使用特定规则过滤明文密码字段的方式实现。

LogRecord

LogRecord是日志的基本单元,每次应用程序调用Logger打印日志时,logging模块都会自动创建一个LogRecord实例,其记录了日志文本、参数、模块、行数乃至进程ID、线程ID等等有用的信息。

>>> type(record)
<class 'logging.LogRecord'>
>>> record.msg
'password=123456 %s %s'
>>> record.args
('1', '2')
>>> record.created
1697184354.6492243
>>> record.levelname
'INFO'
>>> record.name
'__main__'
>>> record.process
200

上面列出了一些LogRecord对象的属性,这些属性大部分也同样是最后格式化日志输出的参数。

filter

filter一般用作匹配并过滤部分日志,判断匹配条件的日志是否允许打印,它提供了一个filter方法,使用布尔值作为返回值,如果返回true则表示允许打印,否则表示不允许。

filter方法以LogRecord作为参数,这也表示除了过滤指定日志的功能以外,也能够对日志做更精细的控制。

class Filter(object):
    """
    Filter instances are used to perform arbitrary filtering of LogRecords.
    """
    def filter(self, record: LogRecord) -> bool:
        """
        Determine if the specified record is to be logged.

        Returns True if the record should be logged, or False otherwise.
        If deemed appropriate, the record may be modified in-place.
        """

formatter

formatter负责将LogRecord转化为最终的输出字符串,它主要是使用args来渲染msg,除此之外,如果LogRecord包含异常堆栈,那么也会打印出来。

formatter方法以LogRecord作为参数,并返回渲染处理后的字符串,当自定义formatter类时,我们能够既能够处理渲染前的LogRecord,也能修改渲染后的字符串。

class Formatter(object):
    """
    Formatter instances are used to convert a LogRecord to text.
    """
    def format(self, record: LogRecord) -> str:
        """
        Format the specified record as text.

        The record's attribute dictionary is used as the operand to a
        string formatting operation which yields the returned string.
        Before formatting the dictionary, a couple of preparatory steps
        are carried out. The message attribute of the record is computed
        using LogRecord.getMessage(). If the formatting string uses the
        time (as determined by a call to usesTime(), formatTime() is
        called to format the event time. If there is exception information,
        it is formatted using formatException() and appended to the message.
        """

使用formatter实现明文密码隐藏

import re
import logging
import logging.config

# 自定义formatter类
class SensitiveFormatter(logging.Formatter):
    """Formatter that removes sensitive information in urls."""
    @staticmethod
    def _mask_passwd(s) -> str:
        return re.sub(r'(?<=password=)\S+', r'***', s)

    def format(self, record) -> str:
        s = super().format(record)
        return self._mask_passwd(s)

LOGGING_CONFIG = {
    "version": 1,
    "formatters": {
        "default": {
            "()": SensitiveFormatter,
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "default",
            "stream": "ext://sys.stdout"
        },
    },
    "loggers": {},
    "root": {
        "level": "DEBUG",
        "handlers": [
            "console",
        ]
    }
}

logging.config.dictConfig(LOGGING_CONFIG)
LOG = logging.getLogger(__name__)

LOG.info('password=123456')
# 2023-10-13 16:58:50,443 - __main__ - INFO - password=***

使用filter实现明文密码隐藏

import re
import logging
import logging.config

# 自定义filter类
class SensitiveFilter(logging.Filter):
    def __init__(self, patterns):
        super().__init__()
        self._patterns = patterns

    def _mask(self, msg):
        if not isinstance(msg, str):
            return msg
        for pattern in self._patterns:
               msg = re.sub(pattern, r'***', msg)
        return msg

    def filter(self, record):
        record.msg = self._mask(record.msg)
        if isinstance(record.args, dict):
            for k in record.args.keys():
                record.args[k] = self._mask(record.args[k])
        elif isinstance(record.args, tuple):
            record.args = tuple(self._mask(arg) for arg in record.args)
        return super().filter(record)

LOGGING_CONFIG = {
    "version": 1,
    "filters": {
        "default": {
            "()": SensitiveFilter,
            "patterns": [
                r'(?<=password=)\S+',
            ],
        },
    },
    "formatters": {
        "default": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "default",
            "filters": [
                "default",
            ],
            "stream": "ext://sys.stdout"
        },
    },
    "loggers": {},
    "root": {
        "level": "DEBUG",
        "handlers": [
            "console",
        ]
    }
}

logging.config.dictConfig(LOGGING_CONFIG)
LOG = logging.getLogger(__name__)

LOG.info('password=123456')
# 2023-10-13 16:59:22,545 - __main__ - INFO - password=***

附录

Hiding Sensitive Data from Logs with Python (relaxdiego.com)

logging — Logging facility for Python — Python 3.12.0 documentation

到此这篇关于Python如何在日志中隐藏明文密码的文章就介绍到这了,更多相关Python日志中隐藏明文密码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • python数据分析近年比特币价格涨幅趋势分布

    python数据分析近年比特币价格涨幅趋势分布

    这篇文章主要为大家介绍了python分析近年来比特币价格涨幅趋势的数据分布,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-11-11
  • python生成tensorflow输入输出的图像格式的方法

    python生成tensorflow输入输出的图像格式的方法

    本篇文章主要介绍了python生成tensorflow输入输出的图像格式的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • 用于ETL的Python数据转换工具详解

    用于ETL的Python数据转换工具详解

    这篇文章主要介绍了用于ETL的Python数据转换工具,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Python中re.findAll()、re.sub()、set()的使用

    Python中re.findAll()、re.sub()、set()的使用

    本文主要介绍了Python中re.findAll()、re.sub()、set()的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Python heapq堆操作全解析

    Python heapq堆操作全解析

    本文主要介绍了Python heapq堆操作全解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-03-03
  • keras.layers.Conv2D()函数参数用法及说明

    keras.layers.Conv2D()函数参数用法及说明

    这篇文章主要介绍了keras.layers.Conv2D()函数参数用法及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • Python实现合并两个字典的8种方法

    Python实现合并两个字典的8种方法

    Python有多种方法可以通过使用各种函数和构造函数来合并字典,本文主要介绍了Python实现合并两个字典的8种方法,具有一定的参考价值,感兴趣的可以了解一下
    2024-07-07
  • Python SQLAlchemy库的实现示例

    Python SQLAlchemy库的实现示例

    SQLAlchemy库是一个强大的工具,为开发人员提供了便捷的方式来处理与数据库的交互,本文主要介绍了Python SQLAlchemy库的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • Python使用pypdf按指定页码范围批量拆分PDF

    Python使用pypdf按指定页码范围批量拆分PDF

    在处理电子书,扫描书籍或技术文档时,经常会遇到一个需求,即按照指定页码范围把一个 PDF 拆分成多个 PDF 文件,本文将介绍一种简单、稳定、无需外部依赖的方法,有需要的可以了解下
    2026-01-01
  • python测试开发django之使用supervisord 后台启动celery 服务(worker/beat)

    python测试开发django之使用supervisord 后台启动celery 服务(worker/beat)

    Supervisor是用Python开发的一个client/server服务,是Linux/Unix系统下的一个进程管理工具,不支持Windows系统,这篇文章主要介绍了python测试开发django之使用supervisord 后台启动celery 服务(worker/beat),需要的朋友可以参考下
    2022-07-07

最新评论