From f0827ef96044219fea423d73147937c4c266827b Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 1 Dec 2019 23:45:45 +0000 Subject: [PATCH] pspp-convert: Support decrypting SPV files. Also, now properly understands and documents the PKCS #7 padding used for all encrypted files. Special thanks to Alan Mead for assistance. --- NEWS | 11 +- doc/dev/encrypted-file-wrappers.texi | 78 +++++++----- doc/pspp-convert.texi | 10 +- src/data/encrypted-file.c | 181 +++++++++++++++++++-------- src/data/encrypted-file.h | 2 - tests/automake.mk | 2 + tests/data/encrypted-file.at | 5 + tests/data/test-decrypted.spv | Bin 0 -> 7713 bytes tests/data/test-encrypted.spv | Bin 0 -> 7764 bytes utilities/pspp-convert.1 | 7 +- utilities/pspp-convert.c | 19 +-- 11 files changed, 201 insertions(+), 114 deletions(-) create mode 100644 tests/data/test-decrypted.spv create mode 100644 tests/data/test-encrypted.spv diff --git a/NEWS b/NEWS index 1cad23fea6..020bc6d148 100644 --- a/NEWS +++ b/NEWS @@ -14,8 +14,15 @@ Changes from 1.2.0 to 1.3.0: * Plain text output is no longer divided into pages, since it is now rarely printed on paper. - * pspp-convert: New "-a", "-l", "--password-list" options to search - for an encrypted file's password. + * pspp-convert: + + - New support to decrypt encrypted viewer (SPV) files. The + encrypted viewer file format is unacceptably insecure, so to + discourage its use PSPP and PSPPIRE do not directly read or write + this format. + + - New "-a", "-l", "--password-list" options to search for an + encrypted file's password. * Improvements to SAVE DATA COLLECTION support for MDD files. diff --git a/doc/dev/encrypted-file-wrappers.texi b/doc/dev/encrypted-file-wrappers.texi index d4fcac207b..c445eec023 100644 --- a/doc/dev/encrypted-file-wrappers.texi +++ b/doc/dev/encrypted-file-wrappers.texi @@ -16,10 +16,10 @@ encrypted wrapper. The wrapper has a common format, regardless of the kind of the file that it contains. @quotation Warning -The SPSS encryption wrapper is poorly designed. It is much cheaper -and faster to decrypt a file encrypted this way than if a well -designed alternative were used. If you must use this format, use a -10-byte randomly generated password. +The SPSS encryption wrapper is poorly designed. When the password is +unknown, it is much cheaper and faster to decrypt a file encrypted +this way than if a well designed alternative were used. If you must +use this format, use a 10-byte randomly generated password. @end quotation @menu @@ -30,13 +30,12 @@ designed alternative were used. If you must use this format, use a @node Common Wrapper Format @section Common Wrapper Format -This section describes the general format of an SPSS encrypted file -wrapper. The following sections describe the details for each kind of -encapsulated file. - An encrypted file wrapper begins with the following 36-byte header, -where @i{xxx} identifies the type of file encapsulated, as described -in the following sections: +where @i{xxx} identifies the type of file encapsulated: @code{SAV} for +a system file, @code{SPS} for a syntax file, @code{SPV} for a viewer +file. PSPP code for identifying these files just checks for the +@code{ENCRYPTED} keyword at offset 8, but the other bytes are also +fixed in practice: @example 0000 1c 00 00 00 00 00 00 00 45 4e 43 52 59 50 54 45 |........ENCRYPTE| @@ -46,10 +45,17 @@ in the following sections: Following the fixed header is essentially the regular contents of the encapsulated file in its usual format, with each 16-byte block -encrypted with AES-256 in ECB mode. Each type of encapsulated file is -processed in a slightly different way before encryption, as described -in the following sections. The AES-256 key is derived from a password -in the following way: +encrypted with AES-256 in ECB mode. + +To make the plaintext an even multiple of 16 bytes in length, the +encryption process appends PKCS #7 padding, as specified in RFC 5652 +section 6.3. Padding appends 1 to 16 bytes to the plaintext, in which +each byte of padding is the number of padding bytes added. If the +plaintext is, for example, 2 bytes short of a multiple of 16, the +padding is 2 bytes with value 02; if the plaintext is a multiple of 16 +bytes in length, the padding is 16 bytes with value 0x10. + +The AES-256 key is derived from a password in the following way: @enumerate @item @@ -102,35 +108,39 @@ The AES-256 key is: @end example @menu -* Encrypted System Files:: -* Encrypted Syntax Files:: +* Checking Passwords:: @end menu -@node Encrypted System Files -@subsection Encrypted System Files - -An encrypted system file uses @code{SAV} as the identifier in its -header. +@node Checking Passwords +@subsection Checking Passwords -Before encryption, a system file is appended with as many null bytes -as needed (possibly zero) to make it a multiple of 16 bytes in length, -so that it fits exactly in a series of AES blocks. (This implies that -encrypted system files must always be compressed, because otherwise a -system file with only a single variable might appear to have an extra -case.) +A program reading an encrypted file may wish to verify that the +password it was given is the correct one. One way is to verify that +the PKCS #7 padding at the end of the file is well formed. However, +any plaintext that ends in byte 01 is well formed PKCS #7, meaning +that about 1 in 256 keys will falsely pass this test. This might be +acceptable for interactive use, but the false positive rate is too +high for a brute-force search of the password space. -@node Encrypted Syntax Files -@subsection Encrypted Syntax Files +A better test requires some knowledge of the file format being +wrapped, to obtain a ``magic number'' for the beginning of the file. -An encrypted syntax file uses @code{SPS} as the identifier in its -header. +@itemize @bullet +@item +The plaintext of system files begins with @code{$FL2@@(#)} or +@code{$FL3@@(#)}. +@item Before encryption, a syntax file is prefixed with a line at the beginning of the form @code{* Encoding: @var{encoding}.}, where @var{encoding} is the encoding used for the rest of the file, -e.g. @code{windows-1252}. The syntax file is then appended with as -many bytes with value 04 as needed (possibly zero) to make it a -multiple of 16 bytes in length. +e.g.@: @code{windows-1252}. Thus, @code{* Encoding} may be used as a +magic number for system files. + +@item +The plaintext of viewer files begins with 50 4b 03 04 14 00 08 (50 4b +is @code{PK}). +@end itemize @node Password Encoding @section Password Encoding diff --git a/doc/pspp-convert.texi b/doc/pspp-convert.texi index 7af90a113c..8c4941f095 100644 --- a/doc/pspp-convert.texi +++ b/doc/pspp-convert.texi @@ -55,11 +55,11 @@ this format.) @end table @command{pspp-convert} can convert most input formats to most output -formats. Encrypted system file and syntax files are exceptions: if -the input file is in an encrypted format, then the output file must be -the same format (decrypted). To decrypt such a file, specify the -encrypted file as @var{input}. The output will be the equivalent -plaintext file. +formats. Encrypted SPSS file formats are exceptions: if the input +file is in an encrypted format, then the output file will be the same +format (decrypted). To decrypt such a file, specify the encrypted +file as @var{input}. The output will be the equivalent plaintext +file. Options for the output format are ignored in this case. The password for encrypted files can be specified a few different ways. If the password is known, use the @option{-p} option diff --git a/src/data/encrypted-file.c b/src/data/encrypted-file.c index e340d04fce..c0124cbee9 100644 --- a/src/data/encrypted-file.c +++ b/src/data/encrypted-file.c @@ -27,6 +27,7 @@ #include "libpspp/cast.h" #include "libpspp/cmac-aes256.h" #include "libpspp/message.h" +#include "libpspp/str.h" #include "gl/minmax.h" #include "gl/rijndael-alg-fst.h" @@ -37,20 +38,20 @@ struct encrypted_file { + const struct file_handle *fh; FILE *file; - enum { SYSTEM, SYNTAX } type; int error; - uint8_t ciphertext[16]; - uint8_t plaintext[16]; - unsigned int ofs, n; + uint8_t ciphertext[256]; + uint8_t plaintext[256]; + unsigned int ofs, n, readable; uint32_t rk[4 * (RIJNDAEL_MAXNR + 1)]; int Nr; }; static bool decode_password (const char *input, char output[11]); -static bool fill_buffer (struct encrypted_file *); +static void fill_buffer (struct encrypted_file *); /* If FILENAME names an encrypted SPSS file, returns 1 and initializes *FP for further use by the caller. @@ -63,12 +64,14 @@ int encrypted_file_open (struct encrypted_file **fp, const struct file_handle *fh) { struct encrypted_file *f; - char header[36 + 16]; + enum { HEADER_SIZE = 36 }; + char data[HEADER_SIZE + sizeof f->ciphertext]; int retval; int n; f = xmalloc (sizeof *f); f->error = 0; + f->fh = fh; f->file = fn_open (fh, "rb"); if (f->file == NULL) { @@ -78,8 +81,8 @@ encrypted_file_open (struct encrypted_file **fp, const struct file_handle *fh) goto error; } - n = fread (header, 1, sizeof header, f->file); - if (n != sizeof header) + n = fread (data, 1, sizeof data, f->file); + if (n < HEADER_SIZE + 2 * 16) { int error = feof (f->file) ? 0 : errno; if (error) @@ -89,19 +92,16 @@ encrypted_file_open (struct encrypted_file **fp, const struct file_handle *fh) goto error; } - if (!memcmp (header + 8, "ENCRYPTEDSAV", 12)) - f->type = SYSTEM; - else if (!memcmp (header + 8, "ENCRYPTEDSPS", 12)) - f->type = SYNTAX; - else + if (memcmp (data + 8, "ENCRYPTED", 9)) { retval = 0; goto error; } - memcpy (f->ciphertext, header + 36, 16); - f->n = 16; + f->n = n - HEADER_SIZE; + memcpy (f->ciphertext, data + HEADER_SIZE, f->n); f->ofs = 0; + f->readable = 0; *fp = f; return 1; @@ -138,12 +138,9 @@ encrypted_file_read (struct encrypted_file *f, void *buf_, size_t n) uint8_t *buf = buf_; size_t ofs = 0; - if (f->error) - return 0; - while (ofs < n) { - unsigned int chunk = MIN (n - ofs, f->n - f->ofs); + unsigned int chunk = MIN (n - ofs, f->readable - f->ofs); if (chunk > 0) { memcpy (buf + ofs, &f->plaintext[f->ofs], chunk); @@ -152,8 +149,9 @@ encrypted_file_read (struct encrypted_file *f, void *buf_, size_t n) } else { - if (!fill_buffer (f)) - return ofs; + fill_buffer (f); + if (!f->readable) + break; } } @@ -165,21 +163,13 @@ encrypted_file_read (struct encrypted_file *f, void *buf_, size_t n) int encrypted_file_close (struct encrypted_file *f) { - int error = f->error; + int error = f->error > 0 ? f->error : 0; if (fclose (f->file) == EOF && !error) error = errno; free (f); return error; } - -/* Returns true if F is an encrypted system file, - false if it is an encrypted syntax file. */ -bool -encrypted_file_is_sav (const struct encrypted_file *f) -{ - return f->type == SYSTEM; -} #define b(x) (1 << (x)) @@ -286,6 +276,26 @@ decode_password (const char *input, char output[11]) return true; } +/* Check for magic number at beginning of plaintext decrypted from F. */ +static bool +is_good_magic (const struct encrypted_file *f) +{ + char plaintext[16]; + rijndaelDecrypt (f->rk, f->Nr, CHAR_CAST (const char *, f->ciphertext), + plaintext); + + const struct substring magic[] = { + ss_cstr ("$FL2@(#)"), + ss_cstr ("$FL3@(#)"), + ss_cstr ("* Encoding"), + ss_buffer ("PK\3\4\x14\0\x8", 7) + }; + for (size_t i = 0; i < sizeof magic / sizeof *magic; i++) + if (ss_equals (ss_buffer (plaintext, magic[i].length), magic[i])) + return true; + return false; +} + /* Attempts to use plaintext password PASSWORD to unlock F. Returns true if successful, otherwise false. */ bool @@ -341,40 +351,107 @@ encrypted_file_unlock__ (struct encrypted_file *f, const char *password) assert (sizeof key == 32); f->Nr = rijndaelKeySetupDec (f->rk, CHAR_CAST (const char *, key), 256); - /* Check for magic number at beginning of plaintext. */ - rijndaelDecrypt (f->rk, f->Nr, - CHAR_CAST (const char *, f->ciphertext), - CHAR_CAST (char *, f->plaintext)); + if (!is_good_magic (f)) + return false; - const char *magic = f->type == SYSTEM ? "$FL?@(#)" : "* Encoding"; - for (int i = 0; magic[i]; i++) - if (magic[i] != '?' && f->plaintext[i] != magic[i]) - return false; + fill_buffer (f); return true; } -static bool +/* Checks the 16 bytes of PLAINTEXT for PKCS#7 padding bytes. Returns the + number of padding bytes (between 1 and 16, inclusive), if well formed, + otherwise 0. */ +static int +check_padding (const uint8_t *plaintext) +{ + uint8_t pad = plaintext[15]; + if (pad < 1 || pad > 16) + return 0; + + for (size_t i = 1; i < pad; i++) + if (plaintext[15 - i] != pad) + return 0; + + return pad; +} + +static void fill_buffer (struct encrypted_file *f) { - f->n = fread (f->ciphertext, 1, sizeof f->ciphertext, f->file); + /* Move bytes between f->ciphertext[f->readable] and f->ciphertext[f->n] to + the beginning of f->ciphertext. + + The first time this is called for a given file, it does nothing because + f->readable is initially 0. After that, in steady state f->readable is 16 + less than f->n, so the final 16 bytes of ciphertext become the first 16 + bytes. This is necessary because we don't know until we hit end-of-file + whether padding in the last 16 bytes will require us to discard up to 16 + bytes of data. */ + memmove (f->ciphertext, f->ciphertext + f->readable, f->n - f->readable); + f->n -= f->readable; + f->readable = 0; f->ofs = 0; - if (f->n == sizeof f->ciphertext) + + if (f->error) /* or assert(!f->error)? */ + return; + + /* Read new ciphernext, extending f->n, until we've filled up f->ciphertext + or until we reach end-of-file or encounter an error. + + Afterward, f->error indicates what happened. */ + while (f->n < sizeof f->ciphertext) { - rijndaelDecrypt (f->rk, f->Nr, - CHAR_CAST (const char *, f->ciphertext), - CHAR_CAST (char *, f->plaintext)); - if (f->type == SYNTAX) + size_t retval = fread (f->ciphertext + f->n, 1, + sizeof f->ciphertext - f->n, f->file); + if (!retval) { - const char *eof = memchr (f->plaintext, '\04', sizeof f->plaintext); - if (eof) - f->n = CHAR_CAST (const uint8_t *, eof) - f->plaintext; + f->error = ferror (f->file) ? errno : EOF; + break; } - return true; + f->n += retval; + } + + /* Calculate the number of readable bytes. If we're at the end of the file, + then we can read everything, otherwise we hold back the last 16 bytes + because they might be padding or not. */ + if (!f->error) + { + assert (f->n == sizeof f->ciphertext); + f->readable = f->n - 16; } else + f->readable = f->n; + + /* If we have an incomplete block then trim it off and complain. */ + unsigned int overhang = f->readable % 16; + if (overhang) { - if (ferror (f->file)) - f->error = errno; - return false; + assert (f->error); + msg (ME, _("%s: encrypted file corrupted (ends in incomplete %u-byte " + "ciphertext block)"), + fh_get_file_name (f->fh), overhang); + f->error = EIO; + f->readable -= overhang; + } + + /* Decrypt all the blocks we have. */ + for (size_t ofs = 0; ofs < f->readable; ofs += 16) + rijndaelDecrypt (f->rk, f->Nr, + CHAR_CAST (const char *, f->ciphertext + ofs), + CHAR_CAST (char *, f->plaintext + ofs)); + + /* If we're at end of file then check the padding and trim it off. */ + if (f->error == EOF) + { + unsigned int pad = check_padding (&f->plaintext[f->n - 16]); + if (!pad) + { + msg (ME, _("%s: encrypted file corrupted (ends with bad padding)"), + fh_get_file_name (f->fh)); + f->error = EIO; + return; + } + + f->readable -= pad; } } diff --git a/src/data/encrypted-file.h b/src/data/encrypted-file.h index 4a2d6b390a..7add68042d 100644 --- a/src/data/encrypted-file.h +++ b/src/data/encrypted-file.h @@ -31,6 +31,4 @@ bool encrypted_file_unlock__ (struct encrypted_file *, const char *password); size_t encrypted_file_read (struct encrypted_file *, void *, size_t); int encrypted_file_close (struct encrypted_file *); -bool encrypted_file_is_sav (const struct encrypted_file *); - #endif /* encrypted-file.h */ diff --git a/tests/automake.mk b/tests/automake.mk index 0a8b8e3657..1dba99efa1 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -275,6 +275,8 @@ EXTRA_DIST += \ tests/data/v13.sav \ tests/data/v14.sav \ tests/data/test-encrypted.sps \ + tests/data/test-decrypted.spv \ + tests/data/test-encrypted.spv \ tests/language/mann-whitney.txt \ tests/language/data-io/Book1.gnm.unzipped \ tests/language/data-io/test.ods \ diff --git a/tests/data/encrypted-file.at b/tests/data/encrypted-file.at index 7f0a1ae197..e3ad1bf715 100644 --- a/tests/data/encrypted-file.at +++ b/tests/data/encrypted-file.at @@ -65,3 +65,8 @@ DESCRIPTIVES /quantity ]) AT_CLEANUP +AT_SETUP([decrypt an encrypted viewer file]) +AT_KEYWORDS([syntax file decrypt pspp-convert spv]) +AT_CHECK([pspp-convert $srcdir/data/test-encrypted.spv test.spv -p Password1]) +AT_CHECK([cmp $srcdir/data/test-decrypted.spv test.spv]) +AT_CLEANUP diff --git a/tests/data/test-decrypted.spv b/tests/data/test-decrypted.spv new file mode 100644 index 0000000000000000000000000000000000000000..891263dccd0cdc2f5a4250a36228e7f9bec8c852 GIT binary patch literal 7713 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 bjpWbStgeiJ_;VG?<5T=7Z;;1z0Kk6%?iym{ literal 0 HcmV?d00001 diff --git a/tests/data/test-encrypted.spv b/tests/data/test-encrypted.spv new file mode 100644 index 0000000000000000000000000000000000000000..da8be2c80fdc62c959c32eaec4fec32a76eac432 GIT binary patch literal 7764 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 Gz5NfRbPzEB literal 0 HcmV?d00001 diff --git a/utilities/pspp-convert.1 b/utilities/pspp-convert.1 index 6e2e9f5224..9c96dead1e 100644 --- a/utilities/pspp-convert.1 +++ b/utilities/pspp-convert.1 @@ -55,9 +55,10 @@ specify the format for unrecognized extensions. . .PP \fBpspp\-convert\fR can convert most input formats to most output -formats. Encrypted system file and syntax files are exceptions: if -the input file is in an encrypted format, then the output file must -be the same format (decrypted). +formats. Encrypted SPSS file formats are exceptions: if the input +file is in an encrypted format, then the output file will be the same +format (decrypted). Options for the output format are ignored in this +case. . .SH "OPTIONS" .SS "General Options" diff --git a/utilities/pspp-convert.c b/utilities/pspp-convert.c index 4a2f0f0291..bb04abbfb2 100644 --- a/utilities/pspp-convert.c +++ b/utilities/pspp-convert.c @@ -184,24 +184,11 @@ main (int argc, char *argv[]) output_fh = fh_create_file (NULL, output_filename, NULL, fh_default_properties ()); if (encrypted_file_open (&enc, input_fh) > 0) { - if (encrypted_file_is_sav (enc)) - { - if (strcmp (output_format, "sav") && strcmp (output_format, "sys")) - error (1, 0, _("can only convert encrypted data file to sav or " - "sys format")); - } - else - { - if (strcmp (output_format, "sps")) - error (1, 0, _("can only convert encrypted syntax file to sps " - "format")); - } - - if (!decrypt_file (enc, input_fh, output_fh, password, + if (decrypt_file (enc, input_fh, output_fh, password, ds_cstr (&alphabet), length, password_list)) + goto exit; + else goto error; - - goto exit; } -- 2.30.2