├── .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 | [](https://asciinema.org/a/401528?speed=2&autoplay=1)
6 |
7 |
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<