一方向認証では、クライアントは証明書を必要とせず、サーバー証明書が正当であることを確認することだけが必要です。ハンドシェイクのプロセスと交換されるメッセージは次のとおりです。
双方向認証では、サーバーとクライアントの両方が相手の証明書の正当性を検証する必要があります。ハンドシェイクのプロセスと交換されるメッセージは次のとおりです。
一方向認証と双方向認証では、合計データの送受信は 3 回のみであり、1 回に送信されるデータには 1 つ以上のメッセージが含まれます。
clientHelloMsg と
serverHelloMsg は暗号化されません、後続のすべてのメッセージは暗号化されます
HelloMsg と
finishedMsg を読み取った後です
メッセージを送信します
と certificateVerifyMsg
という 2 つの追加メッセージを送信します。
一方向認証であろうと双方向認証であろうと、サーバーがクライアントの基本情報を理解できるかどうかは、クライアントがサーバーに積極的に伝えることに完全に依存しており、より重要な情報はクライアント サポート TLS バージョン
, クライアントがサポートする暗号化スイート (cipherSuites)
, クライアントがサポートする署名アルゴリズム、クライアントがサポートする鍵交換プロトコル、および対応する公開鍵
。この情報は clientHelloMsg
に含まれており、JA3 フィンガープリントを生成するための鍵情報でもあり、 clientHelloMsg
と serverHelloMsg
は暗号化されません。暗号化されていないということは、変更の難しさが軽減されることを意味し、独自の JA3 フィンガープリントをカスタマイズする可能性も提供します。
「HTTPS ハンドシェイク プロセスの詳細について知りたい場合は、次の記事をお読みください。 2,000 行を超えるコードがコーディングされましたTLS を明確に説明するためだけにハンドシェイク プロセスTLS ハンドシェイク プロセスを明確に説明するために 2,000 行を超えるコードがコーディングされています (続き)”
このメソッドは、clientHelloMsg
データ パケット内の次のフィールドの 10 進バイト値を収集するために使用されます: TLS バージョン
、受け入れられた暗号数
、拡張機能のリスト
、楕円曲線
、および 楕円曲線形式
。次に、「,」を使用して各フィールドを区切り、「-」を使用して各フィールド内の値を区切って、これらの値を連結します。最後に、これらの文字列の md5 ハッシュが計算され、使いやすく共有しやすい 32 文字のフィンガープリントが生成されます。
これらのデータのソースをさらに詳しく説明するために、Lao Xu は、John Althouse
記事のパケット キャプチャ図と Go ソース コードの clientHelloMsg
構造を組み合わせました。 . フィールドは 1 つずつマッピングされます。
注意深い学生は、前の説明によれば、JA3 フィンガープリントには合計 5 つのデータ フィールドがあることに気づいたかもしれませんが、上の図では 4 つだけがマッピングされています。これは、TLS には多数の拡張フィールドがあるため、それらを 1 つずつ整理するつもりはありません。老徐はそれらを 1 つずつ列挙したわけではありませんが、単体テストを用意しており、詳細な研究に興味のある学生は、この単体テストを使用してデバッグ分析を行うことができます。
https://github.com/Isites/go-coder/blob/master/http2/tls/handsh/msg_test.go
前述の説明によると、JA3 フィンガープリントは md5 文字列です。日常の開発における md5 の使用を思い出してください。
“md5虽然不安全,但是JA3选择md5作为哈希的主要原因是为了更好的向后兼容
”
很明显,JA3指纹也有其类似用途。举个简单的例子,攻击者构建了一个可执行文件,那么该文件的JA3指纹很有可能是唯一的。因此,我们能通过JA3指纹识别出一些恶意软件。
在本小节的最后,老许给大家推荐一个网站,该网站挂出了很多恶意JA3指纹列表。
https://sslbl.abuse.ch/ja3-fingerprints/
前文提到clientHelloMsg
和serverHelloMsg
未经过加密,这为定制自己专属的JA3指纹提供了可能,而在github上面有一个库(https://github.com/refraction-networking/utls)可以在一定程度上修改clientHelloMsg
。下面我们将通过这个库构建一个自己专属的JA3指纹。
// 关键import import ( xtls "github.com/refraction-networking/utls" "crypto/tls" ) // 克隆一个Transport tr := http.DefaultTransport.(*http.Transport).Clone() // 自定义DialTLSContext函数,此函数会用于创建tcp连接和tls握手 tr.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) { dialer := net.Dialer{} // 创建tcp连接 con, err := dialer.DialContext(ctx, network, addr) if err != nil { return nil, err } // 根据地址获取host信息 host, _, err := net.SplitHostPort(addr) if err != nil { return nil, err } // 构建tlsconf xtlsConf := &xtls.Config{ ServerName: host, Renegotiation: xtls.RenegotiateNever, } // 构建tls.UConn xtlsConn := xtls.UClient(con, xtlsConf, xtls.HelloCustom) clientHelloSpec := &xtls.ClientHelloSpec{ // hellomsg中的最大最小tls版本 TLSVersMax: tls.VersionTLS12, TLSVersMin: tls.VersionTLS10, // ja3指纹需要的CipherSuites CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, }, CompressionMethods: []byte{ 0, }, // ja3指纹需要的Extensions Extensions: []xtls.TLSExtension{ &xtls.RenegotiationInfoExtension{Renegotiation: xtls.RenegotiateOnceAsClient}, &xtls.SNIExtension{ServerName: host}, &xtls.UtlsExtendedMasterSecretExtension{}, &xtls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []xtls.SignatureScheme{ xtls.ECDSAWithP256AndSHA256, xtls.PSSWithSHA256, xtls.PKCS1WithSHA256, xtls.ECDSAWithP384AndSHA384, xtls.ECDSAWithSHA1, xtls.PSSWithSHA384, xtls.PSSWithSHA384, xtls.PKCS1WithSHA384, xtls.PSSWithSHA512, xtls.PKCS1WithSHA512, xtls.PKCS1WithSHA1}}, &xtls.StatusRequestExtension{}, &xtls.NPNExtension{}, &xtls.SCTExtension{}, &xtls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, // ja3指纹需要的Elliptic Curve Formats &xtls.SupportedPointsExtension{SupportedPoints: []byte{1}}, // uncompressed // ja3指纹需要的Elliptic Curves &xtls.SupportedCurvesExtension{ Curves: []xtls.CurveID{ xtls.X25519, xtls.CurveP256, xtls.CurveP384, xtls.CurveP521, }, }, }, } // 定义hellomsg的加密套件等信息 err = xtlsConn.ApplyPreset(clientHelloSpec) if err != nil { return nil, err } // TLS握手 err = xtlsConn.Handshake() if err != nil { return nil, err } fmt.Println("当前请求使用协议:", xtlsConn.HandshakeState.ServerHello.AlpnProtocol) return xtlsConn, err }
上述代码总结起来分为三步。
创建TCP连接
构建clientHelloMsg
需要的信息
完成TLS握手
有了上述代码后,我们通过请求https://ja3er.com/json
来得到自己的JA3指纹。
c := http.Client{ Transport: tr, } resp, err := c.Get("https://ja3er.com/json") if err != nil { fmt.Println(err) return } bts, err := ioutil.ReadAll(resp.Body) resp.Body.Close() fmt.Println(string(bts), err)
最后得到的JA3指纹如下。
我们已经得到了第一个JA3指纹,这个时候对代码稍加改动以期得到专属
的JA3指纹。例如我们将2333
这个数值加入到CipherSuites
列表中,最后得到结果如下。
最终,JA3指纹又发生了变化,并且可称得上是自己专属的指纹。不用我说,看标题就应该知道问题还没有结束。从前面请求得到JA3指纹的结果图也可以看出来,当前使用的协议为http1.1
,因此老许从某度中找了一个支持http2的链接继续验证。
看过Go发起HTTP2.0请求流程析(前篇)这篇文章的同学应该知道,http2连接在建立时需要发送PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
这么一个字符串。很明显,在自定义了DialTLSContext
函数之后相关流程缺失。此时,我们该如何构建http2的专属指纹呢?
通过DialTLSContext
拨号之后只能得到一个已经完成TLS握手的连接,此时它还不支持http2的数据帧
、多路复用
等特性。所以,我们需要自己构建一个支持http2各种特性的连接。
下面,我们通过golang.org/x/net/http2
来完成自定义TLS握手流程后的http2请求。
// 手动拨号,得到一个已经完成TLS握手后的连接 con, err := tr.DialTLSContext(context.Background(), "tcp", "dss0.bdstatic.com:443") if err != nil { fmt.Println("DialTLSContext", err) return } // 构建一个http2的连接 tr2 := http2.Transport{} // 这一步很关键,不可缺失 h2Con, err := tr2.NewClientConn(con) if err != nil { fmt.Println("NewClientConn", err) return } req, _ := http.NewRequest("GET", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/newzhidao-da1cf444b0.png", nil) // 向一个支持http2的链接发起请求并读取请求状态 resp2, err := h2Con.RoundTrip(req) if err != nil { fmt.Println("RoundTrip", err) return } io.CopyN(io.Discard, resp2.Body, 2<<10) resp2.Body.Close() fmt.Println("响应code: ", resp2.StatusCode)
结果如下。
可以看到,最终在自定义JA3指纹后,http2的请求也能正常读取。至此,在支持http2的请求中构建专属的JA3指纹就完成了(生成JA3指纹的信息在clientHelloMsg
中,完成本部分仅是为了确保从发起请求到读取响应都能够正常进行)。
少し補足すると、http2 リクエストを手動で完了する NewClientConn
には大きな制限があります。たとえば、接続のライフサイクルを自分で管理する必要がある、自動的に再接続できないなどです。もちろん、これらはすべて後のことなので、これが本当に必要な場合は、開発者が go のソース コードからネット パッケージをフォークし、自分でメンテナンスする必要があるかもしれません。
以上がGo で独自の JA3 フィンガープリントを作成するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。