加密同步涉及的文件使用文件同步解决方案的常见原因之一是,为了创建文件的精确备份,以便在出现问题时能够复制或重建目录结构的元素。
rsync 工具非常适合完成这个任务,因为它只复制两个目录之间有差异的文件,效率很高。更有意义的是,因为 rsync 可以同步到远程系统,所以可以使用它自动创建远程备份,不需要把备份文件单独复制到远程系统。
这个过程的一个限制是,创建的拷贝是未加密的。如果要把文件复制到远程系统,而其他人也能够访问这个远程系统,就需要确保其他人无法读取这些文件(即使他们能够接触到这些文件)。
只使用 rsync 是无法加密文件的。也无法使用 rsync 的算法只加密在上一次同步操作之后修改过的文件。
但是,通过在脚本中执行 rsync,就可以用 rsync 的输出创建文件的辅助拷贝,然后对这个拷贝进行加密。
这个脚本的基本原理是创建原目录结构的两个拷贝。第一个拷贝作为参照拷贝,其中包含目录结构的精确副本。这样,当再次同步目录时,就可以像一般情况一样比较源和目标文件并判断出差异。在 rsync 命令中使用 --itemize-changes 选项,rsync 就会创建一个参照列表,其中列出在同步期间每个文件所发生的情况。输出详细说明文件是否已经修改过(或新建),或文件是否已经删除。清单 3 中给出一个示例。
清单 3. rsync 生成的修改记录1
2
3
4
5
| .d..t...... t1/a/
*deleting t1/a/3
.d..t...... t1/b/
>f.st...... t1/b/1
>f+++++++++ t1/b/6
|
以 .d. 开头的行表示新目录或目录修改。*deleting 行表示文件已经从源目录中删除。>f 行表示文件已经修改过或是新建的文件(>f++++++++)。
通过解析这个输出文件,可以判断出源目录和目标参照目录之间的差异。判断出差异之后,可以在第三个目录中创建原文件的加密版本。通过使用修改记录,只加密(或删除)在上一次同步操作之后修改过的文件。不能使用目录的加密版本直接执行同步,因为文件的加密版本总是与源文件不一样。
完整的脚本见清单 4。
清单 4. 完整脚本1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
| #!/usr/bin/perl
use warnings;
use strict;
use File::Basename;
use File:ath;
my $source = shift;
my $dest = shift;
my $encdest = shift;
if (!defined($source) || !defined($dest) || !defined($encdest))
{
print "Error: Not enough arguments!\n";
print "Usage: $0 source destination encrypteddest\n";
exit(1);
}
print STDERR "Running rsync between $source and $dest ($encdest)\n";
system("rsync --delete --recursive --times -og --links --perms " .
"--hard-links --itemize-changes $source $dest " .
">/tmp/$$.rsynclog 2>&1");
open(DATA,"/tmp/$$.rsynclog") or die "Couldn't open the rsynclog\n";
my @changedfiles;
my @delfiles;
while(<DATA>)
{
next if (m/sending incremental file list/);
chomp;
last if (length($_) == 0);
my ($changes,$filename) = split;
push @changedfiles,$filename if ($changes =~ m/^>f/);
push @delfiles,$filename if ($changes =~ m/^\*del/);
}
close(DATA);
my $counter = 0;
foreach my $file (@changedfiles)
{
if (-f "$dest/$file")
{
my $sourcename = encode_filename("$dest/$file");
my $destname = encode_filename("$encdest/$file");
my $dirname = dirname("$encdest/$file");
mkpath($dirname);
system(sprintf('cat "%s" |openssl enc -des3 ' .
'-pass file:/var/lib/passphrase -a >"%s"',
$sourcename,$destname));
$counter++;
}
}
my $delcounter = 0;
foreach my $file (@delfiles)
{
unlink("$encdest/$file");
$delcounter++;
}
print STDERR "Finished (changed: $counter, deleted: $delcounter)\n";
unlink("/tmp/$$.rsynclog");
sub encode_filename
{
my ($filename) = @_;
$filename =~ s/ /\\ /g;
$filename =~ s/'/\\'/g;
$filename =~ s/"/\\"/g;
$filename =~ s/\(/\\(/g;
$filename =~ s/\)/\\)/g;
$filename =~ s/&/\\&/g;
$filename =~ s/#/\\#/g;
return($filename);
}
|
这个脚本非常简单,很容易使用。在运行脚本时,指定源目录、参照文件的目标目录和文件加密版本的目标目录:$ rsyncrypt source destination destination.enc。
脚本的第一部分在源和目标目录之间执行基本的同步以判断修改(见清单 5)。这个操作生成记录修改的文件(在 /tmp 目录中)。
清单 5. 在源和目标目录之间执行基本的同步1
2
3
| system("rsync --delete --recursive --times -og --links --perms " .
"--hard-links --itemize-changes $source $dest " .
">/tmp/$$.rsynclog 2>&1");
|
接下来,解析修改的列表,生成已经修改和删除的文件的列表(见清单 6)。
清单 6. 解析修改的列表1
2
3
4
5
6
7
8
9
| while(<DATA>)
{
next if (m/sending incremental file list/);
chomp;
last if (length($_) == 0);
my ($changes,$filename) = split;
push @changedfiles,$filename if ($changes =~ m/^>f/);
push @delfiles,$filename if ($changes =~ m/^\*del/);
}
|
对于每个修改过的文件,读取参照版本并在加密目标目录中创建加密版本(见清单 7)。
清单 7. 创建每个修改过的文件的加密版本1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| foreach my $file (@changedfiles)
{
if (-f "$dest/$file")
{
my $sourcename = encode_filename("$dest/$file");
my $destname = encode_filename("$encdest/$file");
my $dirname = dirname("$encdest/$file");
mkpath($dirname);
system(sprintf('cat "%s" |openssl enc -des3 ' .
'-pass file:/var/lib/passphrase -a >"%s"',
$sourcename,$destname));
$counter++;
}
}
|
因为要使用 shell 执行实际的加密,文件名必须进行编码。需要对一些特殊字符进行转义,否则 shell 会解释它们。
对于实际的加密,使用 openssl 和一个简单的文本文件(在 /var/lib/passphrase 中),这个文件包含信息编码所用的密码。还可以创建或使用专门生成的密钥来执行此操作或希望使用的其他加密命令。
最后,因为源目录中可能有已经删除的文件,还要把删除的文件从加密目录内容中删除(见清单 8)。
清单 8. 把删除的文件从加密目录内容中删除1
2
3
4
5
| foreach my $file (@delfiles)
{
unlink("$encdest/$file");
$delcounter++;
}
|
这个脚本非常有效,惟一的缺点是它需要信息的两个拷贝(参照目录和加密版本),而不只是一个。另外,为了简化这个过程,并没有把权限、所有者和时间戳信息同步到加密版本,但是很容易添加这个特性。因为这个脚本使用 rsync 生成修改的列表,这会显著减少需要加密的文件数量,可以使用相同的优化算法把新的文件加密版本同步到远程主机,从而只传输在上一次同步操作之后修改过的加密文件。
结束语本文讨论了几种不同的文件同步方法。基本的 cp 命令并不是真正的同步命令,但是可以用来执行直接复制。对于真正的同步操作,cp 命令花费的时间太长,效率很低。在使用 tar 时,可以指定一个时间参照点,只复制在这个时间点之后修改过的文件。但是,如果修改不明显或无法通过简单的比较查明,这个特性的意义也不大。
rsync 工具是更好的文件同步解决方案。它对源和目标目录执行许多检查和比较,可以实现高效的同步,甚至可以通过网络或公共连接执行同步。为了确保安全,可以结合使用 rsync 与加密技术,确保在没有正确的密码或加密密钥的情况下无法读取远程文件。 |