/* * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). * All rights reserved. * * This code is originally derived from software contributed to * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and * carried further by Ribose Inc (https://www.ribose.com). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include "config.h" #ifndef _MSC_VER #include #else #include "uniwin.h" #endif #include "crypto/s2k.h" #include "defaults.h" #include "rnp.h" #include "types.h" #include "utils.h" #ifdef CRYPTO_BACKEND_BOTAN #include #include "hash_botan.hpp" #endif bool pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysize) { uint8_t *saltptr = NULL; unsigned iterations = 1; switch (s2k->specifier) { case PGP_S2KS_SIMPLE: break; case PGP_S2KS_SALTED: saltptr = s2k->salt; break; case PGP_S2KS_ITERATED_AND_SALTED: saltptr = s2k->salt; if (s2k->iterations < 256) { iterations = pgp_s2k_decode_iterations(s2k->iterations); } else { iterations = s2k->iterations; } break; default: return false; } if (pgp_s2k_iterated(s2k->hash_alg, key, keysize, password, saltptr, iterations)) { RNP_LOG("s2k failed"); return false; } return true; } #ifdef CRYPTO_BACKEND_BOTAN int pgp_s2k_iterated(pgp_hash_alg_t alg, uint8_t * out, size_t output_len, const char * password, const uint8_t *salt, size_t iterations) { char s2k_algo_str[128]; snprintf(s2k_algo_str, sizeof(s2k_algo_str), "OpenPGP-S2K(%s)", rnp::Hash_Botan::name_backend(alg)); return botan_pwdhash(s2k_algo_str, iterations, 0, 0, out, output_len, password, 0, salt, salt ? PGP_SALT_SIZE : 0); } #endif size_t pgp_s2k_decode_iterations(uint8_t c) { // See RFC 4880 section 3.7.1.3 return (16 + (c & 0x0F)) << ((c >> 4) + 6); } size_t pgp_s2k_round_iterations(size_t iterations) { return pgp_s2k_decode_iterations(pgp_s2k_encode_iterations(iterations)); } uint8_t pgp_s2k_encode_iterations(size_t iterations) { /* For compatibility, when an S2K specifier is used, the special value * 254 or 255 is stored in the position where the hash algorithm octet * would have been in the old data structure. This is then followed * immediately by a one-octet algorithm identifier, and then by the S2K * specifier as encoded above. * 0: secret data is unencrypted (no password) * 255 or 254: followed by algorithm octet and S2K specifier * Cipher alg: use Simple S2K algorithm using MD5 hash * For more info refer to rfc 4880 section 3.7.2.1. */ for (uint16_t c = 0; c < 256; ++c) { // This could be a binary search if (pgp_s2k_decode_iterations(c) >= iterations) { return c; } } return 255; } /// Should this function be elsewhere? static uint64_t get_timestamp_usec() { #ifndef _MSC_VER // TODO: Consider clock_gettime struct timeval tv; ::gettimeofday(&tv, NULL); return (static_cast(tv.tv_sec) * 1000000) + static_cast(tv.tv_usec); #else return GetTickCount64() * 1000; #endif } size_t pgp_s2k_compute_iters(pgp_hash_alg_t alg, size_t desired_msec, size_t trial_msec) { if (desired_msec == 0) { desired_msec = DEFAULT_S2K_MSEC; } if (trial_msec == 0) { trial_msec = DEFAULT_S2K_TUNE_MSEC; } // number of iterations to estimate the number of iterations // (sorry, cannot tell it better) const uint8_t NUM_ITERATIONS = 16; uint64_t duration = 0; size_t bytes = 0; try { for (uint8_t i = 0; i < NUM_ITERATIONS; i++) { uint64_t start = get_timestamp_usec(); uint64_t end = start; auto hash = rnp::Hash::create(alg); uint8_t buf[8192] = {0}; while (end - start < trial_msec * 1000ull) { hash->add(buf, sizeof(buf)); bytes += sizeof(buf); end = get_timestamp_usec(); } hash->finish(buf); duration += (end - start); } } catch (const std::exception &e) { RNP_LOG("Failed to hash data: %s", e.what()); return 0; } const uint8_t MIN_ITERS = 96; if (duration == 0) { return pgp_s2k_decode_iterations(MIN_ITERS); } const double bytes_per_usec = static_cast(bytes) / duration; const double desired_usec = desired_msec * 1000.0; const double bytes_for_target = bytes_per_usec * desired_usec; const uint8_t iters = pgp_s2k_encode_iterations(bytes_for_target); return pgp_s2k_decode_iterations((iters > MIN_ITERS) ? iters : MIN_ITERS); }