哈希碰撞攻击与防范机制

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引子 哈希表的原理是用数组来保存键值对,键值对存放的位置(下标)由键的哈希值决定,键的哈希值可以在参数时间内计算出来,这样哈希表插入、查找和删除的时间复杂度为O(1),但是这是理想的情况下,真实的情况是,键的哈希值存在冲突碰撞,也就是不同的键的哈希值可能相等,一个好的哈希函数应该是尽可能的减少碰撞。解决冲突碰撞的方法有分为两种:开放地址法和 链接法,这里不具体展开。哈希表一般都采用链接法来解决冲突碰撞,也就是用一个链表来将分配到同一个桶(键的哈希值一样)的键值对保存起来。 所谓的哈希碰撞攻击就是,针对哈希函数的特性,精心构造数据,使所有数据的哈希值相同,当这些数据保存到哈希表中,哈希表就会退化为单链表,哈希表的各种操作的时间复杂度提升一个数量级,因此会消耗大量CPU资源,导致系统无法快速响应请求,从而达到拒绝服务攻击(Dos)的目的。 绝大多数语言中map的默认实现都是HashMap,比如Golang、Python,Java有2种HashMap和TreeMap 2.攻击方法与防范机制 通过构造哈希值完全相同的字符串 比如 hash(str1) == hash(str2) 找到大量的哈希值相同的str1、str2、str3 … 这种攻击方法依赖的前提是hash函数本身是不变的。 那么如果hash函数是变化的,那么就会大大增加构造哈希值完全相同的字符串的难度。 由此衍生出方案1。 2.1 方案1 加salt 哈希值的计算变为 hash(salt + str) salt为特殊的字符串,一般都是固定值 由于salt是不对外暴露的,所以黑客构造哈希值完全相同的字符串的难度就增大了 2.2 方案2 可变的hash函数 如果hash函数本身就是可变的,那么黑客几乎无法构造哈希值完全相同的字符串。 我们来看Golang是怎么做的 type _type struct { ... kind uint8 alg *typeAlg ... } 每个类型都有其对应的typeAlg // typeAlg is also copied/used in reflect/type.go. // keep them in sync. type typeAlg struct { // function for hashing objects of this type // (ptr to object, seed) -> hash hash func(unsafe.Pointer, uintptr) uintptr // function for comparing objects of this type // (ptr to object A, ptr to object B) -> ==? equal func(unsafe.Pointer, unsafe.Pointer) bool } var algarray = [alg_max]typeAlg{ ... alg_STRING: {strhash, strequal}, alg_INTER: {interhash, interequal}, alg_FLOAT32: {f32hash, f32equal}, alg_FLOAT64: {f64hash, f64equal}, alg_CPLX64: {c64hash, c64equal}, ... } func strhash(a unsafe.Pointer, h uintptr) uintptr { x := (*stringStruct)(a) return memhash(x.str, h, uintptr(x.len)) } 最终我们可以看到 ...

March 7, 2019 · 1 min

玩转Prometheus(2)--计算Top Percentile

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 在笔者的上一篇文章 玩转Prometheus(1)–第1个例子,我提到可以用Prometheus,来统计服务的TP90的请求耗时。 那么TP90到底是什么意思?在Prometheus中,它又是如何计算的? 2. 概念–中位数/Top Percentile 2.1 中位数 中位数(Medians)统计学名词,是指将数据按大小顺序排列起来,形成一个数列,居于数列中间位置的那个数据。中位数用Me表示。当变量值的项数N为奇数时,处于中间位置的变量值即为中位数;当N为偶数时,中位数则为处于中间位置的2个变量值的平均数。 我们来看一个示例,假定有1组请求,耗时(单位毫秒)如下: [5, 8, 6, 50, 7, 10, 9, 11] 将它们按耗时,从小到大排列 [5, 6, 7, 8, 9, 10, 11, 50] 上面的示例N = 8, 中位数取(Array[3] + Array[4])/2 = (8 + 9)/2 为8.5ms 2.2 Top Percentile Top Percentile表示百分比分布统计。TP50表示50%的请求都小于等于某个值,TP90表示90%的请求小于等于某个值。 [ 5, 6, 7, 8, 9, 10, 11, 50] [12.5%, 25%, 37.5%, 50%, 62.5%, 75%, 87.5%, 100%] 上例中TP50=8ms,TP100=50, 50%的请求在8ms以内完成,100%的请求在50ms内完成 ...

January 4, 2019 · 3 min

如何在Golang中制造stack overflow 故障

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 大家都知道golang的栈的动态增长的,并且是放在堆上的,理论上可以相当的大,那么怎么才能制造一个stack overflow 故障呢? 其实只要人为的加入循环引用就能做到 package main import ( //"github.com/json-iterator/go" "encoding/json" ) type A struct { ElementB *B } type B struct { ElementA *A } func main(){ a := A{} b := B{} a.ElementB = &b b.ElementA = &a //var json = jsoniter.ConfigCompatibleWithStandardLibrary json.Marshal(a) } 栈溢出,程序奔溃,并输出 ╰─$ go run test_stackoverflow2.go runtime: goroutine stack exceeds 1000000000-byte limit fatal error: stack overflow runtime stack: runtime.throw(0x10e2e65, 0xe) /usr/local/Cellar/go/1.11.2/libexec/src/runtime/panic.go:608 +0x72 runtime.newstack() /usr/local/Cellar/go/1.11.2/libexec/src/runtime/stack.go:1008 +0x729 runtime.morestack() /usr/local/Cellar/go/1.11.2/libexec/src/runtime/asm_amd64.s:429 +0x8f goroutine 1 [running]: encoding/json.(*structEncoder).encode(0xc0000642d0, 0xc00007e000, 0x10c8d40, 0xc00000c030, 0x199, 0x100) 看来默认栈的最大大小也就1GB。 这个例子其实源于真实的线上故障,经过测试无论是标准的Json库,或是jsoniter都无法避免此问题。 另外如果Golang编译器,或者Marshal函数能够对递归的深度做出判断,超过一定深度就报错,在栈溢出前,抛出err,避免栈溢出,程序崩溃 后记 已经给json-iterator/go 提了issue,并且提了pull request。 有需要也可以直接使用我个改进库vearne/json-iterator-go 默认递归的最大层级限制是1000 ...

November 30, 2018 · 1 min

简单的GOLANG 协程池2 (带Cancel功能)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 有些朋友可能看我的这篇文章 简单的GOLANG 协程池 这是我做的简单协程池,功能还算完备,但是有些任务执行的时间很长,如果想在协程池运行起来以后就退出,就只能死等。这是非常不友好的,因此我又写了一个新的协程池GContextPool,利用context支持在任务运行过程中,结束任务。原来的GPool仍然可以继续使用。 下面我们来看看用法。 1. 安装 go get github.com/vearne/golib/utils 2. 使用 2.1 创建协程池 // 设定协程池的中协程的数量是30个 cxt, cancel := context.WithTimeout(context.Background(), 1 * time.Second) defer cancel() var p *utils.GContextPool = utils.NewGContextPool(ctx, 30) 2.2 定义任务处理函数 任务处理函数形如 type JobContextFunc func(ctx context.Context, key interface{}) *GPResult 执行任务 GContextPool.ApplyAsync(f JobContextFunc, slice []interface{}) f JobContextFunc 是目标函数 slice []interface{} 任务参数列表 示例 ctx_pool.go package main import ( "context" "fmt" "github.com/vearne/golib/utils" "log" "strconv" "time" ) func JudgeStrWithContext2(ctx context.Context, key interface{}) *utils.GPResult { num, _ := strconv.Atoi(key.(string)) result := &utils.GPResult{} var canceled bool = false for i := 0; i < 60; i++ { select { case <-ctx.Done(): canceled = true result.Value = false result.Err = fmt.Errorf("normal termination") default: time.Sleep(time.Millisecond * 50) } } if !canceled { if num < 450 { result.Value = true } else { result.Value = false } } return result } func main() { cxt, cancel := context.WithTimeout(context.Background(), 1 * time.Second) defer cancel() p := utils.NewGContextPool(cxt,30) slice := make([]interface{}, 0) for i := 0; i < 1000; i++ { slice = append(slice, strconv.Itoa(i)) } result := make([]*utils.GPResult, 0, 10) trueCount := 0 falseCount := 0 start := time.Now() for item := range p.ApplyAsync(JudgeStrWithContext2, slice) { result = append(result, item) if item.Err!= nil{ //log.Println("cancel", item.Err) continue } if item.Value.(bool) { trueCount++ } else { falseCount++ } } log.Printf("cancel, %v, true:%v, false:%v, cost:%v\n", len(result), trueCount, falseCount, time.Since(start)) } 请我喝瓶饮料

November 19, 2018 · 2 min

聊聊go-redis的一些高级用法

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 说到Golang的Redis库,用到最多的恐怕是 redigo 和 go-redis。其中 redigo 不支持对集群的访问。 本文想聊聊go-redis 2个高级用法 2. 开启对Cluster中Slave Node的访问 在一个负载比较高的Redis Cluster中,如果允许对slave节点进行读操作将极大的提高集群的吞吐能力。 开启对Slave 节点的访问,受以下3个参数的影响 type ClusterOptions struct { // Enables read-only commands on slave nodes. ReadOnly bool // Allows routing read-only commands to the closest master or slave node. // It automatically enables ReadOnly. RouteByLatency bool // Allows routing read-only commands to the random master or slave node. // It automatically enables ReadOnly. RouteRandomly bool ... } go-redis 选择节点的逻辑如下 ...

November 18, 2018 · 2 min

Golang标准库的读写文件,没有开启用户空间文件缓冲区?

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 大家都知道写文件时数据流转的顺序是 用户空间文件缓冲区 -> 内核空间文件缓冲区 -> 内核空间IO队列 默认的ANSI C库,对用户空间文件缓冲区有三种方式 全缓冲 行缓冲 无缓冲 难道Golang没有?笔者不敢断定,但有以下的对比试验或许能说明些问题 write.go package main import ( "os" "log" "time" ) func main() { file, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) if err != nil { log.Fatal(err) } for i := 0; i < 1000; i++ { time.Sleep(2 * time.Second) file.Write([]byte("xxxx")) } file.Close() } write2.go write2.go 使用了标准库提供的bufio, 给写文件增加了4096byte的缓冲区 package main import ( "os" "log" "time" "bufio" ) func main(){ file, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) writer := bufio.NewWriterSize(file, 4096) if err!= nil{ log.Fatal(err) } for i:=0;i<1000;i++{ time.Sleep(2 * time.Second) writer.Write([]byte("xxxx")) } writer.Flush() file.Close() } 使用strace跟踪系统调用, 在没有使用bufio的情况如下 ...

November 2, 2018 · 2 min

聊聊几种传文件的方式

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 聊聊几种服务器之间传文件的方式 1. 使用scp命令 scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file] [-l limit] [-o ssh_option] [-P port] [-S program] [[user@]host1:]file1 ... [[user@]host2:]file2 DESCRIPTION :scp copies files between hosts on a network. scp 支持传输单个文件和文件夹 scp /home/test/1.mp3 root@192.168.8.100:/home/root/music 2. 使用python 可以直接使用下面命令,启动简单的文件服务器(单线程) python2 python -m SimpleHTTPServer 8080 python3 python3 -m http.server --cgi 8080 下载 wget http://192.168.10.100:8080/tt.png 注意 请自行替换服务地址和文件名 3. 使用nc命令 接收端 nc -l 8080 > tt.png 发送端 nc 192.168.10.100 8080 < tt.png 注意 请自行替换服务地址和文件名 ...

October 31, 2018 · 1 min

gin 统计请求状态信息

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 gin-gonic/gin 是Golang API 开发中最常用到的web框架。我们可以轻松的写一个gin的中间件获取HTTP的状态码, 然后暴露给phrometheus。但是如果我想获取的body体中的错误码呢? 2. 官方的例子 package main import ( "time" "log" "github.com/gin-gonic/gin" ) func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // Set example variable c.Set("example", "12345") // before request c.Next() // after request latency := time.Since(t) log.Println("latency", latency) // access the status we are sending status := c.Writer.Status() log.Println("status_code", status) } } func main() { r := gin.New() r.Use(Logger()) r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string) // it would print: "12345" log.Println(example) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } 3. 读取body中的错误码 假定我们的服务在请求处理失败的情况下,返回如下结构体 ...

October 16, 2018 · 2 min

玩转高性能日志库zap(4)--自定义日志格式

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 uber开源的高性能日志库zap, 除了性能远超logrus之外,还有很多诱人的功能,比如支持数据采样、支持通过HTTP服务动态调整日志级别。 前些天有同事问我,zap默认打印的日志格式,我不喜欢,可否自定义一下。 这个是日志库应该有的功能,zap怎么可能不支持。 下面我们来谈谈如何自定义日志格式,在zap中,它被称为Encoder, 在日志库中logrus,被做Formatter 2. 默认格式 目前 zap只支持2种日志格式 2.1 console 1.5392310605133479e+09 info golab/test_zap.go:34 Info log {"name": "buick2008", "age": 15} 2.2 json {"level":"info","ts":1539234945.009923,"caller":"golab/test_zap.go:34","msg":"Info log","name":"buick2008","age":15} 3. 实现 为了实现自定义的Encoder 需要实现 zapcore.Encoder zapcore.PrimitiveArrayEncoder 所定义的接口 大部分接口实现可以照抄zapcore.jsonEncoder的代码实现 真正需要改动的是 EncodeEntry(ent zapcore.Entry, fields [] zapcore.Field) (*buffer.Buffer, error) 注册Encoder // 注册Encoder zap.RegisterEncoder("console2", MyEncoding) 用法示例 package main import ( "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" "time" "github.com/vearne/golab/myencoder" ) func MyEncoding(config zapcore.EncoderConfig) (zapcore.Encoder, error) { return myencoder.NewConsole2Encoder(config, true), nil } func main() { // 注册一个Encoder zap.RegisterEncoder("console2", MyEncoding) // 默认是Info级别 logcfg := zap.NewProductionConfig() // 启用自定义的Encoding logcfg.Encoding = "console2" logger, err := logcfg.Build() if err != nil { fmt.Println("err", err) } defer logger.Sync() for i := 0; i < 3; i++ { time.Sleep(1 * time.Second) logger.Info("some message", zap.String("name", "buick2008"), zap.Int("age", 15)) } } 打印出的日志效果 ...

October 11, 2018 · 1 min

聊聊Golang中的锁(2)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 在 聊聊golang中的锁(1) 中,笔者提到Golang的锁,可能引发的非常高的CPU消耗,本文我们一起来探究一下,CPU时钟都消耗再了哪里。 2. 分析 修改代码, 使用pprof package main import ( "fmt" "sync" "time" _ "net/http/pprof" "net/http" "log" ) type LockBox struct{ sync.Mutex Value int } func deal(wpg *sync.WaitGroup, bp *LockBox, count int){ for i:=0;i< count;i++{ bp.Lock() bp.Value++ bp.Unlock() } wpg.Done() } func main() { go func() { log.Println(http.ListenAndServe(":18081", nil)) }() timeStart := time.Now() workerCount := 100 taskCount := 10000000000 var wg sync.WaitGroup var box LockBox for i:=0;i<workerCount;i++{ wg.Add(1) go deal(&wg, &box, taskCount/workerCount) } wg.Wait() fmt.Println("cost", time.Since(timeStart)) } 记录采样运行数据 ...

October 10, 2018 · 1 min