分类: 知识储备

  • WebSocket和HTTP Upgrade机制

    HTTP协议用于浏览器和服务器之间传输数据。每次请求是独立的,不记历史,HTTP来轮询服务器更新增加性能支出。

    WebSocket协议旨在取代现有利用HTTP作为传输层的双向通信技术,从而利用现有基础设施(代理、过滤、认证)。但WS是基于HTTP层,而不是为了取代HTTP。

    在HTTP协议中,“Upgrade”机制允许客户端告诉服务器把当前的HTTP连接升级到另一种协议。(RFC 2616)。

    WebSocket协议由开启握手和基础消息框架组成,分层叠加在TCP之上, 该技术的目标是为需要与服务器双向通信的浏览器应用提供一种机制,无需开启多个HTTP连接。WS通过HTTP升级协议握手。一条向服务器的WS握手:

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Origin: http://example.com
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13

    一条向客户端的WS握手:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    Sec-WebSocket-Protocol: chat

    一旦客户端和服务器都发送了握手,如果握手成功,数据传输阶段才开始。这是一个双向通信通道,双方可以独立地随意发送数据。成功握手后,客户端和服务器以本规范中称为“消息”的概念单元进行数据传递。 在线路上,一条消息由一个或多个组成框架。 WebSocket消息不一定对应于特定的网络层框架,因为分段消息可能被中介合并或拆分。

    Sec-WebSocket-Protocol: chat
    用于防止脚本在网页浏览器中使用 WebSocket API 时未经授权交叉使用该服务器。 服务器会被告知生成WebSocket连接请求的脚本来源。 如果服务器不愿意接受来自该源的连接,可以通过发送相应的HTTP错误代码来拒绝连接。 该头字段由浏览器客户端发送;对于非浏览器客户端,如果在客户端上下文中合理,可能会发送该头字段。

    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    对于该头部字段,服务器必须将头部字段中的值与全局唯一标识符字符串形式“258EAFA5-E914-47DA- 95CA-C5AB0DC85B11”串联起来,这通常不会被不理解WebSocket协议的网络端点使用。 该连接的SHA-1哈希值(160位),以base64编码,随后在服务器握手中返回。

    HTTP/1.1 101 Switching Protocols
    除了101以外的任何状态码,都表示WebSocket握手尚未完成,HTTP的语义仍然适用。头部紧跟状态码。

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

    WebSocket客户端会检查这些字段以查找脚本页面。如果 |Sec-WebSocket-Accept|如果缺少头字段,或者 HTTP 状态码不是 101,则连接无法建立,WebSocket帧也不会发送。


    通过升级器,http服务被升级为websocket服务,除非得到websocket握手,否则连接失败:

    #Go
    
    #An upgrader
    var upgrader = websocket.Upgrader{
    	CheckOrigin: func(r *http.Request) bool {
    		return true
    	},
    }
    
    var wsClients   = make(map[*websocket.Conn]bool)
    
    func wsHandler(c *gin.Context) {
    	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
    	if err != nil {
    		return
    	}
    	defer conn.Close()
    	wsClients[conn] = true
    	for {
    		_, _, err := conn.ReadMessage()
    		if err != nil {
    			delete(wsClients, conn)
    			break
    		}
    	}
    }
    
    func main() {
            r := gin.Default()
            r.GET("/ws", wsHandler)
    }
  • CRC(cylic redundancy check)数据帧校验

    crc用于校验数据传输过程的完整性,crc码通常位于数据帧尾部,与数据帧同时传输。

    计算数据帧的crc码与接收的crc码来确认数据完整性:

    初始化一个寄存器为0xFFFF,逐步与每个数据帧进行异或操作。

    # golang
    func CalculateCRC(data []byte) uint16 {
    	crc := uint16(0xFFFF)
    	for _, b := range data {
    		crc ^= uint16(b)
    		for i := 0; i < 8; i++ {
    			if crc&0x0001 != 0 {
    				crc >>= 1
    				crc ^= 0xA001
    			} else {
    				crc >>= 1
    			}
    		}
    	}
    	return crc
    }
  • Golang Range

    Golang的range可以用来遍历array,slice,string,map或channel


    range array:

    func main() {
    	animals := [3]string{}
    	animals[0] = "tiger"
    	animals[1] = "cat"
    	animals[2] = "lion"
    	for index, animal := range animals {
    		fmt.Println(index, animal)
    	}
    }
    
    //output:
    //0 tiger
    //1 cat
    //2 lion

    range slice:

    func main() {
    	urls := []string{"ziyaun.work", "https://ziyuan.work"}
    	for index, url := range urls {
    		fmt.Println(index, url)
    	}
    }
    
    //output:
    //0 ziyaun.work
    //1 https://ziyuan.work

    range string:

    func main() {
    	url := "ziyuan.work"
    	for i, v := range url {
    		fmt.Println(i, v)
    	}
    }
    
    //output:
    //0 122
    //1 105
    //2 121
    //3 117
    //4 97
    //5 110
    //6 46
    //7 119
    //8 111
    //9 114
    //10 107

    range map:

    func main() {
    	userList := make(map[string]string)
    	userList["url"] = "ziyuan.work"
    	for key, value := range userList {
    		value = "I change myself" //改变的只是提取item的值,userlist不受影响
    		fmt.Println(userList[key])
    		fmt.Println(value)
    	}
    }
    
    // output:
    //ziyuan.work
    //I change myself

    range channel:

    func main() {
    	wg := sync.WaitGroup{}
    	wg.Add(1)
    
    	c := make(chan int)
    	go func() {
    		defer wg.Done()
    		for i := 0; i < 10; i++ {
    			c <- i
    		}
    	}()
    
    	go func() {
    		wg.Wait()
    		close(c)
    	}() // 当使用range对channel操作时,会无限遍历,最终变成死锁,因此需要关闭channel
    
    	for value := range c {
    		log.Println(value)
    	}
    }
    
    //output:
    //0
    //1
    //2
    //3
    //4
    //5
    //6
    //7
    //8
    //9
  • BLE应用层

    低功耗蓝牙(Bluetooth Low Energy,BLE)分为三个部分组成。应用(Application)+主机(Host)+控制器(Controller)。单模BLE设备的应用层包含:

    • 通用访问配置文件 Generic Access Profile(GAP)
    • 通用属性配置文件 Generic Attribute Profile(GATT)
    • 逻辑链路控制和适配协议 Logical Link Control and Adaption Protocol (L2CAP)
    • 属性协议 Attribute Protocol(ATT)
    • 安全管理 Security Manager(SM)
    • 主机控制器接口 Host Controller Interface(HCI)

    通用属性配置文件(Generic Attribute Profile,GATT)建立在属性协议(Attribute Protocol,ATT)之上,通过在其上层引入层级化的数据组织结构和抽象模型,实现了对BLE数据的统一建模与访问控制。在BLE协议体系中,GATT可视为应用层数据交互的核心框架,其定义了数据在不同设备应用之间的组织方式及交换机制。GATT定义了一组通用的数据对象,可被各类基于GATT的应用配置文件复用。其继承了ATT所采用的客户端–服务器(Client–Server)架构,但通过引入“服务(Service)”这一逻辑容器,对数据进行了更高层次的封装。每个服务由一个或多个特征(Characteristic)组成。特征用于描述具体的用户数据单元,其不仅包含实际数据值(Value),还可关联多种元数据(Metadata),例如访问权限、用户可读名称、单位、描述符等,从而为数据的发现、访问和解释提供支持。

    通用访问配置文件(Generic Access Profile,GAP)定义了设备在BLE协议栈中进行连接和控制交互的行为规范。GAP被视为BLE的顶层控制模型,描述了设备在不直接涉及具体数据内容的情况下,如何完成设备发现、连接建立、安全机制协商以及角色管理等流程,以确保不同厂商设备之间的互操作性。为实现设备间一致且可互操作的通信行为,GAP进一步规范了以下内容:

    • 设备角色及其交互关系
    • 设备可发现性与可连接性模型及其状态转换
    • 建立可靠通信所需的控制流程
    • 安全模式、安全级别及相关流程
    • 用于广播和扫描的非协议数据(如Advertising Data)的格式定义

    “蓝牙连接”分为广播(broadcasting)或者连接(connections)

    广播(broadcasting)

    在BLE中,广播机制定义了两种相互独立的角色:广播者(Broadcaster)和观察者(Observer)。广播者负责在预定义的广播信道上周期性地发送不可连接的广播数据(Advertising Data),这些数据可被任意处于监听状态的设备接收。广播过程中,广播者不与接收设备建立连接关系。观察者则持续扫描指定的广播信道,以接收当前正在广播的、不可连接的广播数据包。观察者仅被动接收信息,不参与任何连接或数据确认过程。

    广播机制在BLE体系中具有重要意义,因为它是BLE中唯一一种支持单个设备在同一时间向多个设备分发数据的通信方式。该机制充分利用了BLE的低功耗和无连接特性,适用于向周围设备周期性发布状态信息或少量数据的场景。

    标准BLE广播数据包包含最多31字节的有效负载(Payload),用于承载描述广播发送者身份、能力以及服务信息的数据,同时也可包含用户自定义的广播内容。当该负载空间不足以容纳所需信息时,BLE允许使用可选的扫描响应数据(Scan Response)。在此机制下,观察设备在接收到广播数据后,可向广播者发送扫描请求,从而获取额外的31字节扫描响应负载,使单次广播相关的数据总量达到62字节。

    由于广播方式具有实现简单、延迟低且支持多设备同时接收的特点,当需要按照固定周期推送少量数据,或向多个设备发布公共信息时,广播是一种高效且实用的选择。然而,与基于连接的通信方式相比,广播机制存在显著的安全与隐私局限性。广播数据在未加密的情况下发送,任何处于监听状态的观察设备均可接收并解析广播内容。因此,该方式通常不适用于传输敏感或私密数据。

    连接(connections)

    当需要在设备之间进行双向数据传输,或所需传输的数据量超过广播与扫描响应所能承载的容量时,应采用基于连接(Connection)的通信方式。BLE连接是一种在两个设备之间建立的持续通信关系,连接建立后,设备双方在约定的时间点周期性地交换数据。与广播相比,连接通信具有天然的隐私性,其数据仅在连接的两端设备之间传输,不会被其他设备接收,除非第三方设备通过非正常手段对无线信道进行嗅探。

    BLE连接模型中定义了两种通信角色:中心设备(Central)与外围设备(Peripheral)。中心设备通过周期性扫描广播信道,以发现可连接的广播数据包。当检测到符合条件的广播后,中心设备发起连接请求并建立连接。连接建立后,中心设备负责控制连接时序(Timing),并调度周期性的数据交换过程。外围设备则以周期性发送可连接的广播数据包为主要行为,并在接收到连接请求后接受并进入连接状态。在连接建立后,外围设备需遵循中心设备所控制的连接时序,在预定的连接事件(Connection Event)中与中心设备进行数据交换。

    需要注意的是,尽管中心设备负责连接的建立与时序控制,但在每一个连接事件中,数据的发送方向是双向对等的。中心设备与外围设备在数据吞吐量和访问优先级方面并不存在协议层面的强制限制。自蓝牙4.1版本起,BLE规范取消了对角色组合方式的限制,允许设备在同一时间承担多种角色。具体而言:

    • 单个设备可以同时作为中心设备和外围设备
    • 一个中心设备可以同时与多个外围设备建立连接
    • 一个外围设备也可以同时与多个中心设备建立连接

    相较于早期规范中对外围设备只能连接单一中心设备的限制,上述改动显著增强了BLE网络拓扑的灵活性。连接通信的最大优势在于其支持通过更高层协议对数据进行精细化组织和访问控制。BLE通过在连接之上引入通用属性配置文件(Generic Attribute Profile,GATT),将数据组织为服务(Service)和特征(Characteristic)等逻辑单元。服务可包含多个特征,每个特征均具备独立的访问权限及描述性元数据,从而构建出结构清晰、可扩展的数据模型。

    此外,基于连接的通信方式还具备更高的数据吞吐能力、支持安全加密链路的建立,并允许在连接建立过程中协商连接参数,以优化数据传输效率与功耗表现。在功耗方面,连接通信在某些场景下甚至可能优于广播方式。由于通信双方可预先确定连接事件的时间点,无线模块可在非通信时段进入休眠状态,从而降低整体能耗。相比之下,广播方式需要持续在固定频率下发送完整负载,且不区分是否存在接收设备,这在数据量较大或通信频率较高的情况下可能导致额外的能耗开销。


    通用访问配置文件(GAP)

    GAP是BLE设备实现互操作性的基础框架。GAP为所有BLE实现定义了一套统一的行为规范,要求设备能够以标准化的方式完成设备发现、广播数据、建立连接、协商安全机制以及执行其他基础控制操作。对GAP的深入理解至关重要,因为在许多BLE协议栈实现中,GAP通常被作为向上层应用暴露的最底层功能接口之一,为应用开发者提供设备控制和连接管理能力。

    BLE核心规范中,GAP主要从以下几个方面定义了设备之间的交互行为:

    • 角色(Roles)每个BLE设备可以同时承担一个或多个角色。不同角色对设备行为施加特定的约束,并规定了设备在交互过程中的强制行为要求。某些角色组合允许设备之间建立通信关系,而GAP则精确定义了这些角色之间的交互方式。尽管并非绝对如此,角色通常与特定类型的设备或应用场景相关联。在大多数实际实现中,设备角色往往在其生命周期内保持稳定,并不会频繁发生变化。
    • 模式(Modes)模式是在角色之上的进一步抽象,用于描述设备在特定时间段内为实现某一目标而进入的运行状态。模式通常对应某一具体流程的执行条件,用以允许或限制设备在该阶段的行为。模式的切换既可由用户操作触发,也可由协议栈根据运行需要自动触发,其发生频率通常高于角色的切换。
    • 流程(Procedures)流程是指设备为达成特定目标而执行的一系列操作步骤,通常涉及链路层控制或必要的数据交换。一个流程往往依赖于对端设备处于特定模式之中,因此流程与模式在实际交互中通常紧密关联、协同执行。
    • 安全性(Security)GAP在底层安全管理协议之上,通过定义安全模式和安全等级,规定了设备在不同数据交换需求下应采用的安全策略,并描述了这些安全等级的建立与强制执行流程。此外,GAP还定义了一些不依赖于特定模式或流程的通用安全特性,使设备能够根据具体应用需求灵活提升数据保护级别。
    • GAP相关数据格式。为支持设备发现和控制流程,GAP定义了用于广播和扫描的附加数据格式,用以描述设备的基本属性、能力以及服务信息,从而确保设备之间能够以统一且可互操作的方式完成初始交互。

    属性配置文件(GATT)

    通用属性配置文件(Generic Attribute Profile,GATT)定义了如何在蓝牙低功耗(BLE)连接之上交换应用数据和用户数据。与负责设备发现、连接建立及控制行为的通用访问配置文件(GAP)不同,GATT专注于实际数据的组织方式、访问规则以及数据交换流程。

    GATT同时为基于GATT的各类标准配置文件提供了统一的参考框架。这些配置文件由蓝牙SIG定义,用于覆盖特定的应用场景,并确保来自不同厂商的设备能够实现互操作性。所有标准的BLE应用配置文件均建立在GATT之上,并必须遵循其定义的数据模型和操作规则。因此,GATT构成了BLE规范中至关重要的组成部分,所有面向应用和用户的数据项都必须依据GATT的规则进行格式化、封装并传输。

    GATT通过属性协议(Attribute Protocol,ATT)作为底层传输机制,实现设备之间的数据交换。数据在逻辑上按照层级结构进行组织,其中一组相关属性被封装为服务(Service),而服务中与具体用户数据语义相关的最小功能单元被定义为特征(Characteristic)。这一数据组织方式构成了GATT的基本结构模型。

    GATT角色(Roles)

    与蓝牙规范中其他协议和配置文件一致,GATT通过定义参与通信的角色来描述设备间的交互方式。

    • 客户端(Client)GATT客户端与属性协议(ATT)中的客户端角色相对应。客户端负责向服务器发送请求并接收响应,包括读取、写入属性值以及接收由服务器发起的属性更新。初始状态下,GATT客户端并不了解服务器端所包含的属性结构,因此必须首先执行服务发现(Service Discovery)流程,以获取服务器上可用服务及其特征的相关信息。完成服务发现后,客户端即可对已发现服务中的属性进行访问,并接收来自服务器的通知或指示。
    • 服务器(Server)GATT服务器对应于ATT中的服务器角色,负责接收来自客户端的请求并返回相应的响应。在特定配置下,服务器还可以主动向客户端发送属性更新。作为服务器角色,设备需要负责存储和组织用户数据,并以属性的形式向客户端提供访问接口。所有已投入使用的BLE设备至少必须包含一个基本的GATT服务器,以便能够响应客户端请求,即便该响应仅为错误返回。

    需要强调的是,GATT角色与GAP角色之间不存在依赖关系。GATT客户端或服务器角色可以与任意GAP角色组合使用。因此,GAP中的中心设备或外围设备既可以作为GATT客户端,也可以作为GATT服务器,甚至在同一设备中同时承担两种GATT角色。

    UUID(Universally Unique Identifier)

    通用唯一标识符(Universally Unique Identifier,UUID)是一种128位(16字节)的标识符,用于在全球范围内唯一标识对象。UUID并非蓝牙专有,其格式、生成方式和使用规则由ITU-T Rec. X.667(亦即 ISO/IEC 9834-8:2005)进行规范。

    由于完整的128位UUID在BLE链路层中会占用较大的数据负载空间,BLE规范引入了两种压缩形式的UUID:16位UUID和32位UUID。这些缩短形式仅适用于由蓝牙SIG定义和分配的标准UUID,用于标识标准服务、特征及相关类型。缩短形式的UUID可通过嵌入至蓝牙基础UUID(Bluetooth Base UUID)中恢复为完整的128位UUID,其格式如下:

    xxxxxxxx-0000-1000-8000-00805F9B34FB

    其中,xxxxxxxx表示16位或32位的SIG分配值(高位补零)。

    蓝牙SIG为所有标准服务、特征及其定义的配置文件分配了对应的UUID。当应用需求无法由现有标准UUID满足,或需要实现规范之外的自定义功能时,可生成厂商自定义(Vendor-Specific)的UUID。此类UUID必须使用完整的128位形式,并通常通过ITU所规定的UUID生成机制生成。对于不基于蓝牙基础UUID的厂商自定义UUID,不允许使用16位或32位的缩短形式,必须在整个协议交互过程中始终使用完整的128位UUID。


    启动一个广播服务

    // golang
    
    import (
        "github.com/muka/go-bluetooth/api"
        "github.com/muka/go-bluetooth/bluez/profile/adapter"
    )
    
    func main() {
        a, err := adapter.GetDefaultAdapter()
        if err != nil {
            panic(err)
        }
    
        a.SetPowered(true)
        a.SetDiscoverable(true)
        a.SetAlias("My-BLE-Device")
    
        select {}
    }
  • Nginx代理基础

    常用目录

    • /var/www/html/ 默认的站点目录,供外部访问
    • /etc/nginx/ 配置文件目录:

    nginx.conf
    Nginx的核心配置文件,用于设置全局参数(如worker进程数、日志路径、PID文件等)、事件处理模块(events块)以及HTTP服务相关配置,包括http、server和location等模块。

    conf.d/
    该目录用于存放扩展配置文件,常用于按站点或功能划分虚拟主机配置。主配置文件中通常通过include指令引入该目录下的所有.conf文件,实现配置模块化管理。

    mime.types
    该文件用于定义文件扩展名与MIME类型之间的映射关系。Nginx根据此文件确定响应头中的内容类型,例如将.jpg映射为image/jpeg。

    fastcgi_params、uwsgi_params、scgi_params
    这些参数文件定义了与FastCGI、uWSGI、SCGI等后端通信所需的环境变量,便于Nginx在与PHP、Python等应用服务器交互时传递请求信息。

    • /usr/share/nginx/ 默认站点目录,最终为页面看到的内容。

    /usr/share/nginx/html/ 存放默认站点的index.html、404.html等文件

    • /var/log/nginx 日志目录

    代理

    正向代理与反向代理的区别:正向代理,客户端通过代理服务器向远程服务器发送请求;反向代理,客户端访问远程服务器,远程服务器将请求再转发至目标可用服务器。

    负载均衡:代理多个后端服务器时,Nginx可按轮询、IP hash等策略分发请求。

    工作原理

    Nginx包括基于事件驱动、异步非阻塞的高性能Web服务器架构,擅长处理高并发请求,常用于静态资源服务、反向代理、负载均衡等场景。

    Nginx启动一个Master主进程,然后根据配置文件创建一个或多个Worker子进程。Master主进程负责读取配置、管理进程(启动、重载、关闭),只负责“调度管理“。

    Nginx采用异步非阻塞的请求处理机制,使其具备高效处理海量并发请求的能力。相比之下,Apache默认采用的工作模式(尽管也提供异步非阻塞版本,但由于与部分内置模块存在兼容性问题而较少使用)会为每个请求分配独立的工作线程。当并发量达到数千级别时,就意味着需要同时维持数千个运行线程。这种设计会给系统带来显著负担:一方面,大量线程会消耗可观的内存资源;另一方面,频繁的线程上下文切换会产生巨大的CPU开销。这些额外的系统资源消耗不仅无法提升性能,反而会成为系统瓶颈。

    在Nginx中,阻塞式系统调用是大忌。既然不能阻塞,那就必须采用非阻塞的方式——比如某个事件还没准备好,系统调用会立即返回EAGAIN,告诉你:“别急,数据还没到位,等会儿再来查!”于是,你可以先去做别的事情,过会儿再来检查。虽然这种方式避免了线程阻塞,但需要不断轮询事件状态,仍然存在不小的开销。

    于是,更高效的异步非阻塞事件驱动模型应运而生,其核心就是selectpollepoll(Linux)或kqueue(BSD)这样的系统调用。它们的共同特点是:

    • 批量监控多个事件:调用时虽然是阻塞的,但可以设置超时时间,期间只要有事件就绪,就会立即返回。
    • 高效的事件管理:以epoll为例,未就绪的事件会被注册到epoll中,待其就绪后再处理;如果读写时返回EAGAIN,就重新放回epoll等待。这样,程序只需在所有事件都未就绪时才会真正等待,其余时间都在高效处理就绪事件。

    这种模式下,虽然单线程在同一时刻只能处理一个请求,但由于采用了事件循环机制,可以在多个请求间快速切换。关键在于:

    • 切换无代价:不同于多线程的上下文切换,Nginx的切换是主动让出(因事件未就绪),本质上只是循环处理已就绪的事件。
    • 资源占用极低:无需创建线程,每个请求仅消耗少量内存,没有线程切换的开销,使得高并发时不会浪费CPU资源。

    master-workers的好处

    Nginx采用多进程架构,每个Worker进程独立运行,这种设计带来了诸多优势:

    1. 无锁高性能
      • 由于Worker进程之间不共享内存,无需加锁,避免了锁竞争带来的性能损耗,同时也降低了编程复杂度和问题排查难度。
    2. 无缝热升级
      • 支持 nginx -s reload 热部署,在不中断服务的情况下更新配置或二进制文件,确保业务持续可用。
    3. 故障隔离,服务不中断
      • Worker进程之间相互独立,即使某个进程崩溃,也不会影响其他进程,Master进程会迅速重启新的Worker,保证服务稳定运行。
      • 虽然异常的Worker会导致当前处理的请求失败,但仅影响部分请求,不会造成全局服务中断,降低了整体风险。
    4. 高效请求处理
      • 所有Worker进程公平竞争处理请求,即使某个Worker异常退出,剩余进程仍能继续处理新请求,确保服务高可用。

    这种进程隔离+Master守护的机制,使得Nginx在保持高性能的同时,具备极强的稳定性和容错能力。

  • SSH加密算法(Diffie-Hellman、RSA、AES、ECDSA)


    Diffie-Hellman

    Diffie-Hellman(D-H)是一种“密钥交换协议“,主要用于安全地生成共享密钥,适用于需要在不安全网络上建立加密通信的场景,常用于 TLS、IPsec。

    D-H算法用于建立可用于秘密通信的共享密钥,同时使用椭圆曲线通过公共网络交换数据,以生成点并使用参数获取密钥,算法本身不负责“认证 ” 或“数据传输”。

    为了算法的简单性和实际实现,只考虑 4 个变量,一个大质数 P 和 G(P 的元根)和两个私钥值 a 和 b。
    P和G都是公开可用的数字。用户(比如 Alice 和 Bob)选择私有值 a 和 b,然后生成密钥并公开交换。对方收到密钥并生成一个密钥,之后他们使用相同的密钥进行加密。

    D-H算法示例代码(Go):

    // 实现 a^b mod P
    func power(a, b, p int) int {
    	if b == 1 {
    		return a
    	} else {
    		pow := int(math.Pow(float64(a), float64(b)))
    		return pow % p
    	}
    }
    
    func main() {
    	var P int // public key P (大质数):用作模运算的基数,限制计算范围,防止数值过大
    	var G int // public key G (原根):是一个生成元,它的不同指数幂可以生成不同的数,确保密钥交换的安全性
    	P, G = 21, 9
    
    	var a int // a's private key
    	var b int // b's private key
    	a, b = 4, 5
    
    	var x int
    	var y int
    
    	x = power(G, a, P)
    	y = power(G, b, P)
    	fmt.Println("x,y", x, y)
    
    	ka := power(y, a, P) // secret key for a
    	kb := power(x, b, P) // secret key for b
    	fmt.Println("ka,kb", ka, kb)
    }

    代码解释:

    第 1 步:A 和 B 获取公共号码 P = 23,G = 9
    第 2 步:A 选择了一个私钥 a = 4 和
    B 选择了一个私有密钥 b = 5
    第 3 步:A 和 B 计算公共值
    A: x =(9^4 mod 23) = (6561 mod 23) = 6
    B: y = (9^3 mod 23) = (729 mod 23) = 16
    第 4 步:A和B交换公共号码
    第 5 步:A收到公钥y =16和B收到公钥 x = 6
    步骤 6:A和B计算对称密钥
    A:ka = y^a mod p = 65536 mod 23 = 9
    B: kb = x^b mod p = 216 mod 23 = 9
    第 7 步:9 是共享密钥。


    RSA

    非对称加密算法。公钥和私钥是一对,如果用公钥加密,只有对应的私钥才能解密;如果用私钥加密,只有对应的公钥才能解密。

    由于资源消耗较大且运算效率较低,RSA 加密算法并不适合用于对整个消息或文件进行加密。因此,实际应用中通常将 RSA 与其他加密机制结合使用,例如用于加密对称密钥,或用于生成数字签名以确保消息的完整性与真实性。

    数字签名在身份认证和文件验证方面具有重要作用。通过防止数据在传输过程中被篡改或伪造,数字签名提高了敏感文档的安全性。其工作机制基于非对称加密:发送方使用私钥对哈希值进行签名,接收方则使用相应的公钥验证签名的合法性,从而确认消息来源及其未被篡改。

    在加密应用中,用户通常采用 混合加密策略:使用对称加密算法(如 AES)对实际数据加密,再使用 RSA 公钥对对称密钥进行加密。接收方只有在拥有 RSA 私钥的前提下,才能解密该对称密钥,并进一步访问原始消息内容。

    RSA 加密已被广泛集成到多个系统和加密库中,如 OpenSSL、cryptlib、wolfCrypt 等,亦广泛应用于 Web 浏览器、电子邮件通信、VPN 和其他网络协议中。

    在 VPN 场景中,RSA 尤其用于客户端与服务器之间的 安全密钥交换。以 OpenVPN 协议为例,RSA 可用于 TLS 握手阶段,确保密钥交换过程的机密性和双方身份的可信性,从而建立一个安全的数据通信通道。

    加密过程:

    1. 发送方获取接收方的公钥
    2. 将要发送的消息进行编码(转成数字形式)。
    3. 使用接收方的公钥对这个数字进行加密,得到密文。
    4. 将密文发送出去。

    解密过程:

    1. 接收方收到密文后,使用自己的私钥进行解密。
    2. 解密后的结果就是原始消息。

    AES

    AES-128加密过程:

    初始轮(预处理)

    • 将密钥与原始数据进行一次初步的异或混合(叫做轮密钥加)。

    主轮(9 轮): 每一轮包含以下 4 个步骤:

    • 字节代换(SubBytes):对每个字节做替换,使用一个固定的替换表(S盒),增加混淆。
    • 行移位(ShiftRows):将每一行的字节向左循环移动不同的位数,打乱位置,增加扩散。
    • 列混淆(MixColumns):对每一列的数据进行数学变换,进一步打乱数据结构。
    • 轮密钥加(AddRoundKey):将当前轮的密钥与数据块再次混合(异或运算)。

    最后一轮(第10轮)

    • 同样进行字节代换、行移位和轮密钥加,但不执行列混淆

    AES-128解密过程基本上是加密的反向操作,也需要使用相同的密钥,但各步骤顺序和处理方法刚好相反。


    RSA与AES区别

    特性RSAAES
    🔑 加密方式非对称加密(公钥 & 私钥)对称加密(同一个密钥)
    🧠 基本原理数学难题(大数分解)数据混淆与扩散(代数结构变换)
    📦 密钥类型一对密钥:公钥 + 私钥单一密钥
    ⏱️ 速度慢(适合加密小数据)快(适合加密大量数据)
    🧾 用途密钥交换、数字签名数据加密
    🔐 安全强度安全性取决于密钥长度(通常 ≥2048位)非常安全(128/192/256位)
    🛠 应用场景HTTPS 握手、SSH 登录、证书签名文件加密、VPN、数据库加密

    ECDSA

    (Elliptic curve digital signature algorithm)椭圆曲线数字签名算法

    ECDSA基于椭圆曲线密码学(Elliptic Curve Cryptography,简称ECC)。ECC是一种公钥密码学方法,其原理建立在有限域上的椭圆曲线代数结构之上。与传统的公钥加密方法,如RSA(Rivest-Shamir-Adleman),相比,ECC提供了多种优势,包括更小的密钥尺寸、更快的计算速度,以及在相同密钥长度下更高的安全性。

    椭圆曲线通过一个数学方程来定义,形式为:

    y^2 = x^3 + ax + b

    其中a和b是常数。所有满足该方程的点(x,y),以及一个被称为“无穷远”的特殊点,组成了椭圆曲线的一个群体。ECC的安全性依赖于离散对数问题,而在椭圆曲线上解决这个问题被认为是计算上不可行的,从而确保了其加密的安全性。

    在ECDSA中,每个用户生成一对公钥和私钥,用于数字签名的签署与验证。密钥生成的过程包括以下步骤:

    1. 选择一条合适的椭圆曲线,并指定该曲线上的一个点 GGG 作为基点,同时定义n为点G的阶,其中n是一个大质数。基点G和阶数n是公开的,并且在所有用户之间共享。
    2. 从1到n−1的范围内随机选择一个整数d。这个整数d被作为私钥。
    3. 计算公钥Q=dG,其中Q是椭圆曲线上的一个点。这个步骤使用了标量乘法,即重复将基点G与自身相加d次。标量乘法是椭圆曲线密码学中的核心运算。

  • SSH加密概念

    SSH = Secure Shell
    一种安全加密网络传输协议


    加密基础

    对称加密

    发送方和接收方都使用同一个密钥

    发送方:明文+密钥 -> 密文
    接收方:密文+密钥 -> 明文

    加密算法:
    AES(高级加密标准,Advanced Encryption Standard):目前最常用的加密算法(AES-128、AES-192、AES-256),SSH传输数据时会使用AES加密
    DES(数据加密标准,Data Encryption Standard):早期加密标准,密钥长度短(56位)

    非对称加密

    非对称加密使用一对密钥:公钥Public key和私钥Private Key
    公钥用于加密,私钥用于解密,公钥加密的数据只能用私钥解密

    发送方:使用接收方的公钥Pub进行加密
    接收方:使用自己的私钥Private进行解密

    加密算法:
    RSA(Rivest-Shamir-Adleman):基于大整数因子分解难题(常见密钥长度 2048、4096 位)
    ECDSA(椭圆曲线数字签名算法):基于椭圆曲线离散对数问题,密钥短但同等安全
    Ed25519:更快、更安全的椭圆曲线签名算法,SSH 默认使用

    哈希加密

    哈希是一种单向函数,用于计算固定长度的明文值,确保数据完整性。
    哈希值 = hash(明文)

    • 不可逆(无法从哈希值计算明文)
    • 抗碰撞(难以找到一种 hash(明文1)=hash(明文2))
    • 输入敏感(哪怕明文只改动1比特,哈希值也会大幅变化)

    加密算法:
    SHA-256  / SHA-3:广泛用于安全协议,如 TLS、SSH
    MD5(消息摘要算法):不安全,不用于加密传输,可用于验证传输信息完整性


    SSH认证

    固定密码📚或密钥对🔑登陆SSH服务器,弱强度的固定密码有被暴力破解的可能。

    1. 固定密码登陆方式:客户端通过ssh命令向ssh服务器发送登陆请求;服务器向客户端协商密钥用于构建加密通信通道;加密通道建立后,服务器向客户端索要登陆密码,并与服务器上存储的密码比对;密码校验通过后,服务器允许客户端访问Shell。
    服务器协商,对称密钥SHA-256
    1. 密钥对的登陆方式包括公钥和私钥概念,公钥即可以公开的密钥,私钥只有在本地存储才安全。
    密钥对方式登陆

    密钥存储

    1. 本机公钥信息存储在文件:~/.ssh/authorized_keys
      公钥通常的内容是(加密类型+公钥文+域):
    ssh-ed25519 AAAAAAAAAAC1yc2EAAAADAQABAAACAQDRxHaAqjB5LcvNGMuB/W9vL2sfbfvl8CKgkZUT9041MCtV9j== hi@ziyuan.work
    1. ~/.ssh/ 目录还会存放服务器的公钥和私钥文件,通常是.pem.pub.pem文件,文件格式为PEM(Privacy-Enhanced Mail)

    .pem私钥文件内容(私钥文):

    -----BEGIN RSA PRIVATE KEY-----
    AB3NzaC1yc2EAAAADAQABAAABgQDlZICl9XYz0yovhi4ajYlyDfHL5tNt2jmX/Js0I7c+Heqb3ExzOd+Td0aYVruMQkYsulXsQfe/wzQ145OppbeFRkZATKIOkb/DcN2YvTH+YtjO/GJPtdT5WVA6I/3a+pd2AgcXVh
    -----END OPENSSH PRIVATE KEY-----

    .pem.pub公钥文件内容(加密类型+公钥文+域):

    ssh-rsa CCxrInV8Dgij2vuufsw4iLF/05hg2L9rNHdwaQx6vSDZKgjw2VPzWxCufJeKMEZbKh9a4m7D1rMPN7Z1rS6E10/vTFD2OJgbpi8eM1TondTlxVa8BZ0roXLuYCpue/CyqOoYfm74fgmAbTKWUzg86jCVL9ngn66UQsW+AtgBX00d3XZ1oJ1lL9lvlyJwE6NXIMy3f9F5dJ0RZmqXtKLBeAR+SD+lD7sPug6O/nYpEzK5BVDFGwFYtjO/GJPtdT5 hi@ziyuan.work
    1. 密钥生成

    使用ssh-keygen命令可以生成私钥和公钥

    ssh-keygen -t rsa -b 4096 -m PEM -f mykey.pem
    
    # 运行以上命令会在文件夹中得到mykey.pemmykey.pem.pub文件,移动文件到~/.ssh目录
    
    # -t 加密算法,可以有rsa dsa ed25519 ecdsa
    # -b 密钥位数(bit),当-t 为rsa和ecdsa时有效
    # -m 密钥格式,可以有PEM OPENSSH PKCS8
    # -f 文件名,通常为xxxxx.pem
    1. 使用ssh-copy-id命令可以将公钥发送给服务器,实现密钥对登陆
    ssh-copy-id user@192.168.1.10 
    
    # user:远程主机用户
    # 192.168.1.10:远程主机IP