Botan 3.13.0
Crypto and TLS for C&
pkcs12_pbe.cpp
Go to the documentation of this file.
1/*
2* PKCS#12 PBE (RFC 7292 Appendix B)
3* (C) 2026 Damiano Mazzella
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/internal/pkcs12_pbe.h>
9
10#include <botan/asn1_obj.h>
11#include <botan/ber_dec.h>
12#include <botan/cipher_mode.h>
13#include <botan/der_enc.h>
14#include <botan/exceptn.h>
15#include <botan/rng.h>
16#include <botan/internal/fmt.h>
17#include <botan/internal/pkcs12_kdf.h>
18
19#include <algorithm>
20
21#include <botan/internal/pbes2.h>
22
23namespace Botan {
24
25namespace {
26
27// Maps a PKCS#12 PBE OID/name to its cipher parameters
28struct PKCS12_PBE_Params {
29 OID oid;
30 std::string cipher_name;
31 size_t key_len;
32};
33
34PKCS12_PBE_Params pkcs12_pbe_params_for_oid(const OID& oid) {
35 if(oid == OID::from_string("PBE-SHA1-3DES")) {
36 return {oid, "TripleDES/CBC", 24};
37 }
38 if(oid == OID::from_string("PBE-SHA1-2DES")) {
39 return {oid, "TripleDES/CBC", 16};
40 }
41 throw Decoding_Error(fmt("Unsupported PKCS#12 PBE algorithm: {}", oid.to_string()));
42}
43
44PKCS12_PBE_Params pkcs12_pbe_params_for_algo(std::string_view algo) {
45 if(algo == "PBE-SHA1-3DES") {
46 return {OID::from_string("PBE-SHA1-3DES"), "TripleDES/CBC", 24};
47 }
48 if(algo == "PBE-SHA1-2DES") {
49 return {OID::from_string("PBE-SHA1-2DES"), "TripleDES/CBC", 16};
50 }
51 throw Invalid_Argument(fmt("Unsupported PKCS#12 PBE algorithm: {}", algo));
52}
53
54// Derive key and IV via PKCS#12 KDF; expands 2-key 3DES (key_len==16) to 24 bytes.
55// When @p openssl_empty_pwd_compat is true and the password is empty, the KDF
56// is fed an empty byte string (matching OpenSSL's non-conforming behavior);
57// otherwise the RFC 7292 encoding (two-byte {0x00,0x00} terminator for any
58// UTF-8 password, empty encoded as {0x00,0x00}) is used.
59std::pair<secure_vector<uint8_t>, secure_vector<uint8_t>> pkcs12_derive_key_iv(std::string_view password,
60 const std::vector<uint8_t>& salt,
61 size_t iterations,
62 const PKCS12_PBE_Params& params,
63 bool openssl_empty_pwd_compat) {
64 constexpr size_t iv_len = 8; // DES/3DES block size
65 secure_vector<uint8_t> key(params.key_len);
66 secure_vector<uint8_t> iv(iv_len);
67
68 const auto hash = HashFunction::create_or_throw("SHA-1");
69 const bool ossl_empty = openssl_empty_pwd_compat && password.empty();
70
71 if(ossl_empty) {
72 // Feed an empty password byte string to the KDF (OpenSSL compat).
73 pkcs12_kdf({key.data(), params.key_len}, {}, {salt.data(), salt.size()}, iterations, 1, *hash);
74 pkcs12_kdf({iv.data(), iv_len}, {}, {salt.data(), salt.size()}, iterations, 2, *hash);
75 } else {
76 const PKCS12_KDF kdf_key(hash->new_object(), 1, iterations);
77 kdf_key.derive_key(key.data(), params.key_len, password.data(), password.size(), salt.data(), salt.size());
78
79 const PKCS12_KDF kdf_iv(hash->new_object(), 2, iterations);
80 kdf_iv.derive_key(iv.data(), iv_len, password.data(), password.size(), salt.data(), salt.size());
81 }
82
83 if(params.key_len == 16) { // 2DES: expand to 24 bytes by repeating the first key
84 key.resize(24);
85 std::copy(key.begin(), key.begin() + 8, key.begin() + 16);
86 }
87 return {std::move(key), std::move(iv)};
88}
89
90} // namespace
91
92secure_vector<uint8_t> pkcs12_pbe_decrypt(std::span<const uint8_t> ciphertext,
93 std::string_view password,
94 const AlgorithmIdentifier& pbe_algo,
95 bool openssl_empty_pwd_compat) {
96 const OID& oid = pbe_algo.oid();
97
98 if(oid == OID::from_string("PBE-PKCS5v20")) {
99 // PBES2 (RFC 8018) uses PBKDF2, which is unambiguous for empty
100 // passwords, so the OpenSSL empty-password quirk does not apply.
101 return pbes2_decrypt(ciphertext, password, pbe_algo.parameters());
102 }
103
104 std::vector<uint8_t> salt;
105 size_t iterations = 0;
106 BER_Decoder(pbe_algo.parameters())
109 .decode(iterations)
110 .verify_end()
111 .end_cons();
112
113 if(iterations == 0 || iterations > PKCS12_MAX_ITERATIONS) {
114 throw Decoding_Error(fmt("PKCS#12 PBE has invalid iteration count: {}", iterations));
115 }
116
117 const auto params = pkcs12_pbe_params_for_oid(oid);
118 auto [key, iv] = pkcs12_derive_key_iv(password, salt, iterations, params, openssl_empty_pwd_compat);
119
120 auto cipher = Cipher_Mode::create_or_throw(params.cipher_name, Cipher_Dir::Decryption);
121 cipher->set_key(key);
122 cipher->start(iv);
123
124 secure_vector<uint8_t> plaintext(ciphertext.begin(), ciphertext.end());
125 cipher->finish(plaintext);
126
127 return plaintext;
128}
129
130std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pkcs12_pbe_encrypt(std::span<const uint8_t> plaintext,
131 std::string_view password,
132 std::string_view algo,
133 size_t iterations,
135 if(iterations == 0 || iterations > PKCS12_MAX_ITERATIONS) {
136 throw Invalid_Argument(fmt("PKCS#12 PBE: iteration count must be between 1 and {}", PKCS12_MAX_ITERATIONS));
137 }
138
139 if(algo == "PBES2-SHA256-AES256") {
140 auto [aid, ct] = pbes2_encrypt_iter(plaintext, password, iterations, "AES-256/CBC", "SHA-256", rng);
141 return {std::move(aid), std::move(ct)};
142 }
143 if(algo == "PBES2-SHA256-AES128") {
144 auto [aid, ct] = pbes2_encrypt_iter(plaintext, password, iterations, "AES-128/CBC", "SHA-256", rng);
145 return {std::move(aid), std::move(ct)};
146 }
147
148 const auto params = pkcs12_pbe_params_for_algo(algo);
149
150 std::vector<uint8_t> salt(8);
151 rng.randomize(salt.data(), salt.size());
152
153 auto [key, iv] = pkcs12_derive_key_iv(password, salt, iterations, params, false);
154
155 auto cipher = Cipher_Mode::create_or_throw(params.cipher_name, Cipher_Dir::Encryption);
156 cipher->set_key(key);
157 cipher->start(iv);
158
159 std::vector<uint8_t> ciphertext(plaintext.begin(), plaintext.end());
160 cipher->finish(ciphertext);
161
162 std::vector<uint8_t> enc_params;
163 DER_Encoder(enc_params).start_sequence().encode(salt, ASN1_Type::OctetString).encode(iterations).end_cons();
164
165 return {AlgorithmIdentifier(params.oid, enc_params), std::move(ciphertext)};
166}
167
168} // namespace Botan
const std::vector< uint8_t > & parameters() const
Definition asn1_obj.h:693
const OID & oid() const
Definition asn1_obj.h:688
BER_Decoder & decode(bool &out)
Definition ber_dec.h:358
BER_Decoder & verify_end()
Definition ber_dec.cpp:471
BER_Decoder & end_cons()
Definition ber_dec.cpp:630
BER_Decoder start_sequence()
Definition ber_dec.h:275
static std::unique_ptr< Cipher_Mode > create_or_throw(std::string_view algo, Cipher_Dir direction, std::string_view provider="")
DER_Encoder & start_sequence()
Definition der_enc.h:86
DER_Encoder & end_cons()
Definition der_enc.cpp:208
DER_Encoder & encode(bool b)
Definition der_enc.cpp:313
static std::unique_ptr< HashFunction > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition hash.cpp:308
static OID from_string(std::string_view str)
Definition asn1_oid.cpp:80
void randomize(std::span< uint8_t > output)
Definition rng.h:86
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
secure_vector< uint8_t > pkcs12_pbe_decrypt(std::span< const uint8_t > ciphertext, std::string_view password, const AlgorithmIdentifier &pbe_algo, bool openssl_empty_pwd_compat)
std::pair< AlgorithmIdentifier, std::vector< uint8_t > > pbes2_encrypt_iter(std::span< const uint8_t > key_bits, std::string_view passphrase, size_t pbkdf_iter, std::string_view cipher, std::string_view digest, RandomNumberGenerator &rng)
Definition pbes2.cpp:414
secure_vector< uint8_t > pbes2_decrypt(std::span< const uint8_t > key_bits, std::string_view passphrase, const std::vector< uint8_t > &params)
Definition pbes2.cpp:423
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:128
std::pair< AlgorithmIdentifier, std::vector< uint8_t > > pkcs12_pbe_encrypt(std::span< const uint8_t > plaintext, std::string_view password, std::string_view algo, size_t iterations, RandomNumberGenerator &rng)
void pkcs12_kdf(std::span< uint8_t > out, std::span< const uint8_t > pwd_bytes, std::span< const uint8_t > salt, size_t iterations, uint8_t id, HashFunction &hash)
constexpr size_t PKCS12_MAX_ITERATIONS
Definition pkcs12_pbe.h:25