├── .github └── workflows │ └── release.yaml ├── .gitignore ├── .idea ├── .gitignore ├── go-tachicrypt.iml ├── modules.xml └── vcs.xml ├── CHANGELOG.md ├── DOCS ├── decrypt.png ├── encrypt.png └── tachicrypt_logo.jpg ├── LICENSE ├── README.md ├── cmd └── client │ └── client.go ├── go.mod ├── go.sum └── src ├── core └── core.go ├── encryptor └── encryptor.go ├── fileutils └── fileutils.go ├── masterlock └── masterlock.go ├── prettywriter └── prettywriter.go ├── splitter └── splitter.go ├── utils └── utils.go └── zipper └── zipper.go /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | permissions: 8 | contents: write 9 | packages: write 10 | 11 | jobs: 12 | package-release: 13 | name: Release Go Binary 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | # build and publish in parallel: linux/386, linux/amd64, linux/arm64, windows/386, windows/amd64, darwin/amd64, darwin/arm64 18 | goos: [linux, windows, darwin] 19 | goarch: ["386", amd64, arm64] 20 | exclude: 21 | - goarch: "386" 22 | goos: darwin 23 | - goarch: arm64 24 | goos: windows 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: wangyoucao577/go-release-action@v1 28 | with: 29 | github_token: ${{ secrets.GITHUB_TOKEN }} 30 | goos: ${{ matrix.goos }} 31 | goarch: ${{ matrix.goarch }} 32 | project_path: "./cmd/client" 33 | binary_name: "tachicrypt" 34 | extra_files: LICENSE 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/gui/tcg 2 | cmd/gui/tachicryptgui 3 | cmd/gui/tachicrypt 4 | cmd/client/tachicrypt 5 | cmd/client/tachicryptgui 6 | cmd/gui/tachicrypgui 7 | cmd/lab/ 8 | ./idea 9 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/go-tachicrypt.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## Beta-Release 0.4.0 `31.10.2024` 3 | * **Important**: This is a breaking release. Due to the adding of a padding in front of the first part to mitigate possible cleartext attacks on the zip header, the format is incompatible to encrypted files created with previous versions. 4 | * Adding a random length padding in front of the zip data to mitigate the issue of possible known cleartext attack on said zip header ( https://github.com/voodooEntity/go-tachicrypt/issues/5 ) 5 | 6 | ## Alpha-Release 0.3.0 `20.10.2024` 7 | * **Important**: This is a breaking release. Due to the changed format of the parts from base64 to []bytes there will be compatibility issues with older version. 8 | * Completely removing base64 as transport format and working with []byte directly between zip and AES GCM. ( https://github.com/voodooEntity/go-tachicrypt/issues/3 ) 9 | * Adjusting cli output to remove any traces of base64 information ( https://github.com/voodooEntity/go-tachicrypt/issues/3 ) 10 | * Adjusting cli output configuration part to name the given args "path" instead of "directory" since it can either be a file or a directory 11 | * Some code/comment cleanup 12 | * Restructuring and adjusting README.md to changes 13 | 14 | ## Alpha-Release 0.2.0 `19.10.2024` 15 | * Small update of README.md 16 | * Adding prettywriter package for cleaner cli output 17 | * Massive overhaul of cli output 18 | * Some code/comment cleanup 19 | * Some error handling improvements 20 | 21 | ## Alpha-Release 0.1.0 `18.10.2024` 22 | * Adding CHANGELOG.md 23 | * Adjusting the password input method to golang.org/x/term for non visible password input 24 | * Adjust .gitignore 25 | * Some output cleanup 26 | 27 | ## Alpha-Release 0.0.1 `13.10.2024` 28 | * First Alpha release of tachicrypt -------------------------------------------------------------------------------- /DOCS/decrypt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voodooEntity/go-tachicrypt/b27c105e529b87ef9914497595da3b8d46061887/DOCS/decrypt.png -------------------------------------------------------------------------------- /DOCS/encrypt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voodooEntity/go-tachicrypt/b27c105e529b87ef9914497595da3b8d46061887/DOCS/encrypt.png -------------------------------------------------------------------------------- /DOCS/tachicrypt_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voodooEntity/go-tachicrypt/b27c105e529b87ef9914497595da3b8d46061887/DOCS/tachicrypt_logo.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | TachiCrypt 4 |
5 | TachiCrypt 6 |
7 |

8 | 9 |

An experimental cli tool to encrypt & decrypt files/directories.

10 | 11 | 12 |

13 | Key Features • 14 | How To Use • 15 | Screenshots • 16 | How To Build • 17 | Roadmap • 18 | Changelog • 19 | License 20 |

21 | 22 | Important right now this is an experimental project and the safety and stability isn't guaranteed! 23 | 24 | ## Key Features 25 | 26 | - Clean and straightforward CLI interface. 27 | - Supports encryption of both files and directories (with recursive functionality). 28 | - Pure Go implementation with no external dependencies. 29 | - Utilizes AES GCM encryption, avoiding custom or insecure encryption schemes. 30 | - Encrypts data into multiple segments, allowing distribution across various storage locations or transfer channels. 31 | - Each encrypted segment is assigned a random name to enhance security. 32 | - Individual encryption keys are used for each segment, ensuring robust protection. 33 | - A single 'masterlock' file, encrypted with a user-provided password, is used to decrypt the segments. It securely stores the passkeys and the mapping of random filenames to the original sequence. 34 | - The encrypted segments creation/modification timestamps are altered to further obscure the data sequence. 35 | 36 | ## How to use 37 | ### Encrypt 38 | The following command is an example on how to encrypt. The password for the masterlock will be prompted interactively. 39 | ```bash 40 | tachicrypt -hide -data /path/to/your/file/or/directory -output /path/to/where/ecnrypted/data/and/masterlock/should/be/stored -parts INT 41 | ``` 42 | * -hide: Indicates that the data should be encrypted. 43 | * -data: Specifies the path to the file or directory to be encrypted. 44 | * -output: Sets the directory where the encrypted parts and masterlock file will be stored. 45 | * -parts: Determines the number of encrypted parts to create. 46 | 47 | 48 | ### Decrypt 49 | The following command is an example on how to decrypt. The password for the masterlock will be prompted interactively. 50 | ```bash 51 | tachicrypt -unhide -data /path/to/your/encrypted/files/and/masterlock -output /path/to/where/the/decrypted/data/should/be/stored 52 | ``` 53 | * -unhide: Indicates that the data should be decrypted. 54 | * -data: Specifies the path to the directory containing the encrypted parts and masterlock file. 55 | * -output: Sets the directory where the decrypted data will be stored. 56 | 57 | ### Help 58 | You can always use 59 | ```bash 60 | tachicrypt --help 61 | ``` 62 | to print information about the params and example commands for encrypting and decrypting. 63 | 64 | ## Screenshots 65 | ### Encryption 66 | TachiCrypt Encryption cli output example 67 | 68 | ### Decryption 69 | TachiCrypt Decryption cli output example 70 | 71 | ## How to build 72 | 73 | To clone and run this application, you'll need [Git](https://git-scm.com) and [go](https://go.dev/doc/install) installed on your computer. From your command line: 74 | 75 | ```bash 76 | # Move to your GOPATH directory 77 | $ cd $GOPATH 78 | 79 | # Create necessary directories 80 | $ mkdir -p github.com/voodooEntity 81 | 82 | # Move into the newly created directory 83 | $ cd github.com/voodooEntity 84 | 85 | # Clone this repository 86 | $ git clone https://github.com/voodooEntity/go-tachicrypt.git 87 | 88 | # Move to the client directory 89 | $ cd go-tachicrypt/cmd/client 90 | 91 | # Build the application 92 | $ go build -o tachicrypt 93 | 94 | # Copy the resulting binary into your gopath binary directory 95 | $ cp tachicrypt $GOPATH/bin 96 | ``` 97 | 98 | After this steps you can use the command 'tachicrypt' on the cli wherever you are 99 | 100 | ## Roadmap 101 | At this point i want to repeat that this is an experimental project. I got no exact timeline or plans on when i will enhance this software. I probably gonne decide this based on if there is anyone interested and actually will be using it. So if you want me to further improve it - leave a star to let me know .) 102 | 103 | - [x] Add Changelog 104 | - [x] Adjust password input to not show the typed text 105 | - [x] Cli output overhaul 106 | - [x] Implement better error handling 107 | - [x] Remove base64 as transport between zip and AES 108 | - [x] Reconsider the usage of zip as transport format 109 | - [ ] Code cleanup 110 | - [ ] Enhance the padding at the end of uneven last parts to use random data 111 | - [ ] Enhance the strength of generated passkeys for encryption 112 | - [ ] Implement a check to warn the user if the gathered random data is weak 113 | 114 | 115 | ## Changelog 116 | ***Notice: While in beta, minor version updates may be breaking. Even tho i will try to reduce the amount of breaking changes, it still may happen. When breaking changes are to be expected it will be informed about in the breaking versions changelog.*** 117 | 118 | [Check the Changelog](./CHANGELOG.md) 119 | 120 | Latest Release [Beta 0.4.0](https://github.com/voodooEntity/go-tachicrypt/releases/tag/0.4.0) 121 | 122 | 123 | 124 | ## License 125 | [Apache License Version 2.0](./LICENSE) 126 | 127 | --- 128 | 129 | > [laughingman.dev](https://blog.laughingman.dev)  ·  130 | > GitHub [@voodooEntity](https://github.com/voodooEntity) 131 | 132 | -------------------------------------------------------------------------------- /cmd/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/voodooEntity/go-tachicrypt/src/core" 7 | "github.com/voodooEntity/go-tachicrypt/src/prettywriter" 8 | "github.com/voodooEntity/go-tachicrypt/src/utils" 9 | ) 10 | 11 | var version = "Alpha 0.2.0" 12 | 13 | func main() { 14 | // Define flags 15 | hide := flag.Bool("hide", false, "Hide (encrypt) data") 16 | unhide := flag.Bool("unhide", false, "Unhide (decrypt) data") 17 | dataPath := flag.String("data", "", "Path to the data file or directory") 18 | partCount := flag.Int("parts", -1, "Amount of parts that should be created") 19 | outputDir := flag.String("output", "", "Output directory for encrypted data or decrypted data") 20 | help := flag.Bool("help", false, "Show help message") 21 | 22 | // Parse flags 23 | flag.Parse() 24 | 25 | // Show help if --help is specified 26 | if *help { 27 | printUsage() 28 | return 29 | } 30 | 31 | if *hide && -1 == *partCount { 32 | utils.ExitError("Missing mandatory --parts parameter \n") 33 | return 34 | } 35 | 36 | if *hide && *unhide { 37 | utils.ExitError("Cannot use both --hide and --unhide options at the same time. \n") 38 | return 39 | } 40 | 41 | if (*hide || *unhide) && (*dataPath == "" || *outputDir == "") { 42 | utils.ExitError("Both --data and --output must be specified. \n") 43 | return 44 | } 45 | 46 | utils.PrintApplicationHeader(version) 47 | 48 | if *hide { 49 | c := core.New() 50 | err := c.Hide(*dataPath, *partCount, *outputDir, "") 51 | if err != nil { 52 | utils.ExitError(fmt.Sprintf("Error hiding data: %v \n", err)) 53 | return 54 | } 55 | return 56 | } 57 | 58 | if *unhide { 59 | c := core.New() 60 | err := c.Unhide(*dataPath, *outputDir, "") 61 | if err != nil { 62 | utils.ExitError(fmt.Sprintf("Error unhiding data: %v \n", err)) 63 | return 64 | } 65 | return 66 | } 67 | printUsage() 68 | } 69 | 70 | // printUsage prints the usage information for the command-line tool. 71 | func printUsage() { 72 | prettywriter.WriteInBox(40, "Usage: tachicrypt [options]", prettywriter.Green, prettywriter.BlackBG, prettywriter.DoubleLine) 73 | fmt.Println("") 74 | prettywriter.Writeln("Options:", prettywriter.Green, prettywriter.BlackBG) 75 | prettywriter.Writeln(" --hide Hide (encrypt) data", prettywriter.Green, prettywriter.BlackBG) 76 | prettywriter.Writeln(" --unhide Unhide (decrypt) data", prettywriter.Green, prettywriter.BlackBG) 77 | prettywriter.Writeln(" --data [arg] Path to the data file or directory", prettywriter.Green, prettywriter.BlackBG) 78 | prettywriter.Writeln(" --parts [arg] Amount of parts to be created when hiding", prettywriter.Green, prettywriter.BlackBG) 79 | prettywriter.Writeln(" --output [arg] Output directory for encrypted data or decrypted data", prettywriter.Green, prettywriter.BlackBG) 80 | prettywriter.Writeln(" --help Show this help message", prettywriter.Green, prettywriter.BlackBG) 81 | fmt.Println("") 82 | prettywriter.Writeln("Examples:", prettywriter.Green, prettywriter.BlackBG) 83 | prettywriter.Writeln(" Encrypt data: tachicrypt --hide --parts 10 --data /path/to/data --output /path/to/output", prettywriter.Green, prettywriter.BlackBG) 84 | prettywriter.Writeln(" Decrypt data: tachicrypt --data /path/to/encrypted/data --unhide --output /path/to/output ", prettywriter.Green, prettywriter.BlackBG) 85 | fmt.Println("") 86 | } 87 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/voodooEntity/go-tachicrypt 2 | 3 | go 1.19 4 | 5 | require golang.org/x/term v0.25.0 6 | 7 | require golang.org/x/sys v0.26.0 // indirect 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 2 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 3 | golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= 4 | golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 5 | -------------------------------------------------------------------------------- /src/core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/voodooEntity/go-tachicrypt/src/encryptor" 7 | "github.com/voodooEntity/go-tachicrypt/src/fileutils" 8 | "github.com/voodooEntity/go-tachicrypt/src/masterlock" 9 | "github.com/voodooEntity/go-tachicrypt/src/prettywriter" 10 | "github.com/voodooEntity/go-tachicrypt/src/splitter" 11 | "github.com/voodooEntity/go-tachicrypt/src/utils" 12 | "github.com/voodooEntity/go-tachicrypt/src/zipper" 13 | "io/ioutil" 14 | "os" 15 | "path/filepath" 16 | "strconv" 17 | ) 18 | 19 | type Core struct { 20 | PartSize int 21 | KeySize int 22 | SaltSize int 23 | PartCount int 24 | } 25 | 26 | func New() *Core { 27 | return &Core{ 28 | SaltSize: 100, 29 | KeySize: 32, 30 | } 31 | } 32 | 33 | func (c *Core) Hide(dataPath string, partCount int, outputDir string, prefilledPassword string) error { 34 | prettywriter.WriteInBox(40, "Configuration", prettywriter.Green, prettywriter.BlackBG, prettywriter.DoubleLine) 35 | prettywriter.Writeln("[==] Chosen mode: hide (encrypting)", prettywriter.Green, prettywriter.BlackBG) 36 | prettywriter.Writeln("[==] Input path: "+dataPath, prettywriter.Green, prettywriter.BlackBG) 37 | prettywriter.Writeln("[==] Output path: "+outputDir, prettywriter.Green, prettywriter.BlackBG) 38 | prettywriter.Writeln("[==] Amount of parts: "+strconv.Itoa(partCount), prettywriter.Green, prettywriter.BlackBG) 39 | fmt.Println("") 40 | 41 | prettywriter.WriteInBox(40, "Starting Encryption Process", prettywriter.Black, prettywriter.Green, prettywriter.DoubleLine) 42 | prettywriter.Writeln("[>>] Zipping input data", prettywriter.Green, prettywriter.BlackBG) 43 | // Step 1: Create the zip data 44 | c.PartCount = partCount 45 | 46 | zipr := zipper.New() 47 | zipData, err := zipr.Zip(dataPath) 48 | if err != nil { 49 | return fmt.Errorf("error zipping and encoding: %w", err) 50 | } 51 | prettywriter.Writeln("[>>] Splitting zip into padded parts", prettywriter.Green, prettywriter.BlackBG) 52 | 53 | // to tackle known cleartext attack on the zip header we are going to add a random amount of random data at the beginning. this 54 | // might not be the perfect solution tho it requires an attacker to use either allow of brute force or figure some very smart 55 | // frequency analysis to find it. 56 | randomFrontPadding, err := utils.GenerateRandomBytes(1000, 10000) 57 | if err != nil { 58 | return fmt.Errorf("error generating random front padding: %w", err) 59 | } 60 | paddedZipData := append(randomFrontPadding, zipData...) 61 | frontPaddingAmount := len(randomFrontPadding) 62 | 63 | // Step 2: Split the zip slice into parts with padding 64 | parts, backPadding := splitter.SplitBytesWithPadding(paddedZipData, c.PartCount) 65 | 66 | // Step 3: Run encryption on all the parts, store them encrypted and add the info to masterlock 67 | var partInfos []masterlock.PartInfo 68 | for i, part := range parts { 69 | fmt.Print("\r") 70 | prettywriter.Write("[>>] Encrypt and store parts : "+strconv.Itoa(i+1)+"/"+strconv.Itoa(len(parts)), prettywriter.Green, prettywriter.BlackBG) 71 | encryptedPart, key, err := encryptor.EncryptWithRandomKey(part) 72 | if err != nil { 73 | return fmt.Errorf("error encrypting part: %w", err) 74 | } 75 | 76 | filename, err := utils.GenerateRandomFilename() 77 | if err != nil { 78 | return fmt.Errorf("error generating filename: %w", err) 79 | } 80 | 81 | partPath := filepath.Join(outputDir, filename) 82 | err = fileutils.WriteToFile(partPath, encryptedPart) 83 | if err != nil { 84 | return fmt.Errorf("error writing encrypted part to file: %w", err) 85 | } 86 | 87 | partInfos = append(partInfos, masterlock.PartInfo{ 88 | Index: i, 89 | Filename: filename, 90 | Key: key, 91 | }) 92 | } 93 | fmt.Println("") 94 | prettywriter.Writeln("[**] All parts successfully encrypted and stored.", prettywriter.BlackBG, prettywriter.Green) 95 | fmt.Println("") 96 | // Step 4: Create Masterlock, prompt user for pwd and encrypt and store the masterlock 97 | masterLockData, err := masterlock.CreateMasterLock(partInfos, frontPaddingAmount, backPadding) 98 | if err != nil { 99 | return fmt.Errorf("error creating master lock file: %w", err) 100 | } 101 | 102 | password := "" 103 | if "" == prefilledPassword { 104 | password = utils.PromptForPassword("Please enter a password to encrypt the masterlock: ") 105 | } else { 106 | password = prefilledPassword 107 | } 108 | fmt.Println("") 109 | prettywriter.WriteInBox(40, "Handle masterlock file", prettywriter.Black, prettywriter.Green, prettywriter.DoubleLine) 110 | prettywriter.Writeln("[>>] Encrypting masterlock", prettywriter.BlackBG, prettywriter.Green) 111 | encryptedMasterLock, err := encryptor.EncryptWithPassword(masterLockData, password) 112 | if err != nil { 113 | return fmt.Errorf("error encrypting master lock file: %w", err) 114 | } 115 | masterLockPath := filepath.Join(outputDir, "masterlock") 116 | prettywriter.Writeln("[>>] Writing masterlock", prettywriter.BlackBG, prettywriter.Green) 117 | err = fileutils.WriteToFile(masterLockPath, encryptedMasterLock) 118 | if err != nil { 119 | return fmt.Errorf("error writing master lock file: %w", err) 120 | } 121 | prettywriter.Writeln("[**] Masterlock successful written", prettywriter.BlackBG, prettywriter.Green) 122 | fmt.Println("") 123 | prettywriter.WriteInBox(40, "Final Shenanigans", prettywriter.Black, prettywriter.Green, prettywriter.DoubleLine) 124 | prettywriter.Writeln("[>>] Obfuscating timestamps ", prettywriter.BlackBG, prettywriter.Green) 125 | // Step 5: Obfuscate timestamps to hide theoriginal encrypted parts order 126 | err = fileutils.ObfuscateFileTimestamps(outputDir) 127 | if err != nil { 128 | return fmt.Errorf("error obfuscating file timestamps: %w", err) 129 | } 130 | prettywriter.Writeln("[**] Timestamps successful altered", prettywriter.BlackBG, prettywriter.Green) 131 | fmt.Println("") 132 | prettywriter.WriteInBox(40, "Encryption finished", prettywriter.Black, prettywriter.Green, prettywriter.DoubleLine) 133 | return nil 134 | } 135 | 136 | func (c *Core) Unhide(partsDir, outputPath string, prefilledPassword string) error { 137 | prettywriter.WriteInBox(40, "Configuration", prettywriter.Green, prettywriter.BlackBG, prettywriter.DoubleLine) 138 | prettywriter.Writeln("[==] Chosen mode: unhide (decrypting)", prettywriter.Green, prettywriter.BlackBG) 139 | prettywriter.Writeln("[==] Input path: "+partsDir, prettywriter.Green, prettywriter.BlackBG) 140 | prettywriter.Writeln("[==] Output path: "+outputPath, prettywriter.Green, prettywriter.BlackBG) 141 | fmt.Println("") 142 | 143 | prettywriter.WriteInBox(40, "Starting Decryption Process", prettywriter.Black, prettywriter.Green, prettywriter.DoubleLine) 144 | fmt.Println("") 145 | 146 | // Step 1: Decrypt Master Lock File 147 | password := "" 148 | if "" == prefilledPassword { 149 | password = utils.PromptForPassword("Enter the password to decrypt the masterlock: ") 150 | } else { 151 | password = prefilledPassword 152 | } 153 | 154 | fmt.Println() 155 | prettywriter.WriteInBox(40, "Handling masterlock", prettywriter.Black, prettywriter.Green, prettywriter.DoubleLine) 156 | prettywriter.Writeln("[>>] Reading masterlock", prettywriter.BlackBG, prettywriter.Green) 157 | encryptedMasterLock, err := ioutil.ReadFile(filepath.Join(partsDir, "masterlock")) 158 | if err != nil { 159 | return fmt.Errorf("error reading encrypted master lock file: %w", err) 160 | } 161 | 162 | prettywriter.Writeln("[>>] Decrypting masterlock", prettywriter.BlackBG, prettywriter.Green) 163 | decryptedMasterLock, err := encryptor.DecryptWithPassword(encryptedMasterLock, password) 164 | if err != nil { 165 | return fmt.Errorf("error decrypting master lock file: %w", err) 166 | } 167 | 168 | prettywriter.Writeln("[>>] Unpacking masterlock", prettywriter.BlackBG, prettywriter.Green) 169 | var mlock masterlock.MasterLock 170 | err = json.Unmarshal(decryptedMasterLock, &mlock) 171 | if err != nil { 172 | return fmt.Errorf("error unmarshaling master lock file: %w", err) 173 | } 174 | prettywriter.Writeln("[**] Masterlock handled successful", prettywriter.BlackBG, prettywriter.Green) 175 | 176 | // Step 2: Decrypt Each Part 177 | var allParts [][]byte 178 | fmt.Println("") 179 | prettywriter.WriteInBox(40, "Handling encrypted parts", prettywriter.Black, prettywriter.Green, prettywriter.DoubleLine) 180 | for i, partInfo := range mlock.Parts { 181 | fmt.Print("\r") 182 | prettywriter.Write("[>>] Decrypting parts : "+strconv.Itoa(i+1)+"/"+strconv.Itoa(len(mlock.Parts)), prettywriter.Green, prettywriter.BlackBG) 183 | partPath := filepath.Join(partsDir, partInfo.Filename) 184 | encryptedPart, err := os.ReadFile(partPath) 185 | if err != nil { 186 | return fmt.Errorf("error reading encrypted part file: %w", err) 187 | } 188 | 189 | decryptedPart, err := encryptor.DecryptWithRandomKey(encryptedPart, partInfo.Key) 190 | if err != nil { 191 | return fmt.Errorf("error decrypting part: %w", err) 192 | } 193 | 194 | allParts = append(allParts, decryptedPart) 195 | } 196 | fmt.Println("") 197 | prettywriter.Writeln("[**] Parts decrypted successful ", prettywriter.Green, prettywriter.BlackBG) 198 | fmt.Println("") 199 | 200 | // Step 3: Reconstruct zip Data 201 | prettywriter.WriteInBox(40, "Handling zip", prettywriter.Black, prettywriter.Green, prettywriter.DoubleLine) 202 | paddedData := utils.ConcatByteSlices(allParts) 203 | paddedDataLen := len(paddedData) 204 | unpaddedData := paddedData[mlock.FrontPadding : paddedDataLen-mlock.BackPadding] 205 | prettywriter.Writeln("[**] Reconstructed zip data without padding ", prettywriter.Green, prettywriter.BlackBG) 206 | prettywriter.Writeln("[>>] Unpacking zip data ", prettywriter.Green, prettywriter.BlackBG) 207 | zipper := zipper.New() 208 | err = zipper.Extract(unpaddedData, outputPath) 209 | if err != nil { 210 | return fmt.Errorf("error unzipping data: %w", err) 211 | } 212 | prettywriter.Writeln("[**] Successfully unpacked zip data ", prettywriter.Green, prettywriter.BlackBG) 213 | fmt.Println() 214 | prettywriter.WriteInBox(40, "Decryption finished", prettywriter.Black, prettywriter.Green, prettywriter.DoubleLine) 215 | 216 | return nil 217 | } 218 | -------------------------------------------------------------------------------- /src/encryptor/encryptor.go: -------------------------------------------------------------------------------- 1 | package encryptor 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "encoding/base64" 9 | "errors" 10 | "fmt" 11 | "io" 12 | ) 13 | 14 | // deriveKey converts a password string into a 32-byte key using SHA-256 15 | func deriveKey(password string) []byte { 16 | hash := sha256.Sum256([]byte(password)) 17 | return hash[:] 18 | } 19 | 20 | func EncryptWithRandomKey(data []byte) ([]byte, string, error) { 21 | // Generate a random key 22 | key := make([]byte, aes.BlockSize) 23 | if _, err := rand.Read(key); err != nil { 24 | return []byte{}, "", errors.New("error generating random key") 25 | } 26 | 27 | // Encrypt using the generated key 28 | ciphertextBytes, err := EncryptWithPassword(data, string(key)) 29 | if err != nil { 30 | return []byte{}, "", err 31 | } 32 | 33 | // Encode the key in Base64 for storage or transmission 34 | encodedKey := base64.StdEncoding.EncodeToString(key) 35 | 36 | return ciphertextBytes, encodedKey, nil 37 | } 38 | 39 | // DecryptWithRandomKey decrypts a ciphertext using AES with a randomly generated key. 40 | func DecryptWithRandomKey(ciphertextBytes []byte, encodedKey string) ([]byte, error) { 41 | // Decode the key 42 | keyBytes, err := base64.StdEncoding.DecodeString(encodedKey) 43 | if err != nil { 44 | return []byte{}, fmt.Errorf("error decoding key: %+w ", err) 45 | } 46 | 47 | // Decrypt using the decoded key 48 | return DecryptWithPassword(ciphertextBytes, string(keyBytes)) 49 | } 50 | 51 | func DecryptWithPassword(ciphertextBytes []byte, password string) ([]byte, error) { 52 | // Derive a 32-byte key from the password 53 | key := deriveKey(password) 54 | aesCipher, err := aes.NewCipher(key) 55 | if err != nil { 56 | return []byte{}, fmt.Errorf("error creating AES cipher: %w", err) 57 | } 58 | 59 | // Create a new GCM cipher 60 | gcm, err := cipher.NewGCM(aesCipher) 61 | if err != nil { 62 | return []byte{}, fmt.Errorf("error creating GCM cipher: %w", err) 63 | } 64 | 65 | // Split the ciphertext into IV and actual ciphertext 66 | iv := ciphertextBytes[:gcm.NonceSize()] 67 | ciphertextBytes = ciphertextBytes[gcm.NonceSize():] 68 | 69 | // Decrypt the ciphertext using GCM 70 | dst := []byte{} 71 | plaintext, err := gcm.Open(dst, iv, ciphertextBytes, nil) 72 | if err != nil { 73 | return []byte{}, errors.New("error decrypting ciphertext") 74 | } 75 | 76 | return plaintext, nil 77 | } 78 | 79 | func EncryptWithPassword(data []byte, password string) ([]byte, error) { 80 | // Derive a 32-byte key from the password 81 | key := deriveKey(password) 82 | aesCipher, err := aes.NewCipher(key) 83 | if err != nil { 84 | return []byte{}, fmt.Errorf("error creating AES cipher: %+w", err) 85 | } 86 | 87 | // Create a new GCM cipher 88 | gcm, err := cipher.NewGCM(aesCipher) 89 | if err != nil { 90 | return []byte{}, fmt.Errorf("error creating GCM cipher: %+w", err) 91 | } 92 | 93 | // Generate a random initialization vector (IV) 94 | iv := make([]byte, gcm.NonceSize()) // gcm.NonceSize() typically returns 12 95 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 96 | panic(err) 97 | } 98 | 99 | // Encrypt the plaintext using GCM 100 | ciphertext := gcm.Seal(nil, iv, data, nil) 101 | 102 | // Encode the ciphertext and IV in Base64 for storage or transmission 103 | return append(iv, ciphertext...), nil 104 | } 105 | -------------------------------------------------------------------------------- /src/fileutils/fileutils.go: -------------------------------------------------------------------------------- 1 | package fileutils 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "time" 8 | ) 9 | 10 | func WriteToFile(filename string, content []byte) error { 11 | // Open the file for writing with 0644 permissions 12 | file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644) 13 | if err != nil { 14 | return fmt.Errorf("error opening file: %w", err) 15 | } 16 | defer file.Close() // Ensure the file is closed even if an error occurs 17 | 18 | // Write the content to the file 19 | _, err = file.Write(content) 20 | if err != nil { 21 | return fmt.Errorf("error writing to file: %w", err) 22 | } 23 | 24 | return nil 25 | } 26 | 27 | func ObfuscateFileTimestamps(dirPath string) error { 28 | files, err := os.ReadDir(dirPath) 29 | if err != nil { 30 | return err 31 | } 32 | for _, file := range files { 33 | filePath := dirPath + "/" + file.Name() 34 | if err := touchFile(filePath); err != nil { 35 | return err 36 | } 37 | } 38 | return nil 39 | } 40 | 41 | func touchFile(filePath string) error { 42 | rand.Seed(time.Now().UnixNano()) 43 | randomTime := time.Now().Add(time.Duration(rand.Intn(1000000)-500000) * time.Second) 44 | return os.Chtimes(filePath, randomTime, randomTime) 45 | } 46 | -------------------------------------------------------------------------------- /src/masterlock/masterlock.go: -------------------------------------------------------------------------------- 1 | package masterlock 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type PartInfo struct { 9 | Index int `json:"index"` 10 | Filename string `json:"filename"` 11 | Key string `json:"key"` 12 | } 13 | 14 | type MasterLock struct { 15 | Parts []PartInfo `json:"parts"` 16 | FrontPadding int `json:"front_padding"` 17 | BackPadding int `json:"padding"` 18 | } 19 | 20 | func CreateMasterLock(parts []PartInfo, frontPadding int, backPadding int) ([]byte, error) { 21 | masterLock := MasterLock{ 22 | Parts: parts, 23 | FrontPadding: frontPadding, 24 | BackPadding: backPadding, 25 | } 26 | 27 | data, err := json.Marshal(masterLock) 28 | if err != nil { 29 | return nil, fmt.Errorf("Could not json encode master lock: %w", err) 30 | } 31 | 32 | return data, nil 33 | } 34 | -------------------------------------------------------------------------------- /src/prettywriter/prettywriter.go: -------------------------------------------------------------------------------- 1 | package prettywriter 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Color represents the available colors for text formatting. 9 | type Color int 10 | 11 | const ( 12 | // Foreground colors 13 | Black Color = 30 14 | Red Color = 31 15 | Green Color = 32 16 | Yellow Color = 33 17 | Blue Color = 34 18 | Magenta Color = 35 19 | Cyan Color = 36 20 | White Color = 37 21 | 22 | // Background colors 23 | 24 | BlackBG Color = 40 25 | RedBG Color = 41 26 | GreenBG Color = 42 27 | YellowBG Color = 43 28 | BlueBG Color = 44 29 | MagentaBG Color = 45 30 | CyanBG Color = 46 31 | WhiteBG Color = 47 32 | ) 33 | 34 | // Write writes the given message to the console with the specified foreground and background colors. 35 | func Write(message string, foregroundColor, backgroundColor Color) { 36 | fmt.Print("\033[", int(foregroundColor), ";", int(backgroundColor), "m", message, "\033[0m") 37 | } 38 | 39 | // Writeln writes the given message to the console with the specified foreground and background colors, followed by a newline. 40 | func Writeln(message string, foregroundColor, backgroundColor Color) { 41 | Write(message, foregroundColor, backgroundColor) 42 | fmt.Println() 43 | } 44 | 45 | // Writef writes the formatted message to the console with the specified foreground and background colors. 46 | func Writef(format string, foregroundColor, backgroundColor Color, a ...interface{}) { 47 | fmt.Printf(fmt.Sprint("\033[", int(foregroundColor), ";", int(backgroundColor), "m", format, "\033[0m"), a...) 48 | } 49 | 50 | // Writefln writes the formatted message to the console with the specified foreground and background colors, followed by a newline. 51 | func Writefln(format string, foregroundColor, backgroundColor Color, a ...interface{}) { 52 | Writef(format, foregroundColor, backgroundColor, a...) 53 | fmt.Println() 54 | } 55 | 56 | // Style represents the border style of the box. 57 | type Style int 58 | 59 | const ( 60 | SingleLine Style = iota 61 | DoubleLine 62 | ) 63 | 64 | // WriteInBox prints the message in a box format within the provided shell width. 65 | func WriteInBox(shellWidth int, message string, foregroundColor, backgroundColor Color, style Style) { 66 | lines := wrapMessage(message, shellWidth-4) // Account for box corners and padding 67 | 68 | // Calculate top and bottom borders 69 | topBorder := "┌" + getBorder(shellWidth, style) + "┐" 70 | bottomBorder := "└" + getBorder(shellWidth, style) + "┘" 71 | 72 | // Print top border 73 | fmt.Println(topBorder) 74 | 75 | // Iterate through wrapped lines 76 | for _, line := range lines { 77 | // Calculate padding based on line length 78 | padding := strings.Repeat(" ", shellWidth-len(line)-3) // Account for corners, spaces 79 | 80 | // Print line with color and padding 81 | fmt.Printf("|\033[%d;%dm %s %s \033[0m|\n", int(foregroundColor), int(backgroundColor), line, padding) 82 | } 83 | 84 | // Print bottom border 85 | fmt.Println(bottomBorder) 86 | } 87 | 88 | // wrapMessage splits the message into lines based on the provided width, breaking at spaces. 89 | func wrapMessage(message string, maxWidth int) []string { 90 | words := strings.Fields(message) 91 | var lines []string 92 | line := "" 93 | 94 | for _, word := range words { 95 | if len(line)+len(word)+1 > maxWidth { // Account for spaces 96 | lines = append(lines, line) 97 | line = "" 98 | } 99 | line += word + " " 100 | } 101 | 102 | lines = append(lines, strings.TrimSpace(line)) // Add the last line 103 | 104 | return lines 105 | } 106 | 107 | // getBorder generates the border string based on the shell width and style. 108 | func getBorder(width int, style Style) string { 109 | borderChar := "-" 110 | if style == DoubleLine { 111 | borderChar = "=" 112 | } 113 | return strings.Repeat(borderChar, width) 114 | } 115 | -------------------------------------------------------------------------------- /src/splitter/splitter.go: -------------------------------------------------------------------------------- 1 | package splitter 2 | 3 | func SplitBytesWithPadding(data []byte, partCount int) ([][]byte, int) { 4 | partLength := len(data) / partCount 5 | remainder := len(data) % partCount 6 | 7 | parts := make([][]byte, partCount) 8 | padding := remainder 9 | 10 | for i := 0; i < partCount; i++ { 11 | start := i * partLength 12 | end := start + partLength 13 | if i == partCount-1 { 14 | end += remainder 15 | } 16 | part := data[start:end] 17 | if i == partCount-1 && remainder > 0 { 18 | paddingBytes := make([]byte, padding) 19 | for j := range paddingBytes { 20 | paddingBytes[j] = ' ' 21 | } 22 | part = append(part, paddingBytes...) 23 | } 24 | parts[i] = part 25 | } 26 | 27 | return parts, padding 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "github.com/voodooEntity/go-tachicrypt/src/prettywriter" 7 | "golang.org/x/term" 8 | "math/big" 9 | "os" 10 | "syscall" 11 | ) 12 | 13 | var TachiHeading = ` 14 | _____ _ _ _____ _ 15 | |_ _| | | (_) __ \ | | 16 | | | __ _ ___| |__ _| / \/_ __ _ _ _ __ | |_ 17 | | |/ _ |/ __| '_ \| | | | '__| | | | '_ \| __| 18 | | | (_| | (__| | | | | \__/\ | | |_| | |_) | |_ 19 | \_/\__,_|\___|_| |_|_|\____/_| \__, | .__/ \__| 20 | __/ | | 21 | |___/|_| 22 | ` 23 | 24 | func GenerateRandomFilename() (string, error) { 25 | filename := make([]byte, 16) // Adjust length as needed 26 | if _, err := rand.Read(filename); err != nil { 27 | return "", err 28 | } 29 | return fmt.Sprintf("%x", filename), nil 30 | } 31 | 32 | func PromptForPassword(prompt string) string { 33 | prettywriter.WriteInBox(40, prompt, prettywriter.BlackBG, prettywriter.Green, prettywriter.DoubleLine) 34 | password, err := term.ReadPassword(int(syscall.Stdin)) 35 | if err != nil { 36 | prettywriter.Writeln(fmt.Sprintf("\nError reading password: %+v", err), prettywriter.Red, prettywriter.BlackBG) 37 | fmt.Sprintf("\nError reading password: %+v", err) 38 | return PromptForPassword(prompt) 39 | } 40 | if string(password) == "" { 41 | prettywriter.Writeln("Error: Invalid empty password.", prettywriter.Red, prettywriter.BlackBG) 42 | return PromptForPassword(prompt) 43 | } 44 | prettywriter.Write("[**] Password entered successfully", prettywriter.BlackBG, prettywriter.Green) 45 | fmt.Println("") 46 | pwd := string(password) 47 | return pwd 48 | } 49 | 50 | func PrintApplicationHeader(version string) { 51 | prettywriter.Write(TachiHeading, prettywriter.BlackBG, prettywriter.Green) 52 | prettywriter.Writeln("> Version: "+version, prettywriter.BlackBG, prettywriter.Green) 53 | prettywriter.Writeln("> Github: https://github.com/voodooEntity/go-tachicrypt", prettywriter.BlackBG, prettywriter.Green) 54 | prettywriter.Writeln("> Author: voodooEntity", prettywriter.BlackBG, prettywriter.Green) 55 | prettywriter.Writeln("> Contributors: f0o", prettywriter.BlackBG, prettywriter.Green) 56 | fmt.Println("") 57 | } 58 | 59 | func ExitError(message string) { 60 | fmt.Println("") 61 | prettywriter.WriteInBox(40, "!Error!", prettywriter.Black, prettywriter.Red, prettywriter.DoubleLine) 62 | fmt.Println("") 63 | prettywriter.Writeln(message, prettywriter.Black, prettywriter.Red) 64 | fmt.Println("") 65 | os.Exit(1) 66 | } 67 | 68 | func ConcatByteSlices(byteSlices [][]byte) []byte { 69 | totalLength := 0 70 | for _, slice := range byteSlices { 71 | totalLength += len(slice) 72 | } 73 | result := make([]byte, totalLength) 74 | offset := 0 75 | for _, slice := range byteSlices { 76 | copy(result[offset:], slice) 77 | offset += len(slice) 78 | } 79 | return result 80 | } 81 | 82 | func GenerateRandomBytes(min, max int) ([]byte, error) { 83 | // Ensure min is less than or equal to max 84 | if min > max { 85 | min, max = max, min 86 | } 87 | 88 | // Generate a random number between min and max (inclusive) 89 | randAmount, err := rand.Int(rand.Reader, big.NewInt(int64(max-min+1))) 90 | if err != nil { 91 | return nil, err 92 | } 93 | randAmount.Add(randAmount, big.NewInt(int64(min))) 94 | 95 | // Create a byte slice of the desired length 96 | randomBytes := make([]byte, int(randAmount.Int64())) 97 | 98 | // Read random bytes into the slice 99 | _, err = rand.Read(randomBytes) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return randomBytes, nil 105 | } 106 | -------------------------------------------------------------------------------- /src/zipper/zipper.go: -------------------------------------------------------------------------------- 1 | package zipper 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | type Zipper struct { 12 | } 13 | 14 | func New() *Zipper { 15 | return &Zipper{} 16 | } 17 | 18 | func (z *Zipper) Zip(path string) ([]byte, error) { 19 | buf := &bytes.Buffer{} 20 | w := zip.NewWriter(buf) 21 | 22 | // Zip the file(s) 23 | err := z.zipFile(path, "", w) 24 | if err != nil { 25 | w.Close() 26 | return []byte{}, err 27 | } 28 | 29 | // Close the writer before encoding 30 | err = w.Close() 31 | if err != nil { 32 | return []byte{}, err 33 | } 34 | 35 | return buf.Bytes(), nil 36 | } 37 | 38 | func (z *Zipper) zipFile(path string, prefix string, w *zip.Writer) error { 39 | info, err := os.Stat(path) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | if info.IsDir() { 45 | prefix = filepath.Join(prefix, filepath.Base(path)) 46 | files, err := os.ReadDir(path) 47 | if err != nil { 48 | return err 49 | } 50 | for _, file := range files { 51 | if err := z.zipFile(filepath.Join(path, file.Name()), prefix, w); err != nil { 52 | return err 53 | } 54 | } 55 | } else { 56 | f, err := os.Open(path) 57 | if err != nil { 58 | return err 59 | } 60 | defer f.Close() 61 | 62 | zf, err := w.Create(filepath.Join(prefix, filepath.Base(path))) 63 | if err != nil { 64 | return err 65 | } 66 | _, err = io.Copy(zf, f) 67 | return err 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func (z *Zipper) Extract(zipData []byte, destDir string) error { 74 | // Create a ZIP reader directly from the byte slice 75 | reader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData))) 76 | if nil != err { 77 | return err 78 | } 79 | 80 | for _, f := range reader.File { 81 | if f.FileInfo().IsDir() { 82 | err := os.MkdirAll(filepath.Join(destDir, f.Name), f.FileInfo().Mode()) 83 | if err != nil { 84 | return err 85 | } 86 | } else { 87 | rc, err := f.Open() 88 | if err != nil { 89 | return err 90 | } 91 | 92 | outFile := filepath.Join(destDir, f.Name) 93 | 94 | err = os.MkdirAll(filepath.Dir(outFile), 0755) // ### note to myself - 755 fixes a current issue but is not a good chmod 95 | if err != nil { 96 | return err 97 | } 98 | 99 | fw, err := os.Create(outFile) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | _, err = io.Copy(fw, rc) 105 | if err != nil { 106 | return err 107 | } 108 | fw.Close() 109 | rc.Close() 110 | } 111 | } 112 | 113 | return nil 114 | } 115 | --------------------------------------------------------------------------------