Linux 网络和流量加密完整指南(第 1 部分)

大家好!我是大聪明-PLUS

在本文中,我想重点介绍 Linux 系统上的流量加密。我们都了解保护隐私的重要性。在这个许多公司收集数据、黑客有时甚至会拦截我们流量的时代,这一点尤为重要。确保数据安全至关重要。例如,确保您的公司网络不被黑客窃听。如今,信息安全不仅仅是一种时尚,而是一种迫切的需要。网络犯罪日益猖獗,保护流量免遭拦截是任何个人或企业数字生活的基本方面。

在本文中,我们将介绍 Linux 中可用的主要加密方法及其实现方法。我们将探讨协议级加密的工作原理、Linux 中的网络工作原理,以及端到端加密和虚拟专用网络 (VPN) 的含义。

本文对于关注隐私和数据保护的人们(包括公司)尤其有意义。

这是第一部分,在这里我们将了解基础知识(协议、网络操作、为 TCP 连接编写 Python 脚本),在第二部分中我们将讨论虚拟专用网络和代理的主题,以及其他几个主题。


我们先来了解一下网络和网络协议的工作原理。如果你已经了解它们的工作原理,可以跳过本节,直接进入下一节。

什么是网络?网络是由相互连接的设备(计算机、服务器、电话以及任何连接到网络的设备,甚至洗衣机)组成的集合,这些设备可以交换数据。网络使用网络协议,即数据交换的规则。网络协议包括以下几种:

  • HTTP——根据它,浏览器为服务器生成一条消息;

  • DNS——根据它,浏览器通过网站的域名识别网站的IP地址;

  • TCP——根据它建立连接并保证数据传输的完整性;

  • IP——根据它在网络中进行寻址;

  • 以太网 - 根据它,物理数据传输是在网络上的设备之间进行的。

  • FTP——根据它,计算机可以通过网络相互传输文件。

  • SMTP - 根据它,服务器在网络上路由电子邮件。

  • UDP 是一种无连接协议,用于发送数据报(小块数据)。它不保证数据包的送达,也不恢复丢失的数据。然而,UDP 比 TCP 更简单、更快速。而 TCP 则保证数据包的送达、顺序和错误恢复。这使得它比 UDP 更复杂。

传输控制协议/互联网协议(TCP/IP 协议栈)简而言之,是一组位于不同层的交互协议,用于管理网络上的数据交换。每个协议都是一组管理数据交换的规则。因此,TCP/IP 协议栈就是一组规则集。许多协议可以根据其运行的网络层进行分类。最常见的网络模型是 OSI 和 TCP/IP。

在任务关键型系统(例如 DNS)中,UDP 更为常用。在这里,速度比保证传输质量更重要。TCP 数据包发送时,UDP 数据包可能会被返回三次。在其他数据完整性比速度更重要的应用中,则使用 TCP。

网络节点是计算机网络的一部分设备。节点可以分为终端节点和中间节点:

端节点是发送和/或接收数据的节点。简而言之,这些设备充当信息的接收者或来源。

中间节点是将端节点相互连接的节点。

例如,智能手机通过Wi-Fi向服务器发送请求,智能手机和服务器是端点,Wi-Fi路由器是中间节点。

❯ 本世纪的问题是隐私。

最近,对人们数据的追踪行动开始了。这种行为一直存在,有人试图控制每个人,但现在势头越来越猛。就拿众多服务来说吧——它们都有推荐功能,而这些推荐功能需要收集数TB的数据。谁在什么时候访问过?他们在寻找什么?他们来自哪个国家?等等,不一而足。几乎可以构成关于每个人的完整报告。即使没有在线服务,即使是你家附近的一家小商店,也能为你建立一份档案。你买了什么,什么时候买的。例如,一个中年男子早上去商店买了几瓶水和一些水果,两个小时后又回来买了一瓶水。从这些数据中,你可以推断出他是一位运动员,每天早上跑步两个小时,并且非常注重健康。你甚至可以找到他大概的居住地。

公司和政府机构使用各种各样的现代技术——从网站 Cookie 到嵌入汽车和设备的传感器——来收集客户、公民和员工的海量数据,这些数据量前所未有。它们的任务包括评估情绪和偏好、预测需求、提高生产力、检测欺诈、追踪位置、监测健康状况以及监控安全。

这些数据大多出于正当目的收集。然而,没有人能够回答一个最紧迫的问题:界限在哪里?人们希望了解他们的个人信息是如何被处理和存储的,以及这些信息的保密程度。“被遗忘权”以及其他众多国际、国家和地方安全法规是否得到了尊重?

或者,例如,如何保护一家公司的企业网络免受黑客攻击?如果它开始窃听流量,数百份有关公司及其员工的重要数据落入不法之徒之手,该怎么办?如果这家公司是国有企业而非私营企业,该怎么办?

确保安全对于任何公司来说都是一项具有挑战性的任务,尤其是随着云环境的发展。

自2020年以来,人们纷纷转向远程办公,这已成为一种趋势。在家办公意味着许多人将公司电脑和手机用于个人用途。然而,工作设备可能并不像看起来那么安全。使用公司电脑和手机进行个人用途的人常常会疑惑:我的雇主能看到我通过家庭Wi-Fi网络或在家时访问的网站吗?

在本文中,正如我已经说过的,我们将尝试自己保护我们的在线流量。

❯ 流量监控有哪些方法?

Linux 提供了大量用于操作和监控网络的工具。有时我们需要监控网络负载,以了解“幕后”发生的事情。

此类任务在负责监控整个公司网络的系统管理员中尤为常见。然而,它们对普通用户也很有用。

如果您正在在线阅读本文,那么您已经了解网络是什么。值得注意的是,几乎所有公司都拥有某种内部服务,并为其组织了所谓的局域网 (LAN)。我们稍后再讨论这个问题。为了使运行不同操作系统和程序的不同计算机能够相互通信,存在一个名为 OSI 的通用网络模型,它定义了标准。该模型将通信划分为几个步骤,称为层,每层都有各自的规则。这些规则定义了通信的进行方式,称为协议。您可能在某处见过一些协议的名称——IP、DNS、HTTP。尽管标准 OSI 模型假设有七层,但管理员通常使用第二、三和四层,并将第五、六和七层合并为一层,通常称为第七层。

操作系统内核负责实现物理层、数据链路层、网络层和传输层,为应用程序提供与网络工作的统一接口。

让我们回到监控的话题。Linux 拥有丰富的网络实用程序和网络管理工具,范围从 [net-tools和 ] iptables[tcpdumpWireShark]。

我们来看看网络监控实用程序iptraf。它是一个用 C 和 ncurses 编写的小程序,用于监控计算机的网络活动。


apt install iptraf

pacman -S iptraf-ng

yum install iptraf

安装完成后,运行命令sudo iptraf-ng

ncurses 界面将会打开。它包含流量监控、界面设置、统计信息、监控和过滤器以及配置。

例如,要查看每个网络连接及其流量统计信息,请选择“IP 流量监视器”。然后选择网络接口。

接下来,您将看到系统正在使用的所有 IP 地址。您将看到数据包数量、接收的数据量以及其他信息。

您可以在下面看到有关此实用程序的简短帮助:

usage: iptraf-ng [options]
   or: iptraf-ng [options] -B [-i <iface> | -d <iface> | -s <iface> | -z <iface> | -l <iface> | -g]

    -h, --help            show this help message

    -i <iface>            start the IP traffic monitor (use '-i all' for all interfaces)
    -d <iface>            start the detailed statistics facility on an interface
    -s <iface>            start the TCP and UDP monitor on an interface
    -z <iface>            shows the packet size counts on an interface
    -l <iface>            start the LAN station monitor (use '-l all' for all LAN interfaces)
    -g                    start the general interface statistics

    -B                    run in background (use only with one of the above parameters
    -f                    clear all locks and counters
    -t <n>                run only for the specified <n> number of minutes
    -L <logfile>          specifies an alternate log file

我们再来看看另一个工具:iftop。此实用程序允许您实时监控网络使用情况,识别活动连接并分析网络活动。安装非常简单:

# Ubuntu/debian
apt install iftop

# Arch
pacman -S iftop

# Centos/redhat
yum install iftop

要运行,请使用以下命令:

sudo iftop -i

您还可以在日志中记录网络流量:

#!/bin/bash

LOGFILE="$HOME/iftop_traffic.log"
INTERFACE="eth0"

sudo iftop -i $INTERFACE > "$LOGFILE"

echo "Log saved in $LOGFILE"

以下是 iftop 实用程序的帮助:

Synopsis: iftop -h | [-npblNBP] [-i interface] [-f filter code]
                               [-F net/mask] [-G net6/mask6]

   -h                  display this message
   -n                  don't do hostname lookups
   -N                  don't convert port numbers to services
   -p                  run in promiscuous mode (show traffic between other
                       hosts on the same network segment)
   -b                  don't display a bar graph of traffic
   -B                  Display bandwidth in bytes
   -i interface        listen on named interface
   -f filter code      use filter code to select packets to count
                       (default: none, but only IP packets are counted)
   -F net/mask         show traffic flows in/out of IPv4 network
   -G net6/mask6       show traffic flows in/out of IPv6 network
   -l                  display and count link-local IPv6 traffic (default: off)
   -P                  show ports as well as hosts
   -m limit            sets the upper limit for the bandwidth scale
   -c config file      specifies an alternative configuration file
   -t                  use text interface without ncurses

   Sorting orders:
   -o 2s                Sort by first column (2s traffic average)
   -o 10s               Sort by second column (10s traffic average) [default]
   -o 40s               Sort by third column (40s traffic average)
   -o source            Sort by source address
   -o destination       Sort by destination address

   The following options are only available in combination with -t
   -s num              print one single text output afer num seconds, then quit
   -L num              number of lines to print

iftop, version 1.0pre4
copyright (c) 2002 Paul Warren <pdw@ex-parrot.com> and contributors

❯ 协议级安全性:SSL/TLS

为了在网络节点之间安全地传输数据,使用了一种称为 TLS(传输层安全性)/ SSL(安全套接字层)的特殊安全协议。该协议确保传输过程中的数据加密。

在我们讨论协议本身之前,值得讨论一下OSI(开放系统互连模型)网络模型。

OSI网络模型

OSI 是开放系统互连 (OSI) 模型。它是描述数据传输机制的基础和框架,代表了组织网络内各个通信环节的标准方法。

 

开放系统互连

TCP、HTTPS、UDP、FTP、ICMP 等数据传输协议都建立在第 3 层之上,特别是 IP 地址。它们负责许多事情:保证数据传输、安全性以及特定高级数据结构的传输。

OSI 模型是由国际标准化组织 (ISO) 的开发人员于 1984 年创建的。该模型是一个基本基础,每个决定研究网络工作原理的专家都应该了解它。

事实上,该模型的开发始于一小群科学家。其中的关键人物是迈克·卡内帕(Mike Canepa)和查尔斯·巴克曼(Charles Buckman)。20世纪70年代,该团队专注于为霍尼韦尔信息系统公司(Honeywell Information Systems)创建原型系统。20世纪70年代中期,科学家们意识到需要改进通信架构来支持机器。

20世纪80年代,它成为国际标准化组织(ISO)开放系统互连小组的工作成果。该模型未能完整地描述网络,在互联网早期未能获得架构师的支持。互联网后来在互联网工程任务组(IETF)的指导下,演变成了规范性较差的TCP/IP。

值得讨论一下 OSI 层。

SSL/TLS的历史

安全套接字层 (SSL) 协议由 Netscape 于 1995 年开发。其目的是确保互联网上客户端和服务器之间的数据传输安全。SSL 有多个版本,每个版本都比前一个版本更安全。

1999年,它被TLS(传输层安全性)取代。然而,SSL这个术语仍然用于指代这两种协议。

使用 SSL/TLS 协议的站点的特点是其 URL 以“HTTPS”而不是“HTTP”开头,这表明连接是安全的。

TLS/SSL 的工作原理

TLS 和 SSL 用于:

  • 用于身份验证的非对称加密。顺便说一句,这种加密方式更耗费资源,因此它与对称加密相结合。

  • 对称加密以保护隐私。

  • 消息认证码用于维护消息的完整性。

该协议在网络应用中广泛使用。事实上,它们本质上是同一件事,因为 SSL 3.0 版本引入了它的后继者 TLS。然而,TLS 这个名称被沿用至今,SSL 仍然最常用于指代 TLS。

当用户通过 HTTPS 访问网站时,浏览器会向服务器请求证书,服务器会发送包含公共加密密钥的 SSL 证书副本。然后,浏览器会验证并确认此证书的有效性。此外,还会检查证书的有效期以及是否存在由受信任的证书颁发机构颁发的根证书。如果浏览器信任该证书,它会基于公钥生成一个预主密钥,并使用双方支持的最高加密级别。服务器使用其私钥解密预主密钥,同意继续通信,并使用特定的加密方法创建共享密钥(主密钥)。现在,双方使用对称密钥,该密钥仅对本次会话有效。会话结束后,密钥将被销毁,用户下次访问网站时,握手过程将重新开始。

SSL 和 TLS 的工作原理基本相同。它们通过 TCP/IP 协议建立安全通道,并通过应用协议(HTTP、DNS、FTP、SMTP 等)在通道内传输数据。

应用协议驻留在 TLS/SSL 通道中,而 TLS/SSL 通道又驻留在 TCP/IP 中。本质上,数据是通过 TCP/IP 传输的,但关键在于数据是加密的,只有建立连接的机器才能解密。

建立连接分为几个阶段完成:

  1. 客户端与服务器建立连接并请求安全连接。这可以通过在 SSL/TLS 端口(默认 443)上建立连接来实现,也可以在建立常规连接后额外请求安全连接来实现。

  2. 建立连接时,客户端会提供可用的加密算法列表。服务器会将这些算法与自己的列表进行比较,选择最安全的算法,然后将其传达给客户端。

  3. 服务器向客户端发送其数字证书(由认证机构签名)和服务器的公钥。

  4. 客户端可以选择验证服务器的证书。它会联系受信任的授权机构的服务器并验证其有效性。建议客户端验证证书以防止 ARP 欺骗。在这种类型的网络攻击中,攻击者可以充当客户端或服务器。

  5. 生成用于安全连接的会话密钥。生成过程如下:

  • 客户端生成一个随机数字序列;

  • 客户端使用服务器的公钥进行加密,并将结果发送给服务器;

  • 服务器使用私钥解密接收到的序列。

由于采用非对称加密算法,只有服务器才能解密该序列。非对称加密基于两个密钥(私钥和公钥)。公钥用于加密消息,私钥用于解密消息。使用公钥无法解密密钥。

  1. 这将建立一个加密连接。通过该连接传输的数据将被加密和解密,直到连接断开。

根证书

TLS 连接基于“根证书”的概念。根证书是由认证机构颁发的证书,其签名可确认证书的真实性。证书本身还包含服务器名称、到期日期和其他详细信息。当然,过期的证书将无法使用。

证书签名请求(CSR)

要获取签名的服务器证书,您必须生成 CSR 文件,然后将其发送给 CA,CA 将返回签名的证书。CSR 文件是使用生成的加密密钥生成的。

客户端证书

这些证书用于双向验证,即客户端验证服务器是否有效。服务器也验证客户端未被欺骗。这种交互称为相互认证。这种类型的认证可以增强安全性,或取代标准的登录名和密码认证。

哈希

哈希算法的目的是将 SSL 证书的全部内容转换为固定长度的位字符串。哈希值使用认证机构的私钥进行加密,该私钥作为签名包含在证书中。

让我们进一步讨论一下哈希算法。用户密码、证书签名、校验和以及加密货币有什么共同点?哈希算法。

哈希算法基于哈希函数,该函数将任意长度的数据转换为给定长度的数据,几乎可以保证数据的安全性。

哈希的基本原理:

  1. 哈希值始终是固定大小,例如 32 个字符。

  2. 相同的数据会产生相同的哈希值。但是,即使更改一个字符,哈希值也会不同。

  3. 不同的值产生不同的哈希值。

  4. 不可逆的形成。

值得讨论一下什么是哈希冲突。冲突意味着两组不同的数据会产生相同的哈希值。因此,攻击者即使输入错误的密码,哈希值仍然相同,从而获得访问权限。

哈希算法有很多种(md5、sha1、sha256、sha5211、crc-32)。每种算法使用不同的底层算法,因此发生碰撞的可能性也不同。哈希算法主要分为三类:

 

在 SSL/TLS 中,哈希算法的目的是将证书的全部内容转换为固定长度的字节字符串。证书颁发机构的私钥(签名)用于加密哈希值。

哈希算法还使用MAC(消息认证码)。这对于验证传输数据的完整性至关重要。MAC使用映射函数将消息数据表示为固定长度的值,然后对消息进行哈希处理。

TLS 协议使用 HMAC(哈希消息认证码)。其独特之处在于它直接使用共享密钥进行哈希函数运算。这意味着密钥成为数据的一部分,双方必须使用相同的密钥来确认真实性。

目前,支持的哈希算法包括 SHA2,其中最常用的是 SHA-256。它相当安全,且大小不如 SHA-512。SHA-512 的字长为 64 位,而 SHA-256 为 32 位。其他值也更大——每个循环的轮数为 80(而不是 64),并且消息被拆分为 1024 位块(而不是 512 位)。SHA1、MD5 和其他算法曾经被使用,但现在被认为存在漏洞。不过,偶尔仍然可以看到 MD5 哈希值。


让我们尝试实现一个基于 TLS 的客户端-服务器应用程序作为示例(并了解什么是 Diffie-Hellman 协议)。

Diffie-Hellman算法

这是一种特殊的加密协议,允许多方通过安全通信信道获取共享密钥。生成的密钥用于使用对称加密算法加密后续通信。遗憾的是,它并不像看起来那么安全;它容易受到中间人攻击。该算法并非旨在解决此问题。然而,该算法的主要优势之一是密钥的生成无需双方直接交换。

Diffie-Hellman 算法用于允许双方创建共享密钥(也称为“传输密钥”),然后用于加密和解密消息。

使用 Python 实现简单的 SSL 套接字应用程序

让我们尝试用 Python 实现 TLS 连接。在本例中,我将创建一个简单的聊天室,其中一个客户端与服务器通信。我们不需要任何库来实现这一点,但我使用了 rich 来显示美观的消息。

因此,让我们首先导入我们需要的所有库:

from pathlib import Path
from datetime import datetime
import socket
import ssl
import sys
import time
from rich import print

如果有的话,可以使用命令安装丰富的库pip3 install rich

之后,我们将编写一些基本函数:

def print_msg(text: str, msg_type: str='info') -> None:
    """
    """
    msg_type = msg_type.lower()

    if msg_type == 'info':
        message = f'[green]INFO::{datetime.now()}[/green] -- {text}'
    elif msg_type == 'warning':
        message = f'[yellow]WARNING::{datetime.now()}[/yellow] -- {text}'
    elif msg_type == 'error':
        message = f'[red]ERROR::{datetime.now()}[/red] -- {text}'
    elif msg_type == 'debug':
        message = f'[blue]DEBUG::{datetime.now()}[/blue] -- {text}'
    else:
        message = f'[cyan]{msg_type.upper()}::{datetime.now()}[/cyan] -- {text}'

    print(message)


def ping() -> None:
    """
   
    """
    ip_lists = ["8.8.8.8", '1.1.1.1']
    port = 53

    for ip in ip_lists:
        try:
            socket.setdefaulttimeout(3)
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

            print_msg(f'Try ping {ip}...', 'debug')

            server_address = (ip, port)

            sock.connect(server_address)
        except OSError as error:
            print_msg(f'OSError occured when ping {ip}: {error}', 'warning')
        else:
            print_msg(f'Internet connection is enabled!', 'info')
            sock.close()
            return True

    print_msg(f'Connection not acquired', 'error')
    return False


def first_check():
    """
    
    """
    if ping():
        live = " CONNECTION ACQUIRED "
        print('-' * len(live))
        print(live)
        print('-' * len(live))
        connection_acquired_time = datetime.now()
        acquiring_message = "connection acquired at: " + connection_acquired_time.strftime("%Y-%m-%d %H:%M:%S")
        print(acquiring_message)

        return True
    else:
        not_live = ' CONNECTON NOT ACQUIRED '
        print('-' * len(not_live))
        print(not_live)
        print('-' * len(not_live))

        return False

这里我实现了ping地址8.8.8.8和1.1.1.1的功能(检查网络连接),显示“好消息”和检查网络连接的功能。

现在我们的任务是实现服务器:

class Server:
    """


    Каждый объект этого класса содержит:
     + имя хоста
     + порт
     + путь до клиентского сертификата
     + путь до серверного ключа
     + путь до серверного сертификата
     + SSL контекст
    """
    def __init__(self, hostname: str, port: int, client_cert: str, server_key: str, server_cert: str):
        """
        Инициализация TLS-сервера

        :param hostname: имя хоста сервера
        :param port: порт сервера
        :param client_cert: путь до сертификата клиента
        :param server_key: путь до ключа сервера
        :param server_cert: путь до сертификата сервера
        """
        self.hostname = hostname
        self.port = port

        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
            self.sock.bind((self.hostname, self.port))
            print_msg(f'Binding TLS server address: {self.hostname}:{self.port}', 'info')
        except Exception as ex:
            print_msg(f'An error occured when binding address: {ex}', 'error')
            sys.exit(1)
        else:
            print_msg(f'Server address has been binded successfully.', 'debug')

        try:
            self.client_cert = Path(client_cert)
            self.server_key = Path(server_key)
            self.server_cert = Path(server_cert)
        except TypeError as t_ex:
            print_msg(f'Type error when loading file paths: {t_ex}', 'error')
            sys.exit(1)

        self._check_paths()

        self.context = self._create_ssl_context()

    def _check_paths(self) -> bool:
        """
        Скрытый метод для проверки существования путей сертификатов и ключей.
        """
        
        if not self.server_key.exists() or not self.server_cert.exists():
            print_msg('Server keyfile or certfile has not exists. Exiting...', 'error')
        elif not self.client_cert.exists():
            print_msg('Client certfile is not exists', 'warning')
        else:
            print_msg('All files have been loaded successfully.')
            return True

        sys.exit(1)

    def _create_ssl_context(self) -> ssl.SSLContext:
        """
        Скрытый метод для создания SSL-контекста сервера.

        :return: новый объект SSLContext
        
        NOTE: данный метод SSL-контекст при помощи удобной функции:
            ssl.create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)

        NOTE: Протокол и настройки функции create_default_context могут быть изменены в любое
            время без предварительного уведомления. Значения представляют собой справедливый 
            баланс между максимальной совместимостью и безопасностью.
        """
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        
        print_msg(f'Create SSL context (purpose: CLIENT_AUTH)', 'debug')

        try:
            context.load_cert_chain(certfile=self.server_cert, keyfile=self.server_key)
            context.options |= ssl.OP_SINGLE_ECDH_USE
            context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
            print_msg(f'Setup SSL context: cert required; no TLSv1, TLSv1_1, no TLSv1_2.', 'debug')
        except ssl.SSLError as ssl_ex:
            print_msg(f'SSL Context setup exception (SSlError): {ssl_ex}', 'error')
            sys.exit(1)
        else:
            print_msg(f'SSL Context created successfully!', 'debug')

        return context

    def get_message(self, conn, addr):
        """
        Получаем и выводим сообщение от клиента
        """
        print_msg(f"Wait message from {addr}...", 'info')
        message = conn.recv(1024).decode()
        print_msg(f'Received message from {addr}: {message}', 'info')
        print(f'{addr[0]}:{addr[1]} say: {message}')

    def broadcast(self, conn, addr):
        """
        """
        print_msg(f'Start broadcast for {addr}', 'debug')
        print('Enter `exit` for stop broadcasting')

        while True:
            
            self.get_message(conn, addr)

            
            cmd = input(f'{self.hostname}:{self.port} (server) $ ').lower().strip()

            if len(cmd) == "0":
                continue
            elif cmd == 'exit':
                break
            else:
                conn.send(cmd.encode())

        print_msg(f'Stop broadcast for {addr}', 'debug')

    def wrapping(self):
        """
        
        """
        print_msg('Listen socket...', 'debug')
        self.sock.listen(1)
        
        print_msg('Wrap server-side socket...', 'debug')

        try:
            with self.context.wrap_socket(self.sock, server_side=True) as wrapped_sock:
                wrapped_sock.settimeout(3600)       
                conn, addr = wrapped_sock.accept()  
                print_msg(f'New address connected: {addr}', 'info')
                self.broadcast(conn, addr)          
        except KeyboardInterrupt:
            self.sock.close()
            print_msg('KeyboardInterrupt was received. Shutdown server...', 'warning')

和客户:

#!/usr/bin/python3
from pathlib import Path
import socket
import ssl
import sys
from src.utils import print_msg


class Client:
    def __init__(self, hostname: str, port: int, client_key: str, client_cert: str, server_cert: str):
        self.hostname = hostname
        self.port = port

        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
            self.sock.connect((self.hostname, self.port))
            print_msg(f'Connecting to TLS server address: {self.hostname}:{self.port}', 'info')
        except Exception as ex:
            print_msg(f'An error occured when connecting to address: {ex}', 'error')
            sys.exit(1)
        else:
            print_msg(f'Client has been connected successfully.', 'debug')

        try:
            self.client_cert = Path(client_cert)
            self.client_key = Path(client_key)
            self.server_cert = Path(server_cert)
        except TypeError as t_ex:
            print_msg(f'Type error when loading file paths: {t_ex}', 'error')
            sys.exit(1)

        self._check_paths()

        self.context = self._create_ssl_context()

    def _check_paths(self) -> bool:
        """
        
        """
        
        if not self.client_key.exists() or not self.client_cert.exists():
            print_msg('Client keyfile or certfile has not exists. Exiting...', 'error')
        elif not self.server_cert.exists():
            print_msg('Server certfile is not exists', 'warning')
        else:
            print_msg('All files have been loaded successfully.')
            return True

        sys.exit(1)

    def _create_ssl_context(self) -> ssl.SSLContext:
        """
        
        """
        context = ssl.SSLContext(ssl.PROTOCOL_TLS, cafile=self.server_cert)

        print_msg(f'Create SSL context (PROTOCOL_TLS)', 'debug')

        try:
            context.load_cert_chain(certfile=self.client_cert, keyfile=self.client_key)
            context.load_verify_locations(cafile=self.server_cert)
            context.options |= ssl.OP_SINGLE_ECDH_USE
            context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
        except ssl.SSLError as ssl_ex:
            print_msg(f'SSL Context setup exception (SSlError): {ssl_ex}', 'error')
            sys.exit(1)
        else:
            print_msg(f'SSL Context created successfully!', 'debug')

        return context

    def send_messages(self):
        with self.context.wrap_socket(self.sock, server_side=False, server_hostname=self.hostname) as wrapped_sock:
            self.wrapped_sock.settimeout(3600)
            print(f'{wrapped_sock.version()}; Type `exit` for disconnect')
            
            while True:
                message = input("Message > ").lower().strip()

                if len(message) == 0:
                    continue
                elif message == 'exit':
                    sys.exit(1)
                else:
                    wrapped_sock.send(message.encode())

                receives = wrapped_sock.recv(1024).decode()

                print(f'{self.hostname}@{self.port} say: {receives}')

现在你可以测试一下了……但在此之前,你需要生成一个密钥和证书来连接客户端和服务器。你可以使用openssl以下命令来执行此操作:

openssl req -new -newkey rsa:3072 -days 365 -nodes -x509 -keyout client.key -out client.crt
openssl req -new -newkey rsa:3072 -days 365 -nodes -x509 -keyout server.key -out server.crt

最后,您可以编写主函数:

def main():
    if not first_check():
        print_msg('Please, connect to network', 'note')
        sys.exit(1)

    command = input("Server or client? ").lower()

    if command == 'server':
        server = Server("127.0.0.1", 8080, "client.crt", "server.key", "server.crt")
        server.wrapping()
    else:
        client = Client("127.0.0.1", 8080, "client.key", 'client.crt', 'server.crt')
        client.send_messages()


if __name__ == '__main__':
    main()

这是我们创建的小型客户端-服务器应用程序。您可以对其进行扩展,创建一个多线程的安全消息程序。现在,让我们继续本文第一部分的总结。

❯ 结论

这只是第一部分;在下一部分中,我们将了解什么是代理和VPN。我们将学习如何仅用几个命令在服务器上创建您自己的私有网络。我想把所有内容都放在一篇文章里,但那样篇幅就太长了。因此,第二部分将在未来几天发布,或者可能立即发布。如果您喜欢这个主题,并希望看到更多关于此主题的文章,请在评论中告诉我。

希望你喜欢这篇文章。这是我规模最大、耗费精力最多的项目之一,在写作过程中我学到了很多东西。这很有挑战性,我花了好几周时间研究这个主题。

欢迎提出任何批评。我知道我很容易遗漏某些内容,或者描述不清楚(甚至不正确)。我尽力检查了所有内容,但您仍然可能会发现一些不足之处。但生活就是不断自我完善的过程。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值