diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..622d92d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2011 Woody Gilk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index e33eff1..7f98a9d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,203 @@ -# DEPRECATED! NO LONGER MAINTAINED! +# Transparent Git Encryption -Please consider using [AGWA/git-crypt](https://github.com/AGWA/git-crypt) instead. -AGWA's tool has a proven security and is much better than this tool. +The gitcrypt tool is inspired by [this document][1] written by [Ning Shang][2], +which was in turn inspired by [this post][3]. Without these two documents, +by people much smarter than me, gitcrypt would not exist. -If you really need the source of this tool, you can find the whole thing in the -[legacy branch](https://github.com/shadowhand/git-encrypt/tree/legacy). +> There is [some controversy][4] over using this technique, so do your research +and understand the implications of using this tool before you go crazy with it. + +## Requirements +Openssl must be installed and the binary must be available in your $PATH. + +## Installation + +Clone git-encrypt somewhere on your local machine: + + $ git clone https://github.com/shadowhand/git-encrypt + $ cd git-encrypt + +The `gitcrypt` command must be executable: + + $ chmod 0755 gitcrypt + +And it must be accessible in your `$PATH`: + + $ sudo ln -s gitcrypt /usr/local/bin/gitcrypt + +### For Windows + +**Verified on PortableGit Only !** + +Copy the file gitcrypt to your PortableGit/bin location. In my environment PortableGit is +available at E:\PortableGit. + +> copy gitcrypt E:\PortableGit\bin + +Also make sure that PATH environment variable has E:\PortableGit\bin +available in it. + +> Path=C:\Python27\;C:\Python27\Scripts;E:\PortableGit\bin;E:\PortableGit\libexec\git-core;C:\windows\system32;C:\windows\;C:\window +> s\system32\WBEM;c:\windows\System32\WindowsPowerShell\v1.0\;c:\i386\~configs;C:\Users\VKHANORK\AppData\Roaming\Python\Scripts + +Setup gitcrypt: + +> E:\>mkdir TEST +> +> E:\>cd TEST +> +> E:\TEST>git init +> Initialized empty Git repository in E:/TEST/.git/ +> +> E:\TEST>git config core.autocrlf false +> +> E:\TEST>E:\PortableGit\bin\bash.exe E:\PortableGit\bin\gitcrypt init +> Generate a random salt? [Y/n] +> Generate a random password? [Y/n] +> What encryption cipher do you want to use? [aes-256-ecb] +> +> This configuration will be stored: +> +> salt: 5ecc05565042de81 +> pass: iLC#GkuzE1iOmUVItIQww8**oBDTfKE2 +> cipher: aes-256-ecb +> +> Does this look right? [Y/n] +> Do you want to use .git/info/attributes? [Y/n] +> What files do you want encrypted? [*] +> +> E:\TEST> + +## Configuration + +To quickly setup gitcrypt interactively, run `gitcrypt init` from the root +of your git repository. It will ask you for a passphrase, shared salt, +cipher mode, and what files should be encrypted. + + $ cd my-repo + $ gitcrypt init + +Your repository is now set up! Any time you `git add` a file that matches the +filter pattern the `clean` filter is applied, automatically encrypting the file +before it is staged. Using `git diff` will work normally, as it automatically +decrypts file content as necessary. + +### Manual Configuration + +First, you will need to add a shared salt (16 hex characters) and a secure +passphrase to your git configuration: + + $ git config gitcrypt.salt 0000000000000000 + $ git config gitcrypt.pass my-secret-phrase + +> It is possible to set these options globally using `git config --global`, +but more secure to create a separate passphrase for every repository. + +The default [encryption cipher][5] is `aes-256-ebc`, which should be suitable +for almost everyone. However, it is also possible to use a different cipher: + + $ git config gitcrypt.cipher aes-256-ebc + +> An "ECB" mode is used because it encrypts in a format that provides usable +text diff, meaning that a single change will not cause the entire file to be +internally marked as changed. Because a static salt must be used, using "CBC" +would provide very little, if any, increased security over "ECB" mode. + +Next, you need to define what files will be automatically encrypted using the +[.git/info/attributes][6] file. Any file [pattern format][7] can be used here. + +To encrypt all the files in the repo: + + * filter=encrypt diff=encrypt + [merge] + renormalize = true + +To encrypt only one file, you could do this: + + secret.txt filter=encrypt diff=encrypt + +Or to encrypt all ".secure" files: + + *.secure filter=encrypt diff=encrypt + +> If you want this mapping to be included in your repository, use a +`.gitattributes` file instead and **do not** encrypt it. + +Next, you need to map the `encrypt` filter to `gitcrypt`: + + $ git config filter.encrypt.smudge "gitcrypt smudge" + $ git config filter.encrypt.clean "gitcrypt clean" + $ git config diff.encrypt.textconv "gitcrypt diff" + +Or if you prefer to manually edit `.git/config`: + + [filter "encrypt"] + smudge = gitcrypt smudge + clean = gitcrypt clean + [diff "encrypt"] + textconv = gitcrypt diff + +## Decrypting Clones + +To set up decryption from a clone, you will need to repeat the same setup on +the new clone. + +First, clone the repository, but **do not perform a checkout**: + + $ git clone -n git://github.com/johndoe/encrypted.get + $ cd encrypted + +> If you do a `git status` now, it will show all your files as being deleted. +Do not fear, this is actually what we want right now, because we need to setup +gitcrypt before doing a checkout. + +Now you can either run `gitcrypt init` or do the same manual configuration that +performed on the original repository. + +Once configuration is complete, reset and checkout all the files: + + $ git reset --hard HEAD + +All the files in the are now decrypted and ready to be edited. + +# Alternate method: git-encrypt-init.sh + +Contributed by [Jay Taylor](https://jaytaylor.com "jaytaylor.com") + + +The git-encrypt-init.sh shell script automatically performs all prepartion, +setup and configuration for a local repository clone, prompting the user for +any required information (salt and password phrases.) This method of also +ensures that the git-encrypt scripts are automatically installed to +`~/.gitencrypt/`. One drawback to this approach is that it only supports having +1 password. + +One reason to use this alternate approach is because it makes decrypting cloned +repositories as simple as executing one script. + +## Usage + +Once you've cloned git-encrypt using the alternate script is straightforward: + + $ cd /path/to/your/repository + $ sh /path/to/git-encrypt/git-encrypt-init.sh + +Then you can add the files you would like to have encrypted to the +.gitattributes file contained in the root of your repository. + + +# Conclusion + +Enjoy your secure git repository! If you think gitcrypt is totally awesome, +you could [buy me something][wishes] or [donate some money][donate]. + +[1]: http://syncom.appspot.com/papers/git_encryption.txt "GIT transparent encryption" +[2]: http://syncom.appspot.com/ +[3]: http://git.661346.n2.nabble.com/Transparently-encrypt-repository-contents-with-GPG-td2470145.html "Web discussion: Transparently encrypt repository contents with GPG" +[4]: http://article.gmane.org/gmane.comp.version-control.git/113221 "Junio Hamano does not recommend this technique" +[5]: http://en.wikipedia.org/wiki/Cipher +[6]: http://www.kernel.org/pub/software/scm/git/docs/gitattributes.html +[7]: http://www.kernel.org/pub/software/scm/git/docs/gitignore.html#_pattern_format + +[wishes]: http://www.amazon.com/gp/registry/wishlist/1474H3P2204L8 +[donate]: http://www.pledgie.com/campaigns/14931 diff --git a/git-encrypt-init.sh b/git-encrypt-init.sh new file mode 100755 index 0000000..31fd90e --- /dev/null +++ b/git-encrypt-init.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +## +# @author Jay Taylor [@jtaylor] +# +# @date 2012-04-09 +# +# @description Initializes openssl encryption filter into the .git/config file +# of a cloned git repository. +# + + +localGitConfigFile='.git/config' + + +################################################################################ + +# Ensure that we are running in the root of a git repository. +if ! [ -r "$localGitConfigFile" ]; then + echo 'fatal: this script can only be run in the root of a git repository' 1>&2 + echo 'check your current directory (by running `pwd`), correct any issues you find, and then try again' 1>&2 + exit 1 +fi + + +# Define filter scripts and other static executable/reference file contents. +# NB: The semi-colons at the end of each line for the first 3 entries here are +# due to the use of `eval` below. +clean_filter_openssl='#!/usr/bin/env bash; +; +SALT_FIXED={{SALT}}; +#A1F1F8129C4FEBAB3513C174 # 24 or less hex characters; +PASS_FIXED={{PASSWORD}}; +; +openssl enc -base64 -aes-256-ecb -S $SALT_FIXED -k $PASS_FIXED' + +smudge_filter_openssl='#!/usr/bin/env bash; +; +# No salt is needed for decryption.; +PASS_FIXED={{PASSWORD}}; +; +# If decryption fails, use `cat` instead.; +# Error messages are redirected to /dev/null.; +openssl enc -d -base64 -aes-256-ecb -k $PASS_FIXED 2> /dev/null || cat' + +diff_filter_openssl='#!/usr/bin/env bash; +; +# No salt is needed for decryption.; +PASS_FIXED={{PASSWORD}}; +; +# Error messages are redirected to /dev/null.; +openssl enc -d -base64 -aes-256-ecb -k $PASS_FIXED -in "$1" 2> /dev/null || cat "$1"' + +gitattributes='*.md filter=openssl diff=openssl +sensitive.txt filter=openssl diff=openssl +[merge] + renormalize = true' + +gitconfig='[filter "openssl"] + smudge = ~/.gitencrypt/smudge_filter_openssl + clean = ~/.gitencrypt/clean_filter_openssl +[diff "openssl"] + textconv = ~/.gitencrypt/diff_filter_openssl' + + +# Initialize .gitencrypt directory in the users $HOME if not already there. + +if ! [ -d "$HOME/.gitencrypt" ]; then + echo 'info: initializing ~/.gitencrypt' + + # Prompt user for salt and password. + while [ -z "$salt" ]; do + echo 'Enter the salt phrase (16 hexadecimal characters):' + read salt + done + + while [ -z "$password" ]; do + echo 'Enter the encryption pass-phrase:' + read password + done + + mkdir "$HOME/.gitencrypt" + + for filter in clean_filter_openssl smudge_filter_openssl diff_filter_openssl; do + echo "info: generating filter script '$filter'" + filterScriptPath="$HOME/.gitencrypt/$filter" + + # This ugliness is due to `eval` not handling newlines very nicely. + # @see http://stackoverflow.com/a/3524860/293064 for more eval details. + echo -e $(eval "echo \$$filter") | tr ';' '\n' | sed "s/{{SALT}}/$salt/g + s/{{PASSWORD}}/$password/g + s/^ *\(.*\) *$/\1/g" > "$filterScriptPath" + + chmod a+x "$filterScriptPath" + done +fi + + +# Initialize .gitattributes file if it doesn't exist. + +if ! [ -e '.gitattributes' ]; then + echo "info: initializing file '.gitattributes'" + echo -n $gitattributes > .gitattributes +fi + + +# Initialize the .git/conf file for this repository clone if not already. + +checkForPreExistingConf=$(grep '^\[\(filter\|diff\) "openssl"]$' "$localGitConfigFile") + +if [ -n "$checkForPreExistingConf" ]; then + echo 'info: openssl filter/diff already configured for this clone' +else + cat <> "$localGitConfigFile" +$gitconfig +EOF + echo 'info: openssl filter/diff successfuly applied to this clone' +fi + + +# Reset the HEAD to re-check out all of the files [with the encryption filters.] + +echo 'info: re-checking out all of the files to ensure that the encryption filters are applied' +git reset --hard HEAD + diff --git a/gitcrypt b/gitcrypt new file mode 100755 index 0000000..67fa9ec --- /dev/null +++ b/gitcrypt @@ -0,0 +1,167 @@ +#!/bin/bash + +readonly VERSION="0.3.0" +readonly DEFAULT_CIPHER="aes-256-ecb" + +init_config() { + local answer + while [ 1 ]; do + while [ -z "$SALT" ]; do + echo -n "Generate a random salt? [Y/n] " + read answer + + case "$answer" in + n*|N*) + echo -n "Shared salt as hex characters: " + read SALT + + if [ $(echo "$SALT" | grep '[^a-f0-9]' | wc -l) -ne 0 ]; then + echo "Error: non-hex characters in salt" + unset -v SALT + fi + ;; + *) + local md5=$(which md5 2>/dev/null || which md5sum 2>/dev/null) + SALT=$(head -c 10 < /dev/random | $md5 | cut -c-16) + ;; + esac + done + + while [ -z "$PASS" ]; do + echo -n "Generate a random password? [Y/n]" + read answer + + case "$answer" in + n*|N*) + echo -n "Enter your passphrase: " + read PASS + ;; + *) + PASS=$(cat /dev/urandom | tr -dc '!@#$%^&*()_A-Z-a-z-0-9' | head -c32) + ;; + esac + done + + while [ 1 ]; do + echo -n "What encryption cipher do you want to use? [$DEFAULT_CIPHER] " + read CIPHER + [ -z "$CIPHER" ] && CIPHER="$DEFAULT_CIPHER" + + local exists + exists=$(openssl list-cipher-commands | grep "$CIPHER") + [ $? -eq 0 ] && break + + echo "Error: Cipher '$CIPHER' is not available" + done + + echo -e "\nThis configuration will be stored:\n" + echo "salt: $SALT" + echo "pass: $PASS" + echo "cipher: $CIPHER" + echo -e -n "\nDoes this look right? [Y/n] " + read answer + + case "$answer" in + n*|N*) + # Reconfigure + unset -v SALT + unset -v PASS + unset -v CIPHER + ;; + *) + # Finished + break + ;; + esac + done + + echo -n "Do you want to use .git/info/attributes? [Y/n] " + read answer + + local attrs + case "$answer" in + n*|N*) + attrs=".gitattributes" + ;; + *) + attrs=".git/info/attributes" + ;; + esac + + local pattern + echo -n "What files do you want encrypted? [*] " + read pattern + [ -z "$pattern" ] && pattern="*" + + echo "$pattern filter=encrypt diff=encrypt" >> $attrs + echo "[merge]" >> $attrs + echo " renormalize=true" >> $attrs + + # Encryption + git config gitcrypt.salt "$SALT" + git config gitcrypt.pass "$PASS" + git config gitcrypt.cipher "$CIPHER" + + # Filters + git config filter.encrypt.smudge "gitcrypt smudge" + git config filter.encrypt.clean "gitcrypt clean" + git config diff.encrypt.textconv "gitcrypt diff" +} + +_clean() { + # Encrypt using OpenSSL + openssl enc -base64 -$CIPHER -S "$SALT" -k "$PASS" +} + +_smudge() { + # If decryption fails, use `cat` instead + openssl enc -d -base64 -$CIPHER -k "$PASS" 2> /dev/null || cat +} + +_diff() { + # If decryption fails, use `cat` instead + openssl enc -d -base64 -$CIPHER -k "$PASS" -in "$1" 2> /dev/null || cat "$1" +} + +case "$1" in + clean|smudge|diff) + # Need a shared salt + SALT=$(git config gitcrypt.salt) + if [ -z "$SALT" ]; then + echo "Gitcrypt: shared salt (gitcrypt.salt) has not been configured" + exit 1 + fi + + # Need a secure passphrase + PASS=$(git config gitcrypt.pass) + if [ -z "$PASS" ]; then + echo "Gitcrypt: secure passphrase (gitcrypt.pass) has not been configured" + exit 1 + fi + + # And a cipher mode + CIPHER=$(git config gitcrypt.cipher) + [ -z "$CIPHER" ] && CIPHER="$DEFAULT_CIPHER" + + # Execute command + _$1 "$2" + ;; + init) + # Run setup commands + init_config + ;; + version) + # Show version + echo "gitcrypt version $VERSION" + ;; + *) + # Not a valid option + if [ -z "$1" ]; then + echo "Gitcrypt: available options: init, version" + else + echo "Gitcrypt: command does not exist: $1" + fi + exit 1 + ;; +esac +exit 0