├── .github ├── dependabot.yml └── workflows │ └── go.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── LICENSE ├── README.md ├── cmd └── veil │ ├── cmd_decrypt.go │ ├── cmd_derivekey.go │ ├── cmd_encrypt.go │ ├── cmd_publickey.go │ ├── cmd_secretkey.go │ ├── cmd_sign.go │ ├── cmd_verify.go │ └── main.go ├── go.mod ├── go.sum └── pkg └── veil ├── internal ├── hpke │ ├── hpke.go │ └── hpke_test.go ├── internal.go ├── internal_test.go ├── mres │ ├── mres.go │ └── mres_test.go ├── pbenc │ ├── pbenc.go │ └── pbenc_test.go ├── protocol │ └── protocol.go ├── scaldf │ ├── scaldf.go │ └── scaldf_test.go └── schnorr │ ├── schnorr.go │ ├── schnorr_test.go │ └── sigio │ ├── sigio.go │ └── sigio_test.go ├── privatekey.go ├── privatekey_test.go ├── publickey.go ├── publickey_test.go ├── secretkey.go ├── secretkey_test.go ├── signature.go ├── signature_test.go ├── veil.go └── veil_test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Set up Go 9 | uses: actions/setup-go@v2 10 | with: 11 | go-version: 1.16 12 | id: go 13 | 14 | - name: Check out code 15 | uses: actions/checkout@v2.3.4 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Get dependencies 20 | run: | 21 | go get -v -t -d ./... 22 | 23 | - name: Test 24 | run: go test -v ./... 25 | 26 | - name: Lint 27 | uses: golangci/golangci-lint-action@v2.5.2 28 | with: 29 | version: v1.39 30 | skip-go-installation: true 31 | skip-pkg-cache: true 32 | skip-build-cache: true 33 | 34 | - name: Cross-compile 35 | uses: goreleaser/goreleaser-action@v2.5.0 36 | with: 37 | version: latest 38 | args: build --snapshot 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Go template 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | dist 17 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - asciicheck 5 | - bodyclose 6 | - cyclop 7 | - deadcode 8 | - depguard 9 | - dogsled 10 | - dupl 11 | - durationcheck 12 | - errcheck 13 | - errorlint 14 | - exhaustive 15 | - exportloopref 16 | - forcetypeassert 17 | - funlen 18 | - gci 19 | - gochecknoglobals 20 | - gochecknoinits 21 | - gocognit 22 | - goconst 23 | - gocritic 24 | - gocyclo 25 | - godot 26 | - godox 27 | - goerr113 28 | - gofmt 29 | - gofumpt 30 | - golint 31 | - gomoddirectives 32 | - gomodguard 33 | - goprintffuncname 34 | - gosec 35 | - gosimple 36 | - govet 37 | - ifshort 38 | - importas 39 | - ineffassign 40 | - lll 41 | - makezero 42 | - misspell 43 | - nakedret 44 | - nestif 45 | - nilerr 46 | - noctx 47 | - nolintlint 48 | - paralleltest 49 | - prealloc 50 | - predeclared 51 | - revive 52 | - rowserrcheck 53 | - sqlclosecheck 54 | - staticcheck 55 | - structcheck 56 | - stylecheck 57 | - thelper 58 | - tparallel 59 | - unconvert 60 | - unparam 61 | - unused 62 | - varcheck 63 | - wastedassign 64 | - whitespace 65 | - wsl 66 | # - exhaustivestruct 67 | # - forbidigo 68 | # - goheader 69 | # - goimports 70 | # - gomnd 71 | # - interfacer 72 | # - maligned 73 | # - nlreturn 74 | # - scopelint 75 | # - testpackage 76 | # - typecheck 77 | # - wrapcheck 78 | 79 | linters-settings: 80 | funlen: 81 | lines: 150 82 | gocognit: 83 | min-complexity: 10 84 | gocyclo: 85 | min-complexity: 10 86 | gofumpt: 87 | extra-rules: true 88 | maligned: 89 | suggest-new: true 90 | nolintlint: 91 | allow-leading-space: false 92 | require-explanation: true 93 | require-specific: true 94 | wsl: 95 | force-err-cuddling: true 96 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | dir: ./cmd/veil 5 | goos: 6 | - darwin 7 | - linux 8 | - windows 9 | goarch: 10 | - amd64 11 | - arm64 12 | mod_timestamp: "{{.CommitTimestamp}}" 13 | ldflags: 14 | - -s -w -X main.version={{.Version}} -X main.commit={{.ShortCommit}} -X main.date={{.CommitDate}} 15 | flags: 16 | - -trimpath 17 | archives: 18 | - format_overrides: 19 | - goos: windows 20 | format: zip 21 | checksum: 22 | name_template: 'checksums.txt' 23 | snapshot: 24 | name_template: "{{.Tag}}-next" 25 | changelog: 26 | skip: true 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # veil 2 | 3 | _Stupid crypto tricks._ 4 | 5 | WARNING: You should, under no circumstances, use this. 6 | 7 | ## What is Veil? 8 | 9 | Veil is an incredibly experimental hybrid cryptosystem for sending and receiving confidential, 10 | authentic multi-recipient messages which are indistinguishable from random noise by an attacker. 11 | Unlike e.g. GPG messages, Veil messages contain no metadata or format details which are not 12 | encrypted. As a result, a global passive adversary would be unable to gain any information from a 13 | Veil message beyond traffic analysis. Messages can be padded with random bytes to disguise their 14 | true length, and fake recipients can be added to disguise their true number from other recipients. 15 | Further, Veil supports hierarchical key derivation, allowing for domain-specific and disposable 16 | keys. 17 | 18 | ## Design Criteria 19 | 20 | ### Cryptographic Minimalism 21 | 22 | Veil uses just two distinct primitives: 23 | 24 | * [STROBE](https://strobe.sourceforge.io) for confidentiality, authentication, and integrity. 25 | * [ristretto255](https://ristretto.group) for key agreement and signing. 26 | 27 | ristretto255 [uses a safe curve, is a prime-order cyclic group, has non-malleable encodings, and has 28 | no co-factor concerns](https://ristretto.group/why_ristretto.html). STROBE is built on the Keccak 29 | 𝑓-\[1600\] permutation, the core of SHA-3, which has seen [significant scrutiny over the last 30 | decade](https://keccak.team/third_party.html). 31 | 32 | The underlying philosophy is that expressed by [Adam 33 | Langley](https://www.imperialviolet.org/2016/05/16/agility.html): 34 | 35 | > There's a lesson in all this: have one joint and keep it well oiled. … \[O\]ne needs to minimise 36 | > complexity, concentrate all extensibility in a single place and _actively defend it_. 37 | 38 | As a result, the constructions in Veil depend primarily on two relatively stable cryptographic 39 | assumptions: the Gap Diffie-Hellman assumption for ristretto255 and that Keccak 𝑓-\[1600\] is 40 | suitably close to a random permutation. 41 | 42 | ### Integrated Constructions 43 | 44 | Because STROBE provides a wide range of capabilities, it's possible to build fully integrated 45 | cryptographic constructions. Leveraging transcript consistency–the fact that every operation changes 46 | a STROBE protocol's state in a cryptographically secure manner–makes for much simpler protocols with 47 | guarantees that are easier to understand. 48 | 49 | Instead of combining a hash function and a digital signature algorithm, we have a single digital 50 | signature protocol. Instead of combining a key exchange, a KDF, and an AEAD, we have a single hybrid 51 | public key encryption protocol. This integration bakes in logical dependencies on sent and received 52 | data in a feed-forward mechanism, which removes it from the attackable surface area of the protocol. 53 | Because STROBE operations are cryptographically dependent on prior operations, the need for domain 54 | separation identifiers, padding, and framing is eliminated. 55 | 56 | Finally, the use of STROBE means all protocols which end in `RECV_MAC` calls are [compactly 57 | committing](https://eprint.iacr.org/2019/016.pdf). 58 | 59 | ### Indistinguishable From Random Noise 60 | 61 | Veil messages are entirely indistinguishable from random noise. They contain no plaintext metadata, 62 | no plaintext ristretto255 elements, no plaintext framing or padding, and have entirely arbitrary 63 | lengths. This makes them ideal for distribution via steganographic channels and very resistant to 64 | traffic analysis. 65 | 66 | ## Algorithms & Constructions 67 | 68 | ### Hierarchical Key Derivation 69 | 70 | Each participant in Veil has a secret key, which is a 64-byte random string. To derive a private key 71 | from a secret key, the secret key is mapped to a ristretto255 scalar. A delta scalar is derived from 72 | an opaque label value and added to the secret scalar to form a private key. The process is repeated 73 | to derive a private key from another private key. To derive a public key from a public key, the 74 | delta scalar is first multiplied by the curve's base element, then added to the public key element. 75 | 76 | This is used iterative to provide hierarchical key derivation. Public keys are created using 77 | hierarchical IDs like `/friends/alice`, in which the private key `/` is used to derive the private 78 | key `friends`, which is in turn used to derive the private key `alice`. 79 | 80 | ### STROBE Protocols 81 | 82 | Full details and documentation for all the Veil protocols can be found in the 83 | [`pkg/veil/internal`](https://github.com/codahale/veil/tree/main/pkg/veil/internal) directory. 84 | 85 | #### `veil.hpke` 86 | 87 | `veil.hpke` implements an authenticated `C(1e, 2s, ECC DH)` key encapsulation mechanism with 88 | ristretto255 and STROBE. It provides authentication, sender forward security (i.e. if the sender's 89 | private key is compromised, the messages they sent remain confidential), as well as the novel 90 | property of sending no values in cleartext: the ephemeral public key is encrypted with the static 91 | shared secret before sending. 92 | 93 | #### `veil.mres` 94 | 95 | `veil.mres` implements the multi-recipient encryption system for encrypted Veil messages. 96 | 97 | Messages begin with a set of `veil.hpke`-encrypted headers containing copies of the data encryption 98 | key and the length of the encrypted headers. Next, the message is encrypted with STROBE using the 99 | data encryption key. Finally, a `veil.schnorr` signature of the entire ciphertext created with an 100 | ephemeral private key is appended. 101 | 102 | To decrypt, readers search for a decryptable header, recover the DEK, the ephemeral public key, and 103 | headers length, decrypt the message, and finally verify the signature. 104 | 105 | This provides strong confidentiality and authenticity guarantees while still providing repudiability 106 | (no recipient can prove a message's contents and origin without revealing their private key) and 107 | forward security for senders (compromise of a sender's private key will not compromise past messages 108 | they sent). 109 | 110 | #### `veil.pbenc` 111 | 112 | `veil.pbenc` implements a memory-hard AEAD using Balloon Hashing, suitable for encrypting secret 113 | keys. 114 | 115 | #### `veil.scaldf.*` 116 | 117 | `veil.scaldf.*` provides various algorithms for deriving ristretto255 scalars from secret or 118 | non-uniform values. Veil uses them to derive private keys and label scalars. 119 | 120 | #### `veil.schnorr` 121 | 122 | `veil.schnorr` implements a fully integrated Schnorr signature algorithm over ristretto255, as 123 | described by [Fleischhacker et al.](https://eprint.iacr.org/2011/673.pdf). It produces 124 | _indistinguishable_ signatures (i.e., signatures which do not reveal anything about the signing key 125 | or signed message) and when encrypted with an unrelated key (i.e. by `veil.mres`) are _pseudorandom_ 126 | (i.e. indistinguishable from random noise). 127 | 128 | ## License 129 | 130 | Copyright © 2021 Coda Hale 131 | 132 | Distributed under the Apache License 2.0. 133 | -------------------------------------------------------------------------------- /cmd/veil/cmd_decrypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/alecthomas/kong" 7 | ) 8 | 9 | type decryptCmd struct { 10 | SecretKey string `arg:"" type:"existingfile" help:"The path to the secret key."` 11 | KeyID string `arg:"" help:"The ID of the private key to use."` 12 | Ciphertext string `arg:"" type:"existingfile" help:"The path to the ciphertext file."` 13 | Plaintext string `arg:"" type:"path" help:"The path to the plaintext file."` 14 | Sender string `arg:"" repeated:"" help:"The public key of the sender."` 15 | } 16 | 17 | func (cmd *decryptCmd) Run(_ *kong.Context) error { 18 | // Decrypt the secret key. 19 | sk, err := decryptSecretKey(cmd.SecretKey) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | // Decode the public key of the sender. 25 | sender, err := decodePublicKey(cmd.Sender) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | // Open the ciphertext input. 31 | src, err := os.Open(cmd.Ciphertext) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | defer func() { _ = src.Close() }() 37 | 38 | // Open the plaintext output. 39 | dst, err := openOutput(cmd.Plaintext) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | defer func() { _ = dst.Close() }() 45 | 46 | // Decrypt the ciphertext. 47 | _, err = sk.PrivateKey(cmd.KeyID).Decrypt(dst, src, sender) 48 | if err != nil { 49 | // Delete any partial output if decryption fails. 50 | _ = dst.Truncate(0) 51 | _ = dst.Close() 52 | _ = os.Remove(cmd.Plaintext) 53 | } 54 | 55 | return err 56 | } 57 | -------------------------------------------------------------------------------- /cmd/veil/cmd_derivekey.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/alecthomas/kong" 7 | ) 8 | 9 | type deriveKeyCmd struct { 10 | PublicKey string `arg:"" help:"The path to the public key."` 11 | SubKeyID string `arg:"" help:"The sub-key ID."` 12 | Output string `arg:"" type:"path" default:"-" help:"The output path for the public key."` 13 | } 14 | 15 | func (cmd *deriveKeyCmd) Run(_ *kong.Context) error { 16 | // Decode the public key. 17 | pk, err := decodePublicKey(cmd.PublicKey) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | // Open the output. 23 | dst, err := openOutput(cmd.Output) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | defer func() { _ = dst.Close() }() 29 | 30 | // Derive the public key, encode it, and write it to the output. 31 | _, err = io.WriteString(dst, pk.Derive(cmd.SubKeyID).String()) 32 | 33 | return err 34 | } 35 | -------------------------------------------------------------------------------- /cmd/veil/cmd_encrypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/alecthomas/kong" 5 | ) 6 | 7 | type encryptCmd struct { 8 | SecretKey string `arg:"" type:"existingfile" help:"The path to the secret key."` 9 | KeyID string `arg:"" help:"The ID of the private key to use."` 10 | Plaintext string `arg:"" type:"existingfile" help:"The path to the plaintext file."` 11 | Ciphertext string `arg:"" type:"path" help:"The path to the ciphertext file."` 12 | Recipients []string `arg:"" repeated:"" help:"The public keys of the recipients."` 13 | 14 | Fakes int `help:"The number of fake recipients to add."` 15 | Padding int `help:"The number of bytes of random padding to add."` 16 | } 17 | 18 | func (cmd *encryptCmd) Run(_ *kong.Context) error { 19 | // Decrypt the secret key. 20 | sk, err := decryptSecretKey(cmd.SecretKey) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | // Decode the recipients' public keys. 26 | recipients, err := decodePublicKeys(cmd.Recipients) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | // Open the plaintext input. 32 | src, err := openInput(cmd.Plaintext) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | defer func() { _ = src.Close() }() 38 | 39 | // Open the ciphertext output. 40 | dst, err := openOutput(cmd.Ciphertext) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | defer func() { _ = dst.Close() }() 46 | 47 | // Encrypt the plaintext. 48 | _, err = sk.PrivateKey(cmd.KeyID).Encrypt(dst, src, recipients, cmd.Fakes, cmd.Padding) 49 | 50 | return err 51 | } 52 | -------------------------------------------------------------------------------- /cmd/veil/cmd_publickey.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/alecthomas/kong" 7 | ) 8 | 9 | type publicKeyCmd struct { 10 | SecretKey string `arg:"" type:"existingfile" help:"The path to the secret key."` 11 | KeyID string `arg:"" help:"The ID of the public key to generate."` 12 | Output string `arg:"" type:"path" default:"-" help:"The output path for the public key."` 13 | } 14 | 15 | func (cmd *publicKeyCmd) Run(_ *kong.Context) error { 16 | // Decrypt the secret key. 17 | sk, err := decryptSecretKey(cmd.SecretKey) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | // Open the output. 23 | dst, err := openOutput(cmd.Output) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | defer func() { _ = dst.Close() }() 29 | 30 | // Derive the public key, encode it, and write it to the output. 31 | _, err = io.WriteString(dst, sk.PublicKey(cmd.KeyID).String()) 32 | 33 | return err 34 | } 35 | -------------------------------------------------------------------------------- /cmd/veil/cmd_secretkey.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/alecthomas/kong" 7 | "github.com/codahale/veil/pkg/veil" 8 | ) 9 | 10 | type secretKeyCmd struct { 11 | Output string `arg:"" type:"path" help:"The output path for the encrypted secret key."` 12 | } 13 | 14 | func (cmd *secretKeyCmd) Run(_ *kong.Context) error { 15 | // Prompt for the PBE passphrase. 16 | passphrase, err := askPassphrase("Enter passphrase: ") 17 | if err != nil { 18 | return err 19 | } 20 | 21 | // Generate a new secret key. 22 | sk, err := veil.NewSecretKey() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | // Encrypt the secret key with the passphrase. 28 | esk, err := sk.Encrypt(passphrase, nil) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | // Write out the encrypted secret key. 34 | return os.WriteFile(cmd.Output, esk, 0600) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/veil/cmd_sign.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/alecthomas/kong" 5 | ) 6 | 7 | type signCmd struct { 8 | SecretKey string `arg:"" type:"existingfile" help:"The path to the secret key."` 9 | KeyID string `arg:"" help:"The ID of the private key to use."` 10 | Message string `arg:"" type:"existingfile" help:"The path to the message."` 11 | Output string `arg:"" type:"path" help:"The path to the signature file."` 12 | } 13 | 14 | func (cmd *signCmd) Run(_ *kong.Context) error { 15 | // Decrypt the secret key. 16 | sk, err := decryptSecretKey(cmd.SecretKey) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | // Open the message input. 22 | src, err := openInput(cmd.Message) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | defer func() { _ = src.Close() }() 28 | 29 | // Open the signature output. 30 | dst, err := openOutput(cmd.Output) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | defer func() { _ = dst.Close() }() 36 | 37 | // Sign the message. 38 | sig, err := sk.PrivateKey(cmd.KeyID).Sign(src) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | // Encode the signature. 44 | sigText, err := sig.MarshalText() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | // Write out the signature. 50 | _, err = dst.Write(sigText) 51 | 52 | return err 53 | } 54 | -------------------------------------------------------------------------------- /cmd/veil/cmd_verify.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/alecthomas/kong" 7 | "github.com/codahale/veil/pkg/veil" 8 | ) 9 | 10 | type verifyCmd struct { 11 | PublicKey string `arg:"" help:"The signer's public key."` 12 | Message string `arg:"" type:"existingfile" help:"The path to the message."` 13 | Signature string `arg:"" type:"existingfile" help:"The path to the signature file."` 14 | } 15 | 16 | func (cmd *verifyCmd) Run(_ *kong.Context) error { 17 | // Open and decode the public key. 18 | pk, err := decodePublicKey(cmd.PublicKey) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | // Open the message input. 24 | src, err := openInput(cmd.Message) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | defer func() { _ = src.Close() }() 30 | 31 | // Read the signature. 32 | text, err := os.ReadFile(cmd.Signature) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | // Decode the signature. 38 | var sig veil.Signature 39 | if err := sig.UnmarshalText(text); err != nil { 40 | return err 41 | } 42 | 43 | // Verify the signature. 44 | return pk.Verify(src, &sig) 45 | } 46 | -------------------------------------------------------------------------------- /cmd/veil/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/alecthomas/kong" 8 | "github.com/codahale/veil/pkg/veil" 9 | "golang.org/x/term" 10 | ) 11 | 12 | type cli struct { 13 | SecretKey secretKeyCmd `cmd:"" help:"Generate a new secret key."` 14 | PublicKey publicKeyCmd `cmd:"" help:"Derive a public key from a secret key."` 15 | DeriveKey deriveKeyCmd `cmd:"" help:"Derive a public key from another public key."` 16 | Encrypt encryptCmd `cmd:"" help:"Encrypt a message for a set of recipients."` 17 | Decrypt decryptCmd `cmd:"" help:"Decrypt a message."` 18 | Sign signCmd `cmd:"" help:"Create a signature for a message."` 19 | Verify verifyCmd `cmd:"" help:"Verify a signature for a message."` 20 | } 21 | 22 | func main() { 23 | var cli cli 24 | 25 | ctx := kong.Parse(&cli) 26 | err := ctx.Run() 27 | ctx.FatalIfErrorf(err) 28 | } 29 | 30 | func decodePublicKeys(pathsOrKeys []string) (keys []*veil.PublicKey, err error) { 31 | keys = make([]*veil.PublicKey, len(pathsOrKeys)) 32 | 33 | for i, path := range pathsOrKeys { 34 | keys[i], err = decodePublicKey(path) 35 | if err != nil { 36 | return nil, err 37 | } 38 | } 39 | 40 | return 41 | } 42 | 43 | func decodePublicKey(pathOrKey string) (*veil.PublicKey, error) { 44 | // Try decoding the key directly. 45 | var pk veil.PublicKey 46 | if err := pk.UnmarshalText([]byte(pathOrKey)); err == nil { 47 | return &pk, nil 48 | } 49 | 50 | // Otherwise, try reading the contents of it as a file. 51 | b, err := os.ReadFile(pathOrKey) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | // Decode the public key. 57 | if err := pk.UnmarshalText(b); err != nil { 58 | return nil, err 59 | } 60 | 61 | return &pk, nil 62 | } 63 | 64 | func decryptSecretKey(path string) (*veil.SecretKey, error) { 65 | b, err := os.ReadFile(path) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | pwd, err := askPassphrase("Enter passphrase: ") 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | return veil.DecryptSecretKey(pwd, b) 76 | } 77 | 78 | func askPassphrase(prompt string) ([]byte, error) { 79 | defer func() { _, _ = fmt.Fprintln(os.Stderr) }() 80 | 81 | _, _ = fmt.Fprint(os.Stderr, prompt) 82 | 83 | return term.ReadPassword(int(os.Stdin.Fd())) 84 | } 85 | 86 | func openOutput(path string) (*os.File, error) { 87 | if path == "-" { 88 | return os.Stdin, nil 89 | } 90 | 91 | return os.Create(path) 92 | } 93 | 94 | func openInput(path string) (*os.File, error) { 95 | if path == "-" { 96 | return os.Stdin, nil 97 | } 98 | 99 | return os.Open(path) 100 | } 101 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/codahale/veil 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/alecthomas/kong v0.2.16 7 | github.com/codahale/gubbins v0.0.1 8 | github.com/google/go-cmp v0.5.5 9 | github.com/gtank/ristretto255 v0.1.2 10 | github.com/mr-tron/base58 v1.2.0 11 | github.com/pkg/errors v0.9.1 // indirect 12 | github.com/sammyne/strobe v0.0.0-20201028005755-1a7dc1020420 13 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect 14 | golang.org/x/term v0.0.0-20210422114643-f5beecf764ed 15 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/kong v0.2.16 h1:F232CiYSn54Tnl1sJGTeHmx4vJDNLVP2b9yCVMOQwHQ= 2 | github.com/alecthomas/kong v0.2.16/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= 3 | github.com/codahale/gubbins v0.0.1 h1:BQq8wKhnQPvkfkCu1MnU73XWmv+5HG3p4TPYUJhVnGA= 4 | github.com/codahale/gubbins v0.0.1/go.mod h1:GEbM0qmOZZmIBeSy/0elVxeaNbipRIelCsjkdG/+g+4= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 8 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 9 | github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= 10 | github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= 11 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 12 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 13 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 14 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 15 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/sammyne/strobe v0.0.0-20201028005755-1a7dc1020420 h1:1EAf5lkrx70cWAzrmo88iNJaViWMUwjeekLvz/oL1T8= 19 | github.com/sammyne/strobe v0.0.0-20201028005755-1a7dc1020420/go.mod h1:qh9eFhXWV5wasPw/+WZ3ELz1+lWdlje8oHhOgiX5aGU= 20 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 21 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 22 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 23 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw= 24 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 25 | golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0= 26 | golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 27 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 28 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 29 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 30 | -------------------------------------------------------------------------------- /pkg/veil/internal/hpke/hpke.go: -------------------------------------------------------------------------------- 1 | // Package hpke provides the underlying STROBE protocol for Veil's authenticated hybrid public key 2 | // encryption system. Unlike traditional HPKE constructions, this does not have separate KEM/DEM 3 | // components or a specific derived DEK. 4 | // 5 | // Encryption 6 | // 7 | // Encryption is as follows, given the sender's key pair, d_s and Q_s, an ephemeral key pair, d_e 8 | // and Q_e, the receiver's public key, Q_r, a plaintext message P, and MAC size N_mac: 9 | // 10 | // INIT('veil.hpke', level=256) 11 | // AD(LE_U32(N_mac), meta=true) 12 | // AD(Q_r) 13 | // AD(Q_s) 14 | // ZZ_s = Q_r^d_s 15 | // KEY(ZZ_s) 16 | // SEND_ENC(Q_e) -> E 17 | // ZZ_e = Q_r^d_e 18 | // KEY(ZZ_e) 19 | // 20 | // This is effectively an authenticated ECDH KEM, but instead of returning KDF output for use in a 21 | // DEM, we use the keyed protocol to directly encrypt the ciphertext and create a MAC: 22 | // 23 | // SEND_ENC(P) -> C 24 | // SEND_MAC(N_mac) -> M 25 | // 26 | // The resulting ciphertext is the concatenation of E, C, and M. 27 | // 28 | // Decryption 29 | // 30 | // Decryption is then the inverse of encryption, given the recipient's key pair, d_r and Q_r, and 31 | // the sender's public key Q_s: 32 | // 33 | // INIT('veil.hpke', level=256) 34 | // AD(LE_U32(N_max), meta=true) 35 | // AD(Q_r) 36 | // AD(Q_s) 37 | // ZZ_s = Q_s^d_r 38 | // KEY(ZZ_s) 39 | // RECV_ENC(E) -> Q_e 40 | // ZZ_e = Q_e^d_r 41 | // KEY(ZZ_e) 42 | // RECV_ENC(C) -> P 43 | // RECV_MAC(M) 44 | // 45 | // If the RECV_MAC call is successful, the ephemeral public key E and the plaintext message P are 46 | // returned. 47 | // 48 | // IND-CCA2 Security 49 | // 50 | // This construction combines two overlapping KEM/DEM constructions: a "El Gamal-like" KEM combined 51 | // with a STROBE-based AEAD, and an ephemeral ECIES-style KEM combined with a STROBE-based AEAD. 52 | // 53 | // The STROBE-based AEAD is equivalent to Construction 5.6 of Modern Cryptography 3e and is 54 | // CCA-secure per Theorem 5.7, provided STROBE's encryption is CPA-secure. STROBE's SEND_ENC is 55 | // equivalent to Construction 3.31 and is CPA-secure per Theorem 3.29, provided STROBE is a 56 | // sufficiently strong pseudorandom function. 57 | // 58 | // The first KEM/DEM construction is equivalent to Construction 12.19 of Modern Cryptography 3e, and 59 | // is CCA-secure per Theorem 12.22, provided the gap-CDH problem is hard relative to ristretto255 60 | // and STROBE is modeled as a random oracle. 61 | // 62 | // The second KEM/DEM construction is equivalent to Construction 12.23 of Modern Cryptography 3e, 63 | // and is CCA-secure per Corollary 12.24, again provided that the gap-CDH problem is hard relative 64 | // to ristretto255 and STROBE is modeled as a random oracle. 65 | // 66 | // IK-CCA Security 67 | // 68 | // veil.hpke is IK-CCA (per Bellare, https://iacr.org/archive/asiacrypt2001/22480568.pdf), in that 69 | // it is impossible for an attacker in possession of two public keys to determine which of the two 70 | // keys a given ciphertext was encrypted with in either chosen-plaintext or chosen-ciphertext 71 | // attacks. Informally, veil.hpke ciphertexts consist exclusively of STROBE ciphertext and PRF 72 | // output; an attacker being able to distinguish between ciphertexts based on keying material would 73 | // imply STROBE's AEAD construction is not IND-CCA2. 74 | // 75 | // Consequently, a passive adversary scanning for encoded elements would first need the parties' 76 | // static Diffie-Hellman secret in order to distinguish messages from random noise. 77 | // 78 | // Forward Sender Security 79 | // 80 | // Because the ephemeral private key is discarded after encryption, a compromise of the sender's 81 | // private key will not compromise previously-created ciphertexts. If the sender's private key is 82 | // compromised, the most an attacker can discover about previously sent messages is the ephemeral 83 | // public key, not the message itself. 84 | // 85 | // Insider Authenticity 86 | // 87 | // This construction is not secure against insider attacks on authenticity, nor is it intended to 88 | // be. A recipient can forge ciphertexts which appear to be from a sender by re-using the ephemeral 89 | // public key and encrypting an alternate plaintext, but the forgeries will only be decryptable by 90 | // the forger. Because this type of forgery is possible, veil.hpke ciphertexts are therefore 91 | // repudiable. 92 | // 93 | // Randomness Re-Use 94 | // 95 | // The ephemeral key pair, d_e and Q_e, are generated outside of this construction and can be used 96 | // multiple times for a single message. 97 | // This improves the efficiency of the scheme without reducing 98 | // its security, per Bellare et al.'s treatment of Randomness Reusing Multi-Recipient Encryption 99 | // Schemes (http://cseweb.ucsd.edu/~Mihir/papers/bbs.pdf). 100 | // 101 | package hpke 102 | 103 | import ( 104 | "github.com/codahale/veil/pkg/veil/internal" 105 | "github.com/codahale/veil/pkg/veil/internal/protocol" 106 | "github.com/gtank/ristretto255" 107 | ) 108 | 109 | // Overhead is the number of bytes added to each veil.hpke ciphertext. 110 | const Overhead = internal.ElementSize + internal.MACSize 111 | 112 | // Encrypt encrypts the plaintext such that the owner of qR will be able to decrypt it knowing that 113 | // only the owner of qS could have encrypted it. 114 | func Encrypt(dst []byte, dS, dE *ristretto255.Scalar, qS, qE, qR *ristretto255.Element, plaintext []byte) []byte { 115 | buf := make([]byte, internal.ElementSize) 116 | ret, out := internal.SliceForAppend(dst, len(plaintext)+Overhead) 117 | 118 | // Initialize the protocol. 119 | hpke := protocol.New("veil.hpke") 120 | 121 | // Add the MAC size to the protocol. 122 | hpke.MetaAD(protocol.LittleEndianU32(internal.MACSize)) 123 | 124 | // Add the recipient's public key as associated data. 125 | hpke.AD(qR.Encode(buf[:0])) 126 | 127 | // Add the sender's public key as associated data. 128 | hpke.AD(qS.Encode(buf[:0])) 129 | 130 | // Calculate the static shared secret between the sender's private key and the recipient's 131 | // public key. 132 | zzS := ristretto255.NewElement().ScalarMult(dS, qR) 133 | 134 | // Key the protocol with the static shared secret. 135 | hpke.KEY(zzS.Encode(buf[:0])) 136 | 137 | // Encrypt the ephemeral public key. 138 | out = hpke.SendENC(out[:0], qE.Encode(buf[:0])) 139 | 140 | // Calculate the ephemeral shared secret between the ephemeral private key and the recipient's 141 | // public key. 142 | zzE := ristretto255.NewElement().ScalarMult(dE, qR) 143 | 144 | // Key the protocol with the ephemeral shared secret. 145 | hpke.KEY(zzE.Encode(buf[:0])) 146 | 147 | // Encrypt the plaintext. 148 | out = hpke.SendENC(out, plaintext) 149 | 150 | // Create a MAC. 151 | _ = hpke.SendMAC(out) 152 | 153 | // Return the encrypted ephemeral public key, the encrypted message, and the MAC. 154 | return ret 155 | } 156 | 157 | // Decrypt decrypts the ciphertext iff it was encrypted by the owner of qS for the owner of qR and 158 | // no bit of the ciphertext has been modified. 159 | func Decrypt( 160 | dst []byte, dR *ristretto255.Scalar, qR, qS *ristretto255.Element, ciphertext []byte, 161 | ) (*ristretto255.Element, []byte, error) { 162 | buf := make([]byte, internal.ElementSize) 163 | ret, out := internal.SliceForAppend(dst, len(ciphertext)-Overhead) 164 | 165 | // Initialize the protocol. 166 | hpke := protocol.New("veil.hpke") 167 | 168 | // Add the MAC size to the protocol. 169 | hpke.MetaAD(protocol.LittleEndianU32(internal.MACSize)) 170 | 171 | // Add the recipient's public key as associated data. 172 | hpke.AD(qR.Encode(buf[:0])) 173 | 174 | // Add the sender's public key as associated data. 175 | hpke.AD(qS.Encode(buf[:0])) 176 | 177 | // Calculate the static shared secret between the recipient's private key and the sender's 178 | // public key. 179 | zzS := ristretto255.NewElement().ScalarMult(dR, qS) 180 | 181 | // Key the protocol with the static shared secret. 182 | hpke.KEY(zzS.Encode(buf[:0])) 183 | 184 | // Decrypt and decode the ephemeral public key. N.B.: this value has only been decrypted and not 185 | // authenticated. 186 | qE := ristretto255.NewElement() 187 | if err := qE.Decode(hpke.RecvENC(buf[:0], ciphertext[:internal.ElementSize])); err != nil { 188 | return nil, nil, internal.ErrInvalidCiphertext 189 | } 190 | 191 | // Calculate the ephemeral shared secret between the recipient's private key and the ephemeral 192 | // public key. N.B.: this value is derived from the unauthenticated ephemeral public key. 193 | zzE := ristretto255.NewElement().ScalarMult(dR, qE) 194 | 195 | // Key the protocol with the ephemeral shared secret. 196 | hpke.KEY(zzE.Encode(buf[:0])) 197 | 198 | ciphertext = ciphertext[internal.ElementSize:] 199 | 200 | // Decrypt the plaintext. N.B.: this value has only been decrypted and not authenticated. 201 | hpke.RecvENC(out[:0], ciphertext[:len(ciphertext)-internal.MACSize]) 202 | 203 | // Verify the MAC. This establishes authentication for the previous operations in their 204 | // entirety, since the sender and receiver's protocol states must be identical in order for the 205 | // MACs to agree. 206 | if err := hpke.RecvMAC(ciphertext[len(ciphertext)-internal.MACSize:]); err != nil { 207 | return nil, nil, internal.ErrInvalidCiphertext 208 | } 209 | 210 | // Return the ephemeral public key and the authenticated plaintext. 211 | return qE, ret, nil 212 | } 213 | -------------------------------------------------------------------------------- /pkg/veil/internal/hpke/hpke_test.go: -------------------------------------------------------------------------------- 1 | package hpke 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/codahale/gubbins/assert" 8 | "github.com/codahale/veil/pkg/veil/internal" 9 | "github.com/gtank/ristretto255" 10 | ) 11 | 12 | func TestRoundTrip(t *testing.T) { 13 | t.Parallel() 14 | 15 | ciphertext := Encrypt(nil, dS, dE, qS, qE, qR, message) 16 | 17 | q, plaintext, err := Decrypt(nil, dR, qR, qS, ciphertext) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | assert.Equal(t, "plaintext", message, plaintext) 23 | assert.Equal(t, "ephemeral public key", qE.String(), q.String()) 24 | } 25 | 26 | func TestWrongPrivateKey(t *testing.T) { 27 | t.Parallel() 28 | 29 | ciphertext := Encrypt(nil, dS, dE, qS, qE, qR, message) 30 | dX := ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0x69}, internal.UniformBytestringSize)) 31 | 32 | if _, _, err := Decrypt(nil, dX, qR, qS, ciphertext); err == nil { 33 | t.Fatal("should not have decrypted") 34 | } 35 | } 36 | 37 | func TestWrongPublicKey(t *testing.T) { 38 | t.Parallel() 39 | 40 | ciphertext := Encrypt(nil, dS, dE, qS, qE, qR, message) 41 | qX := ristretto255.NewElement().FromUniformBytes(bytes.Repeat([]byte{0x69}, internal.UniformBytestringSize)) 42 | 43 | if _, _, err := Decrypt(nil, dR, qX, qS, ciphertext); err == nil { 44 | t.Fatal("should not have decrypted") 45 | } 46 | } 47 | 48 | func TestWrongSenderKey(t *testing.T) { 49 | t.Parallel() 50 | 51 | ciphertext := Encrypt(nil, dS, dE, qS, qE, qR, message) 52 | qX := ristretto255.NewElement().FromUniformBytes(bytes.Repeat([]byte{0x69}, internal.UniformBytestringSize)) 53 | 54 | if _, _, err := Decrypt(nil, dR, qR, qX, ciphertext); err == nil { 55 | t.Fatal("should not have decrypted") 56 | } 57 | } 58 | 59 | func TestBadEphemeralKey(t *testing.T) { 60 | t.Parallel() 61 | 62 | ciphertext := Encrypt(nil, dS, dE, qS, qE, qR, message) 63 | ciphertext[0] ^= 1 64 | 65 | if _, _, err := Decrypt(nil, dR, qR, qS, ciphertext); err == nil { 66 | t.Fatal("should not have decrypted") 67 | } 68 | } 69 | 70 | func TestBadCiphertext(t *testing.T) { 71 | t.Parallel() 72 | 73 | ciphertext := Encrypt(nil, dS, dE, qS, qE, qR, message) 74 | ciphertext[internal.ElementSize+5] ^= 1 75 | 76 | if _, _, err := Decrypt(nil, dR, qR, qS, ciphertext); err == nil { 77 | t.Fatal("should not have decrypted") 78 | } 79 | } 80 | 81 | func TestBadMAC(t *testing.T) { 82 | t.Parallel() 83 | 84 | ciphertext := Encrypt(nil, dS, dE, qS, qE, qR, message) 85 | ciphertext[len(ciphertext)-4] ^= 1 86 | 87 | if _, _, err := Decrypt(nil, dR, qR, qS, ciphertext); err == nil { 88 | t.Fatal("should not have decrypted") 89 | } 90 | } 91 | 92 | func BenchmarkEncrypt(b *testing.B) { 93 | for i := 0; i < b.N; i++ { 94 | _ = Encrypt(nil, dS, dE, qS, qE, qR, message) 95 | } 96 | } 97 | 98 | func BenchmarkDecrypt(b *testing.B) { 99 | ciphertext := Encrypt(nil, dS, dE, qS, qE, qR, message) 100 | 101 | for i := 0; i < b.N; i++ { 102 | _, _, _ = Decrypt(nil, dR, qR, qS, ciphertext) 103 | } 104 | } 105 | 106 | //nolint:gochecknoglobals // constants 107 | var ( 108 | dS = ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0x4f}, internal.UniformBytestringSize)) 109 | qS = ristretto255.NewElement().ScalarBaseMult(dS) 110 | dE = ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0x69}, internal.UniformBytestringSize)) 111 | qE = ristretto255.NewElement().ScalarBaseMult(dE) 112 | dR = ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0x22}, internal.UniformBytestringSize)) 113 | qR = ristretto255.NewElement().ScalarBaseMult(dR) 114 | 115 | message = []byte("hello this is dog") 116 | ) 117 | -------------------------------------------------------------------------------- /pkg/veil/internal/internal.go: -------------------------------------------------------------------------------- 1 | // Package internal contains various helper functions for writing STROBE protocols. 2 | // 3 | // The subpackages of internal contain all the various STROBE protocols Veil uses. 4 | package internal 5 | 6 | import ( 7 | "crypto/rand" 8 | "errors" 9 | "math/big" 10 | 11 | "github.com/mr-tron/base58" 12 | ) 13 | 14 | // ErrInvalidCiphertext is returned when the ciphertext cannot be decrypted. 15 | var ErrInvalidCiphertext = errors.New("invalid ciphertext") 16 | 17 | const ( 18 | // MACSize is the MAC size in bytes. 19 | MACSize = 16 20 | 21 | // ElementSize is the length of an encoded ristretto255 element. 22 | ElementSize = 32 23 | 24 | // ScalarSize is the length of an encoded ristretto255 scalar. 25 | ScalarSize = 32 26 | 27 | // UniformBytestringSize is the length of a uniform bytestring which can be mapped to either a 28 | // ristretto255 element or scalar. 29 | UniformBytestringSize = 64 30 | ) 31 | 32 | // IntN returns a cryptographically random integer selected uniformly from [0,max). 33 | func IntN(max int) (int, error) { 34 | n, err := rand.Int(rand.Reader, big.NewInt(int64(max))) 35 | if err != nil { 36 | return 0, err 37 | } 38 | 39 | return int(n.Int64()), nil 40 | } 41 | 42 | // ASCIIEncode returns the given data, encoded in base58. 43 | func ASCIIEncode(data []byte) []byte { 44 | return []byte(base58.Encode(data)) 45 | } 46 | 47 | // ASCIIDecode decodes the given base58 text. 48 | func ASCIIDecode(text []byte) ([]byte, error) { 49 | return base58.Decode(string(text)) 50 | } 51 | 52 | // SliceForAppend takes a slice and a requested number of bytes. It returns a slice with the 53 | // contents of the given slice followed by that many bytes and a second slice that aliases into it 54 | // and contains only the extra bytes. If the original slice has sufficient capacity then no 55 | // allocation is performed. 56 | func SliceForAppend(in []byte, n int) (head, tail []byte) { 57 | if total := len(in) + n; cap(in) >= total { 58 | head = in[:total] 59 | } else { 60 | head = make([]byte, total) 61 | copy(head, in) 62 | } 63 | 64 | tail = head[len(in):] 65 | 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /pkg/veil/internal/internal_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "testing" 4 | 5 | func TestIntN(t *testing.T) { 6 | t.Parallel() 7 | 8 | for i := 0; i < 10_000; i++ { 9 | j, err := IntN(10_000) 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | 14 | if 0 > j || j >= 10_000 { 15 | t.Fatalf("%d is outside [0,10_000)", j) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/veil/internal/mres/mres.go: -------------------------------------------------------------------------------- 1 | // Package mres provides the underlying STROBE protocol for Veil's multi-recipient encryption 2 | // system. 3 | // 4 | // Encryption 5 | // 6 | // Encrypting a message begins as follows, given the sender's key pair, d_s and Q_s, a plaintext 7 | // message in blocks P_0…P_n, a list of recipient public keys, Q_r0..Q_rm, and a DEK size N_dek: 8 | // 9 | // INIT('veil.mres', level=256) 10 | // AD(LE_32(N_dek), meta=true) 11 | // AD(Q_s) 12 | // 13 | // The protocol context is cloned and keyed with the sender's private key and a random nonce and used 14 | // to derive a data encryption key, K_dek, and an ephemeral private key, d_e: 15 | // 16 | // KEY(d_s) 17 | // KEY(96) 18 | // PRF(32) -> K_dek 19 | // PRF(64) -> d_e 20 | // 21 | // The ephemeral public key is computed and the cloned context is discarded: 22 | // 23 | // Q_e = G^d_e 24 | // 25 | // The data encryption key and the message offset are encoded into a fixed-length header and copies 26 | // of it are encrypted with veil.hpke for each recipient using d_e and Q_e. Optional random padding 27 | // is added to the end, and the resulting block H is written: 28 | // 29 | // SEND_CLR(H) 30 | // 31 | // The protocol is keyed with the DEK and the encrypted message is written: 32 | // 33 | // KEY(K_dek) 34 | // SEND_ENC('') 35 | // SEND_ENC(P_0, more=true) 36 | // … 37 | // SEND_ENC(P_n, more=true) 38 | // 39 | // Finally, a Schnorr signature S of the entire ciphertext (headers, padding, and DEM ciphertext) is 40 | // created with d_e and encrypted: 41 | // 42 | // SEND_ENC(S) 43 | // 44 | // The resulting ciphertext then contains, in order: the veil.hpke-encrypted headers, random 45 | // padding, message ciphertext, and a Schnorr signature of the headers, padding, and ciphertext. 46 | // 47 | // Decryption 48 | // 49 | // Decryption begins as follows, given the recipient's key pair, d_r and Q_r, the sender's public 50 | // key, Q_s: 51 | // 52 | // INIT('veil.mres', level=256) 53 | // AD(LE_32(N_dek), meta=true) 54 | // AD(Q_s) 55 | // 56 | // The recipient reads through the ciphertext in header-sized blocks, looking for one which is 57 | // decryptable given their key pair and the sender's public key. Having found one, they recover the 58 | // data encryption key K_dek, the message offset, and the ephemeral public key Q_e. 59 | // They then read the remainder of the block of encrypted headers and padding H: 60 | // 61 | // RECV_CLR(H) 62 | // 63 | // The protocol is keyed with the DEK and the plaintext decrypted: 64 | // 65 | // KEY(K_dek) 66 | // RECV_ENC('') 67 | // RECV_ENC(C_0, more=true) 68 | // … 69 | // RECV_ENC(C_n, more=true) 70 | // 71 | // Finally, the signature S is decrypted and verified against the entire ciphertext: 72 | // 73 | // RECV_ENC(S) 74 | // 75 | // Multi-Recipient Confidentiality 76 | // 77 | // To evaluate the confidentiality of this construction, consider an attacker provided with an 78 | // encryption oracle for the sender's private key and a decryption oracle for each recipient, 79 | // engaged in an IND-CCA2 game with the goal of gaining an advantage against any individual 80 | // recipient. The elements they have to analyze and manipulate are the encrypted headers, the random 81 | // padding, the message ciphertext, and the signature. 82 | // 83 | // Each recipient's header is an IND-CCA2-secure ciphertext, so an attacker can gain no advantage 84 | // there. Further, the attacker cannot modify the copy of the DEK, the ephemeral public key, or the 85 | // header length each recipient receives. 86 | // 87 | // The encrypted headers and/or padding for other recipients are not IND-CCA2-secure for all 88 | // recipients, so the attacker may modify those without producing invalid headers. Similarly, the 89 | // encrypted message is only IND-CPA-secure. Any attacker attempting to modify any of those, 90 | // however, will have to forge a valid signature for the overall message to be valid. As 91 | // veil.schnorr is SUF-CMA-secure, this is not possible. 92 | // 93 | // Multi-Recipient Authenticity 94 | // 95 | // Similarly, an attacker engaged in parallel CMA games with recipients has negligible advantage in 96 | // forging messages. The veil.schnorr signature covers the entirety of the ciphertext. 97 | // 98 | // The standard KEM/DEM hybrid construction (i.e. Construction 12.20 from Modern Cryptography 3e) 99 | // provides strong confidentiality (per Theorem 12.14), but no authenticity. A compromised recipient 100 | // can replace the DEM component of the ciphertext with an arbitrary message encrypted with the same 101 | // DEK. Even if the KEM provides strong authenticity against insider attacks, the KEM/DEM 102 | // construction does not. Alwen et al. (https://eprint.iacr.org/2020/1499.pdf) detail this attack 103 | // against the proposed HPKE standard. 104 | // 105 | // In the single-recipient setting, the practical advantages of this attack are limited: the 106 | // attacker can forge messages which appear to be from a sender but are only decryptable by the 107 | // attacker. In the multi-recipient setting, however, the practical advantage is much greater: the 108 | // attacker can present forged messages which appear to be from a sender to other, honest 109 | // recipients. 110 | // 111 | // veil.mres eliminates this attack by using the ephemeral key pair to sign the entire ciphertext 112 | // and including only the public key in the KEM ciphertext. Re-using the KEM ciphertexts with a new 113 | // message requires forging a new signature for a SUF-CMA-secure scheme. The use of an authenticated 114 | // KEM serves to authenticate the ephemeral public key and thus the message: only the possessor of 115 | // the sender's private key can calculate the static shared secret used to encrypt the ephemeral 116 | // public key, and the recipient can only forge KEM ciphertexts with themselves as the intended 117 | // recipient. 118 | // 119 | // Repudiability 120 | // 121 | // Because the sender's private key is only used to calculate shared secrets, a veil.mres ciphertext 122 | // is entirely repudiable unless a recipient reveals their public key. The veil.schnorr keys are 123 | // randomly generated for each message and all other forms of sender identity which are transmitted 124 | // are only binding on public information. 125 | // 126 | // Randomness Re-Use 127 | // 128 | // The ephemeral key pair, d_e and Q_e, are used multiple times: once for each veil.hpke header and 129 | // finally once for the end signature. This improves the efficiency of the scheme without reducing 130 | // its security, per Bellare et al.'s treatment of Randomness Reusing Multi-Recipient Encryption 131 | // Schemes (http://cseweb.ucsd.edu/~Mihir/papers/bbs.pdf). 132 | // 133 | // Ephemeral Scalar Hedging 134 | // 135 | // In deriving the DEK and ephemeral scalar from a cloned context, veil.mres uses Aranha et al.'s "hedged 136 | // signature" technique (https://eprint.iacr.org/2019/956.pdf) to mitigate against both catastrophic 137 | // randomness failures and differential fault attacks against purely deterministic signature 138 | // schemes. 139 | // 140 | package mres 141 | 142 | import ( 143 | "crypto/rand" 144 | "encoding/binary" 145 | "errors" 146 | "fmt" 147 | "io" 148 | 149 | "github.com/codahale/veil/pkg/veil/internal" 150 | "github.com/codahale/veil/pkg/veil/internal/hpke" 151 | "github.com/codahale/veil/pkg/veil/internal/protocol" 152 | "github.com/codahale/veil/pkg/veil/internal/schnorr" 153 | "github.com/codahale/veil/pkg/veil/internal/schnorr/sigio" 154 | "github.com/gtank/ristretto255" 155 | ) 156 | 157 | // Encrypt reads the contents of src, encrypts them such that all members of qRs will be able to 158 | // decrypt and authenticate them, and writes the encrypted contents to dst. 159 | func Encrypt( 160 | dst io.Writer, src io.Reader, dS *ristretto255.Scalar, qS *ristretto255.Element, 161 | qRs []*ristretto255.Element, padding int, 162 | ) (written int64, err error) { 163 | // Initialize the protocol. 164 | mres := initProtocol(qS) 165 | 166 | // Clone the protocol. 167 | clone := mres.Clone() 168 | 169 | // Key the clone with the sender's private key. 170 | clone.KEY(dS.Encode(nil)) 171 | 172 | // Key the clone with a random key. 173 | if err := clone.KEYRand(dekSize + internal.UniformBytestringSize); err != nil { 174 | return 0, fmt.Errorf("error generating random value: %w", err) 175 | } 176 | 177 | // Generate a DEK with the clone. 178 | dek := clone.PRF(nil, dekSize) 179 | 180 | // Generate an ephemeral key pair with the clone. 181 | dE := clone.PRFScalar() 182 | qE := ristretto255.NewElement().ScalarBaseMult(dE) 183 | 184 | // Create a new Schnorr signer and ensure all headers are signed and hashed. 185 | signer := schnorr.NewSigner(dst) 186 | headers := mres.SendCLRStream(signer) 187 | 188 | // Encode header and allocate buffer for header ciphertexts. 189 | header := encodeHeader(dek, encryptedHeaderSize*uint64(len(qRs))+uint64(padding)) 190 | encHeader := make([]byte, encryptedHeaderSize) 191 | 192 | // Encrypt, and write copies of the header for each recipient. 193 | for _, qR := range qRs { 194 | encHeader = hpke.Encrypt(encHeader[:0], dS, dE, qS, qE, qR, header) 195 | n, err := headers.Write(encHeader) 196 | written += int64(n) 197 | 198 | if err != nil { 199 | return written, err 200 | } 201 | } 202 | 203 | // Add padding to the end of the headers. 204 | pn, err := io.CopyN(headers, rand.Reader, int64(padding)) 205 | written += pn 206 | 207 | if err != nil { 208 | return written, err 209 | } 210 | 211 | // Key the protocol with the DEK. 212 | mres.KEY(dek) 213 | 214 | // Encrypt the plaintext and send the ciphertext. 215 | pn, err = io.Copy(mres.SendENCStream(signer), src) 216 | written += pn 217 | 218 | if err != nil { 219 | return written, err 220 | } 221 | 222 | // Create a Schnorr signature of the entire ciphertext. 223 | sig, err := signer.Sign(dE, qE) 224 | if err != nil { 225 | return written, err 226 | } 227 | 228 | // Encrypt and write the signature. 229 | n, err := dst.Write(mres.SendENC(sig[:0], sig)) 230 | written += int64(n) 231 | 232 | return written, err 233 | } 234 | 235 | // Decrypt reads the contents of src, decrypts them iff the owner of qS encrypted them for 236 | // decryption by dR, and writes the decrypted contents to dst. 237 | func Decrypt(dst io.Writer, src io.Reader, dR *ristretto255.Scalar, qR, qS *ristretto255.Element) (int64, error) { 238 | mres := initProtocol(qS) 239 | 240 | // Create a new Schnorr verifier. 241 | verifier := schnorr.NewVerifier(io.Discard) 242 | 243 | // Find a decryptable header and pass the headers through the protocol and the verifier. 244 | dek, qE, err := findHeader(mres.RecvCLRStream(verifier), src, dR, qR, qS) 245 | if err != nil { 246 | return 0, err 247 | } 248 | 249 | // Key the protocol with the DEK. 250 | mres.KEY(dek) 251 | 252 | // Detach the signature from the end of the ciphertext. 253 | sigr := sigio.NewReader(src) 254 | 255 | // Decrypt the message ciphertext and write it to dst. 256 | pn, err := io.Copy(io.MultiWriter(mres.RecvENCStream(dst), verifier), sigr) 257 | if err != nil { 258 | return pn, err 259 | } 260 | 261 | // Decrypt and verify the signature. 262 | if !verifier.Verify(qE, mres.RecvENC(sigr.Signature[:0], sigr.Signature)) { 263 | return pn, internal.ErrInvalidCiphertext 264 | } 265 | 266 | return pn, nil 267 | } 268 | 269 | func findHeader( 270 | dst io.Writer, src io.Reader, dR *ristretto255.Scalar, qR, qS *ristretto255.Element, 271 | ) ([]byte, *ristretto255.Element, error) { 272 | headerOffset := int64(0) 273 | encHeader := make([]byte, encryptedHeaderSize) 274 | 275 | // Iterate through the possible headers, attempting to decrypt each of them. 276 | for { 277 | // Read a possible header. If we hit the end of the file, we didn't find a header we could 278 | // decrypt, so the ciphertext is invalid. 279 | _, err := io.ReadFull(src, encHeader) 280 | if err != nil { 281 | return nil, nil, invalidCiphertextIfEOF(err) 282 | } 283 | 284 | // Record the encrypted headers in the STROBE protocol and the Schnorr verifier. 285 | _, _ = dst.Write(encHeader) 286 | headerOffset += encryptedHeaderSize 287 | 288 | // Try to decrypt the header. 289 | qE, plaintext, err := hpke.Decrypt(encHeader[:0], dR, qR, qS, encHeader) 290 | if err != nil { 291 | // If we can't decrypt it, try the next one. 292 | continue 293 | } 294 | 295 | // Read the remaining headers and any padding. 296 | messageOffset := int64(binary.LittleEndian.Uint64(plaintext[dekSize:])) 297 | if _, err := io.CopyN(dst, src, messageOffset-headerOffset); err != nil { 298 | return nil, nil, err 299 | } 300 | 301 | // Return the authenticated DEK and the ephemeral public key. 302 | return plaintext[:dekSize], qE, nil 303 | } 304 | } 305 | 306 | func initProtocol(qS *ristretto255.Element) *protocol.Protocol { 307 | // Initialize a new protocol. 308 | mres := protocol.New("veil.mres") 309 | 310 | // Add the DEK size as associated metadata. 311 | mres.MetaAD(protocol.LittleEndianU32(dekSize)) 312 | 313 | // Add the sender's public key as associated data. 314 | mres.AD(qS.Encode(nil)) 315 | 316 | return mres 317 | } 318 | 319 | func invalidCiphertextIfEOF(err error) error { 320 | if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) { 321 | return internal.ErrInvalidCiphertext 322 | } 323 | 324 | return err 325 | } 326 | 327 | func encodeHeader(dek []byte, messageOffset uint64) []byte { 328 | header := make([]byte, dekSize+8, headerSize) 329 | copy(header, dek) 330 | 331 | binary.LittleEndian.PutUint64(header[dekSize:], messageOffset) 332 | 333 | return header 334 | } 335 | 336 | const ( 337 | dekSize = 32 338 | headerSize = dekSize + 8 339 | encryptedHeaderSize = headerSize + hpke.Overhead 340 | ) 341 | -------------------------------------------------------------------------------- /pkg/veil/internal/mres/mres_test.go: -------------------------------------------------------------------------------- 1 | package mres 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | 8 | "github.com/codahale/gubbins/assert" 9 | "github.com/codahale/veil/pkg/veil/internal" 10 | "github.com/google/go-cmp/cmp/cmpopts" 11 | "github.com/gtank/ristretto255" 12 | ) 13 | 14 | func TestRoundTrip(t *testing.T) { 15 | t.Parallel() 16 | 17 | plaintext := []byte("this is a welcome change from previous events") 18 | src := bytes.NewReader(plaintext) 19 | dst := bytes.NewBuffer(nil) 20 | 21 | qA := ristretto255.NewElement().FromUniformBytes(bytes.Repeat([]byte{0xf5}, internal.UniformBytestringSize)) 22 | qB := ristretto255.NewElement().FromUniformBytes(bytes.Repeat([]byte{0xf5}, internal.UniformBytestringSize)) 23 | qC := ristretto255.NewElement().FromUniformBytes(bytes.Repeat([]byte{0xf5}, internal.UniformBytestringSize)) 24 | 25 | n, err := Encrypt(dst, src, dS, qS, []*ristretto255.Element{qR, qA, qB, qC}, 322) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | assert.Equal(t, "written bytes", int64(dst.Len()), n) 31 | 32 | src = bytes.NewReader(dst.Bytes()) 33 | dst = bytes.NewBuffer(nil) 34 | 35 | n, err = Decrypt(dst, src, dR, qR, qS) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | assert.Equal(t, "written bytes", int64(dst.Len()), n) 41 | assert.Equal(t, "plaintext", plaintext, dst.Bytes()) 42 | } 43 | 44 | func TestBadSender(t *testing.T) { 45 | t.Parallel() 46 | 47 | plaintext := []byte("this is a welcome change from previous events") 48 | src := bytes.NewReader(plaintext) 49 | dst := bytes.NewBuffer(nil) 50 | 51 | n, err := Encrypt(dst, src, dS, qS, []*ristretto255.Element{qR}, 0) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | assert.Equal(t, "written bytes", int64(dst.Len()), n) 57 | 58 | src = bytes.NewReader(dst.Bytes()) 59 | dst = bytes.NewBuffer(nil) 60 | 61 | n, err = Decrypt(dst, src, dR, qR, qR) 62 | 63 | assert.Equal(t, "written bytes", int64(0), n) 64 | assert.Equal(t, "error", internal.ErrInvalidCiphertext, err, cmpopts.EquateErrors()) 65 | } 66 | 67 | func TestBadRecipient(t *testing.T) { 68 | t.Parallel() 69 | 70 | plaintext := []byte("this is a welcome change from previous events") 71 | src := bytes.NewReader(plaintext) 72 | dst := bytes.NewBuffer(nil) 73 | 74 | n, err := Encrypt(dst, src, dS, qS, []*ristretto255.Element{qR}, 0) 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | 79 | assert.Equal(t, "written bytes", int64(dst.Len()), n) 80 | 81 | src = bytes.NewReader(dst.Bytes()) 82 | dst = bytes.NewBuffer(nil) 83 | 84 | n, err = Decrypt(dst, src, dS, qS, qS) 85 | 86 | assert.Equal(t, "written bytes", int64(0), n) 87 | assert.Equal(t, "error", internal.ErrInvalidCiphertext, err, cmpopts.EquateErrors()) 88 | } 89 | 90 | func TestBadFooter(t *testing.T) { 91 | t.Parallel() 92 | 93 | plaintext := []byte("this is a welcome change from previous events") 94 | src := bytes.NewReader(plaintext) 95 | dst := bytes.NewBuffer(nil) 96 | 97 | n, err := Encrypt(dst, src, dS, qS, []*ristretto255.Element{qR}, 0) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | 102 | assert.Equal(t, "written bytes", int64(dst.Len()), n) 103 | 104 | // Poison the footer. 105 | ct := dst.Bytes() 106 | ct[len(ct)-internal.MACSize-3] ^= 1 107 | 108 | src = bytes.NewReader(ct) 109 | dst = bytes.NewBuffer(nil) 110 | 111 | _, err = Decrypt(dst, src, dR, qR, qS) 112 | 113 | assert.Equal(t, "error", internal.ErrInvalidCiphertext, err, cmpopts.EquateErrors()) 114 | } 115 | 116 | func TestBadMAC(t *testing.T) { 117 | t.Parallel() 118 | 119 | plaintext := []byte("this is a welcome change from previous events") 120 | src := bytes.NewReader(plaintext) 121 | dst := bytes.NewBuffer(nil) 122 | 123 | n, err := Encrypt(dst, src, dS, qS, []*ristretto255.Element{qR}, 0) 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | 128 | assert.Equal(t, "written bytes", int64(dst.Len()), n) 129 | 130 | // Poison the footer. 131 | ct := dst.Bytes() 132 | ct[len(ct)-3] ^= 1 133 | 134 | src = bytes.NewReader(ct) 135 | dst = bytes.NewBuffer(nil) 136 | 137 | _, err = Decrypt(dst, src, dR, qR, qS) 138 | 139 | assert.Equal(t, "error", internal.ErrInvalidCiphertext, err, cmpopts.EquateErrors()) 140 | } 141 | 142 | func TestBadCiphertext(t *testing.T) { 143 | t.Parallel() 144 | 145 | plaintext := []byte("this is a welcome change from previous events") 146 | src := bytes.NewReader(plaintext) 147 | dst := bytes.NewBuffer(nil) 148 | 149 | n, err := Encrypt(dst, src, dS, qS, []*ristretto255.Element{qR}, 0) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | 154 | assert.Equal(t, "written bytes", int64(dst.Len()), n) 155 | 156 | // Poison the ciphertext. 157 | ct := dst.Bytes() 158 | ct[0] ^= 1 159 | 160 | src = bytes.NewReader(ct) 161 | dst = bytes.NewBuffer(nil) 162 | 163 | _, err = Decrypt(dst, src, dR, qR, qS) 164 | 165 | assert.Equal(t, "error", internal.ErrInvalidCiphertext, err, cmpopts.EquateErrors()) 166 | } 167 | 168 | func BenchmarkEncrypt(b *testing.B) { 169 | plaintext := make([]byte, 1024*1024) 170 | recipients := []*ristretto255.Element{qR, qS, qR, qS, qR, qS} 171 | 172 | for i := 0; i < b.N; i++ { 173 | _, err := Encrypt(io.Discard, bytes.NewReader(plaintext), dS, qS, recipients, 0) 174 | if err != nil { 175 | b.Fatal(err) 176 | } 177 | } 178 | } 179 | 180 | func BenchmarkDecrypt(b *testing.B) { 181 | plaintext := make([]byte, 1024*1024) 182 | recipients := []*ristretto255.Element{qR, qS, qR, qS, qR, qS} 183 | out := bytes.NewBuffer(nil) 184 | 185 | _, err := Encrypt(out, bytes.NewReader(plaintext), dS, qS, recipients, 0) 186 | if err != nil { 187 | b.Fatal(err) 188 | } 189 | 190 | ciphertext := out.Bytes() 191 | 192 | for i := 0; i < b.N; i++ { 193 | _, err := Decrypt(io.Discard, bytes.NewReader(ciphertext), dR, qR, qS) 194 | if err != nil { 195 | b.Fatal(err) 196 | } 197 | } 198 | } 199 | 200 | //nolint:gochecknoglobals // constants 201 | var ( 202 | dS = ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0x4f}, internal.UniformBytestringSize)) 203 | qS = ristretto255.NewElement().ScalarBaseMult(dS) 204 | dR = ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0x22}, internal.UniformBytestringSize)) 205 | qR = ristretto255.NewElement().ScalarBaseMult(dR) 206 | ) 207 | -------------------------------------------------------------------------------- /pkg/veil/internal/pbenc/pbenc.go: -------------------------------------------------------------------------------- 1 | // Package pbenc implements memory-hard password-based encryption via STROBE using balloon hashing. 2 | // 3 | // The protocol is initialized as follows, given a passphrase P, salt S, delta constant D, space 4 | // parameter N_space, time parameter N_time, block size N_block, and MAC size N_mac: 5 | // 6 | // INIT('veil.kdf.balloon', level=256) 7 | // AD(LE_U32(D), meta=true) 8 | // AD(LE_U32(N_block), meta=true) 9 | // AD(LE_U32(N_mac), meta=true) 10 | // AD(LE_U32(N_time), meta=true) 11 | // AD(LE_U32(N_space), meta=true) 12 | // KEY(P) 13 | // AD(S) 14 | // 15 | // Then, for each iteration of the balloon hashing algorithm, given a counter C, a left block L, and 16 | // a right block R: 17 | // 18 | // AD(LE_U64(C)) 19 | // AD(L) 20 | // AD(R) 21 | // PRF(N) 22 | // 23 | // The final block B_n of the balloon hashing algorithm is then used to key the protocol: 24 | // 25 | // KEY(B_n) 26 | // 27 | // Encryption of a message M is as follows: 28 | // 29 | // SEND_ENC(M) 30 | // SEND_MAC(T) 31 | // 32 | // The ciphertext C and MAC T are returned. 33 | // 34 | // Decryption of a ciphertext C and MAC T is as follows: 35 | // 36 | // RECV_ENC(C) 37 | // RECV_MAC(T) 38 | // 39 | // If the RECV_MAC call is successful, the plaintext is returned. 40 | // 41 | // It should be noted that there is no standard balloon hashing algorithm, so this protocol is in 42 | // the very, very tall grass of cryptography and should never be used. 43 | // 44 | // See https://eprint.iacr.org/2016/027.pdf 45 | package pbenc 46 | 47 | import ( 48 | "encoding/binary" 49 | 50 | "github.com/codahale/veil/pkg/veil/internal" 51 | "github.com/codahale/veil/pkg/veil/internal/protocol" 52 | ) 53 | 54 | const Overhead = internal.MACSize 55 | 56 | // Encrypt encrypts the plaintext with the passphrase and salt. 57 | func Encrypt(passphrase, salt, plaintext []byte, space, time int) []byte { 58 | pbenc := initProtocol(passphrase, salt, space, time) 59 | ciphertext := make([]byte, 0, len(plaintext)+internal.MACSize) 60 | ciphertext = pbenc.SendENC(ciphertext, plaintext) 61 | ciphertext = pbenc.SendMAC(ciphertext) 62 | 63 | return ciphertext 64 | } 65 | 66 | // Decrypt decrypts the ciphertext with the passphrase and salt. 67 | func Decrypt(passphrase, salt, ciphertext []byte, space, time int) ([]byte, error) { 68 | pbenc := initProtocol(passphrase, salt, space, time) 69 | 70 | plaintext := pbenc.RecvENC(nil, ciphertext[:len(ciphertext)-internal.MACSize]) 71 | 72 | if err := pbenc.RecvMAC(ciphertext[len(ciphertext)-internal.MACSize:]); err != nil { 73 | return nil, internal.ErrInvalidCiphertext 74 | } 75 | 76 | return plaintext, nil 77 | } 78 | 79 | // initProtocol initializes a new STROBE protocol and executes the Balloon Hashing algorithm. The 80 | // final block is then used to key the protocol. 81 | func initProtocol(passphrase, salt []byte, space, time int) *protocol.Protocol { 82 | // Initialize a new protocol. 83 | pbenc := protocol.New("veil.pbenc") 84 | 85 | // Include the delta constant as associated data. 86 | pbenc.MetaAD(protocol.LittleEndianU32(delta)) 87 | 88 | // Include the block size constant as associated data. 89 | pbenc.MetaAD(protocol.LittleEndianU32(n)) 90 | 91 | // Include the MAC size constant as associated data. 92 | pbenc.MetaAD(protocol.LittleEndianU32(internal.MACSize)) 93 | 94 | // Include the space parameter as associated data. 95 | pbenc.MetaAD(protocol.LittleEndianU32(space)) 96 | 97 | // Include the time parameter as associated data. 98 | pbenc.MetaAD(protocol.LittleEndianU32(time)) 99 | 100 | // Key the protocol with the passphrase. 101 | pbenc.KEY(passphrase) 102 | 103 | // Include the salt as associated data. 104 | pbenc.AD(salt) 105 | 106 | // Allocate a 64-bit counter and a buffer for its encoding. 107 | ctr := uint64(0) 108 | ctrBuf := make([]byte, 8) 109 | 110 | // Allocate an index block and the main buffer. 111 | idx := make([]byte, n) 112 | buf := make([]byte, space*n) 113 | 114 | // Step 1: Expand input into buffer. 115 | hashCounter(pbenc, &ctr, ctrBuf, buf[0:n], passphrase, salt) 116 | 117 | for m := 1; m < space-1; m++ { 118 | hashCounter(pbenc, &ctr, ctrBuf, buf[(m*n):(m*n)+n], buf[(m-1)*n:(m-1)*n+n], nil) 119 | } 120 | 121 | // Step 2: Mix buffer contents. 122 | for t := 1; t < time; t++ { 123 | for m := 1; m < space; m++ { 124 | // Step 2a: Hash last and current blocks. 125 | prev := (m - 1) % space 126 | hashCounter(pbenc, &ctr, ctrBuf, buf[m*n:m*n+n], buf[prev*n:prev*n+n], buf[m*n:m*n+n]) 127 | 128 | // Step 2b: Hash in pseudo-randomly chosen blocks. 129 | for i := 0; i < delta; i++ { 130 | // Map indexes to a block and hash it and the salt. 131 | binary.LittleEndian.PutUint32(idx[0:], uint32(t)) 132 | binary.LittleEndian.PutUint32(idx[4:], uint32(m)) 133 | binary.LittleEndian.PutUint32(idx[8:], uint32(i)) 134 | hashCounter(pbenc, &ctr, ctrBuf, idx, salt, idx) 135 | 136 | // Map the hashed index block back to an index and hash that block. 137 | other := int(binary.LittleEndian.Uint64(idx) % uint64(space)) 138 | hashCounter(pbenc, &ctr, ctrBuf, buf[m*n:m*n+n], buf[other*n:other*n+n], nil) 139 | } 140 | } 141 | } 142 | 143 | // Step 3: Extract output from buffer. 144 | pbenc.KEY(buf[(space-1)*n:]) 145 | 146 | return pbenc 147 | } 148 | 149 | func hashCounter(pbenc *protocol.Protocol, ctr *uint64, ctrBuf, dst, left, right []byte) { 150 | // Increment the counter. 151 | *ctr++ 152 | 153 | // Encode the counter as a little-endian value. 154 | binary.LittleEndian.PutUint64(ctrBuf, *ctr) 155 | 156 | // Hash the counter. 157 | pbenc.AD(ctrBuf) 158 | 159 | // Hash the left block. 160 | pbenc.AD(left) 161 | 162 | // Hash the right block. 163 | pbenc.AD(right) 164 | 165 | // Extract a new block. 166 | pbenc.PRF(dst[:0], n) 167 | } 168 | 169 | const ( 170 | n = 32 // n is the size of a block, in bytes. 171 | delta = 3 // delta is the number of dependencies per block. 172 | ) 173 | -------------------------------------------------------------------------------- /pkg/veil/internal/pbenc/pbenc_test.go: -------------------------------------------------------------------------------- 1 | package pbenc 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/codahale/gubbins/assert" 8 | "github.com/codahale/veil/pkg/veil/internal" 9 | "github.com/google/go-cmp/cmp/cmpopts" 10 | ) 11 | 12 | func TestRoundTrip(t *testing.T) { 13 | t.Parallel() 14 | 15 | passphrase := []byte("this is a secure thing") 16 | salt := bytes.Repeat([]byte{0x23}, 32) 17 | message := []byte("this is a real message") 18 | 19 | ciphertext := Encrypt(passphrase, salt, message, 256, 64) 20 | 21 | plaintext, err := Decrypt(passphrase, salt, ciphertext, 256, 64) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | assert.Equal(t, "plaintext", message, plaintext) 27 | } 28 | 29 | func TestBadPassphrase(t *testing.T) { 30 | t.Parallel() 31 | 32 | passphrase := []byte("this is a secure thing") 33 | salt := bytes.Repeat([]byte{0x23}, 32) 34 | message := []byte("this is a real message") 35 | 36 | ciphertext := Encrypt(passphrase, salt, message, 256, 64) 37 | _, err := Decrypt([]byte("boop"), salt, ciphertext, 256, 64) 38 | 39 | assert.Equal(t, "error", internal.ErrInvalidCiphertext, err, cmpopts.EquateErrors()) 40 | } 41 | 42 | func TestBadSalt(t *testing.T) { 43 | t.Parallel() 44 | 45 | passphrase := []byte("this is a secure thing") 46 | salt := bytes.Repeat([]byte{0x23}, 32) 47 | message := []byte("this is a real message") 48 | 49 | ciphertext := Encrypt(passphrase, salt, message, 256, 64) 50 | _, err := Decrypt(passphrase, []byte("boop"), ciphertext, 256, 64) 51 | 52 | assert.Equal(t, "error", internal.ErrInvalidCiphertext, err, cmpopts.EquateErrors()) 53 | } 54 | 55 | func TestBadSpace(t *testing.T) { 56 | t.Parallel() 57 | 58 | passphrase := []byte("this is a secure thing") 59 | salt := bytes.Repeat([]byte{0x23}, 32) 60 | message := []byte("this is a real message") 61 | 62 | ciphertext := Encrypt(passphrase, salt, message, 256, 64) 63 | _, err := Decrypt(passphrase, salt, ciphertext, 128, 64) 64 | 65 | assert.Equal(t, "error", internal.ErrInvalidCiphertext, err, cmpopts.EquateErrors()) 66 | } 67 | 68 | func TestBadTime(t *testing.T) { 69 | t.Parallel() 70 | 71 | passphrase := []byte("this is a secure thing") 72 | salt := bytes.Repeat([]byte{0x23}, 32) 73 | message := []byte("this is a real message") 74 | 75 | ciphertext := Encrypt(passphrase, salt, message, 256, 64) 76 | _, err := Decrypt(passphrase, salt, ciphertext, 256, 32) 77 | 78 | assert.Equal(t, "error", internal.ErrInvalidCiphertext, err, cmpopts.EquateErrors()) 79 | } 80 | 81 | func BenchmarkEncrypt(b *testing.B) { 82 | passphrase := []byte("this is a secure thing") 83 | salt := bytes.Repeat([]byte{0x23}, 32) 84 | message := []byte("this is a real message") 85 | 86 | for i := 0; i < b.N; i++ { 87 | _ = Encrypt(passphrase, salt, message, 256, 64) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /pkg/veil/internal/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "io" 7 | 8 | "github.com/codahale/veil/pkg/veil/internal" 9 | "github.com/gtank/ristretto255" 10 | "github.com/sammyne/strobe" 11 | ) 12 | 13 | type Protocol struct { 14 | s *strobe.Strobe 15 | } 16 | 17 | func New(name string) *Protocol { 18 | s, err := strobe.New(name, strobe.Bit256) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | return &Protocol{s: s} 24 | } 25 | 26 | func (p *Protocol) MetaAD(data []byte) { 27 | if err := p.s.AD(data, metaOpts); err != nil { 28 | panic(err) 29 | } 30 | } 31 | 32 | func (p *Protocol) AD(data []byte) { 33 | if err := p.s.AD(data, defaultOpts); err != nil { 34 | panic(err) 35 | } 36 | } 37 | 38 | func (p *Protocol) KEY(key []byte) { 39 | k := make([]byte, len(key)) 40 | copy(k, key) 41 | 42 | if err := p.s.KEY(k, false); err != nil { 43 | panic(err) 44 | } 45 | } 46 | 47 | func (p *Protocol) KEYRand(n int) error { 48 | k := make([]byte, n) 49 | if _, err := rand.Read(k); err != nil { 50 | return err 51 | } 52 | 53 | if err := p.s.KEY(k, false); err != nil { 54 | panic(err) 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func (p *Protocol) Ratchet() { 61 | // Setting L = sec/8 bytes is sufficient when R ≥ sec/8. That is, set L to 16 bytes or 32 bytes 62 | // for Strobe-128/b and Strobe-256/b, respectively. 63 | if err := p.s.RATCHET(int(strobe.Bit256) / 8); err != nil { 64 | panic(err) 65 | } 66 | } 67 | 68 | func (p *Protocol) PRF(dst []byte, n int) []byte { 69 | ret, out := internal.SliceForAppend(dst, n) 70 | 71 | if err := p.s.PRF(out, false); err != nil { 72 | panic(err) 73 | } 74 | 75 | return ret 76 | } 77 | 78 | func (p *Protocol) PRFScalar() *ristretto255.Scalar { 79 | buf := make([]byte, internal.UniformBytestringSize) 80 | 81 | return ristretto255.NewScalar().FromUniformBytes(p.PRF(buf[:0], internal.UniformBytestringSize)) 82 | } 83 | 84 | func (p *Protocol) SendENC(dst, plaintext []byte) []byte { 85 | ret, out := internal.SliceForAppend(dst, len(plaintext)) 86 | copy(out, plaintext) 87 | 88 | if _, err := p.s.SendENC(out, defaultOpts); err != nil { 89 | panic(err) 90 | } 91 | 92 | return ret 93 | } 94 | 95 | func (p *Protocol) RecvENC(dst, ciphertext []byte) []byte { 96 | ret, out := internal.SliceForAppend(dst, len(ciphertext)) 97 | copy(out, ciphertext) 98 | 99 | if _, err := p.s.RecvENC(out, defaultOpts); err != nil { 100 | panic(err) 101 | } 102 | 103 | return ret 104 | } 105 | 106 | func (p *Protocol) SendMAC(dst []byte) []byte { 107 | ret, out := internal.SliceForAppend(dst, internal.MACSize) 108 | 109 | if err := p.s.SendMAC(out, defaultOpts); err != nil { 110 | panic(err) 111 | } 112 | 113 | return ret 114 | } 115 | 116 | func (p *Protocol) RecvMAC(mac []byte) error { 117 | m := make([]byte, len(mac)) 118 | copy(m, mac) 119 | 120 | return p.s.RecvMAC(m, defaultOpts) 121 | } 122 | 123 | func (p *Protocol) SendCLR(data []byte) { 124 | if err := p.s.SendCLR(data, defaultOpts); err != nil { 125 | panic(err) 126 | } 127 | } 128 | 129 | func (p *Protocol) RecvCLR(data []byte) { 130 | if err := p.s.RecvCLR(data, defaultOpts); err != nil { 131 | panic(err) 132 | } 133 | } 134 | 135 | func (p *Protocol) SendENCStream(dst io.Writer) io.Writer { 136 | p.SendENC(nil, nil) 137 | 138 | // Return a writer. 139 | return &callbackWriter{ 140 | callback: func(dst, b []byte) []byte { 141 | return p.moreSendENC(dst, b) 142 | }, 143 | dst: dst, 144 | } 145 | } 146 | 147 | func (p *Protocol) RecvENCStream(dst io.Writer) io.Writer { 148 | p.RecvENC(nil, nil) 149 | 150 | // Return a writer. 151 | return &callbackWriter{ 152 | callback: func(dst, b []byte) []byte { 153 | return p.moreRecvENC(dst, b) 154 | }, 155 | dst: dst, 156 | } 157 | } 158 | 159 | func (p *Protocol) RecvCLRStream(dst io.Writer) io.Writer { 160 | p.RecvCLR(nil) 161 | 162 | return &callbackWriter{ 163 | callback: func(_, b []byte) []byte { 164 | p.moreRecvCLR(b) 165 | 166 | return b 167 | }, 168 | dst: dst, 169 | } 170 | } 171 | 172 | func (p *Protocol) SendCLRStream(dst io.Writer) io.Writer { 173 | p.SendCLR(nil) 174 | 175 | return &callbackWriter{ 176 | callback: func(_, b []byte) []byte { 177 | p.moreSendCLR(b) 178 | 179 | return b 180 | }, 181 | dst: dst, 182 | } 183 | } 184 | 185 | func (p *Protocol) Clone() *Protocol { 186 | return &Protocol{s: p.s.Clone()} 187 | } 188 | 189 | func (p *Protocol) moreSendENC(dst, plaintext []byte) []byte { 190 | ret, out := internal.SliceForAppend(dst, len(plaintext)) 191 | copy(out, plaintext) 192 | 193 | if _, err := p.s.SendENC(out, streamingOpts); err != nil { 194 | panic(err) 195 | } 196 | 197 | return ret 198 | } 199 | 200 | func (p *Protocol) moreRecvENC(dst, ciphertext []byte) []byte { 201 | ret, out := internal.SliceForAppend(dst, len(ciphertext)) 202 | copy(out, ciphertext) 203 | 204 | if _, err := p.s.RecvENC(out, streamingOpts); err != nil { 205 | panic(err) 206 | } 207 | 208 | return ret 209 | } 210 | 211 | func (p *Protocol) moreSendCLR(data []byte) { 212 | if err := p.s.SendCLR(data, streamingOpts); err != nil { 213 | panic(err) 214 | } 215 | } 216 | 217 | func (p *Protocol) moreRecvCLR(data []byte) { 218 | if err := p.s.RecvCLR(data, streamingOpts); err != nil { 219 | panic(err) 220 | } 221 | } 222 | 223 | // LittleEndianU32 returns n as a 32-bit little endian bit string. 224 | func LittleEndianU32(n int) []byte { 225 | b := make([]byte, 4) 226 | 227 | binary.LittleEndian.PutUint32(b, uint32(n)) 228 | 229 | return b 230 | } 231 | 232 | type callbackWriter struct { 233 | buf []byte 234 | callback func([]byte, []byte) []byte 235 | dst io.Writer 236 | } 237 | 238 | func (w *callbackWriter) Write(p []byte) (n int, err error) { 239 | w.buf = w.callback(w.buf[:0], p) 240 | return w.dst.Write(w.buf) 241 | } 242 | 243 | //nolint:gochecknoglobals // constants 244 | var ( 245 | defaultOpts = &strobe.Options{} 246 | metaOpts = &strobe.Options{Meta: true} 247 | streamingOpts = &strobe.Options{Streaming: true} 248 | ) 249 | -------------------------------------------------------------------------------- /pkg/veil/internal/scaldf/scaldf.go: -------------------------------------------------------------------------------- 1 | // Package scaldf provides the underlying STROBE protocols for Veil's scalar derivation functions, 2 | // which derive ristretto255 scalars from other pieces of data. 3 | // 4 | // Scalars are generated as follows, given a protocol name P and datum D: 5 | // 6 | // INIT(P, level=256) 7 | // KEY(D) 8 | // PRF(64) 9 | // 10 | // The two recognized protocol identifiers are: veil.scaldf.label, used to derive delta scalars 11 | // from labels; veil.scaldf.root, used to derive root scalars from secret keys. 12 | package scaldf 13 | 14 | import ( 15 | "github.com/codahale/veil/pkg/veil/internal" 16 | "github.com/codahale/veil/pkg/veil/internal/protocol" 17 | "github.com/gtank/ristretto255" 18 | ) 19 | 20 | // RootScalar derives a root scalar from the given bytestring. 21 | func RootScalar(r *[internal.UniformBytestringSize]byte) *ristretto255.Scalar { 22 | return scalarDF("veil.scaldf.root", r[:]) 23 | } 24 | 25 | // DeriveElement securely derives a child element from a parent element given a label. Without the 26 | // label, the parent cannot be derived from the child. 27 | func DeriveElement(q *ristretto255.Element, label string) *ristretto255.Element { 28 | // Calculate the delta for the derived element. 29 | r := scalarDF("veil.scaldf.label", []byte(label)) 30 | rG := ristretto255.NewElement().ScalarBaseMult(r) 31 | 32 | // Return the given element plus the delta element. 33 | return ristretto255.NewElement().Add(q, rG) 34 | } 35 | 36 | // DeriveScalar securely derives a child scalar from a parent scalar given a label. Without the 37 | // label, the parent cannot be derived from the child. 38 | func DeriveScalar(d *ristretto255.Scalar, label string) *ristretto255.Scalar { 39 | // Calculate the delta for the derived scalar. 40 | r := scalarDF("veil.scaldf.label", []byte(label)) 41 | 42 | // Return the given scalar plus the delta scalar. 43 | return ristretto255.NewScalar().Add(d, r) 44 | } 45 | 46 | func scalarDF(proto string, data []byte) *ristretto255.Scalar { 47 | // Initialize the protocol. 48 | scaldf := protocol.New(proto) 49 | 50 | // Key the protocol with a copy of the given data. 51 | scaldf.KEY(data) 52 | 53 | // Generate 64 bytes of PRF output and map it to a scalar. 54 | return scaldf.PRFScalar() 55 | } 56 | -------------------------------------------------------------------------------- /pkg/veil/internal/scaldf/scaldf_test.go: -------------------------------------------------------------------------------- 1 | package scaldf 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/codahale/gubbins/assert" 8 | "github.com/codahale/veil/pkg/veil/internal" 9 | "github.com/gtank/ristretto255" 10 | ) 11 | 12 | func TestDerivation(t *testing.T) { 13 | t.Parallel() 14 | 15 | d0 := ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0x89}, internal.UniformBytestringSize)) 16 | q0 := ristretto255.NewElement().ScalarBaseMult(d0) 17 | 18 | // Derive the scalar and element in parallel. 19 | d1 := DeriveScalar(d0, "one") 20 | q1 := DeriveElement(q0, "one") 21 | 22 | // Calculate what the element should be. 23 | q1p := ristretto255.NewElement().ScalarBaseMult(d1) 24 | 25 | assert.Equal(t, "derived elements", q1p.String(), q1.String()) 26 | 27 | // Derive an element with a different label. 28 | if qX := DeriveElement(q0, "two"); qX.Equal(q1) == 1 { 29 | t.Errorf("%s and %s should not be equal", qX, q1) 30 | } 31 | 32 | // Derive a scalar with a different label. 33 | if dX := DeriveScalar(d0, "two"); dX.Equal(d1) == 1 { 34 | t.Errorf("%s and %s should not be equal", dX, d1) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/veil/internal/schnorr/schnorr.go: -------------------------------------------------------------------------------- 1 | // Package schnorr provides the underlying STROBE protocol for Veil's Schnorr signatures. 2 | // 3 | // Signing A Message 4 | // 5 | // Signing is as follows, given a message in blocks M_0…M_n, a private scalar d, and a public 6 | // element Q: 7 | // 8 | // INIT('veil.schnorr', level=256) 9 | // SEND_CLR('', more=false) 10 | // SEND_CLR(M_0, more=true) 11 | // SEND_CLR(M_1, more=true) 12 | // … 13 | // SEND_CLR(M_n, more=true) 14 | // AD(Q) 15 | // 16 | // (The signer's public key is included after the message to allow veil.mres to search for a header 17 | // without having to buffer the results.) 18 | // 19 | // The protocol's state is then cloned, the clone is keyed with 64 bytes of random data and the 20 | // signer's private key, an ephemeral scalar is derived from PRF output: 21 | // 22 | // KEY(rand(64)) 23 | // KEY(d) 24 | // PRF(64) -> r 25 | // 26 | // The clone's state is discarded, and r is returned to the parent: 27 | // 28 | // R = G^r 29 | // AD(R) 30 | // PRF(64) -> c 31 | // s = d_s*c + r 32 | // 33 | // The resulting signature consists of the two scalars, c and s. 34 | // 35 | // Verifying A Signature 36 | // 37 | // To verify, veil.schnorr is run with associated data D, message in blocks M_0…M_n, a public 38 | // element Q: 39 | // 40 | // INIT('veil.schnorr', level=256) 41 | // RECV_CLR('', more=false) 42 | // RECV_CLR(M_0, more=true) 43 | // RECV_CLR(M_1, more=true) 44 | // … 45 | // RECV_CLR(M_n, more=true) 46 | // AD(Q) 47 | // R' = Q^-c + G^s 48 | // AD(R') 49 | // PRF(64) -> c' 50 | // 51 | // Finally, the verifier compares c' == c. If the two scalars are equivalent, the signature is 52 | // valid. 53 | // 54 | // Security, Forgeability, and Malleability 55 | // 56 | // This construction is equivalent to Construction 13.12 of Modern Cryptography 3e, and is the 57 | // combination of the Fiat-Shamir transform applied to the Schnorr identification scheme, and per 58 | // Theorem 13.11, secure if the discrete-logarithm problem is hard relative to ristretto255. 59 | // 60 | // The Schnorr signature scheme is strongly unforgeable under chosen message attack (SUF-CMA) in the 61 | // random oracle model (https://www.di.ens.fr/david.pointcheval/Documents/Papers/2000_joc.pdf) and 62 | // even with practical cryptographic hash functions (http://www.neven.org/papers/schnorr.pdf). As a 63 | // consequence, the signatures are non-malleable. 64 | // 65 | // Indistinguishability and Pseudorandomness 66 | // 67 | // Per Fleischhacker et al. (https://eprint.iacr.org/2011/673.pdf), this construction produces 68 | // indistinguishable signatures (i.e., signatures which do not reveal anything about the signing key 69 | // or signed message). When encrypted with an unrelated key (i.e., via veil.mres), the construction 70 | // is isomorphic to Fleischhacker et al.'s DRPC compiler for producing pseudorandom signatures, 71 | // which are indistinguishable from random. 72 | // 73 | // Ephemeral Scalar Hedging 74 | // 75 | // In deriving the ephemeral scalar from a cloned context, veil.schnorr uses Aranha et al.'s "hedged 76 | // signature" technique (https://eprint.iacr.org/2019/956.pdf) to mitigate against both catastrophic 77 | // randomness failures and differential fault attacks against purely deterministic signature 78 | // schemes. 79 | package schnorr 80 | 81 | import ( 82 | "io" 83 | 84 | "github.com/codahale/veil/pkg/veil/internal" 85 | "github.com/codahale/veil/pkg/veil/internal/protocol" 86 | "github.com/gtank/ristretto255" 87 | ) 88 | 89 | const ( 90 | // SignatureSize is the length of a signature in bytes. 91 | SignatureSize = 2 * internal.ScalarSize 92 | ) 93 | 94 | // Signer is an io.Writer which adds written data to a STROBE protocol for signing. 95 | type Signer struct { 96 | schnorr *protocol.Protocol 97 | 98 | io.Writer 99 | } 100 | 101 | // NewSigner returns a Signer instance with the signer's key pair. 102 | func NewSigner(dst io.Writer) *Signer { 103 | // Initialize a new protocol. 104 | schnorr := protocol.New("veil.schnorr") 105 | 106 | return &Signer{schnorr: schnorr, Writer: schnorr.SendCLRStream(dst)} 107 | } 108 | 109 | // Sign uses the given key pair to construct a Schnorr signature of the previously written data. 110 | func (sn *Signer) Sign(d *ristretto255.Scalar, q *ristretto255.Element) ([]byte, error) { 111 | buf := make([]byte, SignatureSize) 112 | 113 | // Add the signer's public key to the protocol. 114 | sn.schnorr.AD(q.Encode(nil)) 115 | 116 | // Clone the protocol. 117 | clone := sn.schnorr.Clone() 118 | 119 | // Key the clone with a random key. This hedges against differential attacks against purely 120 | // deterministic signature algorithms. 121 | if err := clone.KEYRand(internal.UniformBytestringSize); err != nil { 122 | return nil, err 123 | } 124 | 125 | // Key the clone with the sender's private key. This hedges against randomness failures. The 126 | // protocol's state is already dependent on the message, making the reuse of ephemeral values 127 | // across messages impossible. 128 | clone.KEY(d.Encode(buf[:0])) 129 | 130 | // Derive an ephemeral key pair from the clone. 131 | r := clone.PRFScalar() 132 | R := ristretto255.NewElement().ScalarBaseMult(r) 133 | 134 | // Hash the ephemeral public key. 135 | sn.schnorr.AD(R.Encode(buf[:0])) 136 | 137 | // Extract a challenge scalar from the protocol state. 138 | c := sn.schnorr.PRFScalar() 139 | 140 | // Calculate the signature scalar. 141 | s := ristretto255.NewScalar().Multiply(d, c) 142 | s = s.Add(s, r) 143 | 144 | // Return the challenge and signature scalars. 145 | return s.Encode(c.Encode(buf[:0])), nil 146 | } 147 | 148 | // Verifier is an io.Writer which adds written data to a STROBE protocol for verification. 149 | type Verifier struct { 150 | schnorr *protocol.Protocol 151 | 152 | io.Writer 153 | } 154 | 155 | // NewVerifier returns a new Verifier instance. 156 | func NewVerifier(dst io.Writer) *Verifier { 157 | // Initialize a new protocol. 158 | schnorr := protocol.New("veil.schnorr") 159 | 160 | return &Verifier{schnorr: schnorr, Writer: schnorr.RecvCLRStream(dst)} 161 | } 162 | 163 | // Verify uses the given public key to verify the signature of the previously read data. 164 | func (vr *Verifier) Verify(q *ristretto255.Element, sig []byte) bool { 165 | buf := make([]byte, internal.ElementSize) 166 | 167 | // Check signature length. 168 | if len(sig) != SignatureSize { 169 | return false 170 | } 171 | 172 | // Decode the challenge scalar. 173 | c := ristretto255.NewScalar() 174 | if err := c.Decode(sig[:internal.ScalarSize]); err != nil { 175 | return false 176 | } 177 | 178 | // Decode the signature scalar. 179 | s := ristretto255.NewScalar() 180 | if err := s.Decode(sig[internal.ScalarSize:]); err != nil { 181 | return false 182 | } 183 | 184 | // Re-calculate the ephemeral public key. 185 | S := ristretto255.NewElement().ScalarBaseMult(s) 186 | Qc := ristretto255.NewElement().ScalarMult(ristretto255.NewScalar().Negate(c), q) 187 | Rp := ristretto255.NewElement().Add(S, Qc) 188 | 189 | // Add the signer's public key to the protocol. 190 | vr.schnorr.AD(q.Encode(buf[:0])) 191 | 192 | // Hash the ephemeral public key. 193 | vr.schnorr.AD(Rp.Encode(buf[:0])) 194 | 195 | // Extract a challenge scalar from the protocol state. 196 | cp := vr.schnorr.PRFScalar() 197 | 198 | // Compare the extracted challenge scalar to the received challenge scalar. 199 | return c.Equal(cp) == 1 200 | } 201 | 202 | var ( 203 | _ io.Writer = &Signer{} 204 | _ io.Writer = &Verifier{} 205 | ) 206 | -------------------------------------------------------------------------------- /pkg/veil/internal/schnorr/schnorr_test.go: -------------------------------------------------------------------------------- 1 | package schnorr 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | 8 | "github.com/codahale/veil/pkg/veil/internal" 9 | "github.com/gtank/ristretto255" 10 | ) 11 | 12 | func TestSignAndVerify(t *testing.T) { 13 | t.Parallel() 14 | 15 | // Create a fake private key. 16 | d := ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0xf2}, internal.UniformBytestringSize)) 17 | 18 | // Calculate the public key. 19 | q := ristretto255.NewElement().ScalarBaseMult(d) 20 | 21 | // Write a message to a signer. 22 | signer := NewSigner(io.Discard) 23 | if _, err := io.Copy(signer, bytes.NewBufferString("this is great")); err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | // Sign it. 28 | sig, err := signer.Sign(d, q) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | // Write a message to a verifier. 34 | verifier := NewVerifier(io.Discard) 35 | if _, err := io.Copy(verifier, bytes.NewBufferString("this is great")); err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | // Verify the signature. 40 | if !verifier.Verify(q, sig) { 41 | t.Error("didn't verify") 42 | } 43 | } 44 | 45 | func TestSignAndVerify_BadPublicKey(t *testing.T) { 46 | t.Parallel() 47 | 48 | // Create a fake private key. 49 | d := ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0xf2}, internal.UniformBytestringSize)) 50 | 51 | // Calculate the public key. 52 | q := ristretto255.NewElement().ScalarBaseMult(d) 53 | 54 | // Create a fake public key. 55 | qP := ristretto255.NewElement().FromUniformBytes(bytes.Repeat([]byte{0xf2}, internal.UniformBytestringSize)) 56 | 57 | // Write a message to a signer. 58 | signer := NewSigner(io.Discard) 59 | if _, err := io.Copy(signer, bytes.NewBufferString("this is great")); err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | // Sign it. 64 | sig, err := signer.Sign(d, q) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | // Write a message to a verifier. 70 | verifier := NewVerifier(io.Discard) 71 | if _, err := io.Copy(verifier, bytes.NewBufferString("this is great")); err != nil { 72 | t.Fatal(err) 73 | } 74 | 75 | // Verify the signature. 76 | if verifier.Verify(qP, sig) { 77 | t.Error("didn't verify") 78 | } 79 | } 80 | 81 | func TestSignAndVerify_BadMessage(t *testing.T) { 82 | t.Parallel() 83 | 84 | // Create a fake private key. 85 | d := ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0xf2}, internal.UniformBytestringSize)) 86 | 87 | // Calculate the public key. 88 | q := ristretto255.NewElement().ScalarBaseMult(d) 89 | 90 | // Write a message to a signer. 91 | signer := NewSigner(io.Discard) 92 | if _, err := io.Copy(signer, bytes.NewBufferString("this is great")); err != nil { 93 | t.Fatal(err) 94 | } 95 | 96 | // Sign it. 97 | sig, err := signer.Sign(d, q) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | 102 | // Write a different message to a verifier. 103 | verifier := NewVerifier(io.Discard) 104 | if _, err := io.Copy(verifier, bytes.NewBufferString("this is not great")); err != nil { 105 | t.Fatal(err) 106 | } 107 | 108 | // Verify the signature. 109 | if verifier.Verify(q, sig) { 110 | t.Error("did verify") 111 | } 112 | } 113 | 114 | func TestSignAndVerify_BadSig(t *testing.T) { 115 | t.Parallel() 116 | 117 | // Create a fake private key. 118 | d := ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0xf2}, internal.UniformBytestringSize)) 119 | 120 | // Calculate the public key. 121 | q := ristretto255.NewElement().ScalarBaseMult(d) 122 | 123 | // Write a message to a signer. 124 | signer := NewSigner(io.Discard) 125 | if _, err := io.Copy(signer, bytes.NewBufferString("this is great")); err != nil { 126 | t.Fatal(err) 127 | } 128 | 129 | // Sign it. 130 | sig, err := signer.Sign(d, q) 131 | if err != nil { 132 | t.Fatal(err) 133 | } 134 | 135 | // Modify the signature. 136 | sig[0] ^= 1 137 | 138 | // Write a message to a verifier. 139 | verifier := NewVerifier(io.Discard) 140 | if _, err := io.Copy(verifier, bytes.NewBufferString("this is great")); err != nil { 141 | t.Fatal(err) 142 | } 143 | 144 | // Verify the signature. 145 | if verifier.Verify(q, sig) { 146 | t.Error("did verify") 147 | } 148 | } 149 | 150 | func BenchmarkSigner(b *testing.B) { 151 | d := ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0xf2}, internal.UniformBytestringSize)) 152 | q := ristretto255.NewElement().ScalarBaseMult(d) 153 | message := make([]byte, 1024) 154 | 155 | b.ResetTimer() 156 | 157 | for i := 0; i < b.N; i++ { 158 | signer := NewSigner(io.Discard) 159 | _, _ = signer.Write(message) 160 | _, _ = signer.Sign(d, q) 161 | } 162 | } 163 | 164 | func BenchmarkVerifier(b *testing.B) { 165 | d := ristretto255.NewScalar().FromUniformBytes(bytes.Repeat([]byte{0xf2}, internal.UniformBytestringSize)) 166 | q := ristretto255.NewElement().ScalarBaseMult(d) 167 | message := make([]byte, 1024) 168 | signer := NewSigner(io.Discard) 169 | _, _ = signer.Write(message) 170 | 171 | sig, err := signer.Sign(d, q) 172 | if err != nil { 173 | b.Fatal(err) 174 | } 175 | 176 | b.ResetTimer() 177 | 178 | for i := 0; i < b.N; i++ { 179 | verifier := NewVerifier(io.Discard) 180 | _, _ = verifier.Write(message) 181 | 182 | if !verifier.Verify(q, sig) { 183 | b.Fatal("should have verified") 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /pkg/veil/internal/schnorr/sigio/sigio.go: -------------------------------------------------------------------------------- 1 | package sigio 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | 7 | "github.com/codahale/veil/pkg/veil/internal/schnorr" 8 | ) 9 | 10 | type Reader struct { 11 | Signature []byte 12 | 13 | in io.Reader 14 | scratch []byte 15 | trailerUsed int 16 | error bool 17 | eof bool 18 | } 19 | 20 | func NewReader(src io.Reader) *Reader { 21 | return &Reader{ 22 | Signature: make([]byte, schnorr.SignatureSize), 23 | in: src, 24 | scratch: make([]byte, schnorr.SignatureSize), 25 | } 26 | } 27 | 28 | //nolint:gocognit,nakedret // This is just complicated. 29 | func (tr *Reader) Read(buf []byte) (n int, err error) { 30 | if tr.error { 31 | err = io.ErrUnexpectedEOF 32 | return 33 | } 34 | 35 | if tr.eof { 36 | err = io.EOF 37 | return 38 | } 39 | 40 | // If we haven't yet filled the trailer buffer then we must do that 41 | // first. 42 | for tr.trailerUsed < len(tr.Signature) { 43 | n, err = tr.in.Read(tr.Signature[tr.trailerUsed:]) 44 | tr.trailerUsed += n 45 | 46 | if errors.Is(err, io.EOF) { 47 | if tr.trailerUsed != len(tr.Signature) { 48 | n = 0 49 | err = io.ErrUnexpectedEOF 50 | tr.error = true 51 | 52 | return 53 | } 54 | 55 | tr.eof = true 56 | n = 0 57 | 58 | return 59 | } 60 | 61 | if err != nil { 62 | n = 0 63 | 64 | return 65 | } 66 | } 67 | 68 | // If it's a short read then we read into a temporary buffer and shift 69 | // the data into the caller's buffer. 70 | if len(buf) <= len(tr.Signature) { 71 | n, err = readFull(tr.in, tr.scratch[:len(buf)]) 72 | copy(buf, tr.Signature[:n]) 73 | copy(tr.Signature, tr.Signature[n:]) 74 | copy(tr.Signature[len(tr.Signature)-n:], tr.scratch) 75 | 76 | if n < len(buf) { 77 | tr.eof = true 78 | err = io.EOF 79 | } 80 | 81 | return 82 | } 83 | 84 | n, err = tr.in.Read(buf[len(tr.Signature):]) 85 | copy(buf, tr.Signature) 86 | copy(tr.Signature, buf[n:]) 87 | 88 | if errors.Is(err, io.EOF) { 89 | tr.eof = true 90 | } 91 | 92 | return 93 | } 94 | 95 | func readFull(r io.Reader, buf []byte) (n int, err error) { 96 | n, err = io.ReadFull(r, buf) 97 | if errors.Is(err, io.EOF) { 98 | err = io.ErrUnexpectedEOF 99 | } 100 | 101 | return 102 | } 103 | -------------------------------------------------------------------------------- /pkg/veil/internal/schnorr/sigio/sigio_test.go: -------------------------------------------------------------------------------- 1 | package sigio 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | 8 | "github.com/codahale/gubbins/assert" 9 | "github.com/codahale/veil/pkg/veil/internal/schnorr" 10 | ) 11 | 12 | func TestNewReader(t *testing.T) { 13 | t.Parallel() 14 | 15 | sig := bytes.Repeat([]byte{0xf0}, schnorr.SignatureSize) 16 | src := append([]byte("well cool then explain this"), sig...) 17 | tr := NewReader(bytes.NewReader(src)) 18 | dst := bytes.NewBuffer(nil) 19 | 20 | n, err := io.Copy(dst, tr) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | assert.Equal(t, "bytes read", int64(27), n) 26 | assert.Equal(t, "read", "well cool then explain this", dst.String()) 27 | assert.Equal(t, "signature", sig, tr.Signature) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/veil/privatekey.go: -------------------------------------------------------------------------------- 1 | package veil 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rand" 6 | "io" 7 | 8 | "github.com/codahale/veil/pkg/veil/internal" 9 | "github.com/codahale/veil/pkg/veil/internal/mres" 10 | "github.com/codahale/veil/pkg/veil/internal/scaldf" 11 | "github.com/codahale/veil/pkg/veil/internal/schnorr" 12 | "github.com/gtank/ristretto255" 13 | ) 14 | 15 | // PrivateKey is a private key derived from a SecretKey, used to decrypt and sign messages. 16 | type PrivateKey struct { 17 | d *ristretto255.Scalar 18 | q *ristretto255.Element 19 | } 20 | 21 | // PublicKey returns the corresponding PublicKey for the receiver. 22 | func (pk *PrivateKey) PublicKey() *PublicKey { 23 | return &PublicKey{q: pk.q} 24 | } 25 | 26 | // Encrypt encrypts the data from src such that all recipients will be able to decrypt and 27 | // authenticate it and writes the results to dst. Returns the number of bytes copied and the first 28 | // error reported while encrypting, if any. 29 | func (pk *PrivateKey) Encrypt( 30 | dst io.Writer, src io.Reader, recipients []*PublicKey, fakes, padding int, 31 | ) (int64, error) { 32 | buf := make([]byte, internal.UniformBytestringSize) 33 | qRs := make([]*ristretto255.Element, len(recipients)+fakes) 34 | 35 | // Copy recipients. 36 | for i, pk := range recipients { 37 | qRs[i] = pk.q 38 | } 39 | 40 | // Add fakes. 41 | for i := len(recipients); i < len(qRs); i++ { 42 | if _, err := rand.Read(buf); err != nil { 43 | return 0, err 44 | } 45 | 46 | qRs[i] = ristretto255.NewElement().FromUniformBytes(buf) 47 | } 48 | 49 | // Shuffle the recipients to disguise any ordering information. 50 | if err := shuffle(qRs); err != nil { 51 | return 0, err 52 | } 53 | 54 | in := bufio.NewReader(src) 55 | out := bufio.NewWriter(dst) 56 | 57 | n, err := mres.Encrypt(out, in, pk.d, pk.q, qRs, padding) 58 | if err != nil { 59 | return n, err 60 | } 61 | 62 | return n, out.Flush() 63 | } 64 | 65 | // Decrypt decrypts the data in src if originally encrypted by the given public key. Returns the 66 | // number of decrypted bytes written, and the first reported error, if any. 67 | // 68 | // N.B.: Because Veil messages are streamed, it is possible that this may write some decrypted data 69 | // to dst before it can discover that the ciphertext is invalid. If Decrypt returns an error, all 70 | // output written to dst should be discarded, as it cannot be ascertained to be authentic. 71 | func (pk *PrivateKey) Decrypt(dst io.Writer, src io.Reader, sender *PublicKey) (int64, error) { 72 | in := bufio.NewReader(src) 73 | out := bufio.NewWriter(dst) 74 | 75 | n, err := mres.Decrypt(out, in, pk.d, pk.q, sender.q) 76 | if err != nil { 77 | return n, err 78 | } 79 | 80 | return n, out.Flush() 81 | } 82 | 83 | // Derive derives a PrivateKey from the receiver with the given sub-key ID. 84 | func (pk *PrivateKey) Derive(subKeyID string) *PrivateKey { 85 | d := pk.d 86 | 87 | // Derive the chain of private key scalars. 88 | for _, id := range splitID(subKeyID) { 89 | d = scaldf.DeriveScalar(d, id) 90 | } 91 | 92 | // Calculate the public key element. 93 | q := ristretto255.NewElement().ScalarBaseMult(d) 94 | 95 | return &PrivateKey{d: d, q: q} 96 | } 97 | 98 | // Sign returns a signature of the contents of src. 99 | func (pk *PrivateKey) Sign(src io.Reader) (*Signature, error) { 100 | // Write the message contents to the veil.schnorr STROBE protocol. 101 | signer := schnorr.NewSigner(io.Discard) 102 | if _, err := io.Copy(signer, bufio.NewReader(src)); err != nil { 103 | return nil, err 104 | } 105 | 106 | // Create a signature of the message. 107 | sig, err := signer.Sign(pk.d, pk.q) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | return &Signature{b: sig}, nil 113 | } 114 | 115 | // shuffle performs an in-place Fisher-Yates shuffle, using crypto/rand to pick indexes. 116 | func shuffle(keys []*ristretto255.Element) error { 117 | for i := len(keys) - 1; i > 0; i-- { 118 | // Randomly pick a card from the unshuffled deck. 119 | j, err := internal.IntN(i + 1) 120 | if err != nil { 121 | return err 122 | } 123 | 124 | // Swap it with the current card. 125 | keys[i], keys[j] = keys[j], keys[i] 126 | } 127 | 128 | return nil 129 | } 130 | -------------------------------------------------------------------------------- /pkg/veil/privatekey_test.go: -------------------------------------------------------------------------------- 1 | package veil 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "io" 7 | "testing" 8 | 9 | "github.com/codahale/gubbins/assert" 10 | ) 11 | 12 | func TestPrivateKey_Derive(t *testing.T) { 13 | t.Parallel() 14 | 15 | s, err := NewSecretKey() 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | abcd := s.PrivateKey("/a/b/c/d") 21 | abcdP := s.PrivateKey("/a/b").Derive("/c/d") 22 | 23 | assert.Equal(t, "derived key", abcd.PublicKey().String(), abcdP.PublicKey().String()) 24 | } 25 | 26 | func TestSignAndVerify(t *testing.T) { 27 | t.Parallel() 28 | 29 | sk, err := NewSecretKey() 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | message := []byte("ok there bud") 35 | 36 | sig, err := sk.PrivateKey("example").Sign(bytes.NewReader(message)) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | 41 | if err := sk.PublicKey("example").Verify(bytes.NewReader(message), sig); err != nil { 42 | t.Fatal(err) 43 | } 44 | } 45 | 46 | func TestEncryptAndDecrypt(t *testing.T) { 47 | t.Parallel() 48 | 49 | a, err := NewSecretKey() 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | b, err := NewSecretKey() 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | message := []byte("one two three four I declare a thumb war") 60 | enc := bytes.NewBuffer(nil) 61 | dec := bytes.NewBuffer(nil) 62 | publicKeys := []*PublicKey{a.PublicKey("b"), b.PublicKey("a")} 63 | 64 | eb, err := a.PrivateKey("b").Encrypt(enc, bytes.NewReader(message), publicKeys, 0, 1234) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | db, err := b.PrivateKey("a").Decrypt(dec, bytes.NewReader(enc.Bytes()), a.PublicKey("b")) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | assert.Equal(t, "plaintext", message, dec.Bytes()) 75 | assert.Equal(t, "encrypted bytes", int64(enc.Len()), eb) 76 | assert.Equal(t, "decrypted bytes", int64(dec.Len()), db) 77 | } 78 | 79 | func TestFuzzEncryptAndDecrypt(t *testing.T) { 80 | t.Parallel() 81 | 82 | a, err := NewSecretKey() 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | 87 | b := make([]byte, 1024*1024) 88 | if _, err := rand.Read(b); err != nil { 89 | t.Fatal(err) 90 | } 91 | 92 | _, err = a.PrivateKey("two").Decrypt(io.Discard, bytes.NewReader(b), a.PublicKey("two")) 93 | if err == nil { 94 | t.Fatal("shouldn't have decrypted") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /pkg/veil/publickey.go: -------------------------------------------------------------------------------- 1 | package veil 2 | 3 | import ( 4 | "bufio" 5 | "encoding" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/codahale/veil/pkg/veil/internal" 10 | "github.com/codahale/veil/pkg/veil/internal/scaldf" 11 | "github.com/codahale/veil/pkg/veil/internal/schnorr" 12 | "github.com/gtank/ristretto255" 13 | ) 14 | 15 | // PublicKey is a key that's used to verify and encrypt messages. 16 | // 17 | // It can be marshalled and unmarshalled as a base58 string for human consumption. 18 | type PublicKey struct { 19 | q *ristretto255.Element 20 | } 21 | 22 | // Derive derives a PublicKey from the receiver with the given sub-key ID. 23 | func (pk *PublicKey) Derive(subKeyID string) *PublicKey { 24 | q := pk.q 25 | 26 | for _, id := range splitID(subKeyID) { 27 | q = scaldf.DeriveElement(q, id) 28 | } 29 | 30 | return &PublicKey{q: q} 31 | } 32 | 33 | // Verify returns nil if the given signature was created by the owner of the given public 34 | // key for the contents of src, otherwise ErrInvalidSignature. 35 | func (pk *PublicKey) Verify(src io.Reader, sig *Signature) error { 36 | // Write the message contents to the veil.schnorr STROBE protocol. 37 | verifier := schnorr.NewVerifier(io.Discard) 38 | if _, err := io.Copy(verifier, bufio.NewReader(src)); err != nil { 39 | return err 40 | } 41 | 42 | // Verify the signature against the message. 43 | if !verifier.Verify(pk.q, sig.b) { 44 | return ErrInvalidSignature 45 | } 46 | 47 | return nil 48 | } 49 | 50 | // String returns the public key as base58 text. 51 | func (pk *PublicKey) String() string { 52 | text, err := pk.MarshalText() 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | return string(text) 58 | } 59 | 60 | // MarshalBinary encodes the public key into a 32-byte slice. 61 | func (pk *PublicKey) MarshalBinary() (data []byte, err error) { 62 | return pk.q.Encode(nil), nil 63 | } 64 | 65 | // UnmarshalBinary decodes the public key from a 32-byte slice. 66 | func (pk *PublicKey) UnmarshalBinary(data []byte) error { 67 | q := ristretto255.NewElement() 68 | if err := q.Decode(data); err != nil { 69 | return fmt.Errorf("invalid public key: %w", err) 70 | } 71 | 72 | pk.q = q 73 | 74 | return nil 75 | } 76 | 77 | // MarshalText encodes the public key into base58 text and returns the result. 78 | func (pk *PublicKey) MarshalText() (text []byte, err error) { 79 | return internal.ASCIIEncode(pk.q.Encode(nil)), nil 80 | } 81 | 82 | // UnmarshalText decodes the results of MarshalText and updates the receiver to contain the decoded 83 | // public key. 84 | func (pk *PublicKey) UnmarshalText(text []byte) error { 85 | data, err := internal.ASCIIDecode(text) 86 | if err != nil { 87 | return fmt.Errorf("invalid public key: %w", err) 88 | } 89 | 90 | return pk.UnmarshalBinary(data) 91 | } 92 | 93 | var ( 94 | _ encoding.BinaryMarshaler = &PublicKey{} 95 | _ encoding.BinaryUnmarshaler = &PublicKey{} 96 | _ encoding.TextMarshaler = &PublicKey{} 97 | _ encoding.TextUnmarshaler = &PublicKey{} 98 | _ fmt.Stringer = &PublicKey{} 99 | ) 100 | -------------------------------------------------------------------------------- /pkg/veil/publickey_test.go: -------------------------------------------------------------------------------- 1 | package veil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/codahale/gubbins/assert" 7 | ) 8 | 9 | func TestPublicKey_Derive(t *testing.T) { 10 | t.Parallel() 11 | 12 | s, err := NewSecretKey() 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | abcd := s.PublicKey("/a/b/c/d") 18 | abcdP := s.PublicKey("/a/b").Derive("/c/d") 19 | 20 | assert.Equal(t, "derived key", abcd.String(), abcdP.String()) 21 | } 22 | 23 | func TestPublicKey_UnmarshalText(t *testing.T) { 24 | t.Parallel() 25 | 26 | text := "164abzy93kqFFgkcMbJvgH2JgYHXKSLuESEQzCwv6wK" 27 | 28 | var in PublicKey 29 | if err := in.UnmarshalText([]byte(text)); err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | assert.Equal(t, "round trip", text, in.String()) 34 | } 35 | 36 | func TestPublicKey_MarshalText(t *testing.T) { 37 | t.Parallel() 38 | 39 | want := []byte(`164abzy93kqFFgkcMbJvgH2JgYHXKSLuESEQzCwv6wK`) 40 | 41 | var in PublicKey 42 | if err := in.UnmarshalText(want); err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | got, err := in.MarshalText() 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | assert.Equal(t, "round trip", want, got) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/veil/secretkey.go: -------------------------------------------------------------------------------- 1 | package veil 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "fmt" 8 | 9 | "github.com/codahale/veil/pkg/veil/internal" 10 | "github.com/codahale/veil/pkg/veil/internal/pbenc" 11 | "github.com/codahale/veil/pkg/veil/internal/scaldf" 12 | "github.com/gtank/ristretto255" 13 | ) 14 | 15 | // PBEParams contains the parameters of the passphrase-based KDF. 16 | type PBEParams struct { 17 | Time, Space uint32 // The time and space parameters. 18 | } 19 | 20 | // SecretKey is a key that's used to derive PrivateKey instances (and thus PublicKey instances). 21 | // 22 | // It should never be serialized in plaintext. Use EncryptSecretKey to encrypt it using a 23 | // passphrase. 24 | type SecretKey struct { 25 | r [internal.UniformBytestringSize]byte 26 | } 27 | 28 | // NewSecretKey creates a new secret key. 29 | func NewSecretKey() (*SecretKey, error) { 30 | var sk SecretKey 31 | 32 | // Generate a random 64-byte key. 33 | if _, err := rand.Read(sk.r[:]); err != nil { 34 | return nil, err 35 | } 36 | 37 | return &sk, nil 38 | } 39 | 40 | // DecryptSecretKey decrypts the given secret key with the given passphrase. Returns the decrypted 41 | // secret key. 42 | func DecryptSecretKey(passphrase, ciphertext []byte) (*SecretKey, error) { 43 | var ( 44 | encSK encryptedSecretKey 45 | sk SecretKey 46 | ) 47 | 48 | // Decode the encrypted secret key. 49 | if err := binary.Read(bytes.NewReader(ciphertext), binary.LittleEndian, &encSK); err != nil { 50 | return nil, err 51 | } 52 | 53 | // Decrypt the encrypted secret key. 54 | plaintext, err := pbenc.Decrypt( 55 | passphrase, encSK.Salt[:], encSK.Ciphertext[:], int(encSK.Params.Space), int(encSK.Params.Time)) 56 | if err != nil { 57 | return nil, ErrInvalidCiphertext 58 | } 59 | 60 | // Copy it and return it. 61 | copy(sk.r[:], plaintext) 62 | 63 | return &sk, err 64 | } 65 | 66 | // Encrypt encrypts the secret key with the given passphrase and optional PBE parameters. Returns 67 | // the encrypted key. 68 | func (sk *SecretKey) Encrypt(passphrase []byte, params *PBEParams) ([]byte, error) { 69 | var encSK encryptedSecretKey 70 | 71 | // Use default parameters if none are provided. 72 | if params == nil { 73 | encSK.Params = PBEParams{ 74 | Time: 64, 75 | Space: 1024, 76 | } 77 | } else { 78 | encSK.Params = *params 79 | } 80 | 81 | // Generate a random salt. 82 | if _, err := rand.Read(encSK.Salt[:]); err != nil { 83 | return nil, err 84 | } 85 | 86 | // Encrypt the secret key. 87 | copy(encSK.Ciphertext[:], pbenc.Encrypt( 88 | passphrase, encSK.Salt[:], sk.r[:], int(encSK.Params.Space), int(encSK.Params.Time))) 89 | 90 | // Encode the balloon hashing params, the salt, and the ciphertext. 91 | buf := bytes.NewBuffer(nil) 92 | if err := binary.Write(buf, binary.LittleEndian, &encSK); err != nil { 93 | panic(err) 94 | } 95 | 96 | return buf.Bytes(), nil 97 | } 98 | 99 | // PrivateKey returns a private key for the given key ID. 100 | func (sk *SecretKey) PrivateKey(keyID string) *PrivateKey { 101 | return sk.root().Derive(keyID) 102 | } 103 | 104 | // PublicKey returns a public key for the given key ID. 105 | func (sk *SecretKey) PublicKey(keyID string) *PublicKey { 106 | return sk.PrivateKey(keyID).PublicKey() 107 | } 108 | 109 | // String returns a safe identifier for the key. 110 | func (sk *SecretKey) String() string { 111 | return sk.root().PublicKey().String() 112 | } 113 | 114 | // root returns the root private key, derived from the secret key using veil.scaldf.secret-key. 115 | func (sk *SecretKey) root() *PrivateKey { 116 | d := scaldf.RootScalar(&sk.r) 117 | q := ristretto255.NewElement().ScalarBaseMult(d) 118 | 119 | return &PrivateKey{d: d, q: q} 120 | } 121 | 122 | var _ fmt.Stringer = &SecretKey{} 123 | 124 | // encryptedSecretKey is a fixed-size struct of the encoded values for an encrypted secret key. 125 | type encryptedSecretKey struct { 126 | Params PBEParams 127 | Salt [saltSize]byte 128 | Ciphertext [ciphertextSize]byte 129 | } 130 | 131 | const ( 132 | saltSize = 32 133 | ciphertextSize = internal.UniformBytestringSize + pbenc.Overhead 134 | ) 135 | -------------------------------------------------------------------------------- /pkg/veil/secretkey_test.go: -------------------------------------------------------------------------------- 1 | package veil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/codahale/gubbins/assert" 7 | ) 8 | 9 | func TestPBE(t *testing.T) { 10 | t.Parallel() 11 | 12 | sk, err := NewSecretKey() 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | esk, err := sk.Encrypt([]byte("this is magic"), nil) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | dsk, err := DecryptSecretKey([]byte("this is magic"), esk) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | assert.Equal(t, "decrypted secret key", sk.String(), dsk.String()) 28 | } 29 | 30 | func TestSecretKey_PublicKey(t *testing.T) { 31 | t.Parallel() 32 | 33 | s, err := NewSecretKey() 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | abc := s.PublicKey("/a").Derive("b").Derive("c") 39 | abcP := s.PublicKey("/a/b/c") 40 | 41 | assert.Equal(t, "derived key", abc.String(), abcP.String()) 42 | } 43 | 44 | func TestSecretKey_PrivateKey(t *testing.T) { 45 | t.Parallel() 46 | 47 | s, err := NewSecretKey() 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | abc := s.PrivateKey("/a").Derive("b").Derive("c") 53 | abcP := s.PrivateKey("/a/b/c") 54 | 55 | assert.Equal(t, "derived key", abc.PublicKey().String(), abcP.PublicKey().String()) 56 | } 57 | 58 | func TestSecretKey_String(t *testing.T) { 59 | t.Parallel() 60 | 61 | var sk SecretKey 62 | 63 | copy(sk.r[:], "ayellowsubmarineayellowsubmarineayellowsubmarineayellowsubmarine") 64 | 65 | assert.Equal(t, "string representation", 66 | "ACSC8Phz7fzhtjHXH8JLG9KHfHZ4SkqEJhUW3Ci6FNAr", sk.String()) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/veil/signature.go: -------------------------------------------------------------------------------- 1 | package veil 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | 7 | "github.com/codahale/veil/pkg/veil/internal" 8 | "github.com/codahale/veil/pkg/veil/internal/schnorr" 9 | ) 10 | 11 | // Signature is a digital signature of a message, created by the holder of a secret key, which can 12 | // be verified by anyone with the corresponding public key. 13 | type Signature struct { 14 | b []byte 15 | } 16 | 17 | // MarshalBinary encodes the signature into bytes. 18 | func (s *Signature) MarshalBinary() (data []byte, err error) { 19 | return s.b, nil 20 | } 21 | 22 | // UnmarshalBinary decodes the signature from bytes. 23 | func (s *Signature) UnmarshalBinary(data []byte) error { 24 | if len(data) != schnorr.SignatureSize { 25 | return ErrInvalidSignature 26 | } 27 | 28 | s.b = data 29 | 30 | return nil 31 | } 32 | 33 | // MarshalText encodes the signature into base58 text and returns the result. 34 | func (s *Signature) MarshalText() (text []byte, err error) { 35 | return internal.ASCIIEncode(s.b), nil 36 | } 37 | 38 | // UnmarshalText decodes the results of MarshalText and updates the receiver to contain the decoded 39 | // signature. 40 | func (s *Signature) UnmarshalText(text []byte) error { 41 | data, err := internal.ASCIIDecode(text) 42 | if err != nil { 43 | return fmt.Errorf("invalid signature: %w", err) 44 | } 45 | 46 | return s.UnmarshalBinary(data) 47 | } 48 | 49 | // String returns the signature as base58 text. 50 | func (s *Signature) String() string { 51 | text, err := s.MarshalText() 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | return string(text) 57 | } 58 | 59 | var ( 60 | _ encoding.BinaryMarshaler = &Signature{} 61 | _ encoding.BinaryUnmarshaler = &Signature{} 62 | _ encoding.TextMarshaler = &Signature{} 63 | _ encoding.TextUnmarshaler = &Signature{} 64 | _ fmt.Stringer = &Signature{} 65 | ) 66 | -------------------------------------------------------------------------------- /pkg/veil/signature_test.go: -------------------------------------------------------------------------------- 1 | package veil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/codahale/gubbins/assert" 7 | ) 8 | 9 | func TestSignature_MarshalText(t *testing.T) { 10 | t.Parallel() 11 | 12 | var s Signature 13 | if err := s.UnmarshalBinary([]byte("ayellowsubmarineayellowsubmarineayellowsubmarineayellowsubmarine")); err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | text, err := s.MarshalText() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | assert.Equal(t, "marshalled text", 23 | `2x2qUArEZSrxBKb7b5ojwG4hP3mTeKTzmuzrbZ7b9X9M75Zeq7nWzK2dwXXUZQp3KJvQyhX6vhP26M1GZFJgpxDA`, 24 | string(text)) 25 | } 26 | 27 | func TestSignature_UnmarshalText(t *testing.T) { 28 | t.Parallel() 29 | 30 | var s Signature 31 | if err := s.UnmarshalText( 32 | []byte(`2x2qUArEZSrxBKb7b5ojwG4hP3mTeKTzmuzrbZ7b9X9M75Zeq7nWzK2dwXXUZQp3KJvQyhX6vhP26M1GZFJgpxDA`), 33 | ); err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | b, err := s.MarshalBinary() 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | assert.Equal(t, "unmarshalled key", 43 | []byte("ayellowsubmarineayellowsubmarineayellowsubmarineayellowsubmarine"), b) 44 | } 45 | 46 | func TestSignature_String(t *testing.T) { 47 | t.Parallel() 48 | 49 | var s Signature 50 | if err := s.UnmarshalBinary([]byte("ayellowsubmarineayellowsubmarineayellowsubmarineayellowsubmarine")); err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | assert.Equal(t, "string representation", 55 | `2x2qUArEZSrxBKb7b5ojwG4hP3mTeKTzmuzrbZ7b9X9M75Zeq7nWzK2dwXXUZQp3KJvQyhX6vhP26M1GZFJgpxDA`, 56 | s.String()) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/veil/veil.go: -------------------------------------------------------------------------------- 1 | // Package veil implements the Veil hybrid cryptosystem. 2 | // 3 | // Veil is an incredibly experimental hybrid cryptosystem for sending and receiving confidential, 4 | // authentic multi-recipient messages which are indistinguishable from random noise by an attacker. 5 | // Unlike e.g. GPG messages, Veil messages contain no metadata or format details which are not 6 | // encrypted. As a result, a global passive adversary would be unable to gain any information from a 7 | // Veil message beyond traffic analysis. Messages can be padded with random bytes to disguise their 8 | // true length, and fake recipients can be added to disguise their true number from other 9 | // recipients. 10 | // 11 | // You should not use this. 12 | package veil 13 | 14 | import ( 15 | "errors" 16 | "strings" 17 | 18 | "github.com/codahale/veil/pkg/veil/internal" 19 | ) 20 | 21 | var ( 22 | // ErrInvalidCiphertext is returned when a ciphertext cannot be decrypted, either due to an 23 | // incorrect key or tampering. 24 | ErrInvalidCiphertext = internal.ErrInvalidCiphertext 25 | 26 | // ErrInvalidSignature is returned when a signature, public key, and message do not match. 27 | ErrInvalidSignature = errors.New("invalid signature") 28 | ) 29 | 30 | const idSeparator = "/" 31 | 32 | func splitID(id string) []string { 33 | return strings.Split(strings.Trim(id, idSeparator), idSeparator) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/veil/veil_test.go: -------------------------------------------------------------------------------- 1 | package veil 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | func Example() { 9 | // Alice generates a secret key. 10 | alice, err := NewSecretKey() 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | // Alice derives a private key with the ID "/friends/bea", generates a public key from that, and 16 | // shares the public key with Bea. 17 | aliceBeaPriv := alice.PrivateKey("/friends/bea") 18 | aliceBeaPub := aliceBeaPriv.PublicKey() 19 | 20 | // Bea generates a secret key. 21 | bea, err := NewSecretKey() 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | // Bea derives a private key with the ID "/friends/alice", generates a public key from that, and 27 | // shares the public key with Alice. 28 | beaAlicePriv := bea.PrivateKey("/friends/alice") 29 | beaAlicePub := beaAlicePriv.PublicKey() 30 | 31 | // Alice writes a message. 32 | message := bytes.NewReader([]byte("one two three four I declare a thumb war")) 33 | encrypted := bytes.NewBuffer(nil) 34 | 35 | // Alice encrypts the message for herself, Bea, and 98 fake recipients, adding random padding to 36 | // disguise its true length. She uses the "/friends/bea" private key to encrypt the message 37 | // because it corresponds with the public key she sent Bea. 38 | _, err = aliceBeaPriv.Encrypt(encrypted, message, []*PublicKey{aliceBeaPub, beaAlicePub}, 98, 4829) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | // Alice sends the message to Bea. 44 | received := bytes.NewReader(encrypted.Bytes()) 45 | decrypted := bytes.NewBuffer(nil) 46 | 47 | // Bea decrypts the message. She uses the "/friends/alice" private key because it corresponds 48 | // with the public key she sent Alice. 49 | _, err = beaAlicePriv.Decrypt(decrypted, received, aliceBeaPub) 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | // Bea views the decrypted message. 55 | fmt.Println(decrypted.String()) 56 | // Output: 57 | // one two three four I declare a thumb war 58 | } 59 | --------------------------------------------------------------------------------