
TCP握手

TLS握手

ecdha握手怎么保证前向安全性

前向安全性(Forward Secrecy)是一种安全特性,确保即使长期密钥(如私钥)被泄露,也无法解密之前的会话密钥或通信内容。这意味着每次会话使用不同的临时密钥,确保即使长期密钥遭到泄露,过去的通信仍然是安全的。
ECDH(椭圆曲线迪菲-赫尔曼)握手通过以下机制保证前向安全性:
1. 临时密钥(Ephemeral Keys)的核心作用
- 每次会话生成新密钥对 :在ECDHE(E代表Ephemeral,临时)模式中,通信双方(如客户端和服务器)每次会话都会生成全新的临时椭圆曲线密钥对(包含临时私钥和公钥)。
- 密钥的短暂性:临时私钥仅在单次会话中使用,会话结束后立即销毁。即使攻击者未来获取了长期私钥(如证书中的静态私钥),也无法恢复已销毁的临时私钥。
2. 共享秘密的独立性
- 会话密钥的生成仅依赖临时密钥 :共享秘密通过双方的临时私钥和对方的临时公钥计算得出(公式:( S = a \cdot B = b \cdot A ),其中(a, b)为临时私钥,(A, B)为临时公钥)。长期私钥(如用于身份验证的RSA/ECDSA密钥)不参与共享秘密计算。
- 前向安全性的数学保障:即使攻击者获取长期私钥,由于无法获得历史会话中的临时私钥(a)或(b),无法回溯计算过去的共享秘密。
3. 密钥派生与会话隔离
- 主密钥的混合计算:在TLS等协议中,最终会话密钥由共享秘密与其他随机数(如客户端和服务器的随机数)通过密钥派生函数(HKDF)生成。这确保了即使某次会话的共享秘密泄露,其他会话仍保持安全。
- 会话独立性:每次会话的密钥材料完全独立,攻击者无法通过破解一次会话推断其他会话的密钥。
4. 长期私钥仅用于身份认证
- 签名与密钥交换分离 :在ECDHE-RSA或ECDHE-ECDSA等密码套件中,长期私钥仅用于对临时公钥进行数字签名(身份验证),而不参与密钥交换过程。因此,长期私钥泄露不会影响历史会话密钥的安全性。
5. 前向安全性的实际依赖条件
- 必须使用ECDHE而非静态ECDH :若使用静态ECDH(双方公钥固定),则长期私钥泄露会导致所有历史会话被破解。因此,前向安全性仅存在于临时模式(ECDHE)中。
- 临时私钥的安全销毁:实现需确保临时私钥在会话结束后从内存中彻底清除,防止运行时泄漏。
总结
ECDH通过强制使用临时密钥对(ECDHE),确保每次会话的密钥交换材料独立且短暂。即使攻击者未来获取长期私钥,因无法获得历史临时私钥,无法解密过往通信,从而实现了前向安全性。这一机制被广泛应用于TLS等现代安全协议中,成为保护长期通信隐私的核心设计。
TLS握手rtt
在标准的TLS 1.2协议中,完整的握手过程通常需要两个RTT(Round-Trip Time)。而在TLS 1.3中,通过优化可以实现1个RTT完成握手,甚至在某些情况下(如会话恢复)可以实现0 RTT。
在TLS 1.2中,握手过程分为以下几个步骤:
- 客户端发送ClientHello消息给服务器。
- 服务器收到后回复ServerHello消息,同时附带证书、密钥交换信息等。
- 客户端验证服务器证书,并生成预主密钥(Pre-Master Secret),用服务器公钥加密后发送给服务器。
- 服务器解密预主密钥并计算会话密钥,然后通知客户端开始使用加密通信。
- 客户端和服务器互相发送Finished消息以确认握手成功。
这些步骤涉及两次往返通信,因此需要两个RTT。
在TLS 1.3中,握手过程被优化为:
- 客户端发送ClientHello消息,同时包含密钥共享信息。
- 服务器回复ServerHello消息,同时包含密钥共享信息。
因此,TLS 1.3只需要1个RTT即可完成握手。此外,如果启用了会话恢复机制(如PSK,Pre-Shared Key),可以实现0 RTT。
数据库和缓存一致性
gin框架怎么路由的
- 正确答案:gin框架通过树形结构(通常为radix tree或前缀树)来实现路由匹配。当接收到一个HTTP请求时,gin会根据请求的方法和路径在路由树中进行查找,找到匹配的处理函数并执行。
- 解答思路:gin框架的核心在于其高效的路由匹配机制。它使用了radix tree(又称 Patricia Tree)来存储和管理路由规则。当定义一个新的路由时,gin会将该路由插入到radix tree中。对于每个传入的HTTP请求,gin会解析请求的URL路径,并在radix tree中查找最匹配的路由节点。一旦找到匹配项,就会调用与该路由关联的处理函数。
- 深度知识讲解:
- radix tree原理:radix tree是一种多路搜索树,特别适合用于字符串匹配。在gin中,URL路径被视为字符串,radix tree能够高效地对这些路径进行查找。与普通的二叉树相比,radix tree通过合并公共前缀减少了树的高度,从而提高了查找效率。
- 路由注册过程 :当用户调用
router.GET
、router.POST
等方法时,实际上是在radix tree中插入一个新的节点。每个节点包含路径信息、HTTP方法以及对应的处理函数。 - 动态路由支持 :gin还支持带有参数的动态路由,例如
/user/:id
。在这种情况下,radix tree会将:id
视为一个通配符节点,并在匹配时捕获实际的参数值。 - 性能优化:由于radix tree的特性,gin的路由查找时间复杂度接近O(log n),这使得即使在大量路由规则的情况下,gin依然能保持高性能。
项目跨域
跨域问题通常出现在前端项目中,当一个页面的脚本试图从与该页面不同源的服务器请求资源时就会遇到跨域问题。如果项目中有前端向后端发送请求,并且前后端部署在不同的域名或端口下,那么就存在跨域问题。
跨域问题是因为浏览器的同源策略造成的,同源策略是一种安全机制,限制了一个源的文档或脚本如何与另一个源的资源进行交互。所谓同源是指,域名、协议、端口都相同。如果不满足同源策略,浏览器会阻止JavaScript代码获取不同源的资源。
CORS(Cross-Origin Resource Sharing)是一种由W3C制定的标准,它通过在响应头中增加特定的字段 来允许浏览器访问来自其他域的资源。服务器可以通过设置Access-Control-Allow-Origin等响应头来指定哪些源可以访问其资源。
JSONP(JSON with Padding)是一种通过利用script标签没有跨域限制的特点来实现跨域请求的方法。但是JSONP只能实现GET请求,并且安全性较低。
使用代理服务器也是一种常见的解决跨域问题的方法。前端向同一个域下的代理服务器发送请求,然后由代理服务器向实际的目标服务器发送请求并获取数据,最后将数据返回给前端。
make和new的区别
在 Go 语言中,make
和 new
都是用于分配内存的,但它们的用途和适用场景有所不同。以下是它们的主要区别和用法:
new
-
用途 :
new
用于分配内存并返回指向该类型的指针。它对基本类型或结构体进行初始化,所有字段都会被零值化。 -
返回值:返回的是一个指向类型的指针。
-
示例 :
gotype Person struct { Name string Age int } p := new(Person) // p是*Person类型的指针 p.Name = "Alice" p.Age = 30
make
-
用途 :
make
主要用于初始化切片(slice)、映射(map)和频道(channel)。它不仅分配内存,还初始化数据结构的内部状态。 -
返回值:返回的是初始化后的切片、映射或频道,而不是指针。
-
示例 :
go// 创建一个长度为0,容量为10的切片 s := make([]int, 0, 10) // 创建一个映射 m := make(map[string]int) // 创建一个频道 ch := make(chan int)
关键区别
-
适用类型:
new
适用于任何类型(包括基本类型和结构体)。make
仅适用于切片、映射和频道。
-
返回值类型:
new
返回指向类型的指针(如*Type
)。make
返回初始化后的切片、映射或频道。
-
初始化:
new
只分配内存并零值化,未初始化具体结构的状态。make
负责内存分配并初始化底层数据结构。
总结
- 使用
new
时,适合需要指针的结构体和基本类型。 - 使用
make
时,适合处理切片、映射和频道,确保它们被正确初始化。
make一个切片底层原理
在Go语言中,切片(slice)是一个对数组的抽象数据结构,它并不直接存储数据,而是通过三个字段来描述一个底层数组:指向数组的指针、长度(len)和容量(cap)。切片底层实际上是基于数组实现的,当创建或操作切片时,实际上是在操作底层数组的一部分。
在Go语言中,切片是一种引用类型。它的底层结构可以表示为如下结构体:
type SliceHeader struct {
Data uintptr // 指向底层数组的指针
Len int // 切片的长度
Cap int // 切片的容量
}
- Data:这是一个指向底层数组的指针。切片的操作实际上是基于这个底层数组的。
- Len:这是切片当前的长度,即切片中有效元素的数量。
- Cap:这是切片的最大容量,即从切片起始位置到底层数组末尾之间的元素数量。
当我们创建一个切片时,例如 s := []int{1, 2, 3}
,实际上Go会在内存中分配一个底层数组,并将该数组的地址赋值给切片的 Data
字段。同时,Len
和 Cap
会被初始化为数组的长度。
如果对切片进行追加操作(如使用 append
),且切片的容量不足以容纳新元素,则会创建一个新的更大的底层数组,并将原有数据复制到新数组中。新数组的大小通常是原数组的两倍。
- 扩展知识:
- 切片与数组的区别:数组是固定大小的数据结构,而切片则是动态的,可以根据需要调整大小。
- 切片共享底层数组:多个切片可能共享同一个底层数组。因此,修改一个切片的内容可能会影响到其他切片。
- 切片的零值 :切片的零值是
nil
,表示它没有指向任何底层数组。
nil channel
在Go语言中,对一个nil channel进行读或写操作会导致当前goroutine永远阻塞。这是因为nil channel没有底层的缓冲区或者通信机制,任何试图向nil channel发送数据或者从nil channel接收数据的操作都不会成功,而是直接进入阻塞状态。
golang 协程是否存在父子关系
在Go语言中,协程(goroutine)之间不存在明确的父子关系。每个goroutine都是由Go运行时(runtime)独立调度和管理的轻量级线程,它们之间是平等的关系,没有直接的父子层次结构。
首先需要理解Go语言中的协程(goroutine)是轻量级线程,它们之间是并发运行的,并且彼此独立。当一个协程发生panic时,它只会中断该协程的执行,而不会影响其他协程的执行。如果希望捕获内部协程的panic,可以通过defer和recover机制在内部协程自身中处理panic,或者通过通道(channel)将错误信息传递给外部协程。
压缩字符串
https://leetcode.cn/problems/string-compression/description/
go
func compress(chars []byte) int {
idx := 0
begin := 0
for i,c := range chars{
if i==len(chars)-1 || c!=chars[i+1] {
chars[idx] = c
idx++
if i-begin>=1{
s := strconv.Itoa(i-begin+1)
for j:=0;j<len(s);j++{
chars[idx] = s[j]
idx++
}
}
begin = i+1
}
}
return idx
}