Repository: randombit/botan
Pull request #3417 (2023-03-24 10:41:32)

Change cli to use --cipher and --pbkdf options instead of --pbe


Patch:
diff --git a/doc/cli.rst b/doc/cli.rst
index f880f3134..f6a30d2a4 100644
--- a/doc/cli.rst
+++ b/doc/cli.rst
@@ -70,36 +70,42 @@ Encryption
 
 Public Key Cryptography
 -------------------------------------
-``keygen --algo=RSA --params= --passphrase= --pbe= --pbe-millis=300 --provider= --der-out``
+``keygen --algo=RSA --params= --passphrase= --cipher= --pbkdf= --pbkdf-ms=300 --provider= --der-out``
   Generate a PKCS #8 *algo* private key. If *der-out* is passed, the pair is BER
   encoded.  Otherwise, PEM encoding is used. To protect the PKCS #8 formatted
-  key, it is recommended to encrypt it with a provided *passphrase*. *pbe* is
-  the name of the desired encryption algorithm, which uses *pbe-millis*
-  milliseconds to derive the encryption key from the passed
-  *passphrase*. Algorithm specific parameters, as the desired bit length of an
-  RSA key, can be passed with *params*.
+  key, it is recommended to encrypt it with a provided *passphrase*.
+
+  If a passphrase is used, *cipher* specifies the name of the desired encryption
+  algorithm (such as "AES-256/CBC", or leave empty to use a default), and
+  *pbkdf* can be used to specify the password hashing mechanism (either a hash
+  such as "SHA-256" to select PBKDF2, or "Scrypt").
+
+  The cipher mode must have an object identifier defined, this allows use of
+  ciphers such as AES, Twofish, Serpent, and SM4. Ciphers in CBC, GCM, and SIV
+  modes are supported. However most other implementations support only AES or
+  3DES in CBC mode.
+
+  If encryption is used, the parameter *pbkdf-ms* controls how long the password
+  hashing function will run to derive the encryption key from the passed
+  *passphrase*.
+
+  Algorithm specific parameters, as the desired bit length of an RSA key, can be
+  passed with *params*.
 
     - For RSA *params* specifies the bit length of the RSA modulus. It defaults to 3072.
     - For DH *params* specifies the DH parameters. It defaults to modp/ietf/2048.
     - For DSA *params* specifies the DSA parameters. It defaults to dsa/botan/2048.
     - For EC algorithms *params* specifies the elliptic curve. It defaults to secp256r1.
 
-  The default *pbe* algorithm is "PBES2(AES-256/CBC,SHA-256)".
-
-  With PBES2 scheme, you can select any CBC or GCM mode cipher which has an OID
-  defined (such as 3DES, Camellia, SM4, Twofish or Serpent). However most other
-  implementations support only AES or 3DES in CBC mode. You can also choose
-  Scrypt instead of PBKDF2, by using "Scrypt" instead of the name of a hash
-  function, for example "PBES2(AES-256/CBC,Scrypt)". Scrypt is also supported by
-  some other implementations including OpenSSL.
+``pkcs8 --pass-in= --pub-out --der-out --pass-out= --cipher= --pbkdf= --pbkdf-ms=300 key``
 
-``pkcs8 --pass-in= --pub-out --der-out --pass-out= --pbe= --pbe-millis=300 key``
   Open a PKCS #8 formatted key at *key*. If *key* is encrypted, the passphrase
   must be passed as *pass-in*. It is possible to (re)encrypt the read key with
-  the passphrase passed as *pass-out*. The parameters *pbe-millis* and *pbe*
-  work similarly to ``keygen``.
+  the passphrase passed as *pass-out*. The parameters *cipher*, *pbkdf*, and
+  *pbkdf-ms* work similarly to ``keygen``.
 
 ``sign --der-format --passphrase= --hash=SHA-256 --emsa= --provider= key file``
+
   Sign the data in *file* using the PKCS #8 private key *key*. If *key* is
   encrypted, the used passphrase must be passed as *pass-in*. *emsa* specifies
   the signature scheme and *hash* the cryptographic hash function used in the
diff --git a/src/cli/pubkey.cpp b/src/cli/pubkey.cpp
index b986fb2b2..35fcf2cd0 100644
--- a/src/cli/pubkey.cpp
+++ b/src/cli/pubkey.cpp
@@ -22,6 +22,7 @@
 #include <botan/internal/workfactor.h>
 #include <botan/data_src.h>
 
+#include <sstream>
 #include <fstream>
 
 #if defined(BOTAN_HAS_DL_GROUP)
@@ -34,10 +35,41 @@
 
 namespace Botan_CLI {
 
+namespace {
+
+std::string pbe_string(const std::string& cipher,
+                       const std::string& pbkdf)
+   {
+   if(cipher.empty() && pbkdf.empty())
+      return ""; // just let the library choose a default
+
+   std::ostringstream oss;
+
+   oss << "PBES2(";
+
+   if(cipher.empty())
+      oss << "AES-256/CBC";
+   else
+      oss << cipher;
+
+   oss << ",";
+
+   if(pbkdf.empty())
+      oss << "SHA-512";
+   else
+      oss << pbkdf;
+
+   oss << ")";
+
+   return oss.str();
+   }
+
+}
+
 class PK_Keygen final : public Command
    {
    public:
-      PK_Keygen() : Command("keygen --algo=RSA --params= --passphrase= --pbe= --pbe-millis=300 --provider= --der-out") {}
+      PK_Keygen() : Command("keygen --algo=RSA --params= --passphrase= --cipher= --pbkdf= --pbkdf-ms=300 --provider= --der-out") {}
 
       std::string group() const override
          {
@@ -66,8 +98,8 @@ class PK_Keygen final : public Command
          const std::string pass = get_passphrase_arg("Key passphrase", "passphrase");
          const bool der_out = flag_set("der-out");
 
-         const std::chrono::milliseconds pbe_millis(get_arg_sz("pbe-millis"));
-         const std::string pbe = get_arg("pbe");
+         const std::chrono::milliseconds pbkdf_ms(get_arg_sz("pbkdf-ms"));
+         const std::string pbe = pbe_string(get_arg("cipher"), get_arg("pbkdf"));
 
          if(der_out)
             {
@@ -77,7 +109,7 @@ class PK_Keygen final : public Command
                }
             else
                {
-               write_output(Botan::PKCS8::BER_encode(*key, rng(), pass, pbe_millis, pbe));
+               write_output(Botan::PKCS8::BER_encode(*key, rng(), pass, pbkdf_ms, pbe));
                }
             }
          else
@@ -88,7 +120,7 @@ class PK_Keygen final : public Command
                }
             else
                {
-               output() << Botan::PKCS8::PEM_encode(*key, rng(), pass, pbe_millis, pbe);
+               output() << Botan::PKCS8::PEM_encode(*key, rng(), pass, pbkdf_ms, pbe);
                }
             }
          }
@@ -324,7 +356,7 @@ BOTAN_REGISTER_COMMAND("verify", PK_Verify);
 class PKCS8_Tool final : public Command
    {
    public:
-      PKCS8_Tool() : Command("pkcs8 --pass-in= --pub-out --der-out --pass-out= --pbe= --pbe-millis=300 key") {}
+      PKCS8_Tool() : Command("pkcs8 --pass-in= --pub-out --der-out --pass-out= --cipher= --pbkdf= --pbkdf-ms=300 key") {}
 
       std::string group() const override
          {
@@ -353,8 +385,8 @@ class PKCS8_Tool final : public Command
             key = Botan::PKCS8::load_key(key_src, pass_in);
             }
 
-         const std::chrono::milliseconds pbe_millis(get_arg_sz("pbe-millis"));
-         const std::string pbe = get_arg("pbe");
+         const std::chrono::milliseconds pbkdf_ms(get_arg_sz("pbkdf-ms"));
+         const std::string pbe = pbe_string(get_arg("cipher"), get_arg("pbkdf"));
          const bool der_out = flag_set("der-out");
 
          if(flag_set("pub-out"))
@@ -380,7 +412,7 @@ class PKCS8_Tool final : public Command
                   }
                else
                   {
-                  write_output(Botan::PKCS8::BER_encode(*key, rng(), pass_out, pbe_millis, pbe));
+                  write_output(Botan::PKCS8::BER_encode(*key, rng(), pass_out, pbkdf_ms, pbe));
                   }
                }
             else
@@ -391,7 +423,7 @@ class PKCS8_Tool final : public Command
                   }
                else
                   {
-                  output() << Botan::PKCS8::PEM_encode(*key, rng(), pass_out, pbe_millis, pbe);
+                  output() << Botan::PKCS8::PEM_encode(*key, rng(), pass_out, pbkdf_ms, pbe);
                   }
                }
             }


Test patch:
diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py
index 1c757b612..1864649b0 100755
--- a/src/scripts/test_cli.py
+++ b/src/scripts/test_cli.py
@@ -331,8 +331,8 @@ mlLtJ5JvZ0/p6zP3x+Y9yPIrAR8L/acG5ItSrAKXzzuqQQZMv4aN
     test_cli("pkcs8", "--pub-out --output=%s %s" % (pub_key, priv_key), "")
     test_cli("pkcs8", "--pub-out --der-out --output=%s %s" % (pub_der_key, priv_key), "")
 
-    test_cli("pkcs8", "--pass-out=foof --der-out --output=%s %s" % (enc_der, priv_key), "")
-    test_cli("pkcs8", "--pass-out=foof --output=%s %s" % (enc_pem, priv_key), "")
+    test_cli("pkcs8", "--pass-out=foof --cipher=AES-128/CBC --der-out --output=%s %s" % (enc_der, priv_key), "")
+    test_cli("pkcs8", "--pass-out=foof --pbkdf=Scrypt --output=%s %s" % (enc_pem, priv_key), "")
 
     dec_pem = test_cli("pkcs8", ["--pass-in=foof", enc_pem], None)
     dec_der = test_cli("pkcs8", ["--pass-in=foof", enc_der], None)