From: Ben Pfaff Date: Sat, 12 Jan 2019 20:53:00 +0000 (-0800) Subject: pspp-convert: Add -a and -l options to search for a password. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?p=pspp;a=commitdiff_plain;h=c64c9e72a7040c8b36aa8709848efc5c37b7b72e pspp-convert: Add -a and -l options to search for a password. --- diff --git a/NEWS b/NEWS index 191a9804b4..715e4d3c50 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,9 @@ Changes since 1.2.0: * Plain text output is no longer divided into pages, since it is now rarely printed on paper. + * pspp-convert: New "-a" and "-l" options to search for an encrypted file's + password. + * Bug fix for CVE-2018-20230. Changes from 1.0.1 to 1.2.0: diff --git a/doc/pspp-convert.texi b/doc/pspp-convert.texi index aad7e74d9e..915313298c 100644 --- a/doc/pspp-convert.texi +++ b/doc/pspp-convert.texi @@ -59,8 +59,13 @@ 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. You will be prompted for the password (or use -@option{-p}, documented below). +plaintext file. + +The password for encrypted files can be specified a few different +ways. If the password is known, use the @option{-p} option +(documented below) or allow @command{pspp-convert} to prompt for it. +If the password is unknown, use the @option{-a} and @option{-l} +options to specify how to search for it. Use @code{-O @var{extension}} to override the inferred format or to specify the format for unrecognized extensions. @@ -96,6 +101,24 @@ necessary. Be aware that command-line options, including passwords, may be visible to other users on multiuser systems. +When used with @option{-a} (or @option{--password-alphabet}) and +@option{-l} (or @option{--password-length}), this option specifies the +starting point for the search. This can be used to restart a search +that was interrupted. + +@item -a @var{alphabet} +@item --password-alphabet=@var{alphabet} +Specifies the alphabet of symbols over which to search for an +encrypted file's password. @var{alphabet} may include individual +characters and ranges delimited by @samp{-}. For example, @option{-a +a-z} searches lowercase letters, @option{-a A-Z0-9} searches uppercase +letters and digits, and @option{-a ' -~'} searches all printable ASCII +characters. + +@item -l @var{max-length} +@item --password-length=@var{max-length} +Specifies the maximum length of the passwords to try. + @item -h @itemx --help Prints a usage message on stdout and exits. diff --git a/src/data/encrypted-file.c b/src/data/encrypted-file.c index 27bfe2e3f0..e340d04fce 100644 --- a/src/data/encrypted-file.c +++ b/src/data/encrypted-file.c @@ -49,7 +49,6 @@ struct encrypted_file int Nr; }; -static bool try_password(struct encrypted_file *, const char *password); static bool decode_password (const char *input, char output[11]); static bool fill_buffer (struct encrypted_file *); @@ -122,9 +121,9 @@ encrypted_file_unlock (struct encrypted_file *f, const char *password) { char decoded_password[11]; - return (try_password (f, password) + return (encrypted_file_unlock__ (f, password) || (decode_password (password, decoded_password) - && try_password (f, decoded_password))); + && encrypted_file_unlock__ (f, decoded_password))); } /* Attempts to read N bytes of plaintext from F into BUF. Returns the number @@ -287,12 +286,10 @@ decode_password (const char *input, char output[11]) return true; } -/* If CIPHERTEXT is the first ciphertext block in an encrypted .sav file for - PASSWORD, initializes rk[] and returns an nonzero Nr value. - - Otherwise, returns zero. */ -static bool -try_password(struct encrypted_file *f, const char *password) +/* Attempts to use plaintext password PASSWORD to unlock F. Returns true if + successful, otherwise false. */ +bool +encrypted_file_unlock__ (struct encrypted_file *f, const char *password) { /* NIST SP 800-108 fixed data. */ static const uint8_t fixed[] = { diff --git a/src/data/encrypted-file.h b/src/data/encrypted-file.h index 9e3116b774..4a2d6b390a 100644 --- a/src/data/encrypted-file.h +++ b/src/data/encrypted-file.h @@ -27,6 +27,7 @@ struct file_handle; int encrypted_file_open (struct encrypted_file **, const struct file_handle *); bool encrypted_file_unlock (struct encrypted_file *, const char *password); +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 *); diff --git a/utilities/pspp-convert.1 b/utilities/pspp-convert.1 index bbbdd62a4c..00828c2cb8 100644 --- a/utilities/pspp-convert.1 +++ b/utilities/pspp-convert.1 @@ -90,6 +90,19 @@ prompts for the password. On multiuser systems, this option may not be safe because other users may be able to see the password in process listings. . +.IP "\fB\-a \fIalphabet \fB\-l \fImax-length\fR" +.IQ "\fB\-\-password-alphabet=\fIalphabet\ \fB\-\-password-length=\fImax-length\fR" +These options are an alternative to \fB\-p\fR or \fB\-\-password\fR. +They direct \fBpspp\-convert\fR to search for the correct password +from the set of all passwords of symbols from \fIalphabet\fR (which +may contain character ranges specified with \fB-\fR) and no more than +\fImax-length\fR symbols long. For example, \fB\-a a-z \-l 5\fR +checks all possible lowercase alphabetic passwords no more than 5 +characters long. +.IP +When these options are used, \fB\-p\fR may additionally specify a +starting point for the search. +. .IP "\fB\-h\fR" .IQ "\fB\-\-help\fR" Prints a usage message on stdout and exits. diff --git a/utilities/pspp-convert.c b/utilities/pspp-convert.c index 217576ae7b..7eb6b55668 100644 --- a/utilities/pspp-convert.c +++ b/utilities/pspp-convert.c @@ -50,7 +50,8 @@ static void usage (void); static bool decrypt_file (struct encrypted_file *enc, const struct file_handle *input_filename, const struct file_handle *output_filename, - const char *password); + const char *password, + const char *alphabet, int max_length); int main (int argc, char *argv[]) @@ -69,6 +70,8 @@ main (int argc, char *argv[]) struct file_handle *output_fh = NULL; struct casewriter *writer; const char *password = NULL; + struct string alphabet = DS_EMPTY_INITIALIZER; + int length = 0; long long int i; @@ -83,7 +86,10 @@ main (int argc, char *argv[]) { { "cases", required_argument, NULL, 'c' }, { "encoding", required_argument, NULL, 'e' }, + { "password", required_argument, NULL, 'p' }, + { "password-alphabet", required_argument, NULL, 'a' }, + { "password-length", required_argument, NULL, 'l' }, { "output-format", required_argument, NULL, 'O' }, @@ -94,7 +100,7 @@ main (int argc, char *argv[]) int c; - c = getopt_long (argc, argv, "c:e:p:O:hv", long_options, NULL); + c = getopt_long (argc, argv, "c:e:p:a:l:O:hv", long_options, NULL); if (c == -1) break; @@ -112,6 +118,22 @@ main (int argc, char *argv[]) password = optarg; break; + case 'l': + length = atoi (optarg); + break; + + case 'a': + for (const char *p = optarg; *p; ) + if (p[1] == '-' && p[2] > p[0]) + { + for (int ch = p[0]; ch <= p[2]; ch++) + ds_put_byte (&alphabet, ch); + p += 3; + } + else + ds_put_byte (&alphabet, *p++); + break; + case 'O': output_format = optarg; break; @@ -164,7 +186,8 @@ main (int argc, char *argv[]) "format")); } - if (! decrypt_file (enc, input_fh, output_fh, password)) + if (!decrypt_file (enc, input_fh, output_fh, password, + ds_cstr (&alphabet), length)) goto error; goto exit; @@ -223,6 +246,7 @@ main (int argc, char *argv[]) error (1, 0, _("%s: error writing output file"), output_filename); exit: + ds_destroy (&alphabet); dict_unref (dict); fh_unref (output_fh); fh_unref (input_fh); @@ -232,6 +256,7 @@ exit: return 0; error: + ds_destroy (&alphabet); dict_unref (dict); fh_unref (output_fh); fh_unref (input_fh); @@ -245,22 +270,96 @@ static bool decrypt_file (struct encrypted_file *enc, const struct file_handle *ifh, const struct file_handle *ofh, - const char *password) + const char *password, + const char *alphabet, + int max_length) { FILE *out; int err; const char *input_filename = fh_get_file_name (ifh); const char *output_filename = fh_get_file_name (ofh); - if (password == NULL) + if (alphabet[0] && max_length) { - password = getpass ("password: "); - if (password == NULL) - return false; + size_t alphabet_size = strlen (alphabet); + char *pw = xmalloc (max_length + 1); + int *indexes = xzalloc (max_length * sizeof *indexes); + + for (int len = password ? strlen (password) : 0; + len <= max_length; len++) + { + if (password && len == strlen (password)) + { + for (int i = 0; i < len; i++) + { + const char *p = strchr (alphabet, password[i]); + if (!p) + error (1, 0, _("%s: '%c' is not in alphabet"), + password, password[i]); + indexes[i] = p - alphabet; + pw[i] = *p; + } + } + else + { + memset (indexes, 0, len * sizeof *indexes); + for (int i = 0; i < len; i++) + pw[i] = alphabet[0]; + } + pw[len] = '\0'; + + unsigned int target = 0; + for (unsigned int j = 0; ; j++) + { + if (j >= target) + { + target += 100000; + if (isatty (STDOUT_FILENO)) + { + printf ("\rlength %d: %s", len, pw); + fflush (stdout); + } + } + if (encrypted_file_unlock__ (enc, pw)) + { + printf ("\npassword is: \"%s\"\n", pw); + password = pw; + goto success; + } + + int i; + for (i = 0; i < len; i++) + if (++indexes[i] < alphabet_size) + { + pw[i] = alphabet[indexes[i]]; + break; + } + else + { + indexes[i] = 0; + pw[i] = alphabet[indexes[i]]; + } + if (i == len) + break; + } + } + free (indexes); + free (pw); + + success:; } + else + { + if (password == NULL) + { + password = getpass ("password: "); + if (password == NULL) + return false; + } - if (!encrypted_file_unlock (enc, password)) - error (1, 0, _("sorry, wrong password")); + if (!encrypted_file_unlock (enc, password)) + error (1, 0, _("sorry, wrong password")); + } out = fn_open (ofh, "wb"); if (out == NULL)