├── .cspell.json ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── goreleaser.yml │ └── mega-linter.yml ├── .gitignore ├── .goreleaser.yml ├── .jscpd.json ├── .mega-linter.yml ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum └── secret.go /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "language": "en", 4 | "ignorePaths": [ 5 | "**/node_modules/**", 6 | "**/vscode-extension/**", 7 | "**/.git/**", 8 | ".vscode", 9 | "package-lock.json", 10 | "report" 11 | ], 12 | "words": [] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 3 * * 2' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['go'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | 42 | # codeql no longer needs this and instead recommends not to use it 43 | # - run: git checkout HEAD^2 44 | # if: ${{ github.event_name == 'pull_request' }} 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v1 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v1 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 https://git.io/JvXDl 63 | 64 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 65 | # and modify them (or add more) to build your code if your project 66 | # uses a compiled language 67 | 68 | #- run: | 69 | # make bootstrap 70 | # make release 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v1 74 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: Goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - 18 | name: Set up Go 19 | uses: actions/setup-go@v2 20 | with: 21 | go-version: 1.16 22 | - 23 | name: Run GoReleaser 24 | uses: goreleaser/goreleaser-action@v2 25 | with: 26 | version: latest 27 | args: release --rm-dist 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/mega-linter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Mega-Linter GitHub Action configuration file 3 | # More info at https://nvuillam.github.io/mega-linter 4 | name: Mega-Linter 5 | 6 | on: 7 | # Trigger mega-linter at every push. Action will also be visible from Pull Requests to master 8 | push: # Comment this line to trigger action only on pull-requests (not recommended if you don't pay for GH Actions) 9 | pull_request: 10 | branches: [master, main] 11 | 12 | env: # Comment env block if you do not want to apply fixes 13 | # Apply linter fixes configuration 14 | APPLY_FIXES: all # When active, APPLY_FIXES must also be defined as environment variable (in github/workflows/mega-linter.yml or other CI tool) 15 | APPLY_FIXES_EVENT: pull_request # Decide which event triggers application of fixes in a commit or a PR (pull_request, push, all) 16 | APPLY_FIXES_MODE: commit # If APPLY_FIXES is used, defines if the fixes are directly committed (commit) or posted in a PR (pull_request) 17 | 18 | jobs: 19 | # Cancel duplicate jobs: https://github.com/fkirc/skip-duplicate-actions#option-3-cancellation-only 20 | cancel_duplicates: 21 | name: Cancel duplicate jobs 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: fkirc/skip-duplicate-actions@master 25 | with: 26 | github_token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} 27 | 28 | build: 29 | name: Mega-Linter 30 | runs-on: ubuntu-latest 31 | steps: 32 | # Git Checkout 33 | - name: Checkout Code 34 | uses: actions/checkout@v2 35 | with: 36 | token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} 37 | fetch-depth: 0 38 | 39 | # Mega-Linter 40 | - name: Mega-Linter 41 | id: ml 42 | # You can override Mega-Linter flavor used to have faster performances 43 | # More info at https://nvuillam.github.io/mega-linter/flavors/ 44 | uses: nvuillam/mega-linter/flavors/go@v4 45 | env: 46 | # All available variables are described in documentation 47 | # https://nvuillam.github.io/mega-linter/configuration/ 48 | VALIDATE_ALL_CODEBASE: true # Set ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} to validate only diff with master branch 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | # ADD YOUR CUSTOM ENV VARIABLES HERE TO OVERRIDE VALUES OF .mega-linter.yml AT THE ROOT OF YOUR REPOSITORY 51 | 52 | # Upload Mega-Linter artifacts 53 | - name: Archive production artifacts 54 | if: ${{ success() }} || ${{ failure() }} 55 | uses: actions/upload-artifact@v2 56 | with: 57 | name: Mega-Linter reports 58 | path: | 59 | report 60 | mega-linter.log 61 | 62 | # Create pull request if applicable (for now works only on PR from same repository, not from forks) 63 | - name: Create Pull Request with applied fixes 64 | id: cpr 65 | if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.head_commit.message, 'skip fix') 66 | uses: peter-evans/create-pull-request@v3 67 | with: 68 | token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} 69 | commit-message: "[Mega-Linter] Apply linters automatic fixes" 70 | title: "[Mega-Linter] Apply linters automatic fixes" 71 | labels: bot 72 | - name: Create PR output 73 | if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.head_commit.message, 'skip fix') 74 | run: | 75 | echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" 76 | echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" 77 | 78 | # Push new commit if applicable (for now works only on PR from same repository, not from forks) 79 | - name: Prepare commit 80 | if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/master' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.head_commit.message, 'skip fix') 81 | run: sudo chown -Rc $UID .git/ 82 | - name: Commit and push applied linter fixes 83 | if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/master' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.head_commit.message, 'skip fix') 84 | uses: stefanzweifel/git-auto-commit-action@v4 85 | with: 86 | branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }} 87 | commit_message: "[Mega-Linter] Apply linters fixes" 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | __debug_bin 3 | /cool 4 | .idea 5 | /dist 6 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod download 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - freebsd 14 | - windows 15 | - darwin 16 | - linux 17 | goarch: 18 | - amd64 19 | - arm 20 | - arm64 21 | - 386 22 | ignore: 23 | - goos: windows 24 | goarch: arm64 25 | archives: 26 | - replacements: 27 | darwin: Darwin 28 | linux: Linux 29 | windows: Windows 30 | 386: 32-bit 31 | amd64: x86_64 32 | format_overrides: 33 | - goos: windows 34 | format: zip 35 | 36 | checksum: 37 | name_template: 'checksums.txt' 38 | snapshot: 39 | name_template: "{{ .Tag }}-next" 40 | changelog: 41 | sort: asc 42 | filters: 43 | exclude: 44 | - '^docs:' 45 | - '^test:' 46 | brews: 47 | - 48 | # Repository to push the tap to. 49 | tap: 50 | owner: quackduck 51 | name: homebrew-tap 52 | 53 | # Folder inside the repository to put the formula. 54 | # Default is the root folder. 55 | # folder: uniclip 56 | # Your app's homepage. 57 | # Default is empty. 58 | homepage: 'https://github.com/quackduck/secret' 59 | 60 | # Your app's description. 61 | # Default is empty. 62 | description: 'Encrypt anything with a password' 63 | -------------------------------------------------------------------------------- /.jscpd.json: -------------------------------------------------------------------------------- 1 | { 2 | "threshold": 0, 3 | "reporters": [ 4 | "html", 5 | "markdown" 6 | ], 7 | "ignore": [ 8 | "**/node_modules/**", 9 | "**/.git/**", 10 | "**/.rbenv/**", 11 | "**/.venv'/**", 12 | "**/*cache*/**", 13 | "**/.github/**", 14 | "**/.idea/**", 15 | "**/report/**", 16 | "**/*.svg" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.mega-linter.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for Mega-Linter 2 | # See all available variables at https://nvuillam.github.io/mega-linter/configuration/ and in linters documentation 3 | 4 | APPLY_FIXES: all # all, none, or list of linter keys 5 | DEFAULT_BRANCH: main # Usually master or main 6 | # ENABLE: # If you use ENABLE variable, all other languages/formats/tooling-formats will be disabled by default 7 | # ENABLE_LINTERS: # If you use ENABLE_LINTERS variable, all other linters will be disabled by default 8 | DISABLE: 9 | - COPYPASTE # Comment to disable checks of abusive copy-pastes 10 | - SPELL # Comment to disable checks of spelling mistakes # - SPELL # Uncomment to disable checks of spelling mistakes 11 | SHOW_ELAPSED_TIME: true 12 | FILEIO_REPORTER: false 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Casey Muller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secret - Encrypt anything with a password 2 | 3 | Ever wanted to hide a file? Now you can do it really easily! 4 | 5 | [![asciicast](https://asciinema.org/a/401528.svg)](https://asciinema.org/a/401528?speed=2&autoplay=1) 6 | 7 | Secret - Encrypt anything with a password. | Product Hunt 8 | 9 | ## Usage 10 | 11 | ```text 12 | secret {-e/--encrypt | -d/--decrypt} [] 13 | secret [-h/--help | -v/--version] 14 | ``` 15 | 16 | For example, run: 17 | ```shell 18 | echo "foobardata" > foo.txt 19 | secret --encrypt foo.txt 20 | ``` 21 | You will be prompted for a password that you can use to recover data later. 22 | ```text 23 | Password: 24 | ``` 25 | 26 | After you input your password, Secret will make an encrypted `foo.txt.secret` file. 27 | 28 | Then, when you want to decrypt `foo.txt.secret`, you can run: 29 | ```shell 30 | secret --decrypt foo.txt.secret bar.txt 31 | ``` 32 | You must enter the same password you had when you encrypted the data. 33 | 34 | Secret then decrypts `foo.txt.secret` and writes the data to a new file, `bar.txt`. 35 | 36 | If you didn't specify `bar.txt`, Secret would try to write to `foo.txt`. However, Secret will never overwrite files and so it would print an error. 37 | 38 | Now `bar.txt` and `foo.txt` are exactly the same! (you can check this with `diff`) 39 | 40 | For larger files, Secret shows progress bars that indicate how much data has been encrypted or decrypted and even provides estimates for how much time is remaining. 41 | ```text 42 | Decrypting 33% ████████████ (687 MB/2.0 GB, 304.783 MB/s) [2s:4s] 43 | ``` 44 | 45 | You can also use pipes to specify the password (this can be useful in scripts): 46 | 47 | ```shell 48 | echo "mypass" | secret -e foo # use "mypass" as password and encrypt foo 49 | ``` 50 | 51 | ### Details 52 | ```text 53 | Options: 54 | -e, --encrypt Encrypt the source file and save to destination. If no 55 | destination is specified, secret makes a new file with a 56 | .secret extension. This option reads for a password. 57 | -d, --decrypt Decrypt the source file and save to destination. If no 58 | destination is specified, secret makes a new file without 59 | the .secret extension. This option reads for a password. 60 | -h, --help Display this help message 61 | -v, --version Show secret's version 62 | ``` 63 | 64 | ## Installing 65 | 66 | ```shell 67 | brew install quackduck/tap/secret # works for Linuxbrew too! 68 | ``` 69 | or get an executable from [releases](https://github.com/quackduck/secret/releases). 70 | 71 | ## Uninstalling 72 | ```shell 73 | brew uninstall quackduck/tap/secret 74 | ``` 75 | or on Unix, 76 | ```shell 77 | rm $(which secret) 78 | ``` 79 | or just delete it from wherever you installed the binary. 80 | 81 | ## Implementation details 82 | 83 | Secret uses AES, GCM, Scrypt with N = 2^15, r = 8, p = 1 and a high quality, 32 byte random salt for deriving a key. 84 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1634851050, 6 | "narHash": "sha256-N83GlSGPJJdcqhUxSCS/WwW5pksYf3VP1M13cDRTSVA=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "c91f3de5adaf1de973b797ef7485e441a65b8935", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1634919341, 21 | "narHash": "sha256-iYTckx+vxrGskDsI7US/3N1CDGYjnFl2Wh6gkLBmAKI=", 22 | "owner": "NixOS", 23 | "repo": "nixpkgs", 24 | "rev": "1cab3e231b41f38f2d2cbf5617eb7b88e433428a", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "NixOS", 29 | "ref": "nixpkgs-unstable", 30 | "repo": "nixpkgs", 31 | "type": "github" 32 | } 33 | }, 34 | "root": { 35 | "inputs": { 36 | "flake-utils": "flake-utils", 37 | "nixpkgs": "nixpkgs" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Encrypt anything with a password"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let 10 | pkgs = nixpkgs.legacyPackages.${system}; 11 | in rec { 12 | packages.secret = pkgs.buildGoModule { 13 | name = "secret"; 14 | src = ./.; 15 | vendorSha256 = "sha256-x8NM5/TMnUmwy+8gW+r9WNoyhPNL8TW6acjc0xkUGR4="; 16 | meta = with pkgs.lib; { 17 | description = "Encrypt anything with a password"; 18 | homepage = "https://github.com/quackduck/secret"; 19 | license = licenses.mit; 20 | platforms = platforms.linux ++ platforms.darwin; 21 | }; 22 | }; 23 | defaultPackage = packages.secret; 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module secret 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/fatih/color v1.10.0 7 | github.com/schollz/progressbar/v3 v3.7.6 8 | github.com/secure-io/sio-go v0.3.1 9 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 10 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect 11 | golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= 5 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 6 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= 7 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= 8 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 9 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 10 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 11 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 12 | github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= 13 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 14 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 15 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 19 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 20 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 21 | github.com/schollz/progressbar/v3 v3.7.6 h1:akAvVpTy2IAcePWYndctoBaY9bLE3z4LE1Hn91BJ9g4= 22 | github.com/schollz/progressbar/v3 v3.7.6/go.mod h1:Y9mmL2knZj3LUaBDyBEzFdPrymIr08hnlFMZmfxwbx4= 23 | github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc= 24 | github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs= 25 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 26 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 27 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 28 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 29 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 30 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 31 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 32 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= 33 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 34 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 35 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= 36 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 37 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 38 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc= 46 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 48 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 49 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 50 | golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs= 51 | golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 52 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 53 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 54 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 55 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= 56 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 57 | -------------------------------------------------------------------------------- /secret.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "strings" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/fatih/color" 17 | pb "github.com/schollz/progressbar/v3" 18 | "github.com/secure-io/sio-go" 19 | "golang.org/x/crypto/scrypt" 20 | "golang.org/x/term" 21 | ) 22 | 23 | var ( 24 | version = "dev" 25 | helpMsg = `Secret - Encrypt anything with a password 26 | Usage: 27 | secret {-e/--encrypt | -d/--decrypt} [] 28 | secret [-h/--help | -v/--version] 29 | Options: 30 | -e, --encrypt Encrypt the source file and save to destination. If no 31 | destination is specified, secret makes a new file with a 32 | .secret extension. This option reads for a password. 33 | -d, --decrypt Decrypt the source file and save to destination. If no 34 | destination is specified, secret makes a new file without 35 | the .secret extension. This option reads for a password. 36 | -h, --help Display this help message 37 | -v, --version Show secret's version 38 | Examples: 39 | secret -e foo # creates an encrypted foo.secret file 40 | secret -d foo.secret bar # decrypts foo.secret in bar (now same as foo) 41 | echo "pass" | secret -e foo # use "pass" as password and encrypt foo 42 | Note: 43 | Secret will never overwrite files and will exit with code 1 in this scenario` 44 | cryptoStrength = 15 // scrypt's N = 2^15 = 32,768 // TODO: Make this 16 in Secret 2.0 45 | ) 46 | 47 | // TODO: Consider changing encryption algo to ChaCha20 in Secret 2.0 48 | 49 | func main() { //nolint:gocyclo 50 | var ( 51 | src io.Reader 52 | dst io.Writer 53 | toEncrypt bool 54 | size int64 = -1 55 | ) 56 | if hasOption, _ := argsHaveOption("help", "h"); hasOption { 57 | fmt.Println(helpMsg) 58 | return 59 | } 60 | if hasOption, _ := argsHaveOption("version", "v"); hasOption { 61 | fmt.Println("Secret " + version) 62 | return 63 | } 64 | if len(os.Args) < 3 { // at least two args needed 65 | handleErrStr("Too few arguments") 66 | fmt.Println(helpMsg) 67 | return 68 | } 69 | if os.Args[1] == "-e" || os.Args[1] == "--encrypt" || os.Args[1] == "--make" { 70 | toEncrypt = true 71 | f, err := os.Open(os.Args[2]) 72 | if err != nil { 73 | handleErr(err) 74 | return 75 | } 76 | src = f 77 | stat, err := f.Stat() 78 | if err != nil { 79 | handleErr(err) 80 | return 81 | } 82 | size = stat.Size() 83 | } 84 | if os.Args[1] == "-d" || os.Args[1] == "--decrypt" || os.Args[1] == "--spill" { 85 | toEncrypt = false 86 | f, err := os.Open(os.Args[2]) 87 | if err != nil { 88 | handleErr(err) 89 | return 90 | } 91 | src = f 92 | stat, err := f.Stat() 93 | if err != nil { 94 | handleErr(err) 95 | return 96 | } 97 | size = stat.Size() 98 | } 99 | 100 | var err error 101 | if len(os.Args) > 3 { // check if user wants to write to some file: `secret --encrypt data dst` 102 | dst, err = os.Create(os.Args[3]) 103 | if err != nil { 104 | handleErr(err) 105 | return 106 | } 107 | } else { // automatically determine where to write to 108 | if toEncrypt { 109 | if exists(os.Args[2] + ".secret") { 110 | handleErrStr("Will not overwrite " + os.Args[2] + ".secret") 111 | os.Exit(1) 112 | return 113 | } 114 | dst, err = os.Create(os.Args[2] + ".secret") 115 | if err != nil { 116 | handleErr(err) 117 | return 118 | } 119 | } else { 120 | s := strings.TrimSuffix(os.Args[2], ".secret") 121 | if exists(s) { 122 | handleErrStr("Will not overwrite " + s) 123 | os.Exit(1) 124 | return 125 | } 126 | dst, err = os.Create(s) 127 | if err != nil { 128 | handleErr(err) 129 | return 130 | } 131 | } 132 | } 133 | 134 | var password []byte // default pass 135 | 136 | fi, _ := os.Stdin.Stat() //nolint:errcheck // stat stdin will be fine 137 | 138 | if (fi.Mode() & os.ModeCharDevice) == 0 { // password is being piped 139 | password, err = ioutil.ReadAll(os.Stdin) 140 | if err != nil { 141 | handleErr(err) 142 | return 143 | } 144 | } else { 145 | fmt.Print("Password: ") 146 | password, err = term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert 147 | fmt.Print("\033[2K\r") 148 | if err != nil { 149 | handleErr(err) 150 | return 151 | } 152 | } 153 | err = secret(password, src, dst, toEncrypt, size) 154 | if err != nil { 155 | handleErr(err) 156 | return 157 | } 158 | } 159 | 160 | func exists(path string) bool { 161 | _, err := os.Stat(path) 162 | return !(os.IsNotExist(err)) 163 | } 164 | 165 | func secret(password []byte, src io.Reader, dst io.Writer, toEncrypt bool, size int64) error { 166 | if toEncrypt { 167 | return encrypt(password, src, dst, size) 168 | } 169 | return decrypt(password, src, dst, size) 170 | } 171 | 172 | // Thanks to https://bruinsslot.jp/post/golang-crypto/ for some of the crypto logic 173 | func encrypt(pass []byte, src io.Reader, dst io.Writer, size int64) error { 174 | start := time.Now() 175 | key, salt, err := deriveKey(pass, nil) 176 | keyDur := time.Since(start).Round(time.Millisecond) 177 | if err != nil { 178 | return err 179 | } 180 | blockCipher, err := aes.NewCipher(key) 181 | if err != nil { 182 | return err 183 | } 184 | gcm, err := cipher.NewGCM(blockCipher) 185 | if err != nil { 186 | return err 187 | } 188 | stream := sio.NewStream(gcm, sio.BufSize) 189 | pbar := pb.NewOptions64(size, 190 | pb.OptionEnableColorCodes(true), 191 | pb.OptionShowBytes(true), 192 | pb.OptionSetWriter(os.Stderr), 193 | pb.OptionThrottle(65*time.Millisecond), 194 | pb.OptionShowCount(), 195 | pb.OptionClearOnFinish(), 196 | pb.OptionSetDescription("Encrypting"), 197 | pb.OptionFullWidth(), 198 | pb.OptionSetTheme(pb.Theme{ 199 | Saucer: "█", 200 | SaucerPadding: " ", 201 | BarStart: "", 202 | BarEnd: "", 203 | })) 204 | nonce := make([]byte, stream.NonceSize()) 205 | rand.Read(nonce) //nolint:errcheck 206 | // attach salt at the start 207 | dst.Write(append(salt, nonce...)) //nolint:errcheck 208 | 209 | dst = io.MultiWriter(dst, pbar) // attach progress bar 210 | // make reader encrypted 211 | src = stream.EncryptReader(src, nonce, nil) 212 | _, err = io.Copy(dst, src) 213 | pbar.Finish() //nolint:errcheck 214 | fmt.Println("Done in", time.Since(start).Round(time.Millisecond).String()+".", "Key derivation took", keyDur) 215 | return err 216 | } 217 | 218 | func decrypt(pass []byte, src io.Reader, dst io.Writer, size int64) error { 219 | start := time.Now() 220 | salt := make([]byte, 32) 221 | _, err := src.Read(salt) // read in salt from the beginning 222 | if err != nil { 223 | return err 224 | } 225 | pass, _, err = deriveKey(pass, salt) 226 | if err != nil { 227 | return err 228 | } 229 | blockCipher, err := aes.NewCipher(pass) 230 | if err != nil { 231 | return err 232 | } 233 | gcm, err := cipher.NewGCM(blockCipher) 234 | if err != nil { 235 | return err 236 | } 237 | stream := sio.NewStream(gcm, sio.BufSize) 238 | nonce := make([]byte, stream.NonceSize()) 239 | _, err = src.Read(nonce) // read in nonce 240 | if err != nil { 241 | return err 242 | } 243 | pbar := pb.NewOptions64(size, 244 | pb.OptionEnableColorCodes(true), 245 | pb.OptionShowBytes(true), 246 | pb.OptionSetWriter(os.Stderr), 247 | pb.OptionThrottle(65*time.Millisecond), 248 | pb.OptionShowCount(), 249 | pb.OptionClearOnFinish(), 250 | pb.OptionSetDescription("Decrypting"), 251 | pb.OptionFullWidth(), 252 | pb.OptionSetTheme(pb.Theme{ 253 | Saucer: "█", 254 | SaucerPadding: " ", 255 | BarStart: "█", 256 | BarEnd: "", 257 | })) 258 | 259 | dst = io.MultiWriter(dst, pbar) // attach progress bar 260 | 261 | src = stream.DecryptReader(src, nonce, nil) 262 | _, err = io.Copy(dst, src) 263 | pbar.Finish() //nolint:errcheck 264 | if err == sio.NotAuthentic { 265 | return errors.New("authentication failed") 266 | } 267 | fmt.Println("Done in", time.Since(start).Round(time.Millisecond).String()+".") 268 | return err 269 | } 270 | 271 | func deriveKey(password, salt []byte) ([]byte, []byte, error) { 272 | if salt == nil { 273 | salt = make([]byte, 32) 274 | if _, err := rand.Read(salt); err != nil { 275 | return nil, nil, err 276 | } 277 | } 278 | key, err := scrypt.Key(password, salt, 1<