pspp-convert: Add -a and -l options to search for a password.
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 12 Jan 2019 20:53:00 +0000 (12:53 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 13 Jan 2019 00:59:37 +0000 (16:59 -0800)
NEWS
doc/pspp-convert.texi
src/data/encrypted-file.c
src/data/encrypted-file.h
utilities/pspp-convert.1
utilities/pspp-convert.c

diff --git a/NEWS b/NEWS
index 191a9804b4ed16c09da34a842089080d4b69207a..715e4d3c50d1aa27e390eb3831167dabae618382 100644 (file)
--- 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:
index aad7e74d9e0d7636302a2a2941c81735d8f98373..915313298cbcaa74d6cb8ccfaf31ee261dad5bd6 100644 (file)
@@ -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.
index 27bfe2e3f09c62c89b8f8089b3cf6659ef5efd27..e340d04fce61d9727043758b7abac9a350bfcc9a 100644 (file)
@@ -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[] = {
index 9e3116b774d4d4136a68d79cb13e853f738a687e..4a2d6b390a8ef33eb000ceb471f7bbb7740a12cc 100644 (file)
@@ -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 *);
 
index bbbdd62a4c09f1b1906b2aa68d0b164ac8a1cd42..00828c2cb867b1fdbe0c65a28b5078d927174763 100644 (file)
@@ -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.
index 217576ae7bcac55012f04dd7d1730ef1ede8f65b..7eb6b556684e79e20317112be9b1a6a9ed919603 100644 (file)
@@ -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)