You have a collection of files that you want to sign with GPG. Should you sign each file individually, sign a file containing a list of SHA-256 hashes, or do something different altogether? Which method you choose has important consequences for security.
Problem 1: File name integrity
Signing files one at a time signs the contents of the files. It (typically)
does not protect the file names from tampering. This could be disastrous in
some situations (e.g. an attacker could change blacklist.txt to
whitelist.txt).
Problem 2: Set membership integrity
Signing individual files does not prevent adding or removing files from the set of files that were transmitted. In some cases, this is important: if you’re transmitting a set of log files, an attacker could selectively remove one that captured incriminating information, without that change being detected. If an attacker has the opportunity to see multiple transmissions, they could add files from an old transmission to the new transmission (essentially a variant on replay attacks). The files would still have valid signatures individually, so the additions would not be detected. If you’re transmitting a set of user accounts (one per file), an attacker could revert their account to an earlier state (e.g. back to having admin status).
What should you do?
Given the above, I recommend protecting the transmission as a whole instead of part-by-part. There are too many things that can go wrong when each file is signed individually. The easiest way of getting everything right is to create a tarball or zip file of the files and sign the entire archive:
# Create a zip file
$ zip myfiles.zip file1 file2 file3
# Sign the zip file
$ gpg --detach-sign myfiles.zipThe above commands will create a GPG signature file myfiles.zip.gpg that can
be used to verify that myfiles.zip has not been tampered with. When
receiving the zip file, just verify the signature before unzipping:
# Verify signature
$ gpg --verify myfiles.zip.gpg
# gpg should output 'Good signature from X'
# Unzip files
$ unzip myfiles.zipAn alternative
This may not be possible or practical if, for example, zipping the files would
be too resource-intensive. In that case, it is still possible to avoid most of
the above problems. Do not sign the files individually. Instead, produce a
SHA256SUMS file containing a list of file names and corresponding hashes, and
sign that file. The sha256sum command on Linux makes this easy:
# Save file hashes to SHA256SUMS file
$ sha256sum file1 file2 file3 > SHA256SUMS
# Create SHA256SUMS.sig
$ gpg --detach-sign SHA256SUMS
# Transmit the files, SHA256SUMS, and SHA256SUMS.sigWhen it’s time to check the signature:
# First, verify the GPG signature on SHA256SUMS
$ gpg --verify SHA256SUMS.sig
# should output 'Good signature from X'
# Next, verify the SHA-256 hashes
$ sha256sum --check SHA256SUMS
# should output 'fileX: OK' for each fileMake sure that you use sha256sum and not the older sha1sum or md5sum, as
those hash functions are no longer safe to use.
There’s one catch with this method: sha256sum won’t notice if there are other
files in the current directory that are not listed in the SHA256SUMS file.
If an attacker adds a malicious file to the files you received, sha256sum
won’t detect that anything is wrong, since it only checks the files whose
hashes are listed. Consider whether this problem actually affects the security
of your application: you may need to add additional security checks. This is
why I generally recommend working with signed zip files instead.