美文网首页
深入学习 GRPC - 2. 加密非流式的字节结构

深入学习 GRPC - 2. 加密非流式的字节结构

作者: Platanuses | 来源:发表于2019-12-07 17:39 被阅读0次

本篇主要进行加密非流式 GRPC 的通信在字节层面的讨论,使用带 TLSv1.2 的 nginx 节点代理非加密的 golang 服务端节点,密钥交换使用椭圆曲线,在服务端使用自签名证书,不使用客户端证书,假设读者对 TLS 等已有基本的了解。
使用以下命令生成椭圆曲线密钥和服务端自签名证书:

openssl ecparam -genkey -name secp256r1 | openssl ec -out  hot.key -aes128
openssl req -new -x509 -days 365 -key hot.key -out hot.crt

上一篇的 proto 和 golang 服务端代码不变,golang 客户端代码变为:

package main

import (
    "context"
    "crypto/tls"
    "os"

    "grpc_hot/pb"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func main() {
    port := "30080"
    if len(os.Args) >= 2 {
        port = os.Args[1]
    }

    creds := credentials.NewTLS(&tls.Config{
        InsecureSkipVerify: true,
    })
    conn, err := grpc.Dial("127.0.0.1:"+port, grpc.WithTransportCredentials(creds))
    if nil != err {
        println(err.Error())
        return
    }
    defer conn.Close()
    cli := pb.NewHotClient(conn)
    resp, err := cli.Inc(context.Background(), &pb.IntReq{I: 6})
    if nil != err {
        println(err.Error())
        return
    }
    println("resp:", resp.GetI())
}

nginx 配置文件变为:

upstream grpc_hot {
    server 127.0.0.1:30081;
    server 127.0.0.1:30082;
}
server {
    listen 30080 ssl http2;
    ssl_protocols TLSv1.2;
    ssl_certificate hot.crt;
    ssl_certificate_key hot.key;
    ssl_password_file hot.pass;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256;
    ssl_session_cache shared:grpc_hot_sess:32m;
    ssl_session_timeout 10m;
    keepalive_timeout 60;
        
    location / {
        grpc_pass grpc://grpc_hot;
    }
}

2.1. TLS

启动上述 golang 的服务端和 nginx,调用一次客户端,在客户端连接 30080 端口。使用 wireshark 抓包,总共抓到 40 帧,基本上比第 1 篇多了一倍。
在 OSI 七层结构中,TCP、TLS、HTTP 分别位居第 4、6、7 层,这里第 5 层为空。本篇中我们当然只关心 TCP 的荷载为 TLS 层的帧。TLS 层的结构如下:

+---------------+-------------------------------+------------------------------+
| Cont Type (8) |         Version (16)          |         Length (16)          |
+---------------+-------------------------------+------------------------------+
|                                   Data (*)                                 ...
+------------------------------------------------------------------------------+

在第 4、6、8、9 帧,两端完成了 10 步的 TLS 握手:

  • Client Hello / Server Hello:两端各生成一个随机串告知对方,并由服务端决定使用套件 ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  • Certificate:服务端下发证书,包括公钥。客户端验证证书,这里选择不验证
  • Server Key Exchange / Server Hello Done:服务端随机生成一个服务端临时私钥,根据该私钥在椭圆曲线上计算出一个服务端临时公钥,下发给客户端
  • Client Key Exchange / Client Change Cipher Spec / Client Finished:同样,客户端随机生成一个客户端临时私钥,根据该私钥在椭圆曲线上计算出一个客户端临时公钥,上传给服务端。同时,客户端根据 hello 步的两个随机串、客户端临时私钥和服务端临时公钥,计算出两端分别使用的对称密钥
  • Server Change Cipher Spec / Server Finished:同样,服务端根据 hello 步的两个随机串、服务端临时私钥和客户端临时公钥,计算出两端分别使用的对称密钥。数学的魔力保证了两端分别计算出的对称密钥必然相同,感觉这很浪漫啊。

2.2 HTTP/2

接下来抓到 9 个 TLS 层的帧,它们的 Content type 均为 Application Data (23),显然,其中的 Data 字段均为已被对称密钥加密的内容,解密之后即是 HTTP 层的内容。
这里我们修改了 golang 标准库的 crypto/tls.(*halfConn).encryptcrypto/tls.(*halfConn).decrypt函数,分别在其中打印出加密前和解密后的数据。我们还是逐帧看看它们:

frame source TLS payload(decrypted) / HTTP content
10 server 00 00 12 04 00 00 00 00 00 00 03 00 00 00 80 00
04 00 01 00 00 00 05 00 FF FF FF 00 00 04 08 00
00 00 00 00 7F FF 00 00
11 client 50 52 49 20 2A 20 48 54 54 50 2F 32 2E 30 0D 0A
0D 0A 53 4D 0D 0A 0D 0A
12 client 00 00 00 04 00 00 00 00 00
14 server 00 00 00 04 01 00 00 00 00
15 client 00 00 00 04 01 00 00 00 00
16 client 00 00 3E 01 04 00 00 00 01 83 87 45 89 62 B8 D7
C6 74 B1 92 A2 7F 41 8B 08 9D 5C 0B 81 70 DC 64
00 78 1F 5F 8B 1D 75 D0 62 0D 26 3D 4C 4D 65 64
7A 8A 9A CA C8 B4 C7 60 2B 89 B5 C3 40 02 74 65
86 4D 83 35 05 B1 1F 00 00 07 00 01 00 00 00 01
00 00 00 00 02 08 06
33 server 00 00 35 01 04 00 00 00 01 88 76 8D 3D 65 AA C2
A1 3E 98 0A E1 6D 77 97 17 61 96 DC 34 FD 28 07
54 BE 52 28 20 05 F5 00 ED C6 9B B8 07 54 C5 A3
7F 5F 8B 1D 75 D0 62 0D 26 3D 4C 4D 65 64 00 00
07 00 00 00 00 00 01 00 00 00 00 02 08 07
35 server 00 00 18 01 05 00 00 00 01 00 88 9A CA C8 B2 12
34 DA 8F 01 30 00 89 9A CA C8 B5 25 42 07 31 7F
00
39 client 00 00 04 08 00 00 00 00 00 00 00 00 07 00 00 08
06 00 00 00 00 00 02 04 10 10 09 0E 07 07

拨云见日,熟悉的亚子又回来了。可以看到,服务端的 SETTINGS 帧早于客户端的试探帧,其他差不都不大。
其中,第 16、33、35 帧的首部解码出来分别如下:

:method POST
:scheme https
:path /pb.Hot/Inc
:authority 127.0.0.1:30080
content-type application/grpc
user-agent grpc-go/1.25.1
te trailers
:status 200
server openresty/1.15.8.2
date Sat, 07 Dec 2019 07:45:07 GMT
content-type application/grpc
grpc-status 0
grpc-message

请求首部的 :scheme 字段变为了 https,其它都没有什么变化。而两个 DATA 帧也还是我们熟悉的样子。

References

Elliptic Curve Cryptography: a gentle introduction
RFC-5246: The Transport Layer Security (TLS) Protocol Version 1.2

Licensed under CC BY-SA 4.0

相关文章

  • 深入学习 GRPC - 2. 加密非流式的字节结构

    本篇主要进行加密非流式 GRPC 的通信在字节层面的讨论,使用带 TLSv1.2 的 nginx 节点代理非加密的...

  • 深入学习 GRPC - 1. 非加密非流式的字节结构

    本文基于以下版本: github.com/golang/protobuf: v1.3.2google.golang...

  • Java IO体系学习笔记二(流式部分---字节流)

    上一节回顾 今天我们以字节流作为主要学习的内容: 先来看看流式部分的类结构图: 大家看一下上面的流式部分的类结构图...

  • RC4加解密

    RC4算法rc4是流式加密算法,加密和解密都是按字节逐个处理。设明文是in、密文是out、密钥流是s,对于加密,o...

  • GRPC 的字节结构观察

    本文基于以下版本: github.com/golang/protobuf: v1.3.2google.golang...

  • 第一天,内存对齐

    一对齐规则: 1.非结构体类型 32位 4字节对齐,64位 8字节对齐 2.结构体类型 以结构体中,最大内存的整数...

  • Python-RSA加密实现

    实现RSA不对称加密算法的大数据加密 1.加密模块:传入参数:需要加密的字节数据,返回数据:加密后的字节数据2.解...

  • gRPC 基本概念

    声明 本篇文章是在学习gRPC框架的过程中翻译的官方文档,非作者原创,官方文档参考gRPC,学习gRPC过程中,有...

  • 什么是gRPC

    声明 本篇文章是在学习gRPC框架的过程中翻译的官方文档,非作者原创,官方文档参考gRPC,学习gRPC过程中,有...

  • gRPC实践--加密通信

    gRPC实践--加密通信 前提阅读: gRPC实践--Server&Client[https://blog.csd...

网友评论

      本文标题:深入学习 GRPC - 2. 加密非流式的字节结构

      本文链接:https://www.haomeiwen.com/subject/mgljgctx.html