{"id":13413792,"url":"https://github.com/xaionaro-go/secureio","last_synced_at":"2025-04-28T22:31:49.363Z","repository":{"id":57492356,"uuid":"163088820","full_name":"xaionaro-go/secureio","owner":"xaionaro-go","description":"An easy-to-use XChaCha20-encryption wrapper for io.ReadWriteCloser (even lossy UDP) using ECDH key exchange algorithm, ED25519 signatures and Blake3+Poly1305 checksums/message-authentication for Go (golang). Also a multiplexer.","archived":false,"fork":false,"pushed_at":"2020-06-28T16:32:59.000Z","size":470,"stargazers_count":34,"open_issues_count":1,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-25T07:14:33.951Z","etag":null,"topics":["awesome","blake3","cipher","ecdh","ed25519","encrypt","encryption","golang","hmac","multiplexer","poly1305","psk","read","reader","readwriter","stream","udp","wrapper","writer","xchacha20"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xaionaro-go.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-12-25T14:20:59.000Z","updated_at":"2024-12-20T04:58:07.000Z","dependencies_parsed_at":"2022-09-01T22:11:47.292Z","dependency_job_id":null,"html_url":"https://github.com/xaionaro-go/secureio","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xaionaro-go%2Fsecureio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xaionaro-go%2Fsecureio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xaionaro-go%2Fsecureio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xaionaro-go%2Fsecureio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xaionaro-go","download_url":"https://codeload.github.com/xaionaro-go/secureio/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251397644,"owners_count":21583046,"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":["awesome","blake3","cipher","ecdh","ed25519","encrypt","encryption","golang","hmac","multiplexer","poly1305","psk","read","reader","readwriter","stream","udp","wrapper","writer","xchacha20"],"created_at":"2024-07-30T20:01:49.436Z","updated_at":"2025-04-28T22:31:44.984Z","avatar_url":"https://github.com/xaionaro-go.png","language":"Go","funding_links":[],"categories":["Security","安全","安全领域相关库","Relational Databases"],"sub_categories":["HTTP Clients","HTTP客户端","查询语"],"readme":"[![GoDoc](https://godoc.org/github.com/xaionaro-go/secureio?status.svg)](https://pkg.go.dev/github.com/xaionaro-go/secureio?tab=doc)\n[![go report](https://goreportcard.com/badge/github.com/xaionaro-go/secureio)](https://goreportcard.com/report/github.com/xaionaro-go/secureio)\n[![Build Status](https://travis-ci.org/xaionaro-go/secureio.svg?branch=master)](https://travis-ci.org/xaionaro-go/secureio)\n[![Coverage Status](https://coveralls.io/repos/github/xaionaro-go/secureio/badge.svg?branch=master)](https://coveralls.io/github/xaionaro-go/secureio?branch=master)\n\n# Quick start\n\nPrepare keys (on both sides):\n```sh\n[ -f ~/.ssh/id_ed25519 ] \u0026\u0026 [ -f ~/.ssh/id_ed25519.pub ] || ssh-keygen -t ed25519\nscp ~/.ssh/id_ed25519.pub remote:from_remote_side/\n```\n\nEncrypted `io.ReadWriteCloser`, easy:\n\n```go\n// Generate (if not exists) and read ED25519 keys \nidentity, err := secureio.NewIdentity(`/home/user/.ssh`)\n\n// Read remote identity \nremoteIdentity, err := secureio.NewRemoteIdentityFromPublicKey(`/home/user/from_remote_side/id_ed25519.pub`)\n\n// Create a connection\nconn, err := net.Dial(\"udp\", \"10.0.0.2:1234\")\n\n// Create an encrypted connection (and exchange keys using ECDH and verify remote side by ED25519 signature).\nsession := identity.NewSession(remoteIdentity, conn, nil, nil)\nsession.Start(context.Background())\n\n// Use it!\n\n// Write to it\n_, err = session.Write(someData)\n\n// Or/and read from it\n_, err = session.Read(someData)\n```\n\n## It's also a multiplexer\n\n#### Receive\n\nSetup the receiver:\n```go\nsession.SetHandlerFuncs(secureio.MessageTypeChannel(0), func(payload []byte) {\n    fmt.Println(\"I received a payload:\", payload)\n}, func(err error) {\n    panic(err)\n})\n```\n\n#### Send\n\nSend a message synchronously:\n```go\n_, err := session.WriteMessage(secureio.MessageTypeChannel(0), payload)\n```\n\n**OR**\n\nSend a message asynchronously:\n```go\n// Schedule the sending of the payload\nsendInfo := session.WriteMessageAsync(secureio.MessageTypeChannel(0), payload)\n\n[.. your another stuff here if you want ..]\n\n// Wait until the real sending\n\u003c-sendInfo.Done()\n\n// Here you get the error if any:\nerr := sendInfo.Err\n\n// It's not necessary, but helps to reduce the pressure on GC (so to optimize CPU and RAM utilization)\nsendInfo.Release()\n```\n\n#### MessageTypes\n\n\nA MessageType for a custom channel may be created via function\n`MessageTypeChannel(channelID uint32)`. `channelID` is a custom number to identify\nwhich flow is it (to connect a sender with appropriate receiver on the remote side).\nIn the examples above it was used `0` as the `channelID` value, but it could be any\nvalue in the range: `0 \u003c= x \u003c= 2**31`.\n\nAlso there's a special MessageType `MessageTypeReadWrite` is used for\ndefault `Read()`/`Write()`. But you may redirect this flow to a custom handler.\n\n## Limitations and hints\n\n* Does not support traffic fragmentation. If it's required to make it work over UDP\nthen it's required to disable the fragmentation, see [an example for UDP](https://github.com/xaionaro-go/secureio/blob/aa5c2d2bbf6a8a5f0acfd0f1c996dcfadf6671a9/testutils_linux_test.go#L20).\n* If the underlying writer cannot handle big messages then it's required to adjust\n[`SessionOptions.MaxPayloadSize`](https://github.com/xaionaro-go/secureio/blob/aa5c2d2bbf6a8a5f0acfd0f1c996dcfadf6671a9/session.go#L224).\n* If you don't have multiple writers and you don't need to aggregate messages (see below) then\nyou may set `SessionOptions.SendDelay` to `\u0026[]time.Duration{0}[0]`.\n\n## Benchmark\n\nThe benchmark was performed with communication via an UNIX-socket.\n```\nBenchmarkSessionWriteRead1-8                          \t   10000\t    118153 ns/op\t   0.01 MB/s\t     468 B/op\t       8 allocs/op\nBenchmarkSessionWriteRead16-8                         \t   10000\t    118019 ns/op\t   0.14 MB/s\t     455 B/op\t       8 allocs/op\nBenchmarkSessionWriteRead1024-8                       \t    9710\t    119238 ns/op\t   8.59 MB/s\t     441 B/op\t       8 allocs/op\nBenchmarkSessionWriteRead32000-8                      \t    6980\t    173441 ns/op\t 184.50 MB/s\t     488 B/op\t       9 allocs/op\nBenchmarkSessionWriteRead64000-8                      \t    3994\t    310038 ns/op\t 206.43 MB/s\t     629 B/op\t       9 allocs/op\nBenchmarkSessionWriteMessageAsyncRead1-8              \t 2285032\t       539 ns/op\t   1.86 MB/s\t       0 B/op\t       0 allocs/op\nBenchmarkSessionWriteMessageAsyncRead16-8             \t 2109264\t       572 ns/op\t  27.99 MB/s\t       2 B/op\t       0 allocs/op\nBenchmarkSessionWriteMessageAsyncRead1024-8           \t  480385\t      2404 ns/op\t 425.87 MB/s\t      15 B/op\t       0 allocs/op\nBenchmarkSessionWriteMessageAsyncRead32000-8          \t   30163\t     39131 ns/op\t 817.76 MB/s\t     162 B/op\t       5 allocs/op\nBenchmarkSessionWriteMessageAsyncRead64000-8          \t   15435\t     77898 ns/op\t 821.59 MB/s\t     317 B/op\t      10 allocs/op\n```\n\nThis package is designed to be asynchronous, so\nbasically `Write` is an stupid wrapper around code of `WriteMessageAsync`.\nTo get more throughput it merges all your messages collected\nin 50 microseconds into a one, sends, and then splits them back. It\nallows to reduce amount of syscalls and other overheads. So to achieve\nlike 1.86MiB/s on 1-byte messages you need to send a lot of them\nasynchronously (from each other), so they will be merged while\nsending/receiving through the backend connection.\n\nAlso this 800MiB/s is more about the localhost-case. And more realistic network case (if we have MTU ~= 1400) is:\n```\nBenchmarkSessionWriteMessageAsyncRead1300_max1400-8   \t  117862\t     10277 ns/op\t 126.49 MB/s\t     267 B/op\t      10 allocs/op\n```\n\n# Security design\n\n### Key exchange\n\nThe remote side is authenticated by a ED25519 signature (of the\nkey exchange message).\n\nKey exchange is performed via ECDH with X25519. [If a PSK is set, then\nit is PSK concatenated with a constant salt-value, hashed with `blake3.Sum256` and `sha3.Sum256`\nand used to XOR the (exchanged) key](https://github.com/xaionaro-go/secureio/blob/ccd4d864545620b5483c88df91491817e4f0a442/key_exchanger.go#L111).\n\nThe resulting value is used as the encryption key for XChaCha20.\nThis key is called `cipherKey` within the code.\n\nThe key (received via ECDH) is updated [every minute](https://github.com/xaionaro-go/secureio/blob/ccd4d864545620b5483c88df91491817e4f0a442/key_exchanger.go#L18).\nSo in turn the `cipherKey` is updated every minute as well.\n\n### Encryption\n\nA `SessionID` is exchanged by the very first key-exchange messages.\n`SessionID` is a combination of UnixNano (of when the Session was initialized)\nand random 64-bit integer.\n\nAlso each packet starts with an unique (for a session) plain-text\n`PacketID` (actually if PSK is set then `PacketID` encrypted with\na key derived as a hash of a salted PSK).\n\nThe combination of `PacketID` and `SessionID` is used as IV/NONCE\nfor XChaCha20 and `cipherKey` is used as the key.\n\nWorth to mention that `PacketID` is intended to be unique only\nwithin a Session. So it just starts with zero and then increases\nby 1 for each next message. Therefore if the system clock is\nbroken then uniqueness of NONCE is guaranteed by 64-bit random\nvalue of `SessionID`.\n\n### Message authentication \n\nMessage authentication is done using Poly1305. As key for Poly1305\nused a blake3.Sum256 hash of:\n - [concatenation of `PacketID` and `cipherKey` XOR-ed](https://github.com/xaionaro-go/secureio/blob/ccd4d864545620b5483c88df91491817e4f0a442/message.go#L267) \nby a [constant value](https://github.com/xaionaro-go/secureio/blob/ccd4d864545620b5483c88df91491817e4f0a442/message.go#L40).\n\n`PacketID` is supposed to be increasing only. Received PacketID are remembered in a limited\nwindow of values. If it was received a packet with the same `PacketID` (as it already was) or\nwith a lesser `PackerID` (than the minimal possible value in the window) then the packet\nis just ignored.\n\n# Session states\n\nA successful session with the default settings goes through\nstates/phases/stages:\n\n* Initialization\n* Key exchanging\n* Negotiation\n* Established\n* Closing\n* Closed\n\n### Initialization\n\nThis is the stage where all the options are parsed and all the required\ngoroutines are being initialized.\n\nAfter that `*Session` will be switched to the \"Key Exchanging\" state.\n\n### Key exchanging\n\nAt this stage both parties (the local one and the remote one) are exchanging\nwith ECDH public keys to get a symmetrical shared key (see \"Security Design\").\n\nAlso if a remote identity is set then we verify if it matches, and ignores\nany key-exchange messages with any other ED25519 public keys (don't confuse\nwith ECDH public key): ED25519 keypairs are static for each party (and usually\npre-defined), while ECDH keypairs are generated for each key exchange.\n\nAlso each key-exchanging key is verified by the public key (passed with\nthe message).\n\nWith the default settings, each party also sends acknowledgement messages\nto verify if the messages was received and percieved.\nAnd (with the default settings) each party waits for a acknowledgment message\nfrom the remote side (see also `SessionOptions.AnswersMode`).\n\nIf everything is successful here then the `*Session` goes to the \"Negotiation\"\nphase. But the key exchanging process is still periodically performed in\nbackground.\n\n### Negotiation\n\nAt this stage we try to determine which size of packets the underlying\n`io.Writer` can handle. So we try to send packets of 3 different sizes and\nlook which of them will be able to make a round trip. Then repeat the\nprocedure in a shorter interval. And so on up to 4 times.\n\nThis behavior could be enabled or disabled through\n`SessionOptions.NegotiatorOptions.Enable`.\n\nWhen this procedure will be finished (or if it is disabled), then\nthe `*Session` is switched to the state \"Established\".\n\n### Established\n\nThis is the state in which the `*Session` is operating normally, so you\nmay send and receive messages through it. And messages attempted to be sent\nbefore reaching this stage will be sent as soon as `*Session` will reach this\nstage.\n\n### Closing / Closed\n\n`Closing` is a transitional state before becoming `Closed`.\nIf state `Closed` is reached it means the session is dead and nothing\nwill ever happen to it anymore.\n\n# TODO\n\n* support of fragmented/merged (by backend) traffic.\n* check keyCreatedAt\n* error if key hasn't changed\n* verify TS difference sanity\n* don't use `Async` for sync-writes.\n* route messenger-related errors to the messenger's handler.\n* more comments (in the code)\n* consider `notewakeup` instead of `Cond.Wait`\n\n# I'm not sure if we need\n\n* download key-files by URLs\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxaionaro-go%2Fsecureio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxaionaro-go%2Fsecureio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxaionaro-go%2Fsecureio/lists"}