1
0
mirror of https://github.com/MetaCubeX/mihomo.git synced 2026-10-10 12:13:10 +08:00

chore: allow jls with utls client-fingerprint

This commit is contained in:
wwqgtxx
2026-07-13 08:57:32 +08:00
parent 2b2bdf6d22
commit dc4d4fcb8c
6 changed files with 583 additions and 25 deletions
+1
View File
@@ -450,6 +450,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
if err != nil {
return nil, fmt.Errorf("ss %s initialize jls-plugin error: %w", addr, err)
}
jlsConfig.ClientFingerprint = option.ClientFingerprint
} else if option.Plugin == kcptun.Mode {
obfsMode = kcptun.Mode
kcptunOpt := &kcpTunOption{}
+1
View File
@@ -543,6 +543,7 @@ proxies: # socks5
cipher: chacha20-ietf-poly1305
password: "password"
plugin: jls
client-fingerprint: chrome
plugin-opts:
host: "www.example.com"
username: "jls-user"
+14 -1
View File
@@ -200,6 +200,19 @@ func TestInboundShadowSocks_Restls_tls13(t *testing.T) {
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists, false)
}
func testInboundShadowSocksJLS(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) {
t.Parallel()
t.Run("Conn", func(t *testing.T) {
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists, false)
})
t.Run("UConn", func(t *testing.T) {
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
outboundOptions.ClientFingerprint = "chrome"
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists, false)
})
}
func TestInboundShadowSocks_JLS(t *testing.T) {
username := "jls-user"
password := "jls-password"
@@ -219,7 +232,7 @@ func TestInboundShadowSocks_JLS(t *testing.T) {
"password": password,
},
}
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists, false)
testInboundShadowSocksJLS(t, inboundOptions, outboundOptions)
}
func TestInboundShadowSocks_SimpleObfs_Http(t *testing.T) {
+23 -22
View File
@@ -26,15 +26,17 @@ const (
var (
DefaultALPN = []string{"h2", "http/1.1"}
ErrJLSAuthFailed = tls.ErrJLSAuthFailed
ErrFallbackCompleted = errors.New("jls: connection relayed to fallback")
)
type User = tls.JLSUser
type ClientConfig struct {
ServerName string
User User
ALPN []string
ServerName string
User User
ALPN []string
ClientFingerprint string
}
type ServerConfig struct {
@@ -44,14 +46,6 @@ type ServerConfig struct {
DialContext func(ctx context.Context, network, address string) (net.Conn, error)
}
type Conn struct {
*tls.Conn
}
func (c *Conn) Upstream() any {
return c.Conn.NetConn()
}
func NewClientConfig(serverName, username, password string, alpn []string) (*ClientConfig, error) {
if serverName == "" {
return nil, errors.New("jls: server name is required")
@@ -62,23 +56,30 @@ func NewClientConfig(serverName, username, password string, alpn []string) (*Cli
if password == "" {
return nil, errors.New("jls: password is required")
}
if alpn == nil {
alpn = DefaultALPN
}
return &ClientConfig{
config := &ClientConfig{
ServerName: serverName,
User: User{Username: username, Password: password},
ALPN: append([]string(nil), alpn...),
}, nil
}
if alpn != nil {
config.ALPN = append([]string{}, alpn...)
}
return config, nil
}
func NewClient(ctx context.Context, conn net.Conn, config *ClientConfig) (net.Conn, error) {
if config == nil {
return nil, errors.New("jls: nil client config")
}
if client, ok, err := newUTLSClient(ctx, conn, config); ok {
return client, err
}
alpn := config.ALPN
if alpn == nil {
alpn = DefaultALPN
}
tlsConn := tls.Client(conn, &tls.Config{
ServerName: config.ServerName,
NextProtos: append([]string(nil), config.ALPN...),
NextProtos: append([]string(nil), alpn...),
RootCAs: ca.GetCertPool(),
Time: ntp.Now,
JLSConfig: &tls.JLSConfig{
@@ -90,9 +91,9 @@ func NewClient(ctx context.Context, conn net.Conn, config *ClientConfig) (net.Co
return nil, err
}
if !tlsConn.ConnectionState().JLS.Authenticated {
return nil, tls.ErrJLSAuthFailed
return nil, ErrJLSAuthFailed
}
return &Conn{Conn: tlsConn}, nil
return tlsConn, nil
}
func NewServerConfig(sni, dest string, users []User, alpn []string, rateLimit uint64, dialContext func(context.Context, string, string) (net.Conn, error)) (*ServerConfig, error) {
@@ -166,9 +167,9 @@ func Server(ctx context.Context, conn net.Conn, config *ServerConfig) (net.Conn,
}
recorder.discard()
if !tlsConn.ConnectionState().JLS.Authenticated {
return nil, tls.ErrJLSAuthFailed
return nil, ErrJLSAuthFailed
}
return &Conn{Conn: tlsConn}, nil
return tlsConn, nil
}
func relayFallback(ctx context.Context, inbound net.Conn, prefix []byte, config *ServerConfig) error {
+156 -2
View File
@@ -11,10 +11,24 @@ import (
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/http"
"github.com/metacubex/http/httptest"
tls "github.com/metacubex/jls-tls"
)
func TestJLSClientServer(t *testing.T) {
for _, clientFingerprint := range []string{"", "chrome"} {
name := "Go"
if clientFingerprint != "" {
name = "uTLS"
}
t.Run(name, func(t *testing.T) {
testJLSClientServer(t, clientFingerprint)
})
}
}
func testJLSClientServer(t *testing.T, clientFingerprint string) {
user := User{Username: "test-user", Password: "test-password"}
serverConfig, err := NewServerConfig("camouflage.example", "camouflage.example:443", []User{user}, nil, 0, func(context.Context, string, string) (net.Conn, error) {
return nil, errors.New("authenticated JLS connection dialed fallback")
@@ -26,8 +40,9 @@ func TestJLSClientServer(t *testing.T) {
if err != nil {
t.Fatal(err)
}
clientConfig.ClientFingerprint = clientFingerprint
serverSide, clientSide := net.Pipe()
serverSide, clientSide := newLocalTCPPair(t)
serverDone := make(chan error, 1)
go func() {
conn, err := Server(context.Background(), serverSide, serverConfig)
@@ -36,7 +51,7 @@ func TestJLSClientServer(t *testing.T) {
return
}
defer conn.Close()
state := conn.(*Conn).ConnectionState()
state := conn.(*tls.Conn).ConnectionState()
if !state.JLS.Authenticated || state.JLS.User != user.Username {
serverDone <- errors.New("server did not authenticate JLS user")
return
@@ -66,6 +81,111 @@ func TestJLSClientServer(t *testing.T) {
}
}
func TestJLSUTLSClientRejectsInvalidFallbackCertificate(t *testing.T) {
tlsConfig := newTestTLSServerConfig(t, tls.VersionTLS13)
serverSide, clientSide := newLocalTCPPair(t)
serverDone := make(chan error, 1)
go func() {
server := tls.Server(serverSide, tlsConfig)
serverDone <- server.Handshake()
_ = serverSide.Close()
}()
config, err := NewClientConfig("camouflage.example", "user", "password", nil)
if err != nil {
t.Fatal(err)
}
config.ClientFingerprint = "chrome"
if conn, clientErr := NewClient(context.Background(), clientSide, config); clientErr == nil || errors.Is(clientErr, ErrJLSAuthFailed) {
if conn != nil {
_ = conn.Close()
}
t.Fatalf("client error = %v, want fallback certificate verification error", clientErr)
}
<-serverDone
}
func TestJLSUTLSClientFallback(t *testing.T) {
for _, protocol := range []string{"http/1.1", "h2"} {
t.Run(protocol, func(t *testing.T) {
requestProtocol := make(chan string, 1)
server := httptest.NewUnstartedServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
requestProtocol <- request.Proto
writer.WriteHeader(http.StatusNoContent)
}))
server.EnableHTTP2 = protocol == "h2"
server.StartTLS()
defer func() {
server.CloseClientConnections()
server.Close()
}()
ca.GetCertPool().AddCert(server.Certificate())
clientSide, err := net.Dial("tcp", server.Listener.Addr().String())
if err != nil {
t.Fatal(err)
}
config, err := NewClientConfig("example.com", "user", "password", []string{protocol})
if err != nil {
t.Fatal(err)
}
config.ClientFingerprint = "chrome"
if conn, clientErr := NewClient(context.Background(), clientSide, config); !errors.Is(clientErr, ErrJLSAuthFailed) {
if conn != nil {
_ = conn.Close()
}
t.Fatalf("client error = %v, want %v", clientErr, ErrJLSAuthFailed)
}
wantProtocol := "HTTP/1.1"
if protocol == "h2" {
wantProtocol = "HTTP/2.0"
}
select {
case got := <-requestProtocol:
if got != wantProtocol {
t.Fatalf("fallback protocol = %q, want %q", got, wantProtocol)
}
default:
t.Fatal("fallback HTTP request was not sent")
}
})
}
}
func TestJLSUTLSClientRejectsHelloRetryRequest(t *testing.T) {
user := User{Username: "user", Password: "password"}
serverConfig, err := NewServerConfig("camouflage.example", "camouflage.example:443", []User{user}, nil, 0, func(context.Context, string, string) (net.Conn, error) {
return nil, errors.New("HRR failure dialed fallback")
})
if err != nil {
t.Fatal(err)
}
serverConfig.TLSConfig.CurvePreferences = []tls.CurveID{tls.CurveP256}
serverSide, clientSide := newLocalTCPPair(t)
serverDone := make(chan error, 1)
go func() {
defer serverSide.Close()
_, serverErr := Server(context.Background(), serverSide, serverConfig)
serverDone <- serverErr
}()
clientConfig, err := NewClientConfig("camouflage.example", user.Username, user.Password, nil)
if err != nil {
t.Fatal(err)
}
clientConfig.ClientFingerprint = "chrome"
if conn, clientErr := NewClient(context.Background(), clientSide, clientConfig); clientErr == nil {
_ = conn.Close()
t.Fatal("uTLS client unexpectedly completed a HelloRetryRequest handshake")
}
_ = clientSide.Close()
if err = <-serverDone; err == nil {
t.Fatal("JLS server unexpectedly completed a HelloRetryRequest handshake")
}
}
func TestNewServerConfigRequiresDialContext(t *testing.T) {
_, err := NewServerConfig(
"camouflage.example",
@@ -263,3 +383,37 @@ func newTestTLSServerConfig(t *testing.T, version uint16) *tls.Config {
MaxVersion: version,
}
}
// newLocalTCPPair mirrors crypto/tls's test helper. A real TCP connection has
// enough buffering to avoid net.Pipe deadlocks when TLS handshake writes cross,
// such as a server ticket flight and the client's Finished message.
func newLocalTCPPair(t *testing.T) (server, client net.Conn) {
t.Helper()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
accepted := make(chan struct {
conn net.Conn
err error
}, 1)
go func() {
conn, acceptErr := listener.Accept()
accepted <- struct {
conn net.Conn
err error
}{conn: conn, err: acceptErr}
}()
client, err = net.Dial("tcp", listener.Addr().String())
if err != nil {
t.Fatal(err)
}
result := <-accepted
if result.err != nil {
_ = client.Close()
t.Fatal(result.err)
}
return result.conn, client
}
+388
View File
@@ -0,0 +1,388 @@
package jls
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"crypto/x509"
"errors"
"net"
"strings"
"github.com/metacubex/mihomo/component/ca"
tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/mihomo/ntp"
"github.com/metacubex/http"
"github.com/metacubex/randv2"
utls "github.com/metacubex/utls"
"golang.org/x/crypto/cryptobyte"
)
const (
jlsClientHelloType = 1
jlsServerHelloType = 2
jlsHandshakeHeaderLen = 4
jlsHelloLegacyVersionLen = 2
jlsHelloRandomLen = 32
jlsHelloRandomOffset = jlsHandshakeHeaderLen + jlsHelloLegacyVersionLen
jlsRandomSeedLen = jlsHelloRandomLen / 2
jlsExtensionPreSharedKey = 41
jlsExtensionSupportedVers = 43
jlsExtensionKeyShare = 51
)
func newUTLSClient(ctx context.Context, conn net.Conn, config *ClientConfig) (net.Conn, bool, error) {
fingerprint, ok := tlsC.GetFingerprint(config.ClientFingerprint)
if !ok {
return nil, false, nil
}
verifier := &utlsJLSVerifier{
user: config.User,
serverName: config.ServerName,
}
alpn := config.ALPN
if alpn == nil {
alpn = DefaultALPN
}
// Resumption would require recalculating PSK binders after replacing the
// ClientHello random. uTLS does not expose a hook for that operation, so this
// client deliberately disables session tickets and 0-RTT.
uConn := utls.UClient(conn, &utls.Config{
ServerName: config.ServerName,
NextProtos: append([]string(nil), alpn...),
// JLS authenticates the server in VerifyConnection. TLS still verifies
// CertificateVerify against the camouflage certificate's public key.
InsecureSkipVerify: true,
SessionTicketsDisabled: true,
Time: ntp.Now,
VerifyConnection: verifier.VerifyConnection,
}, fingerprint)
verifier.UConn = uConn
// uTLS has no hook for JLS, so first let it build the complete fingerprint.
// JLS authenticates the exact serialized ClientHello with random zeroed;
// changing the random before the fingerprint is finalized would authenticate
// bytes that can differ from the ClientHello sent on the wire.
if err := uConn.BuildHandshakeState(); err != nil {
return nil, true, err
}
if config.ALPN != nil {
overrideUTLSALPN(uConn, config.ALPN)
if err := uConn.BuildHandshakeState(); err != nil {
return nil, true, err
}
}
hello := uConn.HandshakeState.Hello
if !utlsClientHelloSupportsTLS13(hello) {
return nil, true, errors.New("jls: uTLS fingerprint does not support TLS 1.3")
}
authData, err := jlsClientHelloAuthData(hello.Raw)
if err != nil {
return nil, true, err
}
if len(hello.Random) != jlsHelloRandomLen {
return nil, true, errors.New("jls: invalid uTLS client random")
}
fakeRandom, err := jlsBuildFakeRandom(config.User, hello.Random[:jlsRandomSeedLen], authData)
if err != nil {
return nil, true, err
}
if err = uConn.SetClientRandom(fakeRandom); err != nil {
return nil, true, err
}
if err = uConn.BuildHandshakeState(); err != nil {
return nil, true, err
}
sentClientHello := append([]byte(nil), uConn.HandshakeState.Hello.Raw...)
if err = uConn.HandshakeContext(ctx); err != nil {
return nil, true, err
}
if !verifier.authenticated {
// The fallback certificate is valid, so finish with a plausible HTTP
// request before returning the JLS authentication error. This is
// synchronous because the Shadowsocks caller closes conn on return.
jlsClientHTTPFallback(ctx, uConn, config.ServerName, fingerprint)
return nil, true, ErrJLSAuthFailed
}
// A HelloRetryRequest makes uTLS generate another ClientHello. Its public API
// cannot recalculate the JLS random at that point, so reject it instead of
// accepting a connection whose second ClientHello was not authenticated.
if !bytes.Equal(sentClientHello, uConn.HandshakeState.Hello.Raw) {
_ = uConn.Close()
return nil, true, errors.New("jls: uTLS HelloRetryRequest is not supported")
}
if uConn.ConnectionState().Version != utls.VersionTLS13 {
_ = uConn.Close()
return nil, true, ErrJLSAuthFailed
}
return uConn, true, nil
}
func jlsClientHTTPFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
defer uConn.Close()
// The TLS layer is already established, so HTTP/2 must use h2c mode to avoid
// another TLS handshake. Otherwise use HTTP/1 as negotiated by the server.
protocols := new(http.Protocols)
if conn, ok := uConn.(interface{ ConnectionState() utls.ConnectionState }); ok && conn.ConnectionState().NegotiatedProtocol == "h2" {
protocols.SetUnencryptedHTTP2(true)
} else {
protocols.SetHTTP1(true)
}
client := http.Client{
Transport: &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return uConn, nil
},
Protocols: protocols,
},
}
request, err := http.NewRequestWithContext(ctx, "GET", "https://"+serverName, nil)
if err != nil {
return
}
request.Header.Set("User-Agent", fingerprint.Client)
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", randv2.IntN(32)+30)})
response, err := client.Do(request)
if err != nil {
return
}
response.Body.Close()
client.CloseIdleConnections()
}
func utlsClientHelloSupportsTLS13(hello *utls.PubClientHelloMsg) bool {
for _, version := range hello.SupportedVersions {
if version == utls.VersionTLS13 {
return true
}
}
return false
}
type utlsJLSVerifier struct {
*utls.UConn
user User
serverName string
authenticated bool
}
func (v *utlsJLSVerifier) VerifyConnection(state utls.ConnectionState) error {
serverHello := v.HandshakeState.ServerHello
if serverHello == nil {
return errors.New("jls: uTLS server hello is unavailable")
}
authData, err := jlsServerHelloAuthData(serverHello.Raw)
if err != nil {
return err
}
if !jlsCheckFakeRandom(v.user, serverHello.Random, authData) {
return verifyUTLSCertificate(state, v.serverName)
}
v.authenticated = true
return nil
}
func verifyUTLSCertificate(state utls.ConnectionState, serverName string) error {
certificates := state.PeerCertificates
if len(certificates) == 0 {
return errors.New("jls: fallback server sent no certificates")
}
opts := x509.VerifyOptions{
Roots: ca.GetCertPool(),
DNSName: serverName,
Intermediates: x509.NewCertPool(),
CurrentTime: ntp.Now(),
}
for _, certificate := range certificates[1:] {
opts.Intermediates.AddCert(certificate)
}
_, err := certificates[0].Verify(opts)
return err
}
// overrideUTLSALPN keeps ALPS only when h2 remains advertised.
func overrideUTLSALPN(conn *utls.UConn, protocols []string) {
hasH2 := false
for _, protocol := range protocols {
if protocol == "h2" {
hasH2 = true
break
}
}
hasALPN := false
extensions := conn.Extensions[:0]
for _, extension := range conn.Extensions {
switch extension := extension.(type) {
case *utls.ALPNExtension:
if len(protocols) == 0 {
continue
}
extension.AlpnProtocols = append([]string(nil), protocols...)
hasALPN = true
case *utls.ApplicationSettingsExtension, *utls.ApplicationSettingsExtensionNew:
if !hasH2 {
continue
}
}
extensions = append(extensions, extension)
}
if !hasALPN && len(protocols) > 0 {
extensions = append(extensions, &utls.ALPNExtension{
AlpnProtocols: append([]string(nil), protocols...),
})
}
conn.Extensions = extensions
}
func jlsClientHelloAuthData(raw []byte) ([]byte, error) {
msg, err := cloneJLSHello(raw, jlsClientHelloType)
if err != nil {
return nil, err
}
zeroJLSBytes(msg[jlsHelloRandomOffset : jlsHelloRandomOffset+jlsHelloRandomLen])
return msg, nil
}
func jlsServerHelloAuthData(raw []byte) ([]byte, error) {
msg, err := cloneJLSHello(raw, jlsServerHelloType)
if err != nil {
return nil, err
}
s := cryptobyte.String(msg)
var messageType, compressionMethod uint8
var legacyVersion, cipherSuite uint16
var body, sessionID, extensions cryptobyte.String
var random []byte
if !s.ReadUint8(&messageType) ||
!s.ReadUint24LengthPrefixed(&body) ||
!s.Empty() ||
!body.ReadUint16(&legacyVersion) ||
!body.ReadBytes(&random, jlsHelloRandomLen) ||
!body.ReadUint8LengthPrefixed(&sessionID) ||
!body.ReadUint16(&cipherSuite) ||
!body.ReadUint8(&compressionMethod) ||
!body.ReadUint16LengthPrefixed(&extensions) ||
!body.Empty() {
return nil, errors.New("jls: invalid uTLS server hello")
}
type extension struct {
typeID uint16
wire []byte
}
extensionBytes := extensions
parsed := make([]extension, 0, 3)
for len(extensions) > 0 {
remaining := extensions
var typeID uint16
var data cryptobyte.String
if !extensions.ReadUint16(&typeID) || !extensions.ReadUint16LengthPrefixed(&data) {
return nil, errors.New("jls: invalid uTLS server hello extensions")
}
wireLen := len(remaining) - len(extensions)
parsed = append(parsed, extension{typeID: typeID, wire: remaining[:wireLen]})
}
// rustls decodes ServerHello extensions into fields and serializes them in
// this order when it calculates the JLS random. The wire order is irrelevant
// to TLS but not to JLS, whose authentication input must match byte for byte.
canonicalTypes := [...]uint16{
jlsExtensionKeyShare,
jlsExtensionPreSharedKey,
jlsExtensionSupportedVers,
}
canonical := make(map[uint16][]byte, len(canonicalTypes))
for _, ext := range parsed {
for _, typeID := range canonicalTypes {
if ext.typeID == typeID {
canonical[typeID] = ext.wire
break
}
}
}
// Reinsert the canonical extensions at their first original position. Their
// encoded bytes and all unrelated extensions remain untouched, and the total
// length does not change.
extensionOffset := len(msg) - len(extensionBytes)
result := append([]byte(nil), msg[:extensionOffset]...)
canonicalWritten := false
for _, ext := range parsed {
if _, ok := canonical[ext.typeID]; ok {
if !canonicalWritten {
for _, typeID := range canonicalTypes {
result = append(result, canonical[typeID]...)
}
canonicalWritten = true
}
continue
}
result = append(result, ext.wire...)
}
zeroJLSBytes(result[jlsHelloRandomOffset : jlsHelloRandomOffset+jlsHelloRandomLen])
return result, nil
}
func cloneJLSHello(raw []byte, messageType byte) ([]byte, error) {
if len(raw) < jlsHelloRandomOffset+jlsHelloRandomLen ||
raw[0] != messageType ||
int(raw[1])<<16|int(raw[2])<<8|int(raw[3]) != len(raw)-jlsHandshakeHeaderLen {
return nil, errors.New("jls: invalid uTLS hello")
}
return append([]byte(nil), raw...), nil
}
func jlsBuildFakeRandom(user User, random16, authData []byte) ([]byte, error) {
if len(random16) != jlsRandomSeedLen {
return nil, errors.New("jls: random seed must be 16 bytes")
}
aead, nonce, err := newJLSAEAD(user, authData)
if err != nil {
return nil, err
}
return aead.Seal(nil, nonce[:], random16, nil), nil
}
func jlsCheckFakeRandom(user User, fakeRandom, authData []byte) bool {
if len(fakeRandom) != jlsHelloRandomLen {
return false
}
aead, nonce, err := newJLSAEAD(user, authData)
if err != nil {
return false
}
plain, err := aead.Open(nil, nonce[:], fakeRandom, nil)
return err == nil && len(plain) == jlsRandomSeedLen
}
func newJLSAEAD(user User, authData []byte) (cipher.AEAD, [sha256.Size]byte, error) {
nonce := jlsHash(user.Username, authData)
key := jlsHash(user.Password, authData)
block, err := aes.NewCipher(key[:])
if err != nil {
return nil, nonce, err
}
aead, err := cipher.NewGCMWithNonceSize(block, len(nonce))
return aead, nonce, err
}
func jlsHash(value string, authData []byte) (sum [sha256.Size]byte) {
hash := sha256.New()
_, _ = hash.Write([]byte(value))
_, _ = hash.Write(authData)
hash.Sum(sum[:0])
return sum
}
func zeroJLSBytes(data []byte) {
for i := range data {
data[i] = 0
}
}