{"id":13590850,"url":"https://github.com/refraction-networking/utls","last_synced_at":"2025-05-15T00:00:28.323Z","repository":{"id":24822327,"uuid":"102533852","full_name":"refraction-networking/utls","owner":"refraction-networking","description":" Fork of the Go standard TLS library, providing low-level access to the ClientHello for mimicry purposes.","archived":false,"fork":false,"pushed_at":"2025-05-07T23:04:08.000Z","size":6061,"stargazers_count":1885,"open_issues_count":31,"forks_count":273,"subscribers_count":40,"default_branch":"master","last_synced_at":"2025-05-07T23:32:46.673Z","etag":null,"topics":["anticensorship","cipher-suites","clienthello","crypto","golang","handshake","low-level-tls","obfuscation","parrot","tls","tls-extension"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/refraction-networking.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-09-05T22:01:07.000Z","updated_at":"2025-05-07T23:03:19.000Z","dependencies_parsed_at":"2023-02-19T18:15:29.402Z","dependency_job_id":"43944e0f-5782-481d-8ec6-3e2e38a328b3","html_url":"https://github.com/refraction-networking/utls","commit_stats":{"total_commits":572,"total_committers":146,"mean_commits":"3.9178082191780823","dds":0.8304195804195804,"last_synced_commit":"4f713392d19ce1802ba11415eb456bf9b5ff6be8"},"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/refraction-networking%2Futls","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/refraction-networking%2Futls/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/refraction-networking%2Futls/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/refraction-networking%2Futls/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/refraction-networking","download_url":"https://codeload.github.com/refraction-networking/utls/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254249199,"owners_count":22039029,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anticensorship","cipher-suites","clienthello","crypto","golang","handshake","low-level-tls","obfuscation","parrot","tls","tls-extension"],"created_at":"2024-08-01T16:00:51.126Z","updated_at":"2025-05-15T00:00:28.231Z","avatar_url":"https://github.com/refraction-networking.png","language":"Go","funding_links":[],"categories":["Go","Credits","Fingerprinting Evasion"],"sub_categories":["Index"],"readme":"# ![uTLS](logo_small.png) uTLS\n[![Build Status](https://github.com/refraction-networking/utls/actions/workflows/go.yml/badge.svg?branch=master)](https://github.com/refraction-networking/utls/actions/workflows/go.yml) \n[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/refraction-networking/utls#UConn)\n---\nuTLS is a fork of \"crypto/tls\", which provides ClientHello fingerprinting resistance, low-level access to handshake, fake session tickets and some other features. Handshake is still performed by \"crypto/tls\", this library merely changes ClientHello part of it and provides low-level access.  \n\n**Minimum Go Version**: Go 1.21 \n\nIf you have any questions, bug reports or contributions, you are welcome to publish those on GitHub. If you want to do so in private, ~~you can contact one of developers personally via sergey.frolov@colorado.edu~~.\n\nYou can contact one of developers personally via gaukas.wang@colorado.edu.\n\nDocumentation below may not keep up with all the changes and new features at all times,\nso you are encouraged to use [godoc](https://godoc.org/github.com/refraction-networking/utls#UConn).\n\n*Note: Information provided below in this README.md could be obsolete. We welcome \nany contributions to refresh the documentations in addition to code contributions.*\n\n# Features\n## Low-level access to handshake\n* Read/write access to all bits of client hello message.  \n* Read access to fields of ClientHandshakeState, which, among other things, includes ServerHello and MasterSecret.\n* Read keystream. Can be used, for example, to \"write\" something in ciphertext.\n\n## ClientHello fingerprinting resistance\nGolang's ClientHello has a very unique fingerprint, which especially sticks out on mobile clients,\nwhere Golang is not too popular yet.\nSome members of anti-censorship community are concerned that their tools could be trivially blocked based on\nClientHello with relatively small collateral damage. There are multiple solutions to this issue.\n\n**It is highly recommended to use multiple fingeprints, including randomized ones to avoid relying on a single fingerprint.**\n[utls.Roller](#roller) does this automatically.\n\n### Randomized Fingerprint\nRandomized Fingerprints are supposedly good at defeating blacklists, since\nthose fingerprints have random ciphersuites and extensions in random order.\nNote that all used ciphersuites and extensions are fully supported by uTLS,\nwhich provides a solid moving target without any compatibility or parrot-is-dead attack risks.  \n\nBut note that there's a small chance that generated fingerprint won't work,\nso you may want to keep generating until a working one is found,\nand then keep reusing the working fingerprint to avoid suspicious behavior of constantly changing fingerprints.\n[utls.Roller](#roller) reuses working fingerprint automatically.\n\n#### Generating randomized fingerprints\n\nTo generate a randomized fingerprint, simply do:\n```Golang\nuTlsConn := tls.UClient(tcpConn, \u0026config, tls.HelloRandomized)\n```\nyou can use `helloRandomizedALPN` or `helloRandomizedNoALPN` to ensure presence or absence of\nALPN(Application-Layer Protocol Negotiation) extension.\nIt is recommended, but certainly not required to include ALPN (or use helloRandomized which may or may not include ALPN).\nIf you do use ALPN, you will want to correctly handle potential application layer protocols (likely h2 or http/1.1).\n\n#### Reusing randomized fingerprint\n```Golang\n// oldConn is an old connection that worked before, so we want to reuse it\n// newConn is a new connection we'd like to establish\nnewConn := tls.UClient(tcpConn, \u0026config, oldConn.ClientHelloID)\n```\n\n### Parroting\nThis package can be used to parrot ClientHello of popular browsers.\nThere are some caveats to this parroting:\n* We are forced to offer ciphersuites and tls extensions that are not supported by crypto/tls.\nThis is not a problem, if you fully control the server and turn unsupported things off on server side.\n* Parroting could be imperfect, and there is no parroting beyond ClientHello.\n#### Compatibility risks of available parrots\n\n| Parrot        | Ciphers* | Signature* | Unsupported extensions | TLS Fingerprint ID              |\n| ------------- | -------- | ---------- | ---------------------- | --------------------------------------------- |\n| Chrome 62     | no       | no         | ChannelID              | [0a4a74aeebd1bb66](https://tlsfingerprint.io/id/0a4a74aeebd1bb66) |\n| Chrome 70     | no       | no         | ChannelID, Encrypted Certs | [bc4c7e42f4961cd7](https://tlsfingerprint.io/id/bc4c7e42f4961cd7) |\n| Chrome 72     | no       | no         | ChannelID, Encrypted Certs | [bbf04e5f1881f506](https://tlsfingerprint.io/id/bbf04e5f1881f506) |\n| Chrome 83     | no       | no         | ChannelID, Encrypted Certs | [9c673fd64a32c8dc](https://tlsfingerprint.io/id/9c673fd64a32c8dc) |\n| Firefox 56    | very low | no         | None                   | [c884bad7f40bee56](https://tlsfingerprint.io/id/c884bad7f40bee56) |\n| Firefox 65    | very low | no         | MaxRecordSize                   | [6bfedc5d5c740d58](https://tlsfingerprint.io/id/6bfedc5d5c740d58) |\n| iOS 11.1      | low** | no         | None                   | [71a81bafd58e1301](https://tlsfingerprint.io/id/71a81bafd58e1301) |\n| iOS 12.1      | low** | no         | None                   | [ec55e5b4136c7949](https://tlsfingerprint.io/id/ec55e5b4136c7949) |\n\n\\* Denotes very rough guesstimate of likelihood that unsupported things will get echoed back by the server in the wild,\n*visibly breaking the connection*.  \n\\*\\* No risk, if `utls.EnableWeakCiphers()` is called prior to using it.  \n\n#### Parrots FAQ\n\u003e Does it really look like, say, Google Chrome with all the [GREASE](https://tools.ietf.org/html/draft-davidben-tls-grease-01) and stuff?\n\nIt LGTM, but please open up Wireshark and check. If you see something — [say something](https://github.com/refraction-networking/utls/issues).\n\n\u003e Aren't there side channels? Everybody knows that the ~~bird is a word~~[parrot is dead](https://people.cs.umass.edu/~amir/papers/parrot.pdf)\n\nThere sure are. If you found one that approaches practicality at line speed — [please tell us](https://github.com/refraction-networking/utls/issues).\n\nHowever, there is a difference between this sort of parroting and techniques like SkypeMorth.\nNamely, TLS is highly standardized protocol, therefore simply not that many subtle things in TLS protocol\ncould be different and/or suddenly change in one of mimicked implementation(potentially undermining the mimicry).\nIt is possible that we have a distinguisher right now, but amount of those potential distinguishers is limited.\n\n### Custom Handshake\nIt is possible to create custom handshake by\n1) Use `HelloCustom` as an argument for `UClient()` to get empty config\n2) Fill tls header fields: UConn.Hello.{Random, CipherSuites, CompressionMethods}, if needed, or stick to defaults.\n3) Configure and add various [TLS Extensions](u_tls_extensions.go) to UConn.Extensions: they will be marshaled in order.\n4) Set Session and SessionCache, as needed.\n\nIf you need to manually control all the bytes on the wire(certainly not recommended!),\nyou can set UConn.HandshakeStateBuilt = true, and marshal clientHello into UConn.HandshakeState.Hello.raw yourself.\nIn this case you will be responsible for modifying other parts of Config and ClientHelloMsg to reflect your setup\nand not confuse \"crypto/tls\", which will be processing response from server.\n\n### Fingerprinting Captured Client Hello\nYou can use a captured client hello to generate new ones that mimic/have the same properties as the original.\nThe generated client hellos _should_ look like they were generated from the same client software as the original fingerprinted bytes.\nIn order to do this:\n1) Create a `ClientHelloSpec` from the raw bytes of the original client hello\n2) Use `HelloCustom` as an argument for `UClient()` to get empty config\n3) Use `ApplyPreset` with the generated `ClientHelloSpec` to set the appropriate connection properties\n```\nuConn := UClient(\u0026net.TCPConn{}, nil, HelloCustom)\nfingerprinter := \u0026Fingerprinter{}\ngeneratedSpec, err := fingerprinter.FingerprintClientHello(rawCapturedClientHelloBytes)\nif err != nil {\n  panic(\"fingerprinting failed: %v\", err)\n}\nif err := uConn.ApplyPreset(generatedSpec); err != nil {\n  panic(\"applying generated spec failed: %v\", err)\n}\n```\nThe `rawCapturedClientHelloBytes` should be the full tls record, including the record type/version/length header.\n\n## Roller\n\nA simple wrapper, that allows to easily use multiple latest(auto-updated) fingerprints.\n\n```Golang\n// NewRoller creates Roller object with default range of HelloIDs to cycle\n// through until a working/unblocked one is found.\nfunc NewRoller() (*Roller, error)\n```\n\n```Golang\n// Dial attempts to connect to given address using different HelloIDs.\n// If a working HelloID is found, it is used again for subsequent Dials.\n// If tcp connection fails or all HelloIDs are tried, returns with last error.\n//\n// Usage examples:\n//\n// Dial(\"tcp4\", \"google.com:443\", \"google.com\")\n// Dial(\"tcp\", \"10.23.144.22:443\", \"mywebserver.org\")\nfunc (c *Roller) Dial(network, addr, serverName string) (*UConn, error)\n```\n\n## Fake Session Tickets\nFake session tickets is a very nifty trick that allows power users to hide parts of handshake, which may have some very fingerprintable features of handshake, and saves 1 RTT.\nCurrently, there is a simple function to set session ticket to any desired state:\n\n```Golang\n// If you want you session tickets to be reused - use same cache on following connections\nfunc (uconn *UConn) SetSessionState(session *ClientSessionState)\n```\n\nNote that session tickets (fake ones or otherwise) are not reused.  \nTo reuse tickets, create a shared cache and set it on current and further configs:\n\n```Golang\n// If you want you session tickets to be reused - use same cache on following connections\nfunc (uconn *UConn) SetSessionCache(cache ClientSessionCache)\n```\n\n## Custom TLS extensions\nIf you want to add your own fake (placeholder, without added functionality) extension for mimicry purposes, you can embed `*tls.GenericExtension` into your own struct and override `Len()` and `Read()` methods. For example, [DelegatedCredentials](https://datatracker.ietf.org/doc/draft-ietf-tls-subcerts/) extension can be implemented as follows:\n\n```Golang\nconst FakeDelegatedCredentials uint16 = 0x0022\n\ntype FakeDelegatedCredentialsExtension struct {\n\t*tls.GenericExtension\n\tSignatureAlgorithms []tls.SignatureScheme\n}\n\nfunc (e *FakeDelegatedCredentialsExtension) Len() int {\n\treturn 6 + 2*len(e.SignatureAlgorithms)\n}\n\nfunc (e *FakeDelegatedCredentialsExtension) Read(b []byte) (n int, err error) {\n\tif len(b) \u003c e.Len() {\n\t\treturn 0, io.ErrShortBuffer\n\t}\n\toffset := 0\n\tappendUint16 := func(val uint16) {\n\t\tb[offset] = byte(val \u003e\u003e 8)\n\t\tb[offset+1] = byte(val \u0026 0xff)\n\t\toffset += 2\n\t}\n\n\t// Extension type\n\tappendUint16(FakeDelegatedCredentials)\n\n\talgosLength := 2 * len(e.SignatureAlgorithms)\n\n\t// Extension data length\n\tappendUint16(uint16(algosLength) + 2)\n\n\t// Algorithms list length\n\tappendUint16(uint16(algosLength))\n\n\t// Algorithms list\n\tfor _, a := range e.SignatureAlgorithms {\n\t\tappendUint16(uint16(a))\n\t}\n\treturn e.Len(), io.EOF\n}\n```\n\nThen it can be used just like normal extension:\n\n```Golang\n\u0026tls.ClientHelloSpec{\n\t//...\n\tExtensions: []tls.TLSExtension{\n\t\t//...\n\t\t\u0026FakeDelegatedCredentialsExtension{\n\t\t\tSignatureAlgorithms: []tls.SignatureScheme{\n\t\t\t\ttls.ECDSAWithP256AndSHA256,\n\t\t\t\ttls.ECDSAWithP384AndSHA384,\n\t\t\t\ttls.ECDSAWithP521AndSHA512,\n\t\t\t\ttls.ECDSAWithSHA1,\n\t\t\t},\n\t\t},\n\t\t//...\n\t}\n\t//...\n}\n```\n\n# Client Hello IDs\nSee full list of `clientHelloID` values [here](https://godoc.org/github.com/refraction-networking/utls#ClientHelloID).  \nThere are different behaviors you can get, depending  on your `clientHelloID`:\n\n1. ```utls.HelloRandomized``` adds/reorders extensions, ciphersuites, etc. randomly.  \n`HelloRandomized` adds ALPN in a percentage of cases, you may want to use `HelloRandomizedALPN` or\n`HelloRandomizedNoALPN` to choose specific behavior explicitly, as ALPN might affect application layer.\n2. ```utls.HelloGolang```\n    HelloGolang will use default \"crypto/tls\" handshake marshaling codepath, which WILL\n    overwrite your changes to Hello(Config, Session are fine).\n    You might want to call BuildHandshakeState() before applying any changes.\n    UConn.Extensions will be completely ignored.\n3. ```utls.HelloCustom```\nwill prepare ClientHello with empty uconn.Extensions so you can fill it with TLSExtension's manually.\n4. The rest will will parrot given browser. Such parrots include, for example:\n\t* `utls.HelloChrome_Auto`- parrots recommended(usually latest) Google Chrome version\n\t* `utls.HelloChrome_58` - parrots Google Chrome 58\n\t* `utls.HelloFirefox_Auto` - parrots recommended(usually latest) Firefox version\n\t* `utls.HelloFirefox_55` - parrots Firefox 55\n\t\n# Usage\n## Examples\nFind basic examples [here](examples/examples.go).  \nHere's a more [advanced example](https://github.com/sergeyfrolov/gotapdance/blob//9a777f35a04b0c4c5dacd30bca0e9224eb737b5e/tapdance/conn_raw.go#L275-L292) showing how to generate randomized ClientHello, modify generated ciphersuites a bit, and proceed with the handshake.\n### Migrating from \"crypto/tls\"\nHere's how default \"crypto/tls\" is typically used:\n```Golang\n    dialConn, err := net.Dial(\"tcp\", \"172.217.11.46:443\")\n    if err != nil {\n        fmt.Printf(\"net.Dial() failed: %+v\\n\", err)\n        return\n    }\n\n    config := tls.Config{ServerName: \"www.google.com\"}\n    tlsConn := tls.Client(dialConn, \u0026config)\n    n, err = tlsConn.Write(\"Hello, World!\")\n    //...\n```\nTo start using using uTLS:\n1. Import this library (e.g. `import tls \"github.com/refraction-networking/utls\"`)\n2. Pick the [Client Hello ID](#client-hello-ids)\n3. Simply substitute `tlsConn := tls.Client(dialConn, \u0026config)`\nwith `tlsConn := tls.UClient(dialConn, \u0026config, tls.clientHelloID)`  \n\n### Customizing handshake\nSome customizations(such as setting session ticket/clientHello) have easy-to-use functions for them. The idea is to make common manipulations easy:\n```Golang\n    cRandom := []byte{100, 101, 102, 103, 104, 105, 106, 107, 108, 109,\n        110, 111, 112, 113, 114, 115, 116, 117, 118, 119,\n        120, 121, 122, 123, 124, 125, 126, 127, 128, 129,\n        130, 131}\n    tlsConn.SetClientRandom(cRandom)\n    masterSecret := make([]byte, 48)\n    copy(masterSecret, []byte(\"masterSecret is NOT sent over the wire\")) // you may use it for real security\n\n    // Create a session ticket that wasn't actually issued by the server.\n    sessionState := utls.MakeClientSessionState(sessionTicket, uint16(tls.VersionTLS12),\n        tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,\n        masterSecret,\n        nil, nil)\n    tlsConn.SetSessionState(sessionState)\n```\n\nFor other customizations there are following functions\n```\n// you can use this to build the state manually and change it\n// for example use Randomized ClientHello, and add more extensions\nfunc (uconn *UConn) BuildHandshakeState() error\n```\n```\n// Then apply the changes and marshal final bytes, which will be sent\nfunc (uconn *UConn) MarshalClientHello() error\n```\n\n## Contributors' guide\nPlease refer to [this document](./CONTRIBUTORS_GUIDE.md) if you're interested in internals\n\n## Credits\nThe initial development of uTLS was completed during an internship at [Google Jigsaw](https://jigsaw.google.com/). This is not an official Google product.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frefraction-networking%2Futls","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frefraction-networking%2Futls","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frefraction-networking%2Futls/lists"}