| package cmd |
|
|
| import ( |
| "io" |
| "os" |
| "path" |
| "path/filepath" |
| "strings" |
|
|
| rcCrypt "github.com/rclone/rclone/backend/crypt" |
| "github.com/rclone/rclone/fs/config/configmap" |
| "github.com/rclone/rclone/fs/config/obscure" |
| log "github.com/sirupsen/logrus" |
| "github.com/spf13/cobra" |
| ) |
|
|
| |
|
|
| type options struct { |
| op string |
| src string |
| dst string |
|
|
| pwd string |
| salt string |
| filenameEncryption string |
| dirnameEncryption string |
| filenameEncode string |
| suffix string |
| } |
|
|
| var opt options |
|
|
| |
| var CryptCmd = &cobra.Command{ |
| Use: "crypt", |
| Short: "Encrypt or decrypt local file or dir", |
| Example: `openlist crypt -s ./src/encrypt/ --op=de --pwd=123456 --salt=345678`, |
| Run: func(cmd *cobra.Command, args []string) { |
| opt.validate() |
| opt.cryptFileDir() |
|
|
| }, |
| } |
|
|
| func init() { |
| RootCmd.AddCommand(CryptCmd) |
| |
|
|
| |
| |
| |
|
|
| |
| |
| CryptCmd.Flags().StringVarP(&opt.src, "src", "s", "", "src file or dir to encrypt/decrypt") |
| CryptCmd.Flags().StringVarP(&opt.dst, "dst", "d", "", "dst dir to output,if not set,output to src dir") |
| CryptCmd.Flags().StringVar(&opt.op, "op", "", "de or en which stands for decrypt or encrypt") |
|
|
| CryptCmd.Flags().StringVar(&opt.pwd, "pwd", "", "password used to encrypt/decrypt,if not contain ___Obfuscated___ prefix,will be obfuscated before used") |
| CryptCmd.Flags().StringVar(&opt.salt, "salt", "", "salt used to encrypt/decrypt,if not contain ___Obfuscated___ prefix,will be obfuscated before used") |
| CryptCmd.Flags().StringVar(&opt.filenameEncryption, "filename-encrypt", "off", "filename encryption mode: off,standard,obfuscate") |
| CryptCmd.Flags().StringVar(&opt.dirnameEncryption, "dirname-encrypt", "false", "is dirname encryption enabled:true,false") |
| CryptCmd.Flags().StringVar(&opt.filenameEncode, "filename-encode", "base64", "filename encoding mode: base64,base32,base32768") |
| CryptCmd.Flags().StringVar(&opt.suffix, "suffix", ".bin", "suffix for encrypted file,default is .bin") |
| } |
|
|
| func (o *options) validate() { |
| if o.src == "" { |
| log.Fatal("src can not be empty") |
| } |
| if o.op != "encrypt" && o.op != "decrypt" && o.op != "en" && o.op != "de" { |
| log.Fatal("op must be encrypt or decrypt") |
| } |
| if o.filenameEncryption != "off" && o.filenameEncryption != "standard" && o.filenameEncryption != "obfuscate" { |
| log.Fatal("filename_encryption must be off,standard,obfuscate") |
| } |
| if o.filenameEncode != "base64" && o.filenameEncode != "base32" && o.filenameEncode != "base32768" { |
| log.Fatal("filename_encode must be base64,base32,base32768") |
| } |
|
|
| } |
|
|
| func (o *options) cryptFileDir() { |
| src, _ := filepath.Abs(o.src) |
| log.Infof("src abs is %v", src) |
|
|
| fileInfo, err := os.Stat(src) |
| if err != nil { |
| log.Fatalf("reading file/dir %v failed,err:%v", src, err) |
|
|
| } |
| pwd := updateObfusParm(o.pwd) |
| salt := updateObfusParm(o.salt) |
|
|
| |
| config := configmap.Simple{ |
| "password": pwd, |
| "password2": salt, |
| "filename_encryption": o.filenameEncryption, |
| "directory_name_encryption": o.dirnameEncryption, |
| "filename_encoding": o.filenameEncode, |
| "suffix": o.suffix, |
| "pass_bad_blocks": "", |
| } |
| log.Infof("config:%v", config) |
| cipher, err := rcCrypt.NewCipher(config) |
| if err != nil { |
| log.Fatalf("create cipher failed,err:%v", err) |
|
|
| } |
| dst := "" |
| |
| if o.dst != "" { |
| dst, _ = filepath.Abs(o.dst) |
| checkCreateDir(dst) |
| } |
|
|
| |
| if !fileInfo.IsDir() { |
| if dst == "" { |
| dst = filepath.Dir(src) |
| } |
| o.cryptFile(cipher, src, dst) |
| return |
| } |
|
|
| |
| if dst == "" { |
| |
| dst = path.Join(filepath.Dir(src), fileInfo.Name()+"_crypt") |
| } |
| log.Infof("dst : %v", dst) |
|
|
| dirnameMap := make(map[string]string) |
| pathSeparator := string(os.PathSeparator) |
|
|
| filepath.Walk(src, func(p string, info os.FileInfo, err error) error { |
| if err != nil { |
| log.Errorf("get file %v info failed, err:%v", p, err) |
| return err |
| } |
| if p == src { |
| return nil |
| } |
| log.Infof("current path %v", p) |
|
|
| |
| rp := strings.ReplaceAll(p, src, "") |
| log.Infof("relative path %v", rp) |
|
|
| rpds := strings.Split(rp, pathSeparator) |
|
|
| if info.IsDir() { |
| |
| dd := "" |
|
|
| if o.dirnameEncryption == "true" { |
| if o.op == "encrypt" || o.op == "en" { |
| for i := range rpds { |
| oname := rpds[i] |
| if _, ok := dirnameMap[rpds[i]]; ok { |
| rpds[i] = dirnameMap[rpds[i]] |
| } else { |
| rpds[i] = cipher.EncryptDirName(rpds[i]) |
| dirnameMap[oname] = rpds[i] |
| } |
| } |
| dd = path.Join(dst, strings.Join(rpds, pathSeparator)) |
| } else { |
| for i := range rpds { |
| oname := rpds[i] |
| if _, ok := dirnameMap[rpds[i]]; ok { |
| rpds[i] = dirnameMap[rpds[i]] |
| } else { |
| dnn, err := cipher.DecryptDirName(rpds[i]) |
| if err != nil { |
| log.Fatalf("decrypt dir name %v failed,err:%v", rpds[i], err) |
| } |
| rpds[i] = dnn |
| dirnameMap[oname] = dnn |
| } |
|
|
| } |
| dd = path.Join(dst, strings.Join(rpds, pathSeparator)) |
| } |
|
|
| } else { |
| dd = path.Join(dst, rp) |
| } |
|
|
| log.Infof("create output dir %v", dd) |
| checkCreateDir(dd) |
| return nil |
| } |
|
|
| |
| fdd := dst |
|
|
| if o.dirnameEncryption == "true" { |
| for i := range rpds { |
| if i == len(rpds)-1 { |
| break |
| } |
| fdd = path.Join(fdd, dirnameMap[rpds[i]]) |
| } |
|
|
| } else { |
| fdd = path.Join(fdd, strings.Join(rpds[:len(rpds)-1], pathSeparator)) |
| } |
|
|
| log.Infof("file output dir %v", fdd) |
| o.cryptFile(cipher, p, fdd) |
| return nil |
| }) |
|
|
| } |
|
|
| func (o *options) cryptFile(cipher *rcCrypt.Cipher, src string, dst string) { |
| fileInfo, err := os.Stat(src) |
| if err != nil { |
| log.Fatalf("get file %v info failed,err:%v", src, err) |
|
|
| } |
| fd, err := os.OpenFile(src, os.O_RDWR, 0666) |
| if err != nil { |
| log.Fatalf("open file %v failed,err:%v", src, err) |
|
|
| } |
| defer fd.Close() |
|
|
| var cryptSrcReader io.Reader |
| var outFile string |
| if o.op == "encrypt" || o.op == "en" { |
| filename := fileInfo.Name() |
| if o.filenameEncryption != "off" { |
| filename = cipher.EncryptFileName(fileInfo.Name()) |
| log.Infof("encrypt file name %v to %v", fileInfo.Name(), filename) |
| } else { |
| filename = fileInfo.Name() + o.suffix |
| } |
| cryptSrcReader, err = cipher.EncryptData(fd) |
| if err != nil { |
| log.Fatalf("encrypt file %v failed,err:%v", src, err) |
|
|
| } |
| outFile = path.Join(dst, filename) |
| } else { |
| filename := fileInfo.Name() |
| if o.filenameEncryption != "off" { |
| filename, err = cipher.DecryptFileName(filename) |
| if err != nil { |
| log.Fatalf("decrypt file name %v failed,err:%v", src, err) |
| } |
| log.Infof("decrypt file name %v to %v, ", fileInfo.Name(), filename) |
| } else { |
| filename = strings.TrimSuffix(filename, o.suffix) |
| } |
|
|
| cryptSrcReader, err = cipher.DecryptData(fd) |
| if err != nil { |
| log.Fatalf("decrypt file %v failed,err:%v", src, err) |
|
|
| } |
| outFile = path.Join(dst, filename) |
| } |
| |
| wr, err := os.OpenFile(outFile, os.O_CREATE|os.O_WRONLY, 0755) |
| if err != nil { |
| log.Fatalf("create file %v failed,err:%v", outFile, err) |
|
|
| } |
| defer wr.Close() |
|
|
| _, err = io.Copy(wr, cryptSrcReader) |
| if err != nil { |
| log.Fatalf("write file %v failed,err:%v", outFile, err) |
| } |
|
|
| } |
|
|
| |
| func checkCreateDir(dir string) { |
| _, err := os.Stat(dir) |
|
|
| if os.IsNotExist(err) { |
| err := os.MkdirAll(dir, 0755) |
| if err != nil { |
| log.Fatalf("create dir %v failed,err:%v", dir, err) |
| } |
| return |
| } else if err != nil { |
| log.Fatalf("read dir %v err: %v", dir, err) |
| } |
|
|
| } |
|
|
| func updateObfusParm(str string) string { |
| obfuscatedPrefix := "___Obfuscated___" |
| if !strings.HasPrefix(str, obfuscatedPrefix) { |
| str, err := obscure.Obscure(str) |
| if err != nil { |
| log.Fatalf("update obfuscated parameter failed,err:%v", str) |
| } |
| } else { |
| str, _ = strings.CutPrefix(str, obfuscatedPrefix) |
| } |
| return str |
| } |
|
|