Skip to content

Instantly share code, notes, and snippets.

@jyxjjj
Last active June 4, 2025 06:35
Show Gist options
  • Save jyxjjj/a5c964c677e41bb37899129a1a3276a4 to your computer and use it in GitHub Desktop.
Save jyxjjj/a5c964c677e41bb37899129a1a3276a4 to your computer and use it in GitHub Desktop.
package main
import (
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"sort"
"strings"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "用法: ./程序名 证书路径")
os.Exit(1)
}
certPath := os.Args[1]
cert, err := tls.LoadX509KeyPair(certPath, certPath)
if err != nil {
log.Fatalf("加载证书失败: %v", err)
}
h := func(w http.ResponseWriter, r *http.Request) {
io.Copy(w, r.Body)
}
server := &http.Server{
Addr: ":8444",
Handler: http.HandlerFunc(h),
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
processClientHello(chi)
return &cert, nil
},
},
}
log.Println("Starting HTTPS echo server on :8444 ...")
err = server.ListenAndServeTLS(certPath, certPath)
if err != nil {
log.Fatalf("监听失败: %v", err)
}
}
func processClientHello(chi *tls.ClientHelloInfo) {
// 协议类型
proto := "t"
// TLS 版本
version := "00"
if len(chi.SupportedVersions) > 0 {
versionMap := map[uint16]string{
tls.VersionTLS10: "10",
tls.VersionTLS11: "11",
tls.VersionTLS12: "12",
tls.VersionTLS13: "13",
}
if v, ok := versionMap[chi.SupportedVersions[len(chi.SupportedVersions)-1]]; ok {
version = v
}
}
log.Printf("TLS version: %s", version)
// SNI 类型
sniType := "i"
if chi.ServerName != "" && net.ParseIP(chi.ServerName) == nil {
sniType = "d"
}
// 密码套件
cipherHex := []string{}
cipherCount := "00"
if len(chi.CipherSuites) > 0 {
ciphers := filterGREASE(chi.CipherSuites)
cipherHex = toHexList(ciphers)
cipherCount = fmt.Sprintf("%02d", min(len(cipherHex), 99))
}
// 扩展
extCount := "00"
exts := []string{}
if len(chi.SupportedProtos) > 0 {
exts = chi.SupportedProtos
extCount = fmt.Sprintf("%02d", min(len(exts), 99))
}
// ALPN
alpn := "00"
if len(chi.SupportedProtos) > 0 {
alpn = formatALPN(chi.SupportedProtos[0])
}
// 密码套件哈希
cipherHash := hashList(cipherHex)
// 扩展哈希
extHash := hashList(exts)
// 输出 JA4 字段时加下划线分隔,且保证每个字段都输出
ja4 := fmt.Sprintf("%s%s%s%s%s%s_%s_%s", proto, version, sniType, cipherCount, extCount, alpn, cipherHash, extHash)
log.Printf("JA4: %s", ja4)
}
func filterGREASE(list []uint16) []uint16 {
var result []uint16
for _, v := range list {
if v&0x0f0f == 0x0a0a {
continue
}
result = append(result, v)
}
return result
}
func toHexList(list []uint16) []string {
var hexList []string
for _, v := range list {
hexList = append(hexList, fmt.Sprintf("%04x", v))
}
sort.Strings(hexList)
return hexList
}
func formatALPN(s string) string {
if len(s) == 0 {
return "00"
}
first, last := s[0], s[len(s)-1]
if !isAlnum(first) || !isAlnum(last) {
return fmt.Sprintf("%x%x", first, last)
}
return fmt.Sprintf("%c%c", first, last)
}
func isAlnum(b byte) bool {
return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
}
func hashList(list []string) string {
if len(list) == 0 {
return "000000000000"
}
joined := strings.Join(list, ",")
sum := sha256.Sum256([]byte(joined))
return hex.EncodeToString(sum[:])[:12]
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
<?php
{
$method = match ($request->method()) {
'GET' => 'ge',
'POST' => 'po',
'PUT' => 'pu',
'DELETE' => 'de',
'PATCH' => 'pa',
'HEAD' => 'he',
'OPTIONS' => 'op',
default => '00',
};
$version = $request->server('SERVER_PROTOCOL') === 'HTTP/2.0' ? 20 : 11;
$c = count($request->cookies->all()) > 0 ? 'c' : 'n';
$r = $request->headers->get('Referer', 'n');
$headers = $request->headers->all();
$headers = array_filter($headers, function ($key) {
$header = strtolower($key);
if (str_starts_with($header, 'x-')) {
return false;
}
if (str_starts_with($header, 'cf-')) {
return false;
}
if (str_starts_with($header, 'auth')) {
return false;
}
if ($header === 'cookie') {
return false;
}
if ($header === 'referer') {
return false;
}
return true;
}, ARRAY_FILTER_USE_KEY);
$num = count($headers);
$num = str_pad($num, 2, '0', STR_PAD_LEFT);
if ($num > 99) {
$num = 99;
}
$lang = strtolower(substr(str_replace('-', '', $request->header('Accept-Language', '0000')), 0, 4));
$a = "$method$version$c$r$num$lang";
$headersStr = '';
foreach ($headers as $key => $value) {
$headersStr .= sprintf("%s: %s\n", $key, implode('; ', (array)$value));
}
$headersHash = substr(hash('sha256', $headersStr), 0, 12);
$cookies = $request->cookies->all();
$cookiesStr1 = '';
$cookiesStr2 = '';
ksort($cookies);
foreach ($cookies as $key => $value) {
$cookiesStr1 .= "$key\n";
$cookiesStr2 .= "$key=$value\n";
}
$cookiesHash1 = substr(hash('sha256', $cookiesStr1), 0, 12);
$cookiesHash2 = substr(hash('sha256', $cookiesStr2), 0, 12);
$result = "{$a}_{$headersHash}_{$cookiesHash1}_{$cookiesHash2}";
return $this->success([
'type' => 'ja4h',
'fp' => $result,
'fph' => hash('sha256', $result),
]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment