├── LICENSE ├── README.md ├── archive └── archive.go ├── crypto ├── crypto.go └── crypto_test.go └── filecrypt.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Kyle Isom 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## filecrypt 2 | 3 | This is an encryption utility designed to backup a set of files. The 4 | files are packed into a gzipped tarball in-memory, and this is encrypted 5 | using NaCl via a scrypt-derived key. 6 | 7 | It derives from the `passcrypt` utility in the 8 | [cryptutils](https://github.com/kisom/cryptutils/), and was written as 9 | an example for the book "Practical Cryptography with Go." 10 | 11 | 12 | ## Motivations 13 | 14 | This program arose from the need to backup and archive files on 15 | removeable media that may be restored on multiple platforms. There 16 | aren't any well-supported and readily available disk encryption systems 17 | that work in this type of environment, and using GnuPG requires GnuPG, 18 | the archiver, and a suitable decompression program to be provided. This 19 | program is statically built and can run standalone on all the needed 20 | platforms. 21 | 22 | 23 | ## Security model 24 | 25 | This program assumes that an attacker does not currently have access 26 | to either the machine the archive is generated on, or on the machine 27 | it is unpacked on. It is intended for medium to long-term storage of 28 | sensitive data at rest on removeable media that may be used to load data 29 | onto a variety of platforms (Windows, OS X, Linux, OpenBSD), where the 30 | threat of losing the storage medium is considerably higher than losing a 31 | secured laptop that the archive is generated on. 32 | 33 | Key derivation is done by pairing a password with a randomly-chosen 34 | 256-bit salt using the scrypt parameters N=2^20, r=8, p=1. This makes 35 | it astronomically unlikely that the same key will be derived from the 36 | same passphrase. The key is used as a NaCl secretbox key; the nonce for 37 | encryption is randomly generated. It is thought that this will be highly 38 | unlikely to cause nonce reuse issues. 39 | 40 | The primary weaknesses might come from an attack on the passphrase or 41 | via cryptanalysis of the ciphertext. The ciphertext is produced using 42 | NaCl appended to a random salt, so it is unlikely this will produce any 43 | meaningful information. One exception might be if this program is used 44 | to encrypt a known set of files, and the attacker compares the length of 45 | the archive to a list of known file sizes. 46 | 47 | An attack on the passphrase will most likely come via a successful 48 | dictionary attack. The large salt and high scrypt parameters will 49 | deter attackers without the large resources required to brute force 50 | this. Dictionary attacks will also be expensive for these same reasons. 51 | 52 | 53 | ## Usage 54 | 55 | ``` 56 | filecrypt [-h] [-o filename] [-q] [-t] [-u] [-v] [-x] files... 57 | 58 | -h Print this help message. 59 | 60 | -o filename The filename to output. If an archive is being built, 61 | this is the filename of the archive. If an archive is 62 | being unpacked, this is the directory to unpack in. 63 | If the tarball is being extracted, this is the path 64 | to write the tarball. 65 | 66 | Defaults: 67 | Pack: files.enc 68 | Unpack: . 69 | Extract: files.tgz 70 | 71 | -q Quiet mode. Only print errors and password prompt. 72 | This will override the verbose flag. 73 | 74 | -t List files in the archive. This acts like the list 75 | flag in tar. 76 | 77 | -u Unpack the archive listed on the command line. Only 78 | one archive may be unpacked. 79 | 80 | -v Verbose mode. This acts like the verbose flag in 81 | tar. 82 | 83 | -x Extract a tarball. This will decrypt the archive, but 84 | not decompress or unpack it. 85 | 86 | Examples: 87 | filecrypt -o ssh.enc ~/.ssh 88 | Encrypt the user's OpenSSH directory to ssh.enc. 89 | 90 | filecrypt -o backup/ -u ssh.enc 91 | Restore the user's OpenSSH directory to the backup/ 92 | directory. 93 | 94 | filecrypt -u ssh.enc 95 | Restore the user's OpenSSH directory to the current directory. 96 | 97 | ``` 98 | 99 | 100 | ## License 101 | 102 | filecrypt is released under the ISC license. 103 | 104 | ``` 105 | Copyright (c) 2015 Kyle Isom 106 | 107 | Permission to use, copy, modify, and distribute this software for any 108 | purpose with or without fee is hereby granted, provided that the above 109 | copyright notice and this permission notice appear in all copies. 110 | 111 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 112 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 113 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 114 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 115 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 116 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 117 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 118 | ``` 119 | 120 | -------------------------------------------------------------------------------- /archive/archive.go: -------------------------------------------------------------------------------- 1 | // Package archive packs files into a gzipped tarball with maximum 2 | // compression. It will only pack regular files and directories. 3 | package archive 4 | 5 | import ( 6 | "archive/tar" 7 | "compress/gzip" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "os" 12 | "path/filepath" 13 | 14 | "github.com/kisom/sbuf" 15 | ) 16 | 17 | // Verbose determines whether to print paths as they are packed. 18 | var Verbose = true 19 | 20 | // PackFiles walks the specified paths and archives them; the resulting 21 | // archive is gzipped with maximum compression. 22 | func PackFiles(paths []string) ([]byte, error) { 23 | // Create a buffer to write our archive to. 24 | buf := sbuf.NewBuffer(0) 25 | 26 | zbuf, err := gzip.NewWriterLevel(buf, gzip.BestCompression) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | // Create a new tar archive. 32 | tw := tar.NewWriter(zbuf) 33 | 34 | for _, walkPath := range paths { 35 | walker := func(path string, info os.FileInfo, err error) error { 36 | if info == nil { 37 | return fmt.Errorf("filecrypt: %s could not be read", path) 38 | } 39 | 40 | if !info.Mode().IsDir() && !info.Mode().IsRegular() { 41 | return errors.New("filecrypt: failed to compress " + path) 42 | } 43 | 44 | if Verbose { 45 | fmt.Println("Pack file", path) 46 | } 47 | 48 | filePath := filepath.Clean(path) 49 | hdr, err := tar.FileInfoHeader(info, filePath) 50 | if err != nil { 51 | return err 52 | } 53 | hdr.Name = filePath 54 | 55 | if err = tw.WriteHeader(hdr); err != nil { 56 | return err 57 | } 58 | 59 | if info.Mode().IsRegular() { 60 | file, err := os.Open(path) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | _, err = io.Copy(tw, file) 66 | file.Close() 67 | return err 68 | } 69 | return nil 70 | 71 | } 72 | err := filepath.Walk(walkPath, walker) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | } 78 | 79 | tw.Close() 80 | zbuf.Close() 81 | 82 | return buf.Bytes(), nil 83 | } 84 | 85 | // UnpackFiles decompresses and unarchives the gzipped tarball passed in 86 | // as data and uncompresses it to the top-level directory given. If 87 | // unpack is false, only file names will be listed. 88 | func UnpackFiles(in []byte, top string, unpack bool) error { 89 | buf := sbuf.NewBufferFrom(in) 90 | zbuf, err := gzip.NewReader(buf) 91 | if err != nil { 92 | return err 93 | } 94 | defer zbuf.Close() 95 | 96 | tr := tar.NewReader(zbuf) 97 | 98 | for { 99 | hdr, err := tr.Next() 100 | if err == io.EOF { 101 | return nil 102 | } else if err != nil { 103 | return err 104 | } 105 | 106 | if Verbose || !unpack { 107 | fmt.Println(hdr.Name) 108 | } 109 | 110 | if !unpack { 111 | continue 112 | } 113 | 114 | filePath := filepath.Clean(filepath.Join(top, hdr.Name)) 115 | switch hdr.Typeflag { 116 | case tar.TypeReg, tar.TypeRegA: 117 | file, err := os.Create(filePath) 118 | if err != nil { 119 | return err 120 | } 121 | 122 | _, err = io.Copy(file, tr) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | err = file.Chmod(os.FileMode(hdr.Mode)) 128 | if err != nil { 129 | return err 130 | } 131 | case tar.TypeDir: 132 | err = os.MkdirAll(filePath, os.FileMode(hdr.Mode)) 133 | if err != nil { 134 | return err 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /crypto/crypto.go: -------------------------------------------------------------------------------- 1 | // Package crypto provides message security using the NaCl secretbox 2 | // ciphers and scrypt-derived keys from passphrases. 3 | package crypto 4 | 5 | import ( 6 | "crypto/rand" 7 | "errors" 8 | 9 | "golang.org/x/crypto/nacl/secretbox" 10 | "golang.org/x/crypto/scrypt" 11 | ) 12 | 13 | const ( 14 | // keySize is the size of a NaCl secret key. 15 | keySize = 32 16 | 17 | // nonceSize is the size of a NaCl nonce. 18 | nonceSize = 24 19 | 20 | // saltSize is the size of the scrypt salt. 21 | saltSize = 32 22 | ) 23 | 24 | var ( 25 | // IterationsHigh is the recommended number of iterations for 26 | // file encryption according to the scrypt docs. 27 | IterationsHigh = 1048576 28 | 29 | // IterationsLow is twice the number of iterations for interactive 30 | // encryption as specified in the scrypt docs. 31 | IterationsLow = 32768 32 | 33 | // Iterations contains the number of iterations to be used by 34 | // filecrypt; the default is the standard filecrypt number. 35 | Iterations = IterationsHigh 36 | ) 37 | 38 | func randBytes(size int) ([]byte, error) { 39 | r := make([]byte, size) 40 | _, err := rand.Read(r) 41 | return r, err 42 | } 43 | 44 | // generateNonce creates a new random nonce. 45 | func generateNonce() (*[nonceSize]byte, error) { 46 | nonce := new([nonceSize]byte) 47 | _, err := rand.Read(nonce[:]) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return nonce, nil 53 | } 54 | 55 | var ( 56 | // ErrEncrypt is returned when encryption fails. 57 | ErrEncrypt = errors.New("crypto: encryption failed") 58 | 59 | // ErrDecrypt is returned when decryption fails. 60 | ErrDecrypt = errors.New("crypto: decryption failed") 61 | ) 62 | 63 | // encrypt generates a random nonce and encrypts the input using 64 | // NaCl's secretbox package. The nonce is prepended to the ciphertext. 65 | // A sealed message will the same size as the original message plus 66 | // secretbox.Overhead bytes long. 67 | func encrypt(key *[keySize]byte, message []byte) ([]byte, error) { 68 | nonce, err := generateNonce() 69 | if err != nil { 70 | return nil, ErrEncrypt 71 | } 72 | 73 | out := make([]byte, len(nonce)) 74 | copy(out, nonce[:]) 75 | out = secretbox.Seal(out, message, nonce, key) 76 | return out, nil 77 | } 78 | 79 | // decrypt extracts the nonce from the ciphertext, and attempts to 80 | // decrypt with NaCl's secretbox. 81 | func decrypt(key *[keySize]byte, message []byte) ([]byte, error) { 82 | if len(message) < (nonceSize + secretbox.Overhead) { 83 | return nil, ErrDecrypt 84 | } 85 | 86 | var nonce [nonceSize]byte 87 | copy(nonce[:], message[:nonceSize]) 88 | out, ok := secretbox.Open(nil, message[nonceSize:], &nonce, key) 89 | if !ok { 90 | return nil, ErrDecrypt 91 | } 92 | 93 | return out, nil 94 | } 95 | 96 | // deriveKey generates a new NaCl key from a passphrase and salt. 97 | func deriveKey(pass, salt []byte) *[keySize]byte { 98 | var naclKey = new([keySize]byte) 99 | 100 | // Key only fails with invalid scrypt params. 101 | key, _ := scrypt.Key(pass, salt, Iterations, 8, 1, keySize) 102 | 103 | copy(naclKey[:], key) 104 | Zero(key) 105 | return naclKey 106 | } 107 | 108 | // Seal secures a message using a passphrase. 109 | func Seal(pass, message []byte) ([]byte, error) { 110 | salt, err := randBytes(saltSize) 111 | if err != nil { 112 | return nil, ErrEncrypt 113 | } 114 | 115 | key := deriveKey(pass, salt) 116 | out, err := encrypt(key, message) 117 | Zero(key[:]) // Zero key immediately after 118 | if err != nil { 119 | return nil, ErrEncrypt 120 | } 121 | 122 | out = append(salt, out...) 123 | return out, nil 124 | } 125 | 126 | const overhead = saltSize + secretbox.Overhead + nonceSize 127 | 128 | // Open recovers a message encrypted using a passphrase. 129 | func Open(pass, message []byte) ([]byte, error) { 130 | if len(message) < overhead { 131 | return nil, ErrDecrypt 132 | } 133 | 134 | key := deriveKey(pass, message[:saltSize]) 135 | out, err := decrypt(key, message[saltSize:]) 136 | Zero(key[:]) // Zero key immediately after 137 | if err != nil { 138 | return nil, ErrDecrypt 139 | } 140 | 141 | return out, nil 142 | } 143 | 144 | // Zero attempts to zeroise its input. 145 | func Zero(in []byte) { 146 | for i := 0; i < len(in); i++ { 147 | in[i] = 0 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /crypto/crypto_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "io" 7 | "testing" 8 | 9 | "golang.org/x/crypto/nacl/secretbox" 10 | ) 11 | 12 | var ( 13 | testMessage = []byte("Do not go gentle into that good night.") 14 | testPassword1 = []byte("correct horse battery staple") 15 | testPassword2 = []byte("incorrect horse battery staple") 16 | testKey *[32]byte 17 | ) 18 | 19 | /* 20 | * The following tests verify the positive functionality of this package: 21 | * can an encrypted message be decrypted? 22 | */ 23 | 24 | // generateKey creates a new random secret key. 25 | func generateKey() (*[keySize]byte, error) { 26 | key := new([keySize]byte) 27 | _, err := io.ReadFull(rand.Reader, key[:]) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return key, nil 33 | } 34 | 35 | func TestGenerateKey(t *testing.T) { 36 | var err error 37 | testKey, err = generateKey() 38 | if err != nil { 39 | t.Fatalf("%v", err) 40 | } 41 | } 42 | 43 | func TestEncrypt(t *testing.T) { 44 | ct, err := encrypt(testKey, testMessage) 45 | if err != nil { 46 | t.Fatalf("%v", err) 47 | } 48 | 49 | pt, err := decrypt(testKey, ct) 50 | if err != nil { 51 | t.Fatalf("%v", err) 52 | } 53 | 54 | if !bytes.Equal(testMessage, pt) { 55 | t.Fatalf("messages don't match") 56 | } 57 | } 58 | 59 | /* 60 | * The following tests verify the negative functionality of this package: 61 | * does it fail when it should? 62 | */ 63 | 64 | func prngTester(size int, testFunc func()) { 65 | prng := rand.Reader 66 | buf := &bytes.Buffer{} 67 | 68 | rand.Reader = buf 69 | defer func() { rand.Reader = prng }() 70 | 71 | for i := 0; i < size; i++ { 72 | tmp := make([]byte, i) 73 | buf.Write(tmp) 74 | testFunc() 75 | } 76 | } 77 | 78 | func TestPRNGFailures(t *testing.T) { 79 | testFunc := func() { 80 | _, err := generateKey() 81 | if err == nil { 82 | t.Fatal("expected key generation failure with bad PRNG") 83 | } 84 | } 85 | prngTester(32, testFunc) 86 | 87 | testFunc = func() { 88 | _, err := generateNonce() 89 | if err == nil { 90 | t.Fatal("expected nonce generation failure with bad PRNG") 91 | } 92 | } 93 | prngTester(24, testFunc) 94 | 95 | testFunc = func() { 96 | _, err := encrypt(testKey, testMessage) 97 | if err == nil { 98 | t.Fatal("expected encryption failure with bad PRNG") 99 | } 100 | } 101 | prngTester(24, testFunc) 102 | } 103 | 104 | func TestDecryptFailures(t *testing.T) { 105 | targetLength := 24 + secretbox.Overhead 106 | 107 | for i := 0; i < targetLength; i++ { 108 | buf := make([]byte, i) 109 | if _, err := decrypt(testKey, buf); err == nil { 110 | t.Fatal("expected decryption failure with bad message length") 111 | } 112 | } 113 | 114 | otherKey, err := generateKey() 115 | if err != nil { 116 | t.Fatalf("%v", err) 117 | } 118 | 119 | ct, err := encrypt(testKey, testMessage) 120 | if err != nil { 121 | t.Fatalf("%v", err) 122 | } 123 | 124 | if _, err = decrypt(otherKey, ct); err == nil { 125 | t.Fatal("decrypt should fail with wrong key") 126 | } 127 | } 128 | 129 | func TestEncryptCycle(t *testing.T) { 130 | out, err := Seal(testPassword1, testMessage) 131 | if err != nil { 132 | t.Fatalf("%v", err) 133 | } 134 | 135 | out, err = Open(testPassword1, out) 136 | if err != nil { 137 | t.Fatalf("%v", err) 138 | } 139 | 140 | if !bytes.Equal(testMessage, out) { 141 | t.Fatal("recovered plaintext doesn't match original") 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /filecrypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "time" 11 | 12 | "github.com/gokyle/readpass" 13 | "github.com/kisom/filecrypt/archive" 14 | "github.com/kisom/filecrypt/crypto" 15 | ) 16 | 17 | var version = "1.0.0" 18 | 19 | func dieIf(err error) { 20 | if err != nil { 21 | fmt.Fprintf(os.Stderr, "[!] %v\n", err) 22 | os.Exit(1) 23 | } 24 | } 25 | 26 | var quiet bool 27 | 28 | func print(s string) { 29 | if !quiet { 30 | fmt.Println(s) 31 | } 32 | } 33 | 34 | func printf(fmtstr string, args ...interface{}) { 35 | if !quiet { 36 | fmt.Printf(fmtstr, args...) 37 | } 38 | } 39 | 40 | func dieWith(fs string, args ...interface{}) { 41 | outStr := fmt.Sprintf("[!] %s\n", fs) 42 | fmt.Fprintf(os.Stderr, outStr, args...) 43 | os.Exit(1) 44 | } 45 | 46 | func main() { 47 | help := flag.Bool("h", false, "print a short usage message") 48 | lower := flag.Bool("l", false, "use the lower scrypt settings") 49 | out := flag.String("o", "", "output path") 50 | flag.BoolVar(&quiet, "q", false, "do not print any additional output") 51 | list := flag.Bool("t", false, "list files") 52 | unpack := flag.Bool("u", false, "unpack files instead of packing them") 53 | flag.BoolVar(&archive.Verbose, "v", false, "enable verbose logging") 54 | extract := flag.Bool("x", false, "extract .tgz from archive") 55 | flag.Parse() 56 | 57 | if *help || flag.NArg() == 0 { 58 | usage() 59 | return 60 | } 61 | 62 | if *lower { 63 | crypto.Iterations = crypto.IterationsLow 64 | } 65 | 66 | if *unpack && *extract { 67 | fmt.Fprintf(os.Stderr, "Only one of unpack or extract may be chosen.") 68 | os.Exit(1) 69 | } 70 | 71 | if *out == "" { 72 | if *unpack { 73 | *out = "." 74 | } else if *extract { 75 | *out = "files.tgz" 76 | } else { 77 | *out = "files.enc" 78 | } 79 | } 80 | 81 | var pass []byte 82 | var err error 83 | 84 | if !(*unpack || *extract || *list) { 85 | for { 86 | pass, err = readpass.PasswordPromptBytes("Archive password: ") 87 | dieIf(err) 88 | 89 | var confirm []byte 90 | confirm, err = readpass.PasswordPromptBytes("Confirm: ") 91 | dieIf(err) 92 | 93 | if !bytes.Equal(pass, confirm) { 94 | fmt.Println("Passwords don't match.") 95 | continue 96 | } 97 | 98 | crypto.Zero(confirm) 99 | break 100 | } 101 | } else { 102 | pass, err = readpass.PasswordPromptBytes("Archive password: ") 103 | dieIf(err) 104 | } 105 | 106 | start := time.Now() 107 | if *unpack || *extract || *list { 108 | if flag.NArg() != 1 { 109 | dieWith("only one file may unpacked at a time.") 110 | } 111 | 112 | print("Reading encrypted archive...") 113 | in, err := ioutil.ReadFile(flag.Arg(0)) 114 | dieIf(err) 115 | 116 | print("Decrypting archive...") 117 | in, err = crypto.Open(pass, in) 118 | dieIf(err) 119 | 120 | if *unpack { 121 | print("Unpacking files...") 122 | err = archive.UnpackFiles(in, *out, true) 123 | } else if *list { 124 | err = archive.UnpackFiles(in, *out, false) 125 | } else { 126 | print("Extracting .tgz...") 127 | err = ioutil.WriteFile(*out, in, 0644) 128 | } 129 | dieIf(err) 130 | } else { 131 | print("Packing files...") 132 | fData, err := archive.PackFiles(flag.Args()) 133 | dieIf(err) 134 | 135 | print("Encrypting archive...") 136 | fData, err = crypto.Seal(pass, fData) 137 | dieIf(err) 138 | 139 | print("Writing file...") 140 | err = ioutil.WriteFile(*out, fData, 0644) 141 | dieIf(err) 142 | } 143 | 144 | printf("Completed in %s.\n", time.Since(start)) 145 | } 146 | 147 | func usage() { 148 | progName := filepath.Base(os.Args[0]) 149 | fmt.Printf(`%s version %s, (c) 2015 Kyle Isom 150 | Released under the ISC license. 151 | 152 | Usage: 153 | %s [-h] [-o filename] [-q] [-t] [-u] [-v] [-x] files... 154 | 155 | -h Print this help message. 156 | 157 | -o filename The filename to output. If an archive is being built, 158 | this is the filename of the archive. If an archive is 159 | being unpacked, this is the directory to unpack in. 160 | If the tarball is being extracted, this is the path 161 | to write the tarball. 162 | 163 | Defaults: 164 | Pack: files.enc 165 | Unpack: . 166 | Extract: files.tgz 167 | 168 | -q Quiet mode. Only print errors and password prompt. 169 | This will override the verbose flag. 170 | 171 | -t List files in the archive. This acts like the list 172 | flag in tar. 173 | 174 | -u Unpack the archive listed on the command line. Only 175 | one archive may be unpacked. 176 | 177 | -v Verbose mode. This acts like the verbose flag in 178 | tar. 179 | 180 | -x Extract a tarball. This will decrypt the archive, but 181 | not decompress or unpack it. 182 | 183 | Examples: 184 | %s -o ssh.enc ~/.ssh 185 | Encrypt the user's OpenSSH directory to ssh.enc. 186 | 187 | %s -o backup/ -u ssh.enc 188 | Restore the user's OpenSSH directory to the backup/ 189 | directory. 190 | 191 | %s -u ssh.enc 192 | Restore the user's OpenSSH directory to the current directory. 193 | 194 | `, progName, version, progName, progName, progName, progName) 195 | } 196 | --------------------------------------------------------------------------------