From 727fe60d5bc8b5048330fea91c6686ab2f632b8d Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 1 Apr 2023 22:06:27 +1100 Subject: [PATCH 01/24] Switch version to 3.0.0-alpha1 --- contrib/packaging/pacman/PKGBUILD | 2 +- transcrypt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/packaging/pacman/PKGBUILD b/contrib/packaging/pacman/PKGBUILD index 248a93a..8417706 100644 --- a/contrib/packaging/pacman/PKGBUILD +++ b/contrib/packaging/pacman/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Aaron Bull Schaefer pkgname=transcrypt -pkgver=2.3.0-pre +pkgver=3.0.0-alpha1 pkgrel=1 pkgdesc='A script to configure transparent encryption of files within a Git repository' arch=('any') diff --git a/transcrypt b/transcrypt index 43da7ef..6842e34 100755 --- a/transcrypt +++ b/transcrypt @@ -16,7 +16,7 @@ set -euo pipefail ##### CONSTANTS # the release version of this script -readonly VERSION='2.3.0-pre' +readonly VERSION='3.0.0-alpha1' # the default cipher to utilize readonly DEFAULT_CIPHER='aes-256-cbc' From 48139c69f5dbcba9006d0c8dfbfc7a8f35b3dcd8 Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 1 Apr 2023 22:13:46 +1100 Subject: [PATCH 02/24] Update copyright notices --- LICENSE | 3 ++- README.md | 1 + transcrypt | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 5ed94e0..d925757 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ -Copyright (c) 2014-2019, Aaron Bull Schaefer +Copyright (c) 2020-2023, James Murty +Copyright (c) 2014-2020, Aaron Bull Schaefer Copyright (c) 2011, Woody Gilk Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index 88cedd1..43f8874 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,7 @@ to encrypt a file \_top-secret* in a "super" context: transcrypt is provided under the terms of the [MIT License](https://en.wikipedia.org/wiki/MIT_License). +Copyright © 2020-2023, [James Murty](mailto:james@murty.co). Copyright © 2014-2020, [Aaron Bull Schaefer](mailto:aaron@elasticdog.com). ## Contributing diff --git a/transcrypt b/transcrypt index 6842e34..23c6684 100755 --- a/transcrypt +++ b/transcrypt @@ -8,7 +8,9 @@ set -euo pipefail # a Git repository. It utilizes OpenSSL's symmetric cipher routines and follows # the gitattributes(5) man page regarding the use of filters. # -# Copyright (c) 2014-2019 Aaron Bull Schaefer +# Copyright (c) 2020-2023 James Murty +# Copyright (c) 2014-2020 Aaron Bull Schaefer +# # This source code is provided under the terms of the MIT License # that can be be found in the LICENSE file. # From 3b62803ba55b39ef5beaa5a83960b88260f1ff52 Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 1 Apr 2023 23:40:30 +1100 Subject: [PATCH 03/24] Define magic salted prefix values (plaintext and B64) as constants --- transcrypt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/transcrypt b/transcrypt index 23c6684..dd590f8 100755 --- a/transcrypt +++ b/transcrypt @@ -26,6 +26,13 @@ readonly DEFAULT_CIPHER='aes-256-cbc' # context name must match this regexp to ensure it is safe for git config and attrs readonly CONTEXT_REGEX='[a-z](-?[a-z0-9])*' +# Encrypted files have the prefix "Salted__" ("U2FsdGVk" in Base64) due to an +# OpenSSL convention. We rely on this marker to check if a file is already +# encrypted. Later versions of OpenSSL have dropped this convention, so we must +# sometimes add the prefix back in (see #133 #147). +readonly OPENSSL_ENCRYPTED_SALTED_PREFIX='Salted__' +readonly OPENSSL_ENCRYPTED_SALTED_PREFIX_B64='U2FsdGVk' + ##### FUNCTIONS # load encryption password @@ -158,7 +165,8 @@ derive_context_config_group() { } # Detect OpenSSL major version 3 or later which requires a compatibility -# work-around to include the prefix 'Salted__' and salt value when encrypting. +# work-around to include the prefix 'Salted__' before the salt value when +# encrypting. # # Note that the LibreSSL project's version of the openssl command does NOT # require this work-around for major version 3. @@ -200,7 +208,7 @@ git_clean() { # the first bytes of an encrypted file are always "Salted" in Base64 # The `head + LC_ALL=C tr` command handles binary data in old and new Bash (#116) firstbytes=$(head -c8 "$tempfile" | LC_ALL=C tr -d '\0') - if [[ $firstbytes == "U2FsdGVk" ]]; then + if [[ $firstbytes == "$OPENSSL_ENCRYPTED_SALTED_PREFIX_B64" ]]; then cat "$tempfile" else context_config_group=$(derive_context_config_group "$context") @@ -212,7 +220,7 @@ git_clean() { if [ "$(is_salt_prefix_workaround_required)" == "true" ]; then # Encrypt the file to base64, ensuring it includes the prefix 'Salted__' with the salt. #133 ( - echo -n "Salted__" && echo -n "$salt" | xxd -r -p && + echo -n "$OPENSSL_ENCRYPTED_SALTED_PREFIX" && echo -n "$salt" | xxd -r -p && # Encrypt file to binary ciphertext ENC_PASS=$password "$openssl_path" enc -e "-${cipher}" -md MD5 -pass env:ENC_PASS -S "$salt" -in "$tempfile" ) | @@ -313,7 +321,7 @@ git_pre_commit() { if [[ $firstbytes == "" ]]; then : # Do nothing # The first bytes of an encrypted file must be "Salted" in Base64 - elif [[ $firstbytes != "U2FsdGVk" ]]; then + elif [[ $firstbytes != "$OPENSSL_ENCRYPTED_SALTED_PREFIX_B64" ]]; then printf 'Transcrypt managed file is not encrypted in the Git index: %s\n' "$secret_file" >&2 printf '\n' >&2 printf 'You probably staged this file using a tool that does not apply' >&2 @@ -340,7 +348,7 @@ git_pre_commit() { # Get prefix of raw file in Git's index using the :FILENAME revision syntax # The first bytes of an encrypted file are always "Salted" in Base64 local firstbytes=$(git show :"${secret_file}" | head -c8) - if [[ $firstbytes != "U2FsdGVk" ]]; then + if [[ $firstbytes != "$OPENSSL_ENCRYPTED_SALTED_PREFIX_B64" ]]; then echo "true" >>"${tmp}" fi } From 3b303f26d7c7da2b3ad89e1b869c9976ca5a2fcb Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 1 Apr 2023 23:43:48 +1100 Subject: [PATCH 04/24] Centralise loading of transcrypt config settings and conversion into a base openssl command --- transcrypt | 65 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/transcrypt b/transcrypt index dd590f8..83ece65 100755 --- a/transcrypt +++ b/transcrypt @@ -20,8 +20,9 @@ set -euo pipefail # the release version of this script readonly VERSION='3.0.0-alpha1' -# the default cipher to utilize +# the default encryption settings to recommend readonly DEFAULT_CIPHER='aes-256-cbc' +readonly DEFAULT_DIGEST='md5' # TODO change to 'sha512' # context name must match this regexp to ensure it is safe for git config and attrs readonly CONTEXT_REGEX='[a-z](-?[a-z0-9])*' @@ -185,6 +186,22 @@ is_salt_prefix_workaround_required() { fi } +# Read transcrypt configuration from the Git config and translate the options +# into a "base" OpenSSL command that can then be used to encrypt or decrypt a +# file with minimal extra arguments, as appropriate. +_translate_transcrypt_config_to_openssl_base_arguments() { + context=$(extract_context_name_from_name_value_arg "${1:-}") + context_config_group=$(derive_context_config_group "$context") + + cipher=$(git config --get --local "transcrypt${context_config_group}.cipher" || printf '') + digest=$(git config --get --local "transcrypt${context_config_group}.digest" || printf '%s' $DEFAULT_DIGEST) + password=$(load_password "$context_config_group") + + openssl_path=$(git config --get --local transcrypt.openssl-path || printf '') + + _openssl_base_arguments="ENC_PASS=\"${password}\" ${openssl_path} enc -${cipher} -md ${digest} -pass env:ENC_PASS" +} + # The `decryption -> encryption` process on an unchanged file must be # deterministic for everything to work transparently. To do that, the same # salt must be used each time we encrypt the same file. An HMAC has been @@ -193,10 +210,15 @@ is_salt_prefix_workaround_required() { # then use the last 16 bytes of that HMAC for the file's unique salt. git_clean() { - context=$(extract_context_name_from_name_value_arg "$1") - [[ "$context" ]] && shift + _translate_transcrypt_config_to_openssl_base_arguments "$1" + + # We may or may not get a context argument as $1. Filename is $2 if we do. + if [[ "$context" ]]; then + filename=$2 + else + filename=$1 + fi - filename=$1 # ignore empty files if [[ ! -s $filename ]]; then return @@ -211,10 +233,6 @@ git_clean() { if [[ $firstbytes == "$OPENSSL_ENCRYPTED_SALTED_PREFIX_B64" ]]; then cat "$tempfile" else - context_config_group=$(derive_context_config_group "$context") - cipher=$(git config --get --local "transcrypt${context_config_group}.cipher") - password=$(load_password "$context_config_group") - openssl_path=$(git config --get --local transcrypt.openssl-path) salt=$("${openssl_path}" dgst -hmac "${filename}:${password}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) if [ "$(is_salt_prefix_workaround_required)" == "true" ]; then @@ -222,12 +240,12 @@ git_clean() { ( echo -n "$OPENSSL_ENCRYPTED_SALTED_PREFIX" && echo -n "$salt" | xxd -r -p && # Encrypt file to binary ciphertext - ENC_PASS=$password "$openssl_path" enc -e "-${cipher}" -md MD5 -pass env:ENC_PASS -S "$salt" -in "$tempfile" + eval "${_openssl_base_arguments} -e -S ${salt} -in ${tempfile}" ) | openssl base64 else # Encrypt file to base64 ciphertext - ENC_PASS=$password "$openssl_path" enc -e -a "-${cipher}" -md MD5 -pass env:ENC_PASS -S "$salt" -in "$tempfile" + eval "${_openssl_base_arguments} -e -a -S ${salt} -in ${tempfile}" fi fi } @@ -235,29 +253,28 @@ git_clean() { git_smudge() { tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) trap 'rm -f "$tempfile"' EXIT - context=$(extract_context_name_from_name_value_arg "$1") - context_config_group=$(derive_context_config_group "$context") - cipher=$(git config --get --local "transcrypt${context_config_group}.cipher") - password=$(load_password "$context_config_group") - openssl_path=$(git config --get --local transcrypt.openssl-path) - tee "$tempfile" | ENC_PASS=$password "$openssl_path" enc -d "-${cipher}" -md MD5 -pass env:ENC_PASS -a 2>/dev/null || cat "$tempfile" + + _translate_transcrypt_config_to_openssl_base_arguments "$1" + + tee "$tempfile" | eval "${_openssl_base_arguments} -d -a" 2>/dev/null || cat "$tempfile" } git_textconv() { - context=$(extract_context_name_from_name_value_arg "$1") - [[ "$context" ]] && shift + _translate_transcrypt_config_to_openssl_base_arguments "$1" + + # We may or may not get a context argument as $1. Filename is $2 if we do. + if [[ "$context" ]]; then + filename=$2 + else + filename=$1 + fi - filename=$1 # ignore empty files if [[ ! -s $filename ]]; then return fi - context_config_group=$(derive_context_config_group "$context") - cipher=$(git config --get --local "transcrypt${context_config_group}.cipher") - password=$(load_password "$context_config_group") - openssl_path=$(git config --get --local transcrypt.openssl-path) - ENC_PASS=$password "$openssl_path" enc -d "-${cipher}" -md MD5 -pass env:ENC_PASS -a -in "$filename" 2>/dev/null || cat "$filename" + eval "${_openssl_base_arguments} -d -a -in \"${filename}\"" 2>/dev/null || cat "$filename" } # shellcheck disable=SC2005,SC2002,SC2181 From 4dbd3c2fc69a0ad6560f3c8714c673bc55b8106c Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 1 Apr 2023 23:49:05 +1100 Subject: [PATCH 05/24] Add code comment documentation --- transcrypt | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/transcrypt b/transcrypt index 83ece65..8d2ef9f 100755 --- a/transcrypt +++ b/transcrypt @@ -202,13 +202,22 @@ _translate_transcrypt_config_to_openssl_base_arguments() { _openssl_base_arguments="ENC_PASS=\"${password}\" ${openssl_path} enc -${cipher} -md ${digest} -pass env:ENC_PASS" } +############################################################################## # The `decryption -> encryption` process on an unchanged file must be # deterministic for everything to work transparently. To do that, the same # salt must be used each time we encrypt the same file. An HMAC has been # proven to be a PRF, so we generate an HMAC-SHA256 for each decrypted file -# (keyed with a combination of the filename and transcrypt password), and -# then use the last 16 bytes of that HMAC for the file's unique salt. - +# and then use the last 16 bytes of that HMAC for the file's unique salt (the +# openssl standard for salt is 16 hex bytes). +# +# The HMAC-SHA256 is keyed with a combination of the filename and... +# - for versions before 3, the transcrypt password +# - for version 3 and later, an additional per-repository salt +############################################################################## + +# The clean script ENCRYPTS files into the Git index, before they are sent to +# the remote. The file contents are passed via stdin and the filename is passed +# as $1 (or $2 if $1 is context name definition). git_clean() { _translate_transcrypt_config_to_openssl_base_arguments "$1" @@ -223,10 +232,15 @@ git_clean() { if [[ ! -s $filename ]]; then return fi + # cache STDIN to test if it's already encrypted + # First, create the tempfile, then + # set a trap to remove the tempfile when we exit or if anything goes wrong + # finally write the stdin of this script to the tempfile tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) trap 'rm -f "$tempfile"' EXIT tee "$tempfile" &>/dev/null + # the first bytes of an encrypted file are always "Salted" in Base64 # The `head + LC_ALL=C tr` command handles binary data in old and new Bash (#116) firstbytes=$(head -c8 "$tempfile" | LC_ALL=C tr -d '\0') @@ -250,6 +264,8 @@ git_clean() { fi } +# The smudge script DECRYPTS files when they are checked out to working copy +# files. The file contents are passed via stdin git_smudge() { tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) trap 'rm -f "$tempfile"' EXIT @@ -259,6 +275,10 @@ git_smudge() { tee "$tempfile" | eval "${_openssl_base_arguments} -d -a" 2>/dev/null || cat "$tempfile" } +# The textconv script allows users to see git diffs in plaintext. +# It does this by decrypting the encrypted git globs into plain text before +# passing them to the diff command. +# The filename is passed as $1 (or $2 if $1 is context name definition). git_textconv() { _translate_transcrypt_config_to_openssl_base_arguments "$1" From f15a08ae119dc535089fd5f8171ff3635ecc2f3b Mon Sep 17 00:00:00 2001 From: James Murty Date: Sun, 2 Apr 2023 23:26:22 +1000 Subject: [PATCH 06/24] Add support for setting and using new arguments for PBKDF2: --kdf --digest --iter --- transcrypt | 117 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 103 insertions(+), 14 deletions(-) diff --git a/transcrypt b/transcrypt index 8d2ef9f..ecc61b7 100755 --- a/transcrypt +++ b/transcrypt @@ -22,7 +22,9 @@ readonly VERSION='3.0.0-alpha1' # the default encryption settings to recommend readonly DEFAULT_CIPHER='aes-256-cbc' -readonly DEFAULT_DIGEST='md5' # TODO change to 'sha512' +readonly DEFAULT_DIGEST='sha512' +readonly DEFAULT_KDF='pbkdf2' +readonly DEFAULT_ITERATIONS='1_000_000' # context name must match this regexp to ensure it is safe for git config and attrs readonly CONTEXT_REGEX='[a-z](-?[a-z0-9])*' @@ -194,12 +196,18 @@ _translate_transcrypt_config_to_openssl_base_arguments() { context_config_group=$(derive_context_config_group "$context") cipher=$(git config --get --local "transcrypt${context_config_group}.cipher" || printf '') - digest=$(git config --get --local "transcrypt${context_config_group}.digest" || printf '%s' $DEFAULT_DIGEST) + digest=$(git config --get --local "transcrypt${context_config_group}.digest" || printf 'md5') # md5 for legacy compatibility password=$(load_password "$context_config_group") + kdf=$(git config --get --local "transcrypt${context_config_group}.kdf" || printf '') + iterations=$(git config --get --local "transcrypt${context_config_group}.iterations" || printf '') + openssl_path=$(git config --get --local transcrypt.openssl-path || printf '') - _openssl_base_arguments="ENC_PASS=\"${password}\" ${openssl_path} enc -${cipher} -md ${digest} -pass env:ENC_PASS" + # TODO validate $kdf and $digest + # TODO assert $iterations and $project_salt are set when $kdf is applied + + _openssl_base_arguments="ENC_PASS=\"${password}\" ${openssl_path} enc -pass env:ENC_PASS -${cipher} -md ${digest} ${kdf:+-${kdf}} ${iterations:+-iter ${iterations}}" } ############################################################################## @@ -487,24 +495,38 @@ validate_cipher() { fi } -# ensure we have a cipher to encrypt with -get_cipher() { - while [[ ! $cipher ]]; do +# Helper to prompt the user, store a response, and validate the result +_get_user_input() { + local varname=$1 + local default=$2 + local validate_fn=$3 + local prompt=$4 + + while [[ ! ${!varname:-} ]]; do local answer= if [[ $interactive ]]; then - printf 'Encrypt using which cipher? [%s] ' "$DEFAULT_CIPHER" + printf '%s' "$prompt" read -r answer fi - - # use the default cipher if the user gave no answer; - # otherwise verify the given cipher is supported by openssl + # use the default value if the user gave no answer; otherwise call the + # validate function, which should set the varname to empty if it is + # invalid and the user should continue, otherwise it should die. if [[ ! $answer ]]; then - cipher=$DEFAULT_CIPHER + _set_global "$varname" "$default" else - cipher=$answer - validate_cipher + _set_global "$varname" "$answer" fi done + if [[ $validate_fn ]]; then + ${validate_fn} || die "Invalid setting" + fi +} + +# sets a bash global variable by name +_set_global() { + key=$1 + val=$2 + printf -v "$key" '%s' "$val" } # ensure we have a password to encrypt with @@ -664,7 +686,11 @@ save_configuration() { # write the encryption info git config transcrypt.version "$VERSION" git config "transcrypt${CONTEXT_CONFIG_GROUP}.cipher" "$cipher" + [[ ${digest:-} ]] && git config "transcrypt${CONTEXT_CONFIG_GROUP}.digest" "$digest" + [[ ${kdf:-} ]] && git config "transcrypt${CONTEXT_CONFIG_GROUP}.kdf" "$kdf" + [[ ${iterations:-} ]] && git config "transcrypt${CONTEXT_CONFIG_GROUP}.iterations" "$iterations" save_password "$password" "$CONTEXT_CONFIG_GROUP" + git config transcrypt.openssl-path "$openssl_path" # write the filter settings. Sorry for the horrific quote escaping below... @@ -1203,6 +1229,20 @@ help() { the symmetric cipher to utilize for encryption; defaults to aes-256-cbc + -md, --digest=DIGEST + the message digest used to hash the salted password; + defaults to sha512 + Use md5 for compatibility with transcrypt versions < 3 + + -k, --kdf=KEY_DERIVATION_FUNCTION + a key-derivation function to use, gives best security; + defaults to pbkdf2 + If enabled, all users will need Transcrypt 3+ and modern OpenSSL + + -n, --iter=ITERATIONS + the number of iterations for the key-derivation-function; + defaults to 1_000_000 + -p, --password=PASSWORD the password to derive the key from; defaults to 30 random base64 characters @@ -1351,6 +1391,7 @@ rekey='' show_file='' uninstall='' upgrade='' +set_openssl_path='' openssl_path='openssl' # used to bypass certain safety checks @@ -1392,6 +1433,27 @@ while [[ "${1:-}" != '' ]]; do --cipher=*) cipher=${1#*=} ;; + -md | --digest) + digest=$2 + shift + ;; + --digest=*) + digest=${1#*=} + ;; + -k | --kdf) + kdf=${2} + shift + ;; + --kdf=*) + kdf=${1#*=} + ;; + -n | --iter) + iterations=${2} + shift + ;; + --iter=*) + iterations=${1#*=} + ;; -p | --password) password=$2 shift @@ -1407,6 +1469,7 @@ while [[ "${1:-}" != '' ]]; do context=${1#*=} ;; --set-openssl-path=*) + set_openssl_path='true' openssl_path=${1#*=} # Immediately apply config setting git config transcrypt.openssl-path "$openssl_path" @@ -1557,8 +1620,34 @@ elif [[ $cipher ]]; then validate_cipher fi +# Try to detect when user is initialising transcrypt using a legacy command +# from before version 3, before --kdf or --digest or --iter options were added +if [[ ${cipher:-} ]] && [[ ${password:-} ]] && [[ -z ${digest:-} ]] && [[ -z ${kdf:-} ]] && [[ -z ${iterations:-} ]]; then + assume_legacy_init_command='true' +else + assume_legacy_init_command='' +fi + # perform function calls to configure transcrypt -get_cipher +_get_user_input cipher "$DEFAULT_CIPHER" "validate_cipher" "$(printf 'Encrypt using which cipher? [%s] ' "$DEFAULT_CIPHER")" + +# Prompt user for encryption settings available since version 3, but only if: +# - it doesn't seem like they are using a legacy init command format +# - the user isn't just setting the openssl-path +if [[ "$assume_legacy_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then + _get_user_input digest "$DEFAULT_DIGEST" "" "$(printf 'Encrypt using which digest? [%s] ' "$DEFAULT_DIGEST")" + + printf 'Use a key derivation function for best security? Requires Transcrypt 3+ and modern OpenSSL? [Y/n] ' + read -r -n 1 -s answer + printf '\n' + if [[ $answer =~ $YES_REGEX ]] || [[ ! $answer ]]; then + _get_user_input kdf "$DEFAULT_KDF" "" "$(printf 'Which key derivation function? [%s] ' "$DEFAULT_KDF")" + _get_user_input iterations "$DEFAULT_ITERATIONS" "" "$(printf 'How many iterations? Use "_" separators for clarity [%s] ' "$DEFAULT_ITERATIONS")" + # Strip "_" separator characters from iterations value + iterations=${iterations//_/} + fi +fi + get_password if [[ $rekey ]] && [[ $interactive ]]; then From 4122237dae88ad3d20718cffcff4f42e95ea4aef Mon Sep 17 00:00:00 2001 From: James Murty Date: Sun, 2 Apr 2023 23:35:35 +1000 Subject: [PATCH 07/24] Make file encryption logic reusable --- transcrypt | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/transcrypt b/transcrypt index ecc61b7..9419754 100755 --- a/transcrypt +++ b/transcrypt @@ -210,6 +210,24 @@ _translate_transcrypt_config_to_openssl_base_arguments() { _openssl_base_arguments="ENC_PASS=\"${password}\" ${openssl_path} enc -pass env:ENC_PASS -${cipher} -md ${digest} ${kdf:+-${kdf}} ${iterations:+-iter ${iterations}}" } +_encrypt_file() { + local filepath="$1" + local salt="$2" + + if [ "$(is_salt_prefix_workaround_required)" == "true" ]; then + # Encrypt the file to base64, ensuring it includes the prefix 'Salted__' with the salt. #133 + ( + echo -n "$OPENSSL_ENCRYPTED_SALTED_PREFIX" && echo -n "$salt" | xxd -r -p && + # Encrypt file to binary ciphertext + eval "${_openssl_base_arguments} -e -S ${salt} -in ${filepath}" + ) | + openssl base64 + else + # Encrypt file to base64 ciphertext + eval "${_openssl_base_arguments} -e -a -S ${salt} -in ${filepath}" + fi +} + ############################################################################## # The `decryption -> encryption` process on an unchanged file must be # deterministic for everything to work transparently. To do that, the same @@ -257,18 +275,7 @@ git_clean() { else salt=$("${openssl_path}" dgst -hmac "${filename}:${password}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) - if [ "$(is_salt_prefix_workaround_required)" == "true" ]; then - # Encrypt the file to base64, ensuring it includes the prefix 'Salted__' with the salt. #133 - ( - echo -n "$OPENSSL_ENCRYPTED_SALTED_PREFIX" && echo -n "$salt" | xxd -r -p && - # Encrypt file to binary ciphertext - eval "${_openssl_base_arguments} -e -S ${salt} -in ${tempfile}" - ) | - openssl base64 - else - # Encrypt file to base64 ciphertext - eval "${_openssl_base_arguments} -e -a -S ${salt} -in ${tempfile}" - fi + _encrypt_file "$tempfile" "$salt" fi } From ec4bc961480540f20edb9fcffdd8dde03e3ac870 Mon Sep 17 00:00:00 2001 From: James Murty Date: Sun, 2 Apr 2023 23:46:23 +1000 Subject: [PATCH 08/24] At init, generate project salt for PBKDF2 file encryption as a password-encrypted hash of the same password --- transcrypt | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/transcrypt b/transcrypt index 9419754..13940c6 100755 --- a/transcrypt +++ b/transcrypt @@ -36,6 +36,8 @@ readonly CONTEXT_REGEX='[a-z](-?[a-z0-9])*' readonly OPENSSL_ENCRYPTED_SALTED_PREFIX='Salted__' readonly OPENSSL_ENCRYPTED_SALTED_PREFIX_B64='U2FsdGVk' +readonly SALT_FOR_PBKDF2_PROJECT_SALT='fabc0de8badc0de5' + ##### FUNCTIONS # load encryption password @@ -201,6 +203,7 @@ _translate_transcrypt_config_to_openssl_base_arguments() { kdf=$(git config --get --local "transcrypt${context_config_group}.kdf" || printf '') iterations=$(git config --get --local "transcrypt${context_config_group}.iterations" || printf '') + project_salt=$(git config --get --local "transcrypt${context_config_group}.project-salt" || printf '') openssl_path=$(git config --get --local transcrypt.openssl-path || printf '') @@ -237,10 +240,32 @@ _encrypt_file() { # openssl standard for salt is 16 hex bytes). # # The HMAC-SHA256 is keyed with a combination of the filename and... -# - for versions before 3, the transcrypt password -# - for version 3 and later, an additional per-repository salt +# - if a key-derivation function is NOT set, the transcrypt password +# - if a key-derivation function IS set, a "project salt" value. This extra +# salt value derived from the password using the same key-derivation function +# settings that protect each file and stored in the Git config ############################################################################## +# Generate a project salt value to use for encrypting files when a +# key-derivation function is configured. +# This salt value is an encrypted hash of the password, using the same +# password, and generated with the same high-strength key-derivation encryption +# algorithm as protects each file. +_generate_project_salt() { + local password="$1" + + # TODO Is it safe or wise to save the password to a temporary file? + tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) + trap 'rm -f "$tempfile"' EXIT + echo -n "$password" >"$tempfile" + + _translate_transcrypt_config_to_openssl_base_arguments "$1" + + project_salt=$(_encrypt_file "$tempfile" "$SALT_FOR_PBKDF2_PROJECT_SALT") + + rm -f "$tempfile" +} + # The clean script ENCRYPTS files into the Git index, before they are sent to # the remote. The file contents are passed via stdin and the filename is passed # as $1 (or $2 if $1 is context name definition). @@ -273,9 +298,13 @@ git_clean() { if [[ $firstbytes == "$OPENSSL_ENCRYPTED_SALTED_PREFIX_B64" ]]; then cat "$tempfile" else - salt=$("${openssl_path}" dgst -hmac "${filename}:${password}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) + # Use "project salt" as base salt when available (i.e. when a + # key-derivation function is set) otherwise the password. + base_salt="${project_salt:-${password}}" + + file_salt=$("${openssl_path}" dgst -hmac "${filename}:${base_salt}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) - _encrypt_file "$tempfile" "$salt" + _encrypt_file "$tempfile" "$file_salt" fi } @@ -285,7 +314,7 @@ git_smudge() { tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) trap 'rm -f "$tempfile"' EXIT - _translate_transcrypt_config_to_openssl_base_arguments "$1" + _translate_transcrypt_config_to_openssl_base_arguments "${1:-}" tee "$tempfile" | eval "${_openssl_base_arguments} -d -a" 2>/dev/null || cat "$tempfile" } @@ -700,6 +729,12 @@ save_configuration() { git config transcrypt.openssl-path "$openssl_path" + # if a key derivation function is specified, generate a project salt to use when encrypting files + if [[ ${kdf:-} ]]; then + _generate_project_salt "$password" + git config "transcrypt${CONTEXT_CONFIG_GROUP}.project-salt" "$project_salt" + fi + # write the filter settings. Sorry for the horrific quote escaping below... # shellcheck disable=SC2016 transcrypt_path='"$(git config transcrypt.crypt-dir 2>/dev/null || printf %s/crypt ""$(git rev-parse --git-dir)"")"/transcrypt' From e99f2f404247f8abf17d82a8a7fcda64aba94eb1 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 3 Apr 2023 00:11:45 +1000 Subject: [PATCH 09/24] Don't prompt user to enable key-derivation function when they specified one as an argument --- transcrypt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/transcrypt b/transcrypt index 13940c6..e19826c 100755 --- a/transcrypt +++ b/transcrypt @@ -1679,9 +1679,14 @@ _get_user_input cipher "$DEFAULT_CIPHER" "validate_cipher" "$(printf 'Encrypt us if [[ "$assume_legacy_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then _get_user_input digest "$DEFAULT_DIGEST" "" "$(printf 'Encrypt using which digest? [%s] ' "$DEFAULT_DIGEST")" - printf 'Use a key derivation function for best security? Requires Transcrypt 3+ and modern OpenSSL? [Y/n] ' - read -r -n 1 -s answer - printf '\n' + if [[ -z ${kdf:-} ]]; then + printf 'Use a key derivation function for best security? Requires Transcrypt 3+ and modern OpenSSL? [Y/n] ' + read -r -n 1 -s answer + printf '\n' + else + answer='y' + fi + if [[ $answer =~ $YES_REGEX ]] || [[ ! $answer ]]; then _get_user_input kdf "$DEFAULT_KDF" "" "$(printf 'Which key derivation function? [%s] ' "$DEFAULT_KDF")" _get_user_input iterations "$DEFAULT_ITERATIONS" "" "$(printf 'How many iterations? Use "_" separators for clarity [%s] ' "$DEFAULT_ITERATIONS")" From a93b01d7364762d429ea2cf72a91fa8b2b084e20 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 3 Apr 2023 00:31:47 +1000 Subject: [PATCH 10/24] Update tests to separate those that will be specific to non-KDF (legacy) setups --- tests/_test_helper.bash | 21 +++++++++++++----- tests/test_cleanup.bats | 13 ++++++++++- ...ontexts.bats => test_contexts_no_kdf.bats} | 2 +- ...test_crypt.bats => test_crypt_no_kdf.bats} | 3 +++ .../{test_init.bats => test_init_no_kdf.bats} | 22 +++++++++---------- transcrypt | 13 ++++++----- 6 files changed, 50 insertions(+), 24 deletions(-) rename tests/{test_contexts.bats => test_contexts_no_kdf.bats} (99%) rename tests/{test_crypt.bats => test_crypt_no_kdf.bats} (99%) rename tests/{test_init.bats => test_init_no_kdf.bats} (95%) diff --git a/tests/_test_helper.bash b/tests/_test_helper.bash index ebf2ba3..70b1554 100644 --- a/tests/_test_helper.bash +++ b/tests/_test_helper.bash @@ -39,10 +39,6 @@ function cleanup_all { rm -f "$BATS_TEST_DIRNAME"/sensitive_file } -function init_transcrypt { - "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --password='abc 123' --yes -} - function encrypt_named_file { filename="$1" content=$2 @@ -59,10 +55,25 @@ function encrypt_named_file { run git commit -m "Encrypt file \"$filename\"" } +function init_transcrypt_no_kdf { + "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --password='abc 123' --yes +} + +function init_transcrypt { + "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --digest sha512 --kdf pbkdf2 --iter 99 --password='abc 123' --yes +} + function setup { pushd "$BATS_TEST_DIRNAME" || exit 1 init_git_repo - if [[ ! "$SETUP_SKIP_INIT_TRANSCRYPT" ]]; then + + if [[ "$SETUP_SKIP_INIT_TRANSCRYPT" ]]; then + return + fi + + if [[ "$SETUP_INIT_TRANSCRYPT_NO_KDF" ]]; then + init_transcrypt_no_kdf + else init_transcrypt fi } diff --git a/tests/test_cleanup.bats b/tests/test_cleanup.bats index fc00077..b41e094 100755 --- a/tests/test_cleanup.bats +++ b/tests/test_cleanup.bats @@ -3,7 +3,18 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" SECRET_CONTENT="My secret content" -SECRET_CONTENT_ENC="U2FsdGVkX1/6ilR0PmJpAyCF7iG3+k4aBwbgVd48WaQXznsg42nXbQrlWsf/qiCg" + +# Example generation: +# - Generate project key +# echo -n "abc 123" | ENC_PASS='abc 123' openssl enc -e -a -aes-256-cbc -md sha512 -pass env:ENC_PASS -pbkdf2 -iter 99 -S fabc0de8badc0de5 +# => U2FsdGVkX1/6vA3outwN5QyWIys28v/7MX+ZtGPhqR8= +# - Generate file key +# openssl dgst -hmac "sensitive_file:U2FsdGVkX1/6vA3outwN5QyWIys28v/7MX+ZtGPhqR8=" -sha256 tmp | tr -d '\r\n' | tail -c16 +# => fb9652c7887ca210 +# - Encrypt file +# cat sensitive_file | ENC_PASS='abc 123' openssl enc -e -a -aes-256-cbc -md sha512 -pass env:ENC_PASS -pbkdf2 -iter 99 -S fb9652c7887ca210 +# => U2FsdGVkX1/7llLHiHyiEAxtPlNHk2wE9oy521ml0Ngc81k5o7B+K1UhHgD8/2s9 +SECRET_CONTENT_ENC="U2FsdGVkX1/7llLHiHyiEAxtPlNHk2wE9oy521ml0Ngc81k5o7B+K1UhHgD8/2s9" @test "cleanup: transcrypt -f flush clears cached plaintext" { encrypt_named_file sensitive_file "$SECRET_CONTENT" diff --git a/tests/test_contexts.bats b/tests/test_contexts_no_kdf.bats similarity index 99% rename from tests/test_contexts.bats rename to tests/test_contexts_no_kdf.bats index f0f9de7..4b20a0d 100755 --- a/tests/test_contexts.bats +++ b/tests/test_contexts_no_kdf.bats @@ -9,7 +9,7 @@ SUPER_SECRET_CONTENT_ENC="U2FsdGVkX1+dAkIV/LAKXMmqjDNOGoOVK8Rmhw9tUnbR4dwBDglpkX function setup { pushd "$BATS_TEST_DIRNAME" || exit 1 init_git_repo - init_transcrypt + init_transcrypt_no_kdf # Init transcrypt with 'super-secret' context "$BATS_TEST_DIRNAME"/../transcrypt --context=super-secret --cipher=aes-256-cbc --password=321cba --yes diff --git a/tests/test_crypt.bats b/tests/test_crypt_no_kdf.bats similarity index 99% rename from tests/test_crypt.bats rename to tests/test_crypt_no_kdf.bats index 1186843..24feac6 100755 --- a/tests/test_crypt.bats +++ b/tests/test_crypt_no_kdf.bats @@ -2,6 +2,9 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" +# Custom setup: use no-KDF init transcrypt +SETUP_INIT_TRANSCRYPT_NO_KDF=1 + SECRET_CONTENT="My secret content" SECRET_CONTENT_ENC="U2FsdGVkX1/6ilR0PmJpAyCF7iG3+k4aBwbgVd48WaQXznsg42nXbQrlWsf/qiCg" diff --git a/tests/test_init.bats b/tests/test_init_no_kdf.bats similarity index 95% rename from tests/test_init.bats rename to tests/test_init_no_kdf.bats index cbae5b2..a9c35fd 100755 --- a/tests/test_init.bats +++ b/tests/test_init_no_kdf.bats @@ -15,20 +15,20 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 } @test "init: creates .gitattributes" { - init_transcrypt + init_transcrypt_no_kdf [ -f .gitattributes ] run cat .gitattributes [ "${lines[0]}" = "#pattern filter=crypt diff=crypt merge=crypt" ] } @test "init: creates scripts in .git/crypt/" { - init_transcrypt + init_transcrypt_no_kdf [ -d .git/crypt ] [ -f .git/crypt/transcrypt ] } @test "init: applies git config" { - init_transcrypt + init_transcrypt_no_kdf VERSION=$(../transcrypt -v | awk '{print $2}') [ "$(git config --get transcrypt.version)" = "$VERSION" ] @@ -53,7 +53,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 } @test "init: show details for --display" { - init_transcrypt + init_transcrypt_no_kdf VERSION=$(../transcrypt -v | awk '{print $2}') run ../transcrypt --display @@ -65,7 +65,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 } @test "init: show details for -d" { - init_transcrypt + init_transcrypt_no_kdf VERSION=$(../transcrypt -v | awk '{print $2}') run ../transcrypt -d @@ -80,7 +80,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 git config core.hooksPath ".git/myhooks" [ "$(git config --get core.hooksPath)" = '.git/myhooks' ] - init_transcrypt + init_transcrypt_no_kdf [ -d .git/myhooks ] [ -f .git/myhooks/pre-commit ] @@ -94,7 +94,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 } @test "init: transcrypt.openssl-path config setting defaults to 'openssl'" { - init_transcrypt + init_transcrypt_no_kdf [ "$(git config --get transcrypt.openssl-path)" = 'openssl' ] } @@ -104,7 +104,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 } @test "init: --set-openssl-path is applied during upgrade" { - init_transcrypt + init_transcrypt_no_kdf [ "$(git config --get transcrypt.openssl-path)" = 'openssl' ] # Set openssl path @@ -116,7 +116,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 } @test "init: transcrypt.openssl-path config setting is retained with --upgrade" { - init_transcrypt + init_transcrypt_no_kdf [ "$(git config --get transcrypt.openssl-path)" = 'openssl' ] # Set openssl path @@ -136,7 +136,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 # Set a custom location for the crypt/ directory git config transcrypt.crypt-dir /tmp/crypt - init_transcrypt + init_transcrypt_no_kdf # Confirm crypt/ directory is populated in custom location [ ! -d .git/crypt ] @@ -152,7 +152,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 # Set a custom location for the crypt/ directory git config transcrypt.crypt-dir /tmp/crypt - init_transcrypt + init_transcrypt_no_kdf SECRET_CONTENT="My secret content" SECRET_CONTENT_ENC="U2FsdGVkX1/6ilR0PmJpAyCF7iG3+k4aBwbgVd48WaQXznsg42nXbQrlWsf/qiCg" diff --git a/transcrypt b/transcrypt index e19826c..588d230 100755 --- a/transcrypt +++ b/transcrypt @@ -1662,21 +1662,22 @@ elif [[ $cipher ]]; then validate_cipher fi -# Try to detect when user is initialising transcrypt using a legacy command -# from before version 3, before --kdf or --digest or --iter options were added +# Try to detect when user is initialising transcrypt using a command format +# that does not specify a KDF or related settings, e.g. from before version 3. if [[ ${cipher:-} ]] && [[ ${password:-} ]] && [[ -z ${digest:-} ]] && [[ -z ${kdf:-} ]] && [[ -z ${iterations:-} ]]; then - assume_legacy_init_command='true' + is_no_kdf_init_command='true' else - assume_legacy_init_command='' + is_no_kdf_init_command='' fi # perform function calls to configure transcrypt _get_user_input cipher "$DEFAULT_CIPHER" "validate_cipher" "$(printf 'Encrypt using which cipher? [%s] ' "$DEFAULT_CIPHER")" # Prompt user for encryption settings available since version 3, but only if: -# - it doesn't seem like they are using a legacy init command format +# - it doesn't seem like they are using a legacy init command format, or just +# one that doesn't specify a KDF # - the user isn't just setting the openssl-path -if [[ "$assume_legacy_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then +if [[ "$is_no_kdf_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then _get_user_input digest "$DEFAULT_DIGEST" "" "$(printf 'Encrypt using which digest? [%s] ' "$DEFAULT_DIGEST")" if [[ -z ${kdf:-} ]]; then From baa47037c991f993b0a16f2fdbb6e49792d3a930 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 3 Apr 2023 00:54:19 +1000 Subject: [PATCH 11/24] Include kdf settings in configuration display --- transcrypt | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/transcrypt b/transcrypt index 588d230..0df8098 100755 --- a/transcrypt +++ b/transcrypt @@ -774,8 +774,15 @@ save_configuration() { # display the current configuration settings display_configuration() { - local current_cipher - current_cipher=$(git config --get --local "transcrypt${CONTEXT_CONFIG_GROUP}.cipher") + local cipher + cipher=$(git config --get --local "transcrypt${CONTEXT_CONFIG_GROUP}.cipher") + local digest + digest=$(git config --get --local "transcrypt${CONTEXT_CONFIG_GROUP}.digest" || printf '') + local kdf + kdf=$(git config --get --local "transcrypt${CONTEXT_CONFIG_GROUP}.kdf" || printf '') + local iterations + iterations=$(git config --get --local "transcrypt${CONTEXT_CONFIG_GROUP}.iterations" || printf '') + local current_password current_password=$(load_password "$CONTEXT_CONFIG_GROUP") local escaped_password=${current_password//\'/\'\\\'\'} @@ -787,18 +794,21 @@ display_configuration() { [[ ! $REPO ]] || printf ' GIT_WORK_TREE: %s\n' "$REPO" printf ' GIT_DIR: %s\n' "$GIT_DIR" printf ' GIT_ATTRIBUTES: %s\n\n' "$GIT_ATTRIBUTES" - printf ' CONTEXT: %s\n' "$CONTEXT" - printf ' CIPHER: %s\n' "$current_cipher" - printf ' PASSWORD: %s\n\n' "$current_password" + printf ' CONTEXT: %s\n' "$CONTEXT" + printf ' CIPHER: %s\n' "$cipher" + [[ "${digest:-}" ]] && printf ' DIGEST: %s\n' "$digest" + [[ "${kdf:-}" ]] && printf ' KDF: %s\n' "$kdf" + [[ "${iterations:-}" ]] && printf ' ITERATIONS: %s\n' "$iterations" + printf ' PASSWORD: %s\n\n' "$current_password" if [[ "$contexts_count" -gt "1" ]]; then printf 'The repository has %s contexts: %s\n\n' "$contexts_count" "$CONFIGURED_CONTEXTS" fi printf "Copy and paste the following command to initialize a cloned repository%s:\n\n" "$CONTEXT_DESCRIPTION" if [[ $CONTEXT != 'default' ]]; then - printf " transcrypt -C $CONTEXT -c %s -p '%s'\n" "$current_cipher" "$escaped_password" - else - printf " transcrypt -c %s -p '%s'\n" "$current_cipher" "$escaped_password" + context_extra=" -C $CONTEXT" fi + + printf " transcrypt${context_extra:-} -c ${cipher}${digest:+ -md ${digest}}${kdf:+ -k ${kdf}}${iterations:+ -iter ${iterations}} -p '%s'\n" "$escaped_password" } # remove transcrypt-related settings from the repository's git config From e358b807474c232e7e551351ab73c660ac036e59 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 3 Apr 2023 00:58:24 +1000 Subject: [PATCH 12/24] Fix tests following tweaks to display of configuration --- tests/test_contexts_no_kdf.bats | 6 +++--- tests/test_init_no_kdf.bats | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_contexts_no_kdf.bats b/tests/test_contexts_no_kdf.bats index 4b20a0d..bd4b7be 100755 --- a/tests/test_contexts_no_kdf.bats +++ b/tests/test_contexts_no_kdf.bats @@ -86,9 +86,9 @@ function teardown { [ "$status" -eq 0 ] [ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ] [ "${lines[1]}" = "and has the following configuration for context 'super-secret':" ] - [ "${lines[5]}" = " CONTEXT: super-secret" ] - [ "${lines[6]}" = " CIPHER: aes-256-cbc" ] - [ "${lines[7]}" = " PASSWORD: 321cba" ] + [ "${lines[5]}" = " CONTEXT: super-secret" ] + [ "${lines[6]}" = " CIPHER: aes-256-cbc" ] + [ "${lines[7]}" = " PASSWORD: 321cba" ] [ "${lines[8]}" = "The repository has 2 contexts: default super-secret" ] [ "${lines[9]}" = "Copy and paste the following command to initialize a cloned repository for context 'super-secret':" ] [ "${lines[10]}" = " transcrypt -C super-secret -c aes-256-cbc -p '321cba'" ] diff --git a/tests/test_init_no_kdf.bats b/tests/test_init_no_kdf.bats index a9c35fd..74f039a 100755 --- a/tests/test_init_no_kdf.bats +++ b/tests/test_init_no_kdf.bats @@ -59,8 +59,8 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 run ../transcrypt --display [ "$status" -eq 0 ] [[ "${output}" = *"The current repository was configured using transcrypt version $VERSION"* ]] - [[ "${output}" = *" CIPHER: aes-256-cbc"* ]] - [[ "${output}" = *" PASSWORD: abc 123"* ]] + [[ "${output}" = *" CIPHER: aes-256-cbc"* ]] + [[ "${output}" = *" PASSWORD: abc 123"* ]] [[ "${output}" = *" transcrypt -c aes-256-cbc -p 'abc 123'"* ]] } @@ -71,8 +71,8 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 run ../transcrypt -d [ "$status" -eq 0 ] [[ "${output}" = *"The current repository was configured using transcrypt version $VERSION"* ]] - [[ "${output}" = *" CIPHER: aes-256-cbc"* ]] - [[ "${output}" = *" PASSWORD: abc 123"* ]] + [[ "${output}" = *" CIPHER: aes-256-cbc"* ]] + [[ "${output}" = *" PASSWORD: abc 123"* ]] [[ "${output}" = *" transcrypt -c aes-256-cbc -p 'abc 123'"* ]] } @@ -88,8 +88,8 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 run ../transcrypt --display [ "$status" -eq 0 ] [[ "${output}" = *"The current repository was configured using transcrypt version $VERSION"* ]] - [[ "${output}" = *" CIPHER: aes-256-cbc"* ]] - [[ "${output}" = *" PASSWORD: abc 123"* ]] + [[ "${output}" = *" CIPHER: aes-256-cbc"* ]] + [[ "${output}" = *" PASSWORD: abc 123"* ]] [[ "${output}" = *" transcrypt -c aes-256-cbc -p 'abc 123'"* ]] } From 034786a59a7baa995f9b9ee504aba64fc1e485a7 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 3 Apr 2023 22:34:35 +1000 Subject: [PATCH 13/24] Keep "_" separator character for iteration count for config and display, for clarity --- transcrypt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/transcrypt b/transcrypt index 0df8098..be1e3d4 100755 --- a/transcrypt +++ b/transcrypt @@ -207,6 +207,9 @@ _translate_transcrypt_config_to_openssl_base_arguments() { openssl_path=$(git config --get --local transcrypt.openssl-path || printf '') + # Strip "_" separator characters from iterations value + iterations=${iterations//_/} + # TODO validate $kdf and $digest # TODO assert $iterations and $project_salt are set when $kdf is applied @@ -1701,8 +1704,6 @@ if [[ "$is_no_kdf_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then if [[ $answer =~ $YES_REGEX ]] || [[ ! $answer ]]; then _get_user_input kdf "$DEFAULT_KDF" "" "$(printf 'Which key derivation function? [%s] ' "$DEFAULT_KDF")" _get_user_input iterations "$DEFAULT_ITERATIONS" "" "$(printf 'How many iterations? Use "_" separators for clarity [%s] ' "$DEFAULT_ITERATIONS")" - # Strip "_" separator characters from iterations value - iterations=${iterations//_/} fi fi From 04603d0fa1e8f379f8383cfb4e8aca92bf1a21f0 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 3 Apr 2023 22:39:25 +1000 Subject: [PATCH 14/24] Remove last remaining hard-coded use of outdated md5 hash algorithm, this time for pre-commit installation --- transcrypt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/transcrypt b/transcrypt index be1e3d4..8a3badc 100755 --- a/transcrypt +++ b/transcrypt @@ -935,9 +935,9 @@ uninstall_transcrypt() { pre_commit_hook="${GIT_HOOKS}/pre-commit" pre_commit_hook_installed="${GIT_HOOKS}/pre-commit-crypt" if [[ -f "$pre_commit_hook" ]]; then - hook_md5=$("${openssl_path}" md5 -hex <"$pre_commit_hook") - installed_md5=$("${openssl_path}" md5 -hex <"$pre_commit_hook_installed") - if [[ "$hook_md5" = "$installed_md5" ]]; then + hook_sha1=$("${openssl_path}" sha1 -hex <"$pre_commit_hook") + installed_sha1=$("${openssl_path}" sha1 -hex <"$pre_commit_hook_installed") + if [[ "$hook_sha1" = "$installed_sha1" ]]; then rm "$pre_commit_hook" else printf 'WARNING: Cannot safely disable Git pre-commit hook %s please check it yourself\n' "$pre_commit_hook" From 226db82f79546afa927253e906e520607dfd476e Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 3 Apr 2023 23:25:18 +1000 Subject: [PATCH 15/24] Validate digest, kdf, and iterations configuration settings --- transcrypt | 132 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 111 insertions(+), 21 deletions(-) diff --git a/transcrypt b/transcrypt index 8a3badc..b40399c 100755 --- a/transcrypt +++ b/transcrypt @@ -508,28 +508,97 @@ run_safety_checks() { fi } +# Check if the first arg is contained in the space separated second arg +_is_contained_str() { + arg=$1 + values=$2 + echo "$values" | tr -s ' ' '\n' | grep -Fx "$arg" &>/dev/null +} + +# Checks if the target variable is in the set of valid values. If it is not, it +# unsets the global target variable, then if not in interactive mode it calls die. +_validate_variable_str() { + local varname=$1 + local valid_values=$2 + local varval=${!varname} + if ! _is_contained_str "$varval" "$valid_values"; then + message=$(printf '%s is "%s", but must be one of:\n%s' "$varname" "$varval" "$valid_values") + if [[ $interactive ]]; then + _set_global "$varname" "" + echo "$message" + return 1 + else + die 1 "$message" + fi + fi +} + # unset the cipher variable if it is not supported by openssl validate_cipher() { - local list_cipher_commands + local valid_ciphers if "${openssl_path}" list-cipher-commands &>/dev/null; then - # OpenSSL < v1.1.0 - list_cipher_commands="${openssl_path} list-cipher-commands" + # OpenSSL < v1.1.0 or LibreSSL + valid_ciphers=$("${openssl_path}" list-cipher-commands) else # OpenSSL >= v1.1.0 - list_cipher_commands="${openssl_path} list -cipher-commands" + valid_ciphers=$("${openssl_path}" list -cipher-commands) + fi + + # Force global variable value to lowercase + cipher=$(echo "${cipher}" | tr '[:upper:]' '[:lower:]') + + _validate_variable_str "cipher" "$valid_ciphers" +} + +validate_digest() { + local valid_digests + if "${openssl_path}" list-message-digest-commands &>/dev/null; then + # LibreSSL + # BEWARE: LibreSSL return error code 0 for faulty list commands so we + # must try its command variant first to avoid accepting the error + # output we would get from the other OpenSSL command variants + valid_digests=$("${openssl_path}" list-message-digest-commands) + elif "${openssl_path}" list-digest-commands &>/dev/null; then + # OpenSSL < v1.1.0 + valid_digests=$("${openssl_path}" list-digest-commands) + elif "${openssl_path}" list -digest-commands &>/dev/null; then + # OpenSSL >= v1.1.0 + valid_digests=$("${openssl_path}" list -digest-commands) fi - local supported - supported=$($list_cipher_commands | tr -s ' ' '\n' | grep -Fx "$cipher") || true - if [[ ! $supported ]]; then + # Force global variable value to lowercase + digest=$(echo "${digest}" | tr '[:upper:]' '[:lower:]') + + _validate_variable_str "digest" "$valid_digests" +} + +# Only the "pbkdf2" key-derivation function is currently supported +validate_kdf() { + local valid_kdfs + valid_kdfs="pbkdf2" + + # Force global variable value to lowercase + kdf=$(echo "${kdf}" | tr '[:upper:]' '[:lower:]') + + _validate_variable_str "kdf" "$valid_kdfs" +} + +# Iteration count must be a positive integer +validate_iterations() { + # Strip "_" separator characters from iterations value + local iterations_value + iterations_value=${iterations//_/} + + if test "$iterations_value" -gt 0 2>/dev/null; then + : # Valid integer > 0 + else + message=$(printf 'iterations is "%s", but must be a positive integer' "$iterations_value") if [[ $interactive ]]; then - printf '"%s" is not a valid cipher; choose one of the following:\n\n' "$cipher" - $list_cipher_commands | column -c 80 - printf '\n' - cipher='' + _set_global "$varname" "" + echo "$message" + return 1 else - # shellcheck disable=SC2016 - die 1 '"%s" is not a valid cipher; see `%s`' "$cipher" "$list_cipher_commands" + die 1 "$message" fi fi } @@ -555,10 +624,19 @@ _get_user_input() { else _set_global "$varname" "$answer" fi + + # Run validate function if provided. + if [[ $validate_fn ]]; then + # In interactive mode, failed validation just clears the variable + # to permit re-entry within the while loop + if [[ $interactive ]]; then + ${validate_fn} || _set_global "$varname" "" + # In non-interactive mode, failed validation causes script to die + else + ${validate_fn} || die "Invalid setting" + fi + fi done - if [[ $validate_fn ]]; then - ${validate_fn} || die "Invalid setting" - fi } # sets a bash global variable by name @@ -1673,6 +1751,12 @@ elif [[ $gpg_import_file ]]; then import_gpg elif [[ $cipher ]]; then validate_cipher +elif [[ ${digest:-} ]]; then + validate_digest +elif [[ ${kdf:-} ]]; then + validate_kdf +elif [[ ${iterations:-} ]]; then + validate_iterations fi # Try to detect when user is initialising transcrypt using a command format @@ -1684,17 +1768,21 @@ else fi # perform function calls to configure transcrypt -_get_user_input cipher "$DEFAULT_CIPHER" "validate_cipher" "$(printf 'Encrypt using which cipher? [%s] ' "$DEFAULT_CIPHER")" +_get_user_input cipher "$DEFAULT_CIPHER" "validate_cipher" \ + "$(printf 'Encrypt using which cipher? [%s] ' "$DEFAULT_CIPHER")" # Prompt user for encryption settings available since version 3, but only if: # - it doesn't seem like they are using a legacy init command format, or just # one that doesn't specify a KDF # - the user isn't just setting the openssl-path if [[ "$is_no_kdf_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then - _get_user_input digest "$DEFAULT_DIGEST" "" "$(printf 'Encrypt using which digest? [%s] ' "$DEFAULT_DIGEST")" + _get_user_input digest "$DEFAULT_DIGEST" "validate_digest" \ + "$(printf 'Encrypt using which digest? [%s] ' "$DEFAULT_DIGEST")" if [[ -z ${kdf:-} ]]; then - printf 'Use a key derivation function for best security? Requires Transcrypt 3+ and modern OpenSSL? [Y/n] ' + printf 'Use a key derivation function for best security?' + printf ' Requires Transcrypt 3+ and modern OpenSSL' + printf ' [Y/n] ' read -r -n 1 -s answer printf '\n' else @@ -1702,8 +1790,10 @@ if [[ "$is_no_kdf_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then fi if [[ $answer =~ $YES_REGEX ]] || [[ ! $answer ]]; then - _get_user_input kdf "$DEFAULT_KDF" "" "$(printf 'Which key derivation function? [%s] ' "$DEFAULT_KDF")" - _get_user_input iterations "$DEFAULT_ITERATIONS" "" "$(printf 'How many iterations? Use "_" separators for clarity [%s] ' "$DEFAULT_ITERATIONS")" + _get_user_input kdf "$DEFAULT_KDF" "validate_kdf" \ + "$(printf 'Which key derivation function? [%s] ' "$DEFAULT_KDF")" + _get_user_input iterations "$DEFAULT_ITERATIONS" "validate_iterations" \ + "$(printf 'How many iterations? Use "_" separators for clarity [%s] ' "$DEFAULT_ITERATIONS")" fi fi From 51a08e82340bc68a115dbbaea2d557e103a7ef30 Mon Sep 17 00:00:00 2001 From: James Murty Date: Tue, 4 Apr 2023 01:20:20 +1000 Subject: [PATCH 16/24] Rekey example sensitive_file to use PBKDF2 --- README.md | 19 ++++++++---- sensitive_file | 80 +++++++++++++++++++++++++------------------------- 2 files changed, 54 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 43f8874..ff0fa50 100644 --- a/README.md +++ b/README.md @@ -151,16 +151,25 @@ repository. The owner of the origin repository can dump the credentials for you by running the `--display` command line option: $ transcrypt --display - The current repository was configured using transcrypt v0.2.0 + + The current repository was configured using transcrypt version 3.0.0-alpha1 and has the following configuration: - CONTEXT: default - CIPHER: aes-256-cbc - PASSWORD: correct horse battery staple + GIT_WORK_TREE: transcrypt + GIT_DIR: transcrypt/.git + GIT_ATTRIBUTES: transcrypt/.gitattributes + + CONTEXT: default + CIPHER: aes-256-cbc + DIGEST: sha512 + KDF: pbkdf2 + ITERATIONS: 1_000_000 + PASSWORD: correct horse battery staple Copy and paste the following command to initialize a cloned repository: - transcrypt -c aes-256-cbc -p 'correct horse battery staple' + transcrypt -c aes-256-cbc -md sha512 -k pbkdf2 -n 1_000_000 \ + -p 'correct horse battery staple' Once transcrypt has stored the matching credentials, it will force a checkout of any exising encrypted files in order to decrypt them. diff --git a/sensitive_file b/sensitive_file index 547ad71..7baa969 100644 --- a/sensitive_file +++ b/sensitive_file @@ -1,40 +1,40 @@ -U2FsdGVkX1//6vyAEUROfUrBgZuXaA15WddyGnu4qyMwDAzBjDpLwEqdK+lGuahk -zcurTKIJ36gmdZSd5f2928EQaHGdusIRGzjWfWQ720UUTYzERPuJxGVQSXZIA7a4 -o7t2LdFOloWw5g3SRWn+cPBt8lvLkuVuA4x+B4MuzBR0qq7qsk5Qvywfuk2In4Fh -gWMWnUFDpdO/dUPefgZ1okXwWmb2bna7hr7j7Q1Qz+X8/ZPV7epZfonTOCvILVDy -qJlhhH+qrkUwpS8qKMBwyfsNEdKFm60fhPCjWZxyS475Pc3DcG9CQX+AkQqG0frA -aViFCpUkUClSJtoFCg+PaUHPbiN4g/OG7rUcIfVuFDH3Stz3CuqtzJSNkPKNX0Zm -4xgViApifWvPIijXl/VIHQ7SdzaYiWo2u1G5dCXQw39VnTikx+HWn85wgy0F9IoR -c6FiowxnGsl3ErIwyvuFOqeI8/Xge/7bgWmzqVZSLrpFMPjM/JNO7htRslByo0LD -h5+ngarmfzhI8fspFkmUJWN7YulBRKe4Zh5mohPLhXp/+27KdHC/kBWJtuWUTBx9 -RV8cp/g/uIQ6hr/qAnWLdxHgANExGXuf/1zVJYacfnP5cKEqmhYq4gyjs04n8w3a -gjpINQ8bUVzl3rEEv47nlT7o6ZYCxVL4WjWqcCB75KYvDtkDG+lIbu5SBQ1GwW8q -uvcdpV1l9UdXrVuPJvcXLn28xL2KItyfoa/T8rGERrSu875/hwunNmArclvv1UCW -ZRzOhZYMGTHQY5TDC7H05Lwx1wiwRoKJnd+iaE9pw80WnSyarkFkokoHjoBBIO6W -In+mUDJWSg+VTcJxsT91OmKQyfqGYSm3NRshcvhDgyX/Nle2ixtk1KbBM1+06Cyg -zWQ2My4uYJtQAU3RYsC3fIPw9QYfwpyrChVzFVImQwGixInNCm3hilEju9MuwKkT -9yU7oKnZO5027UrwYb7nn8tUab92R3qpfwkR+ZXspTi5CjBZnU61/yw+7Klv8yBQ -rXfRXVncM2tdcVWlrq7GaRwN3byeo87EQ6/QqyzwHOpNWomk6MHcIAy6pTY6ZIDs -hDrBwUkBDrIyQYntHDAR4LICepnrkouWydW6A5jqR5ySpchsSPSHdR41UcouPtmA -hKk1iYMS9TNu3eG69KiKAZ3djYb2GQl8Z1r/1SGAtKj263nUjazWBUkGuzdNX0ny -yuqXYgXd+lh4YOuL7Dn8JyW5s0IctFj6D4gUnvG4lV/rZYOusIG5rxZn9+c88Did -VWrMzIuAzbWQXweHA8EVZVb+ntqVKpYKixrLdmjNTt21oYW6LgFdxio8gyq6YGMT -vjG6G/5ZM30WOsso4XFp+8i7GzVKNXQrZSEZKbEqrD/+RICVUxzXLRVXm66nfW0r -xEhbuO9v6khlhM6Px1e1seyPZekvBskrB4n8CYsrTTqYww136r/WHZ8/VO+Xu0iN -1Bt+73pln+PjxiEkIcoHFaCqkqbzHjgGLXeWkfy+0tK/Yr8sTVOrzqNccDg5os98 -UyZG3psbOjuw8JOj2TgLVBIDJWejQLBdewflRviinAzM3jcfAS/GejhMK4NQrdm2 -SAXhMU+32lnJUfqEzkT3LY1PUxBWFwU0IHTQuqp23v23lFOUt4xKo9+TvbDu7V/W -8BzmtXMZl2PPTOvuEbpu8AfzzvUFkuOktKrlAGNIijx39fabFr+46rra46BeT1XG -yP3LQcXB5pkjQnwl10BKOGXE014R5BmiAkcyEZiF4ZLhHFpmCJP7U/xDA4g5H4AX -7WLNu1Mn/IvM7U2Y4AwTJy1GFLCufxL5MRjmAlMwhwebwRvhi3Pamh/StzjssQ1h -2jgJ+z86DndYpeqg9A7KAMX2FBAry9YbyTT28LNnZRjSRAOWqwRFkFBHryTFgwA2 -IKbR/mA/BFavB7UoxBEmijPTs/IbAoXGgQUN6g3DKCfaHbeTJPI8GPemmkA6AYgb -gDE/nVNe8ajQvzktXcM27ivLhjeHVjtCJYjsC3p6GFAMu6/LxKE0hWFRRnMw3RbR -Bmx8n5DWfRCJVgF+pbOah0tPL7iYa4+lprBBGClLpGP4/1KWmSCkxPa2l6QenY0D -C7m0hPUpL99PoAQCvCGssfLzdpDHdb0ZK808CwLnypBd52mSROpHk/4RQ3S0v68R -LpLRdEL0aDBQgHWD374YihPM0dYG7pCghTxuKSZXouQkscQ6xoqxVxyWhTRMcTBz -9ggEdI0dRV8AY+HSkpOW2Ixca1Opn3UIfznQe7JaPXzpk2j3oRR9A2uXcif+zfp2 -IRIqhSa+oP/1wo9RxLybnoheMPZftRqpabjR9AnOzt9KLt/9mu7/lF8YWhALLu6h -dLukBe1mEeVsQQ8CNcKqFK80jNCx7sR6QZCWyaxcgqw6YtOQ7ZszPRSHtCLgcGHc -BY9xgAUe3FaJszt5bed9Cxh/FvY7lQwWkLvVscS/IDtA+sq8Ww3D4/JyqEMaaZcY -L/aeTVBw2BnDs2K48meuFw== +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 +d4A7J4LFkkYut4folcVvQQ== From 57f8fdd60df8bf9684a5916cd79cba2e36a184ed Mon Sep 17 00:00:00 2001 From: James Murty Date: Tue, 4 Apr 2023 01:46:05 +1000 Subject: [PATCH 17/24] Ubuntu 18.04 is no longer supported for GitHub Workflow tests https://github.com/actions/runner-images/issues/6002 --- .github/workflows/run-bats-core-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-bats-core-tests.yml b/.github/workflows/run-bats-core-tests.yml index 1cc4e5d..c1d8f8f 100644 --- a/.github/workflows/run-bats-core-tests.yml +++ b/.github/workflows/run-bats-core-tests.yml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04, macos-latest] + os: [ubuntu-20.04, ubuntu-22.04, macos-latest] steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it From a37d0168527ced2a6a953648da93fdf054a88a7c Mon Sep 17 00:00:00 2001 From: James Murty Date: Sun, 16 Jul 2023 23:56:44 +1000 Subject: [PATCH 18/24] Abandon bad idea to derive project salt from password for KDF, use random or user-provided value instead --- README.md | 19 ++++++++ transcrypt | 125 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 101 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 8db1b3b..c8c25a0 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,25 @@ directory. the symmetric cipher to utilize for encryption; defaults to aes-256-cbc + -md, --digest=DIGEST + the message digest used to hash the salted password; + defaults to sha512 + Use md5 for compatibility with transcrypt versions < 3 + + -k, --kdf=KEY_DERIVATION_FUNCTION + a key-derivation function to use for strongest encryption; + defaults to pbkdf2 + If enabled, all users will need Transcrypt 3+ and modern OpenSSL + + -n, --iter=ITERATIONS + when using a key-derivation function, its number of iterations; + defaults to 1_000_000 + + -ps, --salt=PROJECT_SALT + when using a key-derivation function, an extra value to + strengthen per-file salt values; + defaults to 18 random base64 characters + -p, --password=PASSWORD the password to derive the key from; defaults to 30 random base64 characters diff --git a/transcrypt b/transcrypt index 0c1db3b..a023047 100755 --- a/transcrypt +++ b/transcrypt @@ -42,8 +42,6 @@ readonly CONTEXT_REGEX='[a-z](-?[a-z0-9])*' readonly OPENSSL_ENCRYPTED_SALTED_PREFIX='Salted__' readonly OPENSSL_ENCRYPTED_SALTED_PREFIX_B64='U2FsdGVk' -readonly SALT_FOR_PBKDF2_PROJECT_SALT='fabc0de8badc0de5' - ##### FUNCTIONS # load encryption password @@ -63,6 +61,23 @@ save_password() { git config "transcrypt${context_config_group}.password" "$password" } +# load project salt +# by default is stored in git config, modify this function to move elsewhere +load_project_salt() { + local context_config_group=${1:-} + local project_salt + project_salt=$(git config --get --local "transcrypt${context_config_group}.project-salt") + echo "$project_salt" +} + +# save project salt +# by default is stored in git config, modify this function to move elsewhere +save_project_salt() { + local project_salt=$1 + local context_config_group=${2:-} + git config "transcrypt${context_config_group}.project-salt" "$project_salt" +} + # print a canonicalized absolute pathname realpath() { local path=$1 @@ -242,39 +257,20 @@ _encrypt_file() { ############################################################################## # The `decryption -> encryption` process on an unchanged file must be -# deterministic for everything to work transparently. To do that, the same -# salt must be used each time we encrypt the same file. An HMAC has been -# proven to be a PRF, so we generate an HMAC-SHA256 for each decrypted file -# and then use the last 16 bytes of that HMAC for the file's unique salt (the -# openssl standard for salt is 16 hex bytes). +# deterministic for everything to work transparently. To do that, the same salt +# must be used each time we encrypt the same file. An HMAC has been proven to +# be a PRF, so we generate an HMAC-SHA256 for each decrypted file and then use +# the last 16 bytes of that HMAC for the file's unique salt (the openssl +# standard for salt is 16 hex bytes). # # The HMAC-SHA256 is keyed with a combination of the filename and... # - if a key-derivation function is NOT set, the transcrypt password -# - if a key-derivation function IS set, a "project salt" value. This extra -# salt value derived from the password using the same key-derivation function -# settings that protect each file and stored in the Git config +# - if a key-derivation function IS set, a "project salt" value that is set +# when the repository is first initialised, from a value provided or by +# generating a random value, and stored in the Git config. This project salt +# must be provided along with the password to decrypt the repo. ############################################################################## -# Generate a project salt value to use for encrypting files when a -# key-derivation function is configured. -# This salt value is an encrypted hash of the password, using the same -# password, and generated with the same high-strength key-derivation encryption -# algorithm as protects each file. -_generate_project_salt() { - local password="$1" - - # TODO Is it safe or wise to save the password to a temporary file? - tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) - trap 'rm -f "$tempfile"' EXIT - echo -n "$password" >"$tempfile" - - _translate_transcrypt_config_to_openssl_base_arguments "$1" - - project_salt=$(_encrypt_file "$tempfile" "$SALT_FOR_PBKDF2_PROJECT_SALT") - - rm -f "$tempfile" -} - # The clean script ENCRYPTS files into the Git index, before they are sent to # the remote. The file contents are passed via stdin and the filename is passed # as $1 (or $2 if $1 is context name definition). @@ -307,11 +303,11 @@ git_clean() { if [[ $firstbytes == "$OPENSSL_ENCRYPTED_SALTED_PREFIX_B64" ]]; then cat "$tempfile" else - # Use "project salt" as base salt when available (i.e. when a + # Use "project salt" as extra salt when available (i.e. when a # key-derivation function is set) otherwise the password. - base_salt="${project_salt:-${password}}" + extra_salt="${project_salt:-${password}}" - file_salt=$("${openssl_path}" dgst -hmac "${filename}:${base_salt}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) + file_salt=$("${openssl_path}" dgst -hmac "${filename}:${extra_salt}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) _encrypt_file "$tempfile" "$file_salt" fi @@ -677,6 +673,30 @@ get_password() { done } +# ensure we have a project salt to encrypt with when using a key-derivation function +get_project_salt() { + while [[ ! $project_salt ]]; do + printf 'Projects using a key-derivation function require a project salt\n' + local answer= + if [[ $interactive ]]; then + printf 'Generate a random project salt? [Y/n] ' + read -r -n 1 -s answer + printf '\n' + fi + + # generate a random project salt value if the user answered yes; + # otherwise prompt the user for the hex salt value + if [[ $answer =~ $YES_REGEX ]] || [[ ! $answer ]]; then + local project_salt_length=18 # 18 bytes gives a 25 char B64 string + project_salt=$(${openssl_path} rand -base64 $project_salt_length) + else + printf 'Project salt: ' + read -r project_salt + [[ $project_salt ]] || printf 'no project salt was specified\n' + fi + done +} + # confirm the transcrypt configuration confirm_configuration() { local answer= @@ -822,14 +842,13 @@ save_configuration() { [[ ${iterations:-} ]] && git config "transcrypt${CONTEXT_CONFIG_GROUP}.iterations" "$iterations" save_password "$password" "$CONTEXT_CONFIG_GROUP" - git config transcrypt.openssl-path "$openssl_path" - # if a key derivation function is specified, generate a project salt to use when encrypting files if [[ ${kdf:-} ]]; then - _generate_project_salt "$password" - git config "transcrypt${CONTEXT_CONFIG_GROUP}.project-salt" "$project_salt" + save_project_salt "$project_salt" "$CONTEXT_CONFIG_GROUP" fi + git config transcrypt.openssl-path "$openssl_path" + # write the filter settings. Sorry for the horrific quote escaping below... # shellcheck disable=SC2016 transcrypt_path='"$(git config transcrypt.crypt-dir 2>/dev/null || printf %s/crypt ""$(git rev-parse --git-dir)"")"/transcrypt' @@ -878,6 +897,8 @@ display_configuration() { local iterations iterations=$(git config --get --local "transcrypt${CONTEXT_CONFIG_GROUP}.iterations" || printf '') + local project_salt + project_salt=$(load_project_salt "$CONTEXT_CONFIG_GROUP") local current_password current_password=$(load_password "$CONTEXT_CONFIG_GROUP") local escaped_password=${current_password//\'/\'\\\'\'} @@ -894,6 +915,7 @@ display_configuration() { [[ "${digest:-}" ]] && printf ' DIGEST: %s\n' "$digest" [[ "${kdf:-}" ]] && printf ' KDF: %s\n' "$kdf" [[ "${iterations:-}" ]] && printf ' ITERATIONS: %s\n' "$iterations" + [[ "${project_salt:-}" ]] && printf ' PROJECT SALT: %s\n' "$project_salt" printf ' PASSWORD: %s\n\n' "$current_password" if [[ "$contexts_count" -gt "1" ]]; then printf 'The repository has %s contexts: %s\n\n' "$contexts_count" "$CONFIGURED_CONTEXTS" @@ -903,7 +925,7 @@ display_configuration() { context_extra=" -C $CONTEXT" fi - printf " transcrypt${context_extra:-} -c ${cipher}${digest:+ -md ${digest}}${kdf:+ -k ${kdf}}${iterations:+ -iter ${iterations}} -p '%s'\n" "$escaped_password" + printf " transcrypt${context_extra:-} -c ${cipher}${digest:+ -md ${digest}}${kdf:+ -k ${kdf}}${iterations:+ -n ${iterations}}${project_salt:+ -ps ${project_salt}} -p '%s'\n" "$escaped_password" } # remove transcrypt-related settings from the repository's git config @@ -1109,6 +1131,8 @@ upgrade_transcrypt() { fi fi + # TODO Retain new PBKDF2 settings across upgrade + # Keep current cipher and password cipher=$(git config --get --local "transcrypt${CONTEXT_CONFIG_GROUP}.cipher") password=$(load_password "$CONTEXT_CONFIG_GROUP") @@ -1382,14 +1406,19 @@ help() { Use md5 for compatibility with transcrypt versions < 3 -k, --kdf=KEY_DERIVATION_FUNCTION - a key-derivation function to use, gives best security; + a key-derivation function to use for strongest encryption; defaults to pbkdf2 If enabled, all users will need Transcrypt 3+ and modern OpenSSL -n, --iter=ITERATIONS - the number of iterations for the key-derivation-function; + when using a key-derivation function, its number of iterations; defaults to 1_000_000 + -ps, --salt=PROJECT_SALT + when using a key-derivation function, an extra value to + strengthen per-file salt values; + defaults to 18 random base64 characters + -p, --password=PASSWORD the password to derive the key from; defaults to 30 random base64 characters @@ -1533,6 +1562,7 @@ gpg_import_file='' gpg_recipient='' interactive='true' list='' +project_salt='' password='' rekey='' show_file='' @@ -1601,6 +1631,13 @@ while [[ "${1:-}" != '' ]]; do --iter=*) iterations=${1#*=} ;; + -ps | --salt) + project_salt=${2} + shift + ;; + --salt=*) + project_salt=${1#*=} + ;; -p | --password) password=$2 shift @@ -1794,9 +1831,9 @@ if [[ "$is_no_kdf_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then "$(printf 'Encrypt using which digest? [%s] ' "$DEFAULT_DIGEST")" if [[ -z ${kdf:-} ]]; then - printf 'Use a key derivation function for best security?' - printf ' Requires Transcrypt 3+ and modern OpenSSL' - printf ' [Y/n] ' + printf 'Use a key derivation function for best security?\n' + printf ' * Requires Transcrypt 3+ and modern OpenSSL *\n' + printf '[Y/n] ' read -r -n 1 -s answer printf '\n' else @@ -1808,6 +1845,8 @@ if [[ "$is_no_kdf_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then "$(printf 'Which key derivation function? [%s] ' "$DEFAULT_KDF")" _get_user_input iterations "$DEFAULT_ITERATIONS" "validate_iterations" \ "$(printf 'How many iterations? Use "_" separators for clarity [%s] ' "$DEFAULT_ITERATIONS")" + + get_project_salt fi fi From 2dafed1aca3aedc9b4ec1b228016085b4db7e15a Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 17 Jul 2023 00:06:35 +1000 Subject: [PATCH 19/24] Include KDF information in interactive feedback for confirming config and before rekey --- transcrypt | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/transcrypt b/transcrypt index a023047..d3a4ca0 100755 --- a/transcrypt +++ b/transcrypt @@ -706,9 +706,13 @@ confirm_configuration() { printf ' GIT_DIR: %s\n' "$GIT_DIR" printf ' GIT_ATTRIBUTES: %s\n\n' "$GIT_ATTRIBUTES" printf 'The following configuration will be saved:\n\n' - printf ' CONTEXT: %s\n' "$CONTEXT" - printf ' CIPHER: %s\n' "$cipher" - printf ' PASSWORD: %s\n\n' "$password" + printf ' CONTEXT: %s\n' "$CONTEXT" + printf ' CIPHER: %s\n' "$cipher" + [[ "${digest:-}" ]] && printf ' DIGEST: %s\n' "$digest" + [[ "${kdf:-}" ]] && printf ' KDF: %s\n' "$kdf" + [[ "${iterations:-}" ]] && printf ' ITERATIONS: %s\n' "$iterations" + [[ "${project_salt:-}" ]] && printf ' PROJECT SALT: %s\n' "$project_salt" + printf ' PASSWORD: %s\n\n' "$password" printf 'Does this look correct? [Y/n] ' read -r -n 1 -s answer @@ -730,9 +734,13 @@ confirm_rekey() { printf ' GIT_DIR: %s\n' "$GIT_DIR" printf ' GIT_ATTRIBUTES: %s\n\n' "$GIT_ATTRIBUTES" printf 'The following configuration will be saved:\n\n' - printf ' CONTEXT: %s\n' "$CONTEXT" - printf ' CIPHER: %s\n' "$cipher" - printf ' PASSWORD: %s\n\n' "$password" + printf ' CONTEXT: %s\n' "$CONTEXT" + printf ' CIPHER: %s\n' "$cipher" + [[ "${digest:-}" ]] && printf ' DIGEST: %s\n' "$digest" + [[ "${kdf:-}" ]] && printf ' KDF: %s\n' "$kdf" + [[ "${iterations:-}" ]] && printf ' ITERATIONS: %s\n' "$iterations" + [[ "${project_salt:-}" ]] && printf ' PROJECT SALT: %s\n' "$project_salt" + printf ' PASSWORD: %s\n\n' "$password" printf 'You are about to re-encrypt all encrypted files using new credentials.\n' printf 'Once you do this, their historical diffs will no longer display in plain text.\n\n' printf 'Proceed with rekey? [y/N] ' From 669dc57bc21300466e412942908a12ff3d26ad46 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 17 Jul 2023 00:16:45 +1000 Subject: [PATCH 20/24] Reduce default iteration count for PBKDF2 from 1 million to 256k, which still exceeds 2013 OWASP recommendations See https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 --- README.md | 6 +++--- transcrypt | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c8c25a0..0248a48 100644 --- a/README.md +++ b/README.md @@ -163,12 +163,12 @@ by running the `--display` command line option: CIPHER: aes-256-cbc DIGEST: sha512 KDF: pbkdf2 - ITERATIONS: 1_000_000 + ITERATIONS: 256_000 PASSWORD: correct horse battery staple Copy and paste the following command to initialize a cloned repository: - transcrypt -c aes-256-cbc -md sha512 -k pbkdf2 -n 1_000_000 \ + transcrypt -c aes-256-cbc -md sha512 -k pbkdf2 -n 256_000 \ -p 'correct horse battery staple' Once transcrypt has stored the matching credentials, it will force a checkout of @@ -221,7 +221,7 @@ directory. -n, --iter=ITERATIONS when using a key-derivation function, its number of iterations; - defaults to 1_000_000 + defaults to 256_000 -ps, --salt=PROJECT_SALT when using a key-derivation function, an extra value to diff --git a/transcrypt b/transcrypt index d3a4ca0..4319f04 100755 --- a/transcrypt +++ b/transcrypt @@ -30,7 +30,9 @@ readonly VERSION='3.0.0-alpha1' readonly DEFAULT_CIPHER='aes-256-cbc' readonly DEFAULT_DIGEST='sha512' readonly DEFAULT_KDF='pbkdf2' -readonly DEFAULT_ITERATIONS='1_000_000' +# See OWASP PBKDF2 iteration count recommedations: +# https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 +readonly DEFAULT_ITERATIONS='256_000' # context name must match this regexp to ensure it is safe for git config and attrs readonly CONTEXT_REGEX='[a-z](-?[a-z0-9])*' @@ -1420,7 +1422,7 @@ help() { -n, --iter=ITERATIONS when using a key-derivation function, its number of iterations; - defaults to 1_000_000 + defaults to 256_000 -ps, --salt=PROJECT_SALT when using a key-derivation function, an extra value to From 8008363c738df79c66bba029b7aeb06673dfc7e1 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 17 Jul 2023 00:31:52 +1000 Subject: [PATCH 21/24] Rekey example sensitive file to use project salt and 256k iterations instead of 1 million --- README.md | 3 +- sensitive_file | 80 +++++++++++++++++++++++++------------------------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 0248a48..e04530c 100644 --- a/README.md +++ b/README.md @@ -164,12 +164,13 @@ by running the `--display` command line option: DIGEST: sha512 KDF: pbkdf2 ITERATIONS: 256_000 + PROJECT SALT: 5J0QY8uOTe7/B9eYSJ2kOy91 PASSWORD: correct horse battery staple Copy and paste the following command to initialize a cloned repository: transcrypt -c aes-256-cbc -md sha512 -k pbkdf2 -n 256_000 \ - -p 'correct horse battery staple' + -ps 5J0QY8uOTe7/B9eYSJ2kOy91 -p 'correct horse battery staple' Once transcrypt has stored the matching credentials, it will force a checkout of any exising encrypted files in order to decrypt them. diff --git a/sensitive_file b/sensitive_file index 7baa969..8ce6c13 100644 --- a/sensitive_file +++ b/sensitive_file @@ -1,40 +1,40 @@ -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 -d4A7J4LFkkYut4folcVvQQ== +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 +GsaN2D2yI3S3YjuOOIpjHA== From 4f482147af1426c0f5f8f57500b77ee28489fce5 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 17 Jul 2023 00:47:00 +1000 Subject: [PATCH 22/24] Fix the few PBDKF2 tests to work for the new project salt approach --- tests/_test_helper.bash | 2 +- tests/test_cleanup.bats | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/_test_helper.bash b/tests/_test_helper.bash index 70b1554..4aa43ac 100644 --- a/tests/_test_helper.bash +++ b/tests/_test_helper.bash @@ -60,7 +60,7 @@ function init_transcrypt_no_kdf { } function init_transcrypt { - "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --digest sha512 --kdf pbkdf2 --iter 99 --password='abc 123' --yes + "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --digest sha512 --kdf pbkdf2 --iter 99 --salt 5J0Q --password='abc 123' --yes } function setup { diff --git a/tests/test_cleanup.bats b/tests/test_cleanup.bats index b41e094..7796960 100755 --- a/tests/test_cleanup.bats +++ b/tests/test_cleanup.bats @@ -5,16 +5,14 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" SECRET_CONTENT="My secret content" # Example generation: -# - Generate project key -# echo -n "abc 123" | ENC_PASS='abc 123' openssl enc -e -a -aes-256-cbc -md sha512 -pass env:ENC_PASS -pbkdf2 -iter 99 -S fabc0de8badc0de5 -# => U2FsdGVkX1/6vA3outwN5QyWIys28v/7MX+ZtGPhqR8= +# - Using project salt: 5J0Q # - Generate file key -# openssl dgst -hmac "sensitive_file:U2FsdGVkX1/6vA3outwN5QyWIys28v/7MX+ZtGPhqR8=" -sha256 tmp | tr -d '\r\n' | tail -c16 -# => fb9652c7887ca210 +# openssl dgst -hmac "sensitive_file:5J0Q" -sha256 sensitive_file | tr -d '\r\n' | tail -c16 +# => ec32c0fbf2261d18 # - Encrypt file -# cat sensitive_file | ENC_PASS='abc 123' openssl enc -e -a -aes-256-cbc -md sha512 -pass env:ENC_PASS -pbkdf2 -iter 99 -S fb9652c7887ca210 -# => U2FsdGVkX1/7llLHiHyiEAxtPlNHk2wE9oy521ml0Ngc81k5o7B+K1UhHgD8/2s9 -SECRET_CONTENT_ENC="U2FsdGVkX1/7llLHiHyiEAxtPlNHk2wE9oy521ml0Ngc81k5o7B+K1UhHgD8/2s9" +# cat sensitive_file | ENC_PASS='abc 123' openssl enc -e -a -aes-256-cbc -md sha512 -pass env:ENC_PASS -pbkdf2 -iter 99 -S ec32c0fbf2261d18 +# => U2FsdGVkX1+NiURgsIjgkwyiBw0TSC8WhhDRly2h4x2exuwjay6y/nOahblrBL62 +SECRET_CONTENT_ENC="U2FsdGVkX1+NiURgsIjgkwyiBw0TSC8WhhDRly2h4x2exuwjay6y/nOahblrBL62" @test "cleanup: transcrypt -f flush clears cached plaintext" { encrypt_named_file sensitive_file "$SECRET_CONTENT" From dd02ca4e9d3db7013d16e2a2fb69b0adce115638 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 17 Jul 2023 01:20:34 +1000 Subject: [PATCH 23/24] Don't prompt user about which KDF to use since we only support "pbkdf2" for now --- transcrypt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/transcrypt b/transcrypt index 4319f04..3e99b6a 100755 --- a/transcrypt +++ b/transcrypt @@ -1841,7 +1841,7 @@ if [[ "$is_no_kdf_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then "$(printf 'Encrypt using which digest? [%s] ' "$DEFAULT_DIGEST")" if [[ -z ${kdf:-} ]]; then - printf 'Use a key derivation function for best security?\n' + printf 'Use the PBKDF2 key derivation function for best security?\n' printf ' * Requires Transcrypt 3+ and modern OpenSSL *\n' printf '[Y/n] ' read -r -n 1 -s answer @@ -1851,8 +1851,12 @@ if [[ "$is_no_kdf_init_command" == "" ]] && [[ -z ${set_openssl_path:-} ]]; then fi if [[ $answer =~ $YES_REGEX ]] || [[ ! $answer ]]; then - _get_user_input kdf "$DEFAULT_KDF" "validate_kdf" \ - "$(printf 'Which key derivation function? [%s] ' "$DEFAULT_KDF")" + # Only KDF currently supported is PBKDF2 so no need to prompt for it + kdf='pbkdf2' + + # _get_user_input kdf "$DEFAULT_KDF" "validate_kdf" \ + # "$(printf 'Which key derivation function? [%s] ' "$DEFAULT_KDF")" + _get_user_input iterations "$DEFAULT_ITERATIONS" "validate_iterations" \ "$(printf 'How many iterations? Use "_" separators for clarity [%s] ' "$DEFAULT_ITERATIONS")" From 0497f616c49cfb2891c5641297dfaab6a172c79e Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 17 Jul 2023 20:56:55 +1000 Subject: [PATCH 24/24] Fix lint warning causing build failure --- transcrypt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transcrypt b/transcrypt index 3e99b6a..899f814 100755 --- a/transcrypt +++ b/transcrypt @@ -29,7 +29,7 @@ readonly VERSION='3.0.0-alpha1' # the default encryption settings to recommend readonly DEFAULT_CIPHER='aes-256-cbc' readonly DEFAULT_DIGEST='sha512' -readonly DEFAULT_KDF='pbkdf2' +#readonly DEFAULT_KDF='pbkdf2' # Commented out for now # See OWASP PBKDF2 iteration count recommedations: # https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 readonly DEFAULT_ITERATIONS='256_000'